diff --git a/.gitignore b/.gitignore index 7d649b6..7d1f1d4 100644 --- a/.gitignore +++ b/.gitignore @@ -5,7 +5,6 @@ coverage_report *.info *.gcda *.gcno -*.exe -test.out -test.exe +*.out +*.exe diff --git a/Makefile b/Makefile index 476cfdf..18e9715 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ .PHONY: all report -#OSTAG = LINUX -OSTAG = WINDOWS +OSTAG = LINUX +#OSTAG = WINDOWS EXT_WINDOWS = .exe EXT_LINUX = .out @@ -14,6 +14,7 @@ EXT = ${EXT_${OSTAG}} all: gcc -o simple_server$(EXT) examples/simple_server.c tinyhttp.c -Wall -Wextra -DHTTP_CLIENT=0 -DHTTP_ROUTER=0 $(LFLAGS) + gcc -o blocking_server_with_engine$(EXT) examples/blocking_server_with_engine.c tinyhttp.c -Wall -Wextra -DHTTP_CLIENT=0 -DHTTP_ROUTER=0 $(LFLAGS) -g3 gcc -o test$(EXT) tests/test.c tests/test_branch_coverage_parse.c tests/test_branch_coverage_engine.c tests/test_fuzz_engine.c tinyhttp.c -fprofile-arcs -ftest-coverage $(LFLAGS) report: diff --git a/examples/blocking_server_with_engine.c b/examples/blocking_server_with_engine.c new file mode 100644 index 0000000..6b62a02 --- /dev/null +++ b/examples/blocking_server_with_engine.c @@ -0,0 +1,244 @@ +#include + +#ifdef _WIN32 +#include +#include +#include +#define CLOSE_SOCKET closesocket +#else +#include +#include +#include +#include +#include +#include +#include +#define SOCKET int +#define INVALID_SOCKET -1 +#define CLOSE_SOCKET close +#endif + +#include "../tinyhttp.h" + +// This example showcases how to use the engine interface +// to build a blocking HTTP server that works on Windows +// and Linux. + +// Callback used by the engine to manage dynamic memory +static void *memfunc(HTTP_MemoryFuncTag tag, + void *ptr, int len, void *data) +{ + (void) data; + switch (tag) { + + case HTTP_MEMFUNC_MALLOC: + return malloc(len); + + case HTTP_MEMFUNC_FREE: + free(ptr); + return NULL; + } + return NULL; +} + +static void produce_response(HTTP_Engine *eng) +{ + // All considerations in simple_server.c for how responses + // are built also applies here, where http_engine_XXX functions + // are used instead of http_response_XXX functions. + + // Set the response status + http_engine_status(eng, 200); + + // Set zero or more headers + http_engine_header(eng, "Server: tinyhttp", -1); + + // Set some bytes in the body + http_engine_body(eng, "Hello, world!", 13); + + // This is one difference from the http_response_XXX API. + // It's possible to write response content directly into + // the engine's output buffer. This avoids copies in some + // circumstances. + + char msg[] = " What's up??"; + + // First, set how many bytes the output buffer will need + // to hold at least: + http_engine_bodycap(eng, sizeof(msg)-1); + + // Now get the location for the write. The returned pointer + // points to a region of size "cap", which equal or greater + // to the previously set minimum capacity. + int cap; + char *dst = http_engine_bodybuf(eng, &cap); + + // If an error occurred internally, the returned pointer will + // be NULL and the capacity 0. In this case, you can just skip + // this write. The engine will automatically be closed when the + // "http_engine_done" function is called. + if (dst) { + memcpy(dst, msg, sizeof(msg)-1); + + // Tell the engine how many bytes the application wrote to the + // provided buffer. + http_engine_bodyack(eng, sizeof(msg)-1); + } + + // If an error occurs, you can undo all progress and start + // from scratch + int error = rand() & 1; + if (error) { + http_engine_undo(eng); + http_engine_status(eng, 500); + http_engine_done(eng); + return; + } + + http_engine_done(eng); +} + +int main(void) +{ + // Interface and port the server will be listening on. + char *addr = "127.0.0.1"; + int port = 8080; + +#ifdef _WIN32 + WSADATA wd; + if (WSAStartup(MAKEWORD(2, 2), &wd)) + return -1; +#endif + + // Create the listening socket + SOCKET listen_fd = socket(AF_INET, SOCK_STREAM, 0); + if (listen_fd == INVALID_SOCKET) + return -1; + + // Ignore the cooldown time for the bound interface to + // avoid that annoying "address in use" error + int reuse = 1; + setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, (void*) &reuse, sizeof(reuse)); + + // Fill out the address struct + struct sockaddr_in bind_buf; + { + struct in_addr addr_buf; + if (inet_pton(AF_INET, addr, &addr_buf) != 1) + return -1; + + bind_buf.sin_family = AF_INET; + bind_buf.sin_port = htons(port); + bind_buf.sin_addr = addr_buf; + memset(&bind_buf.sin_zero, 0, sizeof(bind_buf.sin_zero)); + } + + // Associate the listening socket to the interface + if (bind(listen_fd, (struct sockaddr*) &bind_buf, sizeof(bind_buf)) < 0) + return -1; + + // Allow incoming connections + if (listen(listen_fd, 32) < 0) + return -1; + + for (;;) { + + printf("Waiting for a connection\n"); + + // Get an incoming connection from the kernel + SOCKET client_fd = accept(listen_fd, NULL, NULL); + if (client_fd == INVALID_SOCKET) + continue; + + printf("New connection\n"); + + // Initialize the HTTP state machine + HTTP_Engine eng; + http_engine_init(&eng, 0, memfunc, NULL); + + for (;;) { + + // At this point, the engine can be in one + // of four states: + // 1) RECV_BUF: The engine is waiting for bytes from the network + // 2) SEND_BUF: The engine wants to write bytes from the network + // 3) CLOSED: The connection shut down at the HTTP layer + // 4) PREP_STATUS: A request was received and the associated response + // needs to be generated. + HTTP_EngineState state = http_engine_state(&eng); + + if (state == HTTP_ENGINE_STATE_SERVER_PREP_STATUS) { + + produce_response(&eng); + + } else if (state == HTTP_ENGINE_STATE_SERVER_RECV_BUF) { + + printf("Receiving bytes\n"); + + // Get a pointer to the engine's input buffer + int cap; + char *dst = http_engine_recvbuf(&eng, &cap); + + // The application can write up to "cap" bytes + // to the "dst" buffer. + + int ret = recv(client_fd, dst, cap, 0); + if (ret <= 0) { + // If the peer disconnected or an error occurred, + // we can "close" the engine. This makes it so any + // further operation on the engine will be a no-op + // and the next time we query the state we will get + // CLOSED. + http_engine_close(&eng); + ret = 0; + } + + printf("Received %d bytes\n", ret); + + // Tell the engine how many bytes we wrote to + // the buffer. + http_engine_recvack(&eng, ret); + + } else if (state == HTTP_ENGINE_STATE_SERVER_SEND_BUF) { + + // This code is the same as the recv case except + // we read from the buffer instead of writing. + + printf("Sending bytes\n"); + + int len; + char *src = http_engine_sendbuf(&eng, &len); + + // Here "src" points to "len" bytes that need to + // be sent over the network. + int ret = send(client_fd, src, len, 0); + if (ret < 0) { + http_engine_close(&eng); + ret = 0; + } + + printf("Sent %d bytes\n", ret); + + http_engine_sendack(&eng, ret); + + } else { + // HTTP_ENGINE_STATE_SERVER_CLOSED + printf("HTTP close\n"); + break; + } + } + + printf("Closing connection\n"); + + http_engine_free(&eng); + CLOSE_SOCKET(client_fd); + } + + CLOSE_SOCKET(listen_fd); + +#ifdef _WIN32 + WSACleanup(); +#endif + + return 0; +} \ No newline at end of file