hellerf / EmbeddableWebServer

Cross-platform, single .h file HTTP server (Windows, Linux, Mac OS X)
https://forrestheller.com/embeddable-c-web-server
102 stars 13 forks source link

EWS - Single .h File C Embeddable Web Server

Latest Version: 1.1.4 released September 9, 2021
Supported platforms: Linux, Mac OS X, Windows
License: BSD 2-clause

Embeddable Web Server is a web server in a single header file and has no external dependencies in the tradition of the STB libraries. It can serve static files and handle responses with C/C++ code (the server compiles in both). Here's how to use it:

  1. #include "EmbeddableWebServer.h" (and #define EWS_HEADER_ONLY if you included it somewhere else)
  2. Call acceptConnectionsUntilStoppedFromEverywhereIPv4(NULL), which will initialize a new server and block Note: If you want to take connections from a specific inteface/localhost you can use acceptConnectionsUntilStopped
  3. Fill out createResponseForRequest. Use the responseAlloc* functions to return a response or take over the connection yourself and return NULL. The easiest way to serve static files is responseAllocServeFileFromRequestPath. The easiest way to serve HTML is responseAllocHTML. The easiest way to serve JSON is responseAllocJSON. The server will free() your response once it's been sent. When compiled with Objective-C, an autoreleasepool will be created for you.
  4. Use the connectionDebugStringCreate to aid in debugging.


See the EWSDemo.cpp file for more examples like chunked transfer, HTML forms, and JSON responses.

If you want to control server setup/teardown use serverInit, serverStop, and serverDeInit and pass that same Server in acceptConnectionsUntilStopped.

Quick Example

#include "EmbeddableWebServer.h"
#pragma comment(lib, "ws2_32")

int main(int argc, char* argv[]) {
    return acceptConnectionsUntilStoppedFromEverywhereIPv4(NULL, 8080);
}

struct Response* createResponseForRequest(const struct Request* request, struct Connection* connection) {
    if (0 == strcmp(request->pathDecoded, "/welcome")) {
        return responseAllocHTML("<html><body><marquee><h1>Welcome to my home page</h1></marquee></body></html>");
    }
    if (0 == strcmp(request->pathDecoded, "/status/json")) {
        static const char* statuses[] = { ":-)", ":-(", ":-|" };
        int status = rand() % (sizeof(statuses) / sizeof(*statuses));
        /* There is also a family of responseAllocJSON functions */
        return responseAllocWithFormat(200, "OK", "application/json", "{ \"status\" : \"%s\" }", statuses[status]);
    }
    if (0 == strcmp(request->pathDecoded, "/100_random_numbers")) {
        struct Response* response = responseAllocHTML("<html><body><h1>100 Random Numbers</h1><ol>");
        for (int i = 1; i <= 100; i++) {
            heapStringAppendFormat(&response->body, "<li>%d</li>\n", rand());
        }
        heapStringAppendString(&response->body, "</ol></body></html>");
        return response;
    }
    /* Serve files from the current directory */
    if (request->pathDecoded == strstr(request->pathDecoded, "/files")) {
        return responseAllocServeFileFromRequestPath("/files", request->path, request->pathDecoded, ".");
    }
    return responseAlloc404NotFoundHTML("What?!");
}

Features and use cases

Warning

This server is suitable for controlled applications which will not be accessed over the general Internet. If you are determined to use this on Internet I advise you to use a proxy server in front (like haproxy, squid, or nginx). However I found and fixed only 2 crashes with alf-fuzz...

Implementation

The server is implemented in a thread-per-connection model. This way you can do slow, hacky things in a request and not stall other requests. On the other hand this uses ~40KB + request body + response body of memory per connection. All strings are assumed to be UTF-8. On Windows, UTF-8 file paths are converted to their wide-character (wchar_t) equivalent so you can serve files with Chinese characters and so on.

The server assumes all strings are UTF-8. When accessing the file system on Windows, EWS will convert to/from the wchar_t representation and use the appropriate APIs.

pthreads wrapper for Windows

Since EWS uses threads we need to have a way to launch threads on all platforms. pthreads are supported on most of the operating systems this targets. Hence, EWS targets pthreads directly. EWS includes a very light wrapper for pthreads that supports thread creation, mutexes, and condition variables.

Example of launching a server thread from your app

#include "EmbeddableWebServer.h"
#ifdef WIN32
#pragma comment(lib, "ws2_32") // link against Winsock on Windows
#endif

static int counter = 0;
static struct Server server;
static THREAD_RETURN_TYPE STDCALL_ON_WIN32 acceptConnectionsThread(void* unusedParam) {
    serverInit(&server);
    const uint16_t portInHostOrder = 8080;
    acceptConnectionsUntilStoppedFromEverywhereIPv4(&server, portInHostOrder);
    return (THREAD_RETURN_TYPE) 0;
}

int main() {
    pthread_t threadHandle;
    pthread_create(&threadHandle, NULL, &acceptConnectionsThread, NULL);
    while (1) {
        counter++;
    }
    // rest of the program
    return 0;
}

struct Response* createResponseForRequest(const struct Request* request, struct Connection* connection) {
    return responseAllocHTMLWithFormat("The counter is currently %d\n", counter);
}

Comparison to other really light embeddable web servers

Change log

1.1.4

1.1.3

1.1.2

1.1.1

1.1.0

1.0

Things I would change

If rewriting this from scratch I would change: