http-server.c

Basic HTTP server and router in C
git clone git://git.finwo.net/lib/http-server.c
Log | Files | Refs | README

commit 3aba4217e755998f4246325fdf0590535fd80f59
Author: Yersa Nordman <yersa@finwo.nl>
Date:   Sun, 21 May 2023 23:22:08 +0200

Initial setup, responses can be sent

Diffstat:
A.gitignore | 3+++
AMakefile | 31+++++++++++++++++++++++++++++++
Aexample.c | 34++++++++++++++++++++++++++++++++++
Apackage.ini | 6++++++
Asrc/http-server.c | 133+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/http-server.h | 23+++++++++++++++++++++++
6 files changed, 230 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,3 @@ +/lib/ +*.o +/http-server diff --git a/Makefile b/Makefile @@ -0,0 +1,31 @@ +BIN:=http-server + +CPP=g++ +CC=gcc + +LIBS:= +SRC:= + +SRC+=src/http-server.c +SRC+=example.c + +override CFLAGS?=-Wall -s -O2 + +INCLUDES:= +INCLUDES+=-I src + +include lib/.dep/config.mk + +OBJ:=$(SRC:.c=.o) +OBJ:=$(OBJ:.cc=.o) + +override CFLAGS+=$(INCLUDES) + +default: $(BIN) + +.PHONY: clean +clean: + rm -rf $(OBJ) + +$(BIN): $(OBJ) + $(CPP) $(LDFLAGS) $(OBJ) -o $@ diff --git a/example.c b/example.c @@ -0,0 +1,34 @@ +#include <stdio.h> +#include <stdlib.h> + +#include "finwo/http-parser.h" + +#include "http-server.h" + +void onServing(const char **addrs, int naddrs, void *udata) { + for (int i = 0; i < naddrs; i++) { + printf("Serving at %s\n", addrs[i]); + } +} + +void route_get_hello(struct hs_udata *reqdata) { + http_parser_header_set(reqdata->reqres->response, "Content-Type", "text/plain"); + reqdata->reqres->response->body = strdup("Hello World!!"); + reqdata->reqres->response->bodysize = strlen(reqdata->reqres->response->body); + http_server_response_send(reqdata, true); + return; +} + +int main() { + const char *addrs[] = { "tcp://localhost:4000" }; + + struct http_server_events evs = { + .tick = NULL, + .serving = onServing, + .error = NULL, + .close = NULL, + }; + + http_server_route("GET", "/hello", route_get_hello); + http_server_main(addrs, sizeof(addrs) / sizeof(void*), &evs, NULL); +} diff --git a/package.ini b/package.ini @@ -0,0 +1,6 @@ +[dependencies] +finwo/http-parser=https://github.com/finwo/http-parser/archive/refs/tags/edge.tar.gz +tidwall/evio=https://raw.githubusercontent.com/finwo/dep-repository/main/tidwall/evio/package.ini +[package] +deps=lib +name=finwo/http-parser diff --git a/src/http-server.c b/src/http-server.c @@ -0,0 +1,133 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "finwo/http-parser.h" +#include "tidwall/evio.h" + +#include "http-server.h" + +struct evio_udata { + struct http_server_events *hsevs; + void *udata; +}; + +struct hs_route { + void *next; + char *method; + char *path; + void (*fn)(struct hs_udata*); +}; + +struct hs_route *registered_routes = NULL; + +void _hs_onServing(const char **addrs, int naddrs, void *udata) { + struct evio_udata *info = udata; + if (info->hsevs->serving) { + info->hsevs->serving(addrs, naddrs, info->udata); + } +} + +void _hs_onError(const char *message, bool fatal, void *udata) { + struct evio_udata *info = udata; + if (info->hsevs->error) { + info->hsevs->error(message, fatal, info->udata); + } +} + +int64_t _hs_onTick(void *udata) { + struct evio_udata *info = udata; + if (info->hsevs->tick) { + return info->hsevs->tick(info->udata); + } + return 1e9; // 1 second +} + +static void _hs_onRequest(struct http_parser_event *ev) { + struct hs_udata *hsdata = ev->udata; + struct hs_route *route = registered_routes; + struct hs_route *selected_route = NULL; + + // Method/path matching, should be more intricate later + while(route) { + if ( + (!strcmp(ev->request->method, route->method)) && + (!strcmp(ev->request->path , route->path )) + ) { + selected_route = route; + } + route = route->next; + } + + // No 404 handler (yet) + if (!selected_route) { + evio_conn_close(hsdata->connection); + return; + } + + // Call the route handler + selected_route->fn(hsdata); +} + + +void _hs_onOpen(struct evio_conn *conn, void *udata) { + struct evio_udata *info = udata; + struct hs_udata *hsdata = malloc(sizeof(struct hs_udata)); + hsdata->connection = conn; + hsdata->reqres = http_parser_pair_init(hsdata); + hsdata->reqres->onRequest = _hs_onRequest; + evio_conn_set_udata(conn, hsdata); +} + +void _hs_onClose(struct evio_conn *conn, void *udata) { + struct evio_udata *info = udata; + struct hs_udata *hsdata = evio_conn_udata(conn); + + if (info->hsevs->close) { + info->hsevs->close(hsdata, info->udata); + } + + http_parser_pair_free(hsdata->reqres); + free(hsdata); +} + +void _hs_onData(struct evio_conn *conn, const void *data, size_t len, void *udata) { + struct hs_udata *hsdata = evio_conn_udata(conn); + http_parser_pair_request_data(hsdata->reqres, data, len); +} + +void http_server_response_send(struct hs_udata *hsdata, bool close) { + char *response_buffer = http_parser_sprint_response(hsdata->reqres->response); + evio_conn_write(hsdata->connection, response_buffer, strlen(response_buffer)); + free(response_buffer); + if (close) { + evio_conn_close(hsdata->connection); + } +} + +void http_server_route(char *method, char *path, void (*fn)(struct hs_udata*)) { + struct hs_route *route = calloc(1, sizeof(struct hs_route)); + route->next = registered_routes; + route->method = method; + route->path = path; + route->fn = fn; + registered_routes = route; +} + +void http_server_main(char **addrs, int naddrs, struct http_server_events *hsevs, void *udata) { + struct evio_udata *info = calloc(1, sizeof(struct evio_udata)); + info->hsevs = hsevs; + info->udata = udata; + + struct evio_events evs = { + .serving = _hs_onServing, + .error = _hs_onError, + .tick = _hs_onTick, + .opened = _hs_onOpen, + .closed = _hs_onClose, + .data = _hs_onData, + }; + + // This is a forever function + evio_main(addrs, naddrs, evs, info); +} diff --git a/src/http-server.h b/src/http-server.h @@ -0,0 +1,23 @@ +#ifndef __FINWO_HTTP_SERVER_H__ +#define __FINWO_HTTP_SERVER_H__ + +#include <stdbool.h> +#include <stdint.h> + +struct hs_udata { + struct evio_conn *connection; // From the underlaying connection library + struct http_parser_pair *reqres; // The request/response pair +}; + +struct http_server_events { + int64_t (*tick)(void *udata); + void (*serving)(const char **addrs, int naddrs, void *udata); + void (*error)(const char *message, bool fatal, void *udata); + void (*close)(struct hs_udata *conn, void *udata); +}; + +void http_server_response_send(struct hs_udata *hsdata, bool close); +void http_server_route(char *method, char *path, void (*fn)(struct hs_udata*)); +void http_server_main(char **addrs, int naddrs, struct http_server_events *hsevs, void *udata); + +#endif // __FINWO_HTTP_SERVER_H__