Skip to content

Commit

Permalink
update documentation and install instructions
Browse files Browse the repository at this point in the history
ngn13 committed Jan 14, 2025
1 parent 81f3c00 commit b938aac
Showing 7 changed files with 175 additions and 108 deletions.
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -82,4 +82,8 @@ docs:
example:
$(MAKE) -C $@

.PHONY: install uninstall docs format clean example
test:
make example
./scripts/test.sh

.PHONY: install uninstall docs format clean example test
78 changes: 51 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
```
__
_____/ /__________ ____ ___
/ ___/ __/ ___/ __ \/ __ `__ \
/ /__/ /_/ / / /_/ / / / / / /
\___/\__/_/ \____/_/ /_/ /_/ 1.5
_____/ /_____ _________ ___
/ ___/ __/ __ \/ ___/ __ `__ \
/ /__/ /_/ /_/ / / / / / / / /
\___/\__/\____/_/ /_/ /_/ /_/ 1.6
```

@@ -13,7 +13,7 @@
![](https://img.shields.io/github/v/tag/ngn13/ctorm?label=version)
![](https://img.shields.io/github/license/ngn13/ctorm)

ctorm is a multi-threaded HTTP server for `HTTP/1.1` and `HTTP/1.0`.
ctorm is a multi-threaded, simple web server framework for `HTTP/1.1` and `HTTP/1.0`.
It has an easy API for general web server applications.

> [!WARNING]
@@ -26,70 +26,93 @@ if you are interested.
### Features
- Wildcard routes
- Middleware support
- Form body parsing
- URL queries (parameters)
- URL encoded body parsing
- JSON support with [cJSON](https://github.com/DaveGamble/cJSON)
- Handling 404 (all) routes
- Sending files and static file serving

### Installation
You will need the following software in order to build and install ctorm:
- GNU tar to extract the release archive (`tar`)
- GCC and other general build tools (`build-essential`)
- If you want to build the man pages, [`doxygen`](https://www.doxygen.org/)
- If you want JSON support, cJSON and it's headers (`cjson`, `libcjson-dev`)
- tar (to extract the release archive)

First [download the latest release](https://github.com/ngn13/ctorm/tags) archive,
**do not compile from the latest commit unless you are doing development**:
First [download the latest release archive](https://github.com/ngn13/ctorm/tags),
**do not compile from the latest commit or a branch unless you are doing development**:
```bash
wget https://github.com/ngn13/ctorm/archive/refs/tags/1.5.tar.gz
tar xf 1.5.tar.gz && cd ctorm-1.5
```

Then use the `make` command to build and install:
Then use the `make` command to compile the library:
```bash
make
```
**If you don't have cJSON installed**, you need to run this command with `CTORM_JSON_SUPPORT=0`
option to disable JSON support:
```bash
make CTORM_JSON_SUPPORT=0
```
**If you installed `doxygen`, and you want to build the man pages** run `make` with
the `docs` command:
```bash
make docs
```
To install the library (and if you've built it, the documentation) run `make` with
the `install` command **as root**:
```bash
make && sudo make install
make install
```

### Getting started
#### Hello world application
```c
#include <ctorm/all.h>
#include <ctorm/ctorm.h>

void hello_world(req_t *req, res_t *res) {
void GET_index(ctorm_req_t *req, ctorm_res_t *res) {
// send the "Hello world!" message
RES_SEND("Hello world!");
}

int main() {
// create the app with default configuration
app_t *app = app_new(NULL);
ctorm_app_t *app = ctorm_app_new(NULL);

// setup the routes
GET(app, "/", hello_world);
GET(app, "/", GET_index);

// run the app
if (!app_run(app, "0.0.0.0:8080"))
error("app failed: %s", app_geterror());
if (!ctorm_app_run(app, "0.0.0.0:8080"))
ctorm_fail("failed to start the application: %s", ctorm_geterror());

// clean up
app_free(app);
ctorm_app_free(app);
return 0;
}
```
#### Other functions
Here are some nicely formatted markdown documents that explain all the functions you will
most likely gonna use:
- [App](docs/app.md)
- [Error](docs/error.md)
- [Logging](docs/log.md)
- [Request](docs/req.md)
- [Response](docs/res.md)
You can also checkout the man pages if you built and installed during the [installation](#installation).
#### Example applications
Repository also contains few example applications in the `example` folder, you can
build these by running `make example`.
Repository also contains few example applications in the `example` directory. You can
build these by running:
```bash
make example
```

#### Deploying your application
You can use the docker image (built with actions) to easily deploy your application, here is
an example:
You can use the docker image (built by github actions) to easily deploy your
application, here is an example:
```Dockerfile
FROM ghcr.io/ngn13/ctorm:latest

@@ -108,12 +131,13 @@ CMD ["/app/server"]
```

### Development
For development, you can compile the library with debug mode:
For development, you can compile the library with the `CTORM_DEBUG=1` option to enable debug
messages:
```bash
make CTORM_DEBUG=1
```
then you can use the example applications for testing:
Then you can use the example applications and the test scripts in the `scripts` directory
for testing:
```bash
make example
LD_LIBRARY_PATH=./dist ./dist/example_hello
make test
```
62 changes: 27 additions & 35 deletions docs/app.md
Original file line number Diff line number Diff line change
@@ -3,14 +3,14 @@
Before creating an application, you can create a custom
configuration:
```c
app_config_t config;
ctorm_config_t config;
```
But before using it you should initialize it:
```c
app_config_new(&config);
ctorm_config_new(&config);
```
This will set all the default values, you can modify these by
directly accessing them through the `app_config_t` structure,
directly accessing them through the `ctorm_config_t` structure,
for example:
```c
// disable request/response logging
@@ -20,36 +20,36 @@ config.disable_logging = false;
### Managing the application
To create an application:
```c
app_t *app = app_new(&config);
ctorm_app_t *app = ctorm_app_new(&config);
```
If you don't have a custom configuration and you want
to use the default configuration:
```c
app_t *app = app_new(NULL);
ctorm_app_t *app = ctorm_app_new(NULL);
```
And to start the application:
```c
app_run(app, "0.0.0.0:8080")
ctorm_app_run(app, "0.0.0.0:8080")
```
This will start the application on port 8080, all interfaces,
after the app stops, you should clean up the `app_t` pointer
after the app stops, you should clean up the `ctorm_app_t` pointer
to free all the resources, to do this:
```c
app_free(app);
ctorm_app_free(app);
```

### Simple routing
Handlers used for routing should follow this structure:
```c
void route(req_t*, res_t*);
void route(ctorm_req_t*, ctorm_res_t*);
```
The `req_t` pointer points to the request object, and the `res_t`
The `ctorm_req_t` pointer points to the request object, and the `ctorm_res_t`
pointer points to the response object. To learn what you can do with them,
check out the [request](req.md) and [response](res.md) documentation.
check out the [request](req.md) and [response documentation](res.md).
To setup routes, you can use these simple macros:
```c
// get_index will handle any GET request for /
// get_index will handle any GET request for /
GET(app, "/", get_index);
// set_route will handle any PUT request for any routes
@@ -67,6 +67,18 @@ DELETE(app, "/", delete_index);
OPTIONS(app, "/", options_index);
```

### URL parameters
You can use the `:` character to specify URL parameters in the routes:
```c
GET(app, "/blog/:lang/desc", get_blog_desc);
```
For example this route will act the same as `/blog/*/desc` route. However
whatever fills the asterisk (wildcard) will be used as the value of the
`lang` URL parameter.
Later during the handler call, URL parameters can be accessed using the request
pointer. See the [request documentation](req.md) for more information.
### Middleware
Middleware handlers have the exact same structure with the routes,
however they have different macros:
@@ -81,37 +93,17 @@ MIDDLEWARE_OPTIONS(app, "/", options_index_mid);
```

### Set static directory
To setup a static route you can use the `app_static` function,
To setup a static route you can use the `ctorm_app_static` function,
**please note that ctorm only supports a single static route.**
```c
// static files be served at '/static' path,
// from the './files' directory
app_static(app, "/static", "./files");
ctorm_app_static(app, "/static", "./files");
```
### Setup 404 (all) route
By default, routes that does not match with any other will be redirected
to a 404 page, you set a custom route for this:
```c
app_all(app, all_route);
```
### Error handling
When a function fails, depending on the return value, you may receive a `false`
or a `NULL` return. To learn what went wrong and why the function failed, you can
use the `app_geterror` function, here is an example:
```c
if(!app_run(app, "127.0.0.1:8080"))
error("something went wrong: %s", app_geterror());
```
The error code also will be set the with the `errno` variable:
```c
#include <errno.h>
...
if(!app_run(argv[1])){
if(errno == BadAddress)
error("you specified an invalid address");
else
error("failed to start the app: %s", app_geterror());
}
ctorm_app_all(app, all_route);
```
30 changes: 30 additions & 0 deletions docs/error.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Error functions
### Checking the error code
When a function fails, depending on the return value, you may receive a `false`
or a `NULL` return. To learn what went wrong and why the function failed, you can
use check the `errno`:
```c
#include <errno.h>
...
if(!ctorm_app_run(app, argv[1])){
if(errno == BadAddress)
ctorm_fail("you specified an invalid address");
else
ctorm_fail("something else went wrong: %d", errno);
return EXIT_FAILURE;
}
```
See [errors.h](../inc/errors.h) for the full list of error codes.

### Getting the error description
To get the string description of an error, you can use `ctorm_geterror`:
```c
if(!ctorm_app_run(app, "0.0.0.0:8080")){
ctorm_fail("something went wrong: %s", ctorm_geterror());
return EXIT_FAILURE;
}
```
Or you can get the description of a specific error code:
```c
ctorm_info("BadAddress: %s", ctorm_geterror_from_code(BadAddress));
```
16 changes: 8 additions & 8 deletions docs/log.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# Log Functions
ctorm provides a simple, colored logging system for your
# Log Functions
ctorm provides a simple, colored logging system for your
general logging usage.

### General logging functions
### General logging functions
```c
info("some information");
warn("there may be something wrong");
error("PANIC!!");
ctorm_info("some information");
ctorm_warn("you better read this");
ctorm_fail("PANIC!!");
```
### Enable/Disable request logging
By default ctorm will log information about every single request
on the console, you can disable/enabled this with the [app configuration](app.md).
By default ctorm will log information about every single request,
you can disable/enabled this with the [app configuration](app.md).
57 changes: 36 additions & 21 deletions docs/req.md
Original file line number Diff line number Diff line change
@@ -5,16 +5,16 @@
> [!WARNING]
> Most of these functions are macros, and will only work if the
> `req_t` pointer is named `req`, otherwise you should directly
> `ctorm_req_t` pointer is named `req`, otherwise you should directly
> use the original functions.
### Request path
There are two different sections of the `req_t` which you can
There are two different sections of the `ctorm_req_t` which you can
use to access the request path, however you should not directly modify
these sections:
```c
info("URL encoded full path with the queries: %s", req->encpath);
info("URL decoded full path without the queries: %s", req->path);
ctorm_info("URL encoded full path with the queries: %s", req->encpath);
ctorm_info("URL decoded full path without the queries: %s", req->path);
```
### Request method
@@ -48,11 +48,11 @@ you provide:
```c
// get the body size
uint64_t size = REQ_BODY_SIZE();
// uint64_t size = req_body_size(req);
// uint64_t size = ctorm_req_body_size(req);

// check if body size is valid
if(size == 0){
error("request does not contain a body");
ctorm_fail("request does not contain a body");
return;
}

@@ -61,55 +61,70 @@ char *body = malloc(size);

// read "size" bytes of body into the "buffer"
REQ_BODY(body, size);
// req_body(req, body, size);
// ctorm_req_body(req, body, size);
```
ctrom also contains few helper functions to work with certain
body formats:
```c
// parse the form encoded body
enc_url_t *form = REQ_FORM();
// enc_url_t *form = req_form(req);
char *username = enc_url_get(form, "username"); // do not free or directly modify
enc_url_free(form); // "username" now points to an invalid address
ctorm_url_t *form = REQ_FORM();
// enc_url_t *form = ctorm_req_form(req);
char *username = ctorm_url_get(form, "username"); // do not free or directly modify
ctorm_url_free(form); // "username" now points to an invalid address
// parse the JSON encoded body
cJSON *json = REQ_JSON();
// cJSON *json = req_json(req);
// cJSON *json = ctorm_req_json(req);
cJSON *un_item = cJSON_GetObjectItem(json, "username");
char *un = cJSON_GetStringValue(un_item); // do not free or directly modify
enc_json_free(json); // "un" and "un_item" now points to an invalid address
ctorm_json_free(json); // "un" and "un_item" now points to an invalid address
```

### Request queries
To get URL decoded request queries, you can use the `REQ_QUERY` macro or the
`req_query` function:
```c
char *username = REQ_QUERY("username"); // do not free or directly modify
// char *username = req_query(req, "username");
// char *username = ctorm_req_query(req, "username");

if(NULL == username){
error("username query is not specified");
ctorm_fail("username query is not specified");
return;
}

info("username: %s", username);
ctorm_info("username: %s", username);
```
### Request parameters
If the route uses a URL parameter (see [app documentation](app.md) for more information)
then you can access this parameter by it's name:
```c
char *lang = REQ_PARAM("lang");
// char *lang = ctorm_req_param(req, "lang");
if(NULL == lang){
ctorm_warn("no language specified, using the default");
lang = "en";
}
ctorm_info("language: %s", lang);
```

### Request headers
To get HTTP headers, you can use the `REQ_HEADER` macro or the
`req_header` function, please note that HTTP headers are case-insensitive.
`ctorm_req_header` function, please note that HTTP headers are case-insensitive.

Also, if the client sent multiple headers with the same name, this macro/function
will return the first one in the header list.
```c
char* agent = REQ_GET("User-Agent");
// char *agent = req_get(req, "User-Agent");
char* agent = REQ_GET("user-agent");
// char *agent = ctorm_req_get(req, "user-agent");

if(NULL == agent){
error("user-agent header is not set");
ctorm_fail("user-agent header is not set");
return;
}

info("user-agent is %s", agent);
ctorm_info("user-agent is %s", agent);
```
34 changes: 18 additions & 16 deletions docs/res.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Response functions
> [!IMPORTANT]
# Response functions
> [!IMPORTANT]
> You should **NOT** `free()` any data returned by this functions
> **UNLESS** it's explicitly told to do so.
> [!WARNING]
> Most of these functions are macros, and will only work if the
> `res_t` pointer is named `res`, otherwise you should directly
> `ctorm_res_t` pointer is named `res`, otherwise you should directly
> use the original functions.
### Setting the response code
@@ -19,7 +19,7 @@ Or you can directly modify the response code:
res->code = 403;
```

### Working with the response body
### Working with the response body
There are few different ways to work with the response body:
```c
// you can use local data, it will be copied to heap
@@ -31,12 +31,12 @@ RES_SEND("hello world!");
// at the end
char raw[128];
...
res_send(res, data, sizeof(raw));
ctorm_res_send(res, data, sizeof(raw));

// if the data is null terminated, you can set the size
// to 0, and the size will be calculated with strlen()
// internally
res_send(res, "hello world!", 0);
ctorm_res_send(res, "hello world!", 0);
```
If you have formatted text, you can use the formatted
@@ -49,7 +49,7 @@ res_fmt(res, "username: %s", username);
// this macro/function will append to the response body
RES_ADD("age: %d", age);
res_add(res, "age: %d", age);
ctorm_res_add(res, "age: %d", age);
```

You can also send JSON data with response body using `cJSON`
@@ -62,7 +62,7 @@ cJSON_AddStringToObject(json, "username", "John");
RES_JSON(json);

// same thing without using the macro
res_json(res, json);
ctorm_res_json(res, json);
```
If for whatever reason, you want to completely clear the
@@ -71,18 +71,20 @@ response body:
RES_CLEAR();
// without using the macro
res_clear(res);
ctorm_res_clear(res);
```

### Sending a file
Using relative or absolute paths, you can send files with
the response using the `RES_SENDFILE` macro or the `res_sendfile`
function, `Content-Type` will be set for `html`, `css`, `js` and `json`
files based on the extension, for any other file type the `Content-Type`
the response using the `RES_SENDFILE` macro or the `ctorm_res_sendfile`
function.

`Content-Type` header will be set for `html`, `css`, `js` and `json`
files based on the extension, for any other file type the `Content-Type`
will be set to `text/plain` and you may need to manually set it.
```c
RES_SENDFILE("files/index.html");
res_sendfile(res, "files/index.html");
ctorm_res_sendfile(res, "files/index.html");
```
### Working with headers
@@ -91,20 +93,20 @@ function:
```c
// just like the other body functions, you can use local data
RES_SET("Cool", "yes");
res_set(res, "Cool", "yes");
ctorm_res_set(res, "Cool", "yes");
```

To remove a header, you can use `RES_DEL` macro or the `res_del`
function:
```c
RES_DEL("Cool");
res_del(res, "Cool");
ctorm_res_del(res, "Cool");
```
### Redirecting
To redirect the client to another page or a URL, you can use the
`RES_REDIRECT` macro or the `res_redirect` function:
```c
RES_REDIRECT("/login");
res_redirect(res, "/login");
ctorm_res_redirect(res, "/login");
```

0 comments on commit b938aac

Please sign in to comment.