Skip to content

A library that sits on top of C websockets that uses Express.js style functionality

License

Notifications You must be signed in to change notification settings

charlie-map/teru

Repository files navigation

Meet Teru!

Charlie Hall

This repository is an implementation (rework, build, etc.) of nodejs Express packed in a library to support easier server building in C. This functionality matches very closely with many functions in Express (if not quite as built out as Express currently). This README goes through the process of creating a simple web server and how each component works. The following code is an example of a simple Teru server that sends a simple HTML file home.html when a socket joins and goes to / on the page:

#include <stdio.h>
#include <stdlib.h>

#include "teru.h"

#define HOST "localhost"
#define PORT "8888"

void home_page(req_t req, res_t res) {
	res_end("Hi there, welcome to the home page");
}

int main() {
  // initialize the server
	teru_t app = teru();

	// setup listener routes
	app_get(app, "/", home_page);

  // listen on the port and host defined above
	app_listen(HOST, PORT, app);

	return 0;
}

Wow! Looks pretty similar to the Express functions. Below lays out the current functions within Teru:

  1. teru() -- new Teru

App functionalities:

  1. app_use() -- Public directories and library usage
  2. app_set() -- Views directory

Send functionalities:

  1. res_sendFile() -- Send a file
  2. res_end() -- Send a string
  3. res_render() -- Send a file with match keys that replace to allow for dynamic HTML pages

Request parameters:

  1. req_query()
  2. req_body()

New Teru

teru() returns a teru_t struct which will have some default parameters set but remains mostly untouched. The functions following this will get into how to personalize Teru for your project. From the example code above, creating a new Teru instance just involves running:

teru_t app = teru();

This app variable will be used throughout the following function examples.

Use Teru

The app_use function will be how to add any extra components onto the Teru instance. Not done

Set Teru

To app_set, this takes in a key and a value pair for what to load in. For example, loading a views directory would look like:

app_set(app, "views", getenv("PWD"), "/views/");

getenv("PWD") is the current working directory of the server file. Then sending any files look in this directory for the file instead of the default behavior of looking in the same directory as the main program. Currently views is the only functionality for app_set(), more to come in the future.

Send File to User

res_sendFile() takes in the name of a file from which to read the data. This data is then sent as the body of the response to the user. If home.html contained:

<!DOCTYPE html>
<html>
	<head>
		<title>Example Page</title>
	</head>
	<body>
		<h1>Hey there!!!</h1>
	</body>
</html>

Then attaching this to a listener would involve creating a simple listener that watches for requests at /:

void home_page(req_t req, res_t res) {
	res_sendFile(res, "home.html");
}

Then attaching to / would involve using app_get() which then handles the rest:

app_get(app, "/", home_page);

Teru is ready to say Hey there!!! to all the interested users!

Send Message to User

res_end() works similarly to res_sendFile(), but instead of a file name as the second parameter takes in a string message:

void hello_there(req_t req, res_t res) {
  res_end(res, "Hello there.");
}

Then connect to an endpoint:

app_get(app, "/hi", hello_there);

Render File to User

This allows for a server to take an HTML page and find and replace occurences of match strings. There are a few steps for setting this up:

  • Create an HTML file with match strings. The start match and end match can be whatever strings you wish. However, these strings must match what you give the render() function in the following steps.
<html>
	<head>
		<title>Render Example</title>
	</head>
	<body>
		<h1>Hi there {{NAME}}!</h1>
	</body>
</html>
  • Next, set the match parameters using the res_matches() function, which for the previous example would look like the following. Note that res references the second parameter of the handler function (see res_sendFile for an example).
res_matches(res, "NAME", "charlie-map");
  • Finally, use the res_render() function to interpret the match strings and send the result to the user. res_render() takes in res, the name of the file, and the start match and end match. Assuming the above HTML file is named "home.html", the res_render() call would look like:
res_render(res, "home", "{{", "}}");

See Request Query Parameters

Request query parameters are added at the end of the URL (for example localhost:8888/hi?name=Charlie). req_query() allows access to these by inserting the name of the query parameter:

void hello_name(req_t req, res_t res) {
  char *name = req_query(req, "name");
  
  printf("name: %s\n", name); // expect "Charlie" as output
  
  res_end(res, "Hi!");
}

Then you could easily add that name into the response string. As with previous examples, connect to Teru using app_get() or app_post()

See Request Body Parameters

When using app_post() for a specific listener, you have access to req_body(). Taking the query example, instead you would have:

void hello_name(req_t req, res_t req) {
  char *name = req_body(req, "name");
  
  printf("name: %s\n", name);
  
  res_end(res, "Hi!");
}

The only difference involves connecting the listener to Teru with app_post() instead:

app_post(app, "/hi", hello_name);

Teru's Future

Currently there are a few bucket list items that will be check off over time. However, feel free to leave an issue with any suggested enhancements.

  • app_put(), app_delete(), etc.
  • more app_set() functionality

About

A library that sits on top of C websockets that uses Express.js style functionality

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages