Simple HTTP server implemented in C: new trace technology
Tags: gcc, trace, opensource, github
Published: 2023-01-11 10:46:53

This article mainly introduces a tracing technology in the Melon library, and use of an HTTP server as an example to illustrate.

Regarding the Melon library, this is an open source C language library. This library does not depend on any libraries, so it is easy to install and can be used out of the box. **Github repository: [repo](https://github.com/Water-Melon/Melon).**

Let’s go directly to the code

```c
// http.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include "mln_core.h"
#include "mln_log.h"
#include "mln_http.h"
#include "mln_iothread.h"
#include "mln_trace.h"


static void *mln_thread_entry(void *args);
static void mln_accept(mln_event_t *ev, int fd, void *data);
static int mln_http_recv_body_handler(mln_http_t *http, mln_chain_t **in, mln_chain_t **nil);
static void mln_recv(mln_event_t *ev, int fd, void *data);
static void mln_quit(mln_event_t *ev, int fd, void *data);
static void mln_send(mln_event_t *ev, int fd, void *data);
static int mln_http_send_body_handler(mln_http_t *http, mln_chain_t **body_head, mln_chain_t **body_tail);

static void mln_accept(mln_event_t *ev, int fd, void *data)
{
    mln_tcp_conn_t *connection;
    mln_http_t *http;
    int connfd;
    socklen_t len;
    struct sockaddr_in addr;

    while (1) {
        memset(&addr, 0, sizeof(addr));
        len = sizeof(addr);
        connfd = accept(fd, (struct sockaddr *)&addr, &len);
        if (connfd < 0) {
            if (errno == EAGAIN) break;
            if (errno == EINTR) continue;
            perror("accept");
            exit(1);
        }

        connection = (mln_tcp_conn_t *)malloc(sizeof(mln_tcp_conn_t));
        if (connection == NULL) {
            fprintf(stderr, "3No memory.\n");
            close(connfd);
            continue;
        }
        if (mln_tcp_conn_init(connection, connfd) < 0) {
            fprintf(stderr, "4No memory.\n");
            close(connfd);
            free(connection);
            continue;
        }

        http = mln_http_init(connection, NULL, mln_http_recv_body_handler);
        if (http == NULL) {
            fprintf(stderr, "5No memory.\n");
            mln_tcp_conn_destroy(connection);
            free(connection);
            close(connfd);
            continue;
        }

        if (mln_event_fd_set(ev, \
                             connfd, \
                             M_EV_RECV|M_EV_NONBLOCK, \
                             M_EV_UNLIMITED, \
                             http, \
                             mln_recv) < 0)
        {
            fprintf(stderr, "6No memory.\n");
            mln_http_destroy(http);
            mln_tcp_conn_destroy(connection);
            free(connection);
            close(connfd);
            continue;
        }
    }
}

static void mln_quit(mln_event_t *ev, int fd, void *data)
{
    mln_http_t *http = (mln_http_t *)data;
    mln_tcp_conn_t *connection = mln_http_get_connection(http);

    mln_event_fd_set(ev, fd, M_EV_CLR, M_EV_UNLIMITED, NULL, NULL);
    mln_http_destroy(http);
    mln_tcp_conn_destroy(connection);
    free(connection);
    close(fd);
}

static void mln_recv(mln_event_t *ev, int fd, void *data)
{
    mln_http_t *http = (mln_http_t *)data;
    mln_tcp_conn_t *connection = mln_http_get_connection(http);
    int ret, rc;
    mln_chain_t *c;

    while (1) {
        ret = mln_tcp_conn_recv(connection, M_C_TYPE_MEMORY);
        if (ret == M_C_FINISH) {
            continue;
        } else if (ret == M_C_NOTYET) {
            c = mln_tcp_conn_remove(connection, M_C_RECV);
            if (c != NULL) {
                rc = mln_http_parse(http, &c);
                if (c != NULL) {
                    mln_tcp_conn_append_chain(connection, c, NULL, M_C_RECV);
                }
                if (rc == M_HTTP_RET_OK) {
                    return;
                } else if (rc == M_HTTP_RET_DONE) {
                    mln_send(ev, fd, data);
                } else {
                    fprintf(stderr, "Http parse error. error_code:%u\n", mln_http_get_error(http));
                    mln_quit(ev, fd, data);
                    return;
                }
            }
            break;
        } else if (ret == M_C_CLOSED) {
            c = mln_tcp_conn_remove(connection, M_C_RECV);
            if (c != NULL) {
                rc = mln_http_parse(http, &c);
                if (c != NULL) {
                    mln_tcp_conn_append_chain(connection, c, NULL, M_C_RECV);
                }
                if (rc == M_HTTP_RET_ERROR) {
                    fprintf(stderr, "Http parse error. error_code:%u\n", mln_http_get_error(http));
                }
            }
            mln_quit(ev, fd, data);
            return;
        } else if (ret == M_C_ERROR) {
            mln_quit(ev, fd, data);
            return;
        }
    }
}

static int mln_http_recv_body_handler(mln_http_t *http, mln_chain_t **in, mln_chain_t **nil)
{
    mln_u32_t method = mln_http_get_method(http);
    mln_trace("s", method == M_HTTP_GET? "GET": "OTHERS");
    if (method == M_HTTP_GET)
        return M_HTTP_RET_DONE;
    mln_http_set_error(http, M_HTTP_NOT_IMPLEMENTED);
    return M_HTTP_RET_ERROR;
}

static void mln_send(mln_event_t *ev, int fd, void *data)
{
    mln_http_t *http = (mln_http_t *)data;
    mln_tcp_conn_t *connection = mln_http_get_connection(http);
    mln_chain_t *c = mln_tcp_conn_get_head(connection, M_C_SEND);
    int ret;

    if (c == NULL) {
        mln_http_reset(http);
        mln_http_set_status(http, M_HTTP_OK);
        mln_http_set_version(http, M_HTTP_VERSION_1_0);
        mln_http_set_type(http, M_HTTP_RESPONSE);
        mln_http_set_handler(http, mln_http_send_body_handler);
        mln_chain_t *body_head = NULL, *body_tail = NULL;
        if (mln_http_generate(http, &body_head, &body_tail) == M_HTTP_RET_ERROR) {
            fprintf(stderr, "mln_http_generate() failed. %u\n", mln_http_get_error(http));
            mln_quit(ev, fd, data);
            return;
        }
        mln_tcp_conn_append_chain(connection, body_head, body_tail, M_C_SEND);
    }

    while ((c = mln_tcp_conn_get_head(connection, M_C_SEND)) != NULL) {
        ret = mln_tcp_conn_send(connection);
        if (ret == M_C_FINISH) {
            mln_quit(ev, fd, data);
            break;
        } else if (ret == M_C_NOTYET) {
            mln_chain_pool_release_all(mln_tcp_conn_remove(connection, M_C_SENT));
            mln_event_fd_set(ev, fd, M_EV_SEND|M_EV_APPEND|M_EV_NONBLOCK, M_EV_UNLIMITED, data, mln_send);
            return;
        } else if (ret == M_C_ERROR) {
            mln_quit(ev, fd, data);
            return;
        } else {
            fprintf(stderr, "Shouldn't be here.\n");
            abort();
        }
    }
}

static int mln_http_send_body_handler(mln_http_t *http, mln_chain_t **body_head, mln_chain_t **body_tail)
{
    mln_u8ptr_t buf;
    mln_alloc_t *pool = mln_http_get_pool(http);
    mln_string_t cttype_key = mln_string("Content-Type");
    mln_string_t cttype_val = mln_string("text/html");

    buf = mln_alloc_m(pool, 5);
    if (buf == NULL) {
        mln_http_set_error(http, M_HTTP_INTERNAL_SERVER_ERROR);
        return M_HTTP_RET_ERROR;
    }
    memcpy(buf, "hello", 5);

    if (mln_http_set_field(http, &cttype_key, &cttype_val) == M_HTTP_RET_ERROR) {
        mln_http_set_error(http, M_HTTP_INTERNAL_SERVER_ERROR);
        return M_HTTP_RET_ERROR;
    }

    mln_string_t ctlen_key = mln_string("Content-Length");
    mln_string_t ctlen_val = mln_string("5");
    if (mln_http_set_field(http, &ctlen_key, &ctlen_val) == M_HTTP_RET_ERROR) {
        mln_http_set_error(http, M_HTTP_INTERNAL_SERVER_ERROR);
        return M_HTTP_RET_ERROR;
    }

    mln_chain_t *c = mln_chain_new(pool);
    if (c == NULL) {
        mln_http_set_error(http, M_HTTP_INTERNAL_SERVER_ERROR);
        return M_HTTP_RET_ERROR;
    }
    mln_buf_t *b = mln_buf_new(pool);
    if (b == NULL) {
        mln_chain_pool_release(c);
        mln_http_set_error(http, M_HTTP_INTERNAL_SERVER_ERROR);
        return M_HTTP_RET_ERROR;
    }
    c->buf = b;
    b->left_pos = b->pos = b->start = buf;
    b->last = b->end = buf + 5;
    b->in_memory = 1;
    b->last_buf = 1;
    b->last_in_chain = 1;

    if (*body_head == NULL) {
        *body_head = *body_tail = c;
    } else {
        (*body_tail)->next = c;
        *body_tail = c;
    }

    return M_HTTP_RET_DONE;
}

static void *mln_thread_entry(void *args)
{
    mln_event_dispatch((mln_event_t *)args);
    return NULL;
}

int main(int argc, char *argv[])
{
    mln_event_t *ev;
    mln_iothread_t t;
    struct sockaddr_in addr;
    int val = 1, listenfd;
    int nthread = 1;
    mln_u16_t port = 1234;
    mln_s8_t ip[] = "0.0.0.0";
    struct mln_core_attr cattr;
    struct mln_iothread_attr tattr;

    /* init library */
    cattr.argc = 0;
    cattr.argv = NULL;
    cattr.global_init = NULL;
    cattr.master_process = NULL;
    cattr.worker_process = NULL;
    mln_core_init(&cattr);

    /* create event instance */
    if ((ev = mln_event_new()) == NULL) {
        mln_log(error, "event new error.\n");
        return -1;
    }

    /* set listen fd */
    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd < 0) {
        mln_log(error, "listen socket error\n");
        return -1;
    }
    if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0) {
        mln_log(error, "setsockopt error\n");
        return -1;
    }
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    addr.sin_addr.s_addr = inet_addr(ip);
    if (bind(listenfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        mln_log(error, "bind error\n");
        return -1;
    }
    if (listen(listenfd, 511) < 0) {
        mln_log(error, "listen error\n");
        return -1;
    }
    if (mln_event_fd_set(ev, \
                         listenfd, \
                         M_EV_RECV|M_EV_NONBLOCK, \
                         M_EV_UNLIMITED, \
                         NULL, \
                         mln_accept) < 0)
    {
        mln_log(error, "listen sock set event error\n");
        return -1;
    }

    /* init trace */
    if (mln_trace_init(ev, mln_trace_path()) < 0) {
        mln_log(error, "trace init failed.\n");
        return -1;
    }

    /* create threads */
    tattr.nthread = nthread;
    tattr.entry = (mln_iothread_entry_t)mln_thread_entry;
    tattr.args = ev;
    tattr.handler = NULL;
    if (mln_iothread_init(&t, &tattr) < 0) {
        mln_log(error, "iothread init failed\n");
        return -1;
    }

    mln_event_dispatch(ev);
    return 0;
}
```
In these 330 lines of code, we implemented a simple HTTP1.1 server by the Melon library. The behavior of this server is that it will reply a `Hello` when it receives a `GET` request.

In this example, we started two threads (main thread + 1 worker thread), and both threads will operate on the same event object (`ev`). The socket of our HTTP server depends on this event object `ev`. In other words, event objects can be dispatched across threads, and the event model helps us shield the problem of thundering herd.

> In fact, only single thread can be used here, just to demonstrate and prove to readers that the event model supports multithreading.

Then let’s discuss tracing in Melon. We can see that the `mln_trace_init` function is called in main to initialize the tracing task. The tracing data in this example is processed by a script task. Although the script task also rely on the event model, but it does not prevent socket events from being fired and processed. **Simply speaking, all file descriptor and timer events, and script execution are processed in a time-sharing manner.** Infinite loop in the script will not blocked event processing. In addition, if trace configuration is not existent or inactivated, the program will not emit trace information (refer to `mln_trace` called in `mln_accept`).

The trace script uses the built-in scripting language [Melang](https://melang.org) in the Melon library. Let’s take a look at the Melang script to this example:

```
sys = Import('sys');

Pipe('subscribe');
beg = sys.time();
sum = [];
while (1) {
    ret = Pipe('recv');
    if (!ret) {
       now = sys.time();
       if (now - beg >= 3) {
           sys.print(sum);
           beg = now;
       } fi
       sys.msleep(10);
    } else {
       n = sys.size(ret);
       for (i = 0; i < n; ++i) {
           if (!(sys.has(sum, ret[i][0])))
               sum[ret[i][0]] = 0;
           fi
           sum[ret[i][0]]++;
       }
    }
}
Pipe('unsubscribe');
```
In this code, we will classify and count all requests according to its method and output the statistical results every 3 seconds.

Next, we need to modify the configuration file of Melon, and remove the comments from the `trace_mode` configuration item.

Note: If you are starting the http server in the Melon source directory, you should modify the `Melon/trace/trace.m` file. Otherwise, the `/usr/local/lib/melang/trace/trace.m` file should be modified, because the interpreter will first check whether there is such a file in the current path.

Then let’s run the program

```shell
#tty1
$ ./http
```

```shell
#tty2
$ ab -c 100 -n 100 http://127.1:1234/
```
Then we can see that tty1 will output

```
...
[]
[]
[100, ]
...
```
And as ab is called multiple times, the value in the array will also increase accordingly.

Finally, summarize the benefits of doing this.
1. With this trace mode, you can turn on and off the trace as needed, and you can even call the trace module interfaces in the program to dynamically turn on and off the trace mode.
2. The Melang script itself also supports communication with MySQL, so the trace data can be summarized and stored in DB from the script layer. So that there is no need to add a large number of statistical variables in the C program.