Embeddable HTTP server.

Overview

yocto HTTP server

The yocto HTTP server is a small embeddable web server, with a convenient public domain licence. Use it to add a web server to your program for debugging, introspection or remote control.

You specify the paths of “files” and “folders” you want to make available, and callbacks to be called when they are requested, and the yocto HTTP server handles the rest. When your callback is called, you can use convenient stdio-style functions to send text or binary data to the browser, or transmit image data.

Of course, if you just want some files serving from folders on disk, the yocto HTTP server will do that.

Also, WebSockets.

The yocto HTTP server has been written for ease of embedding and ease of use, under the assumption that it will be used as a development and debugging aid. Security and performance were not design goals.

Installation

iOS, Mac OS X and Windows (VC++) are supported. It can be built as C89, C99 or C++.

  1. Add yhs.c and yhs.h to your project;
  2. Include yhs.h in files that need it;
  3. Add function calls as described below;
  4. Add #ifdef (etc.) to make very sure you won’t ship with it running;
  5. PROFIT.

Use

This file provides a conversational overview. Please consult the header file as well.

Start, update and stop server

A particular server (serving a particular tree of “files” on a particular port) is represented by a yhsServer pointer:

yhsServer *server;

Create one using yhs_new_server, supplying port:

server = yhs_new_server(80);

You can name your server, if you like. Its name will appear in any error pages.

yhs_set_server_name(server,"my amazing server");

Each time round your main loop, call yhs_update to keep the server ticking over:

yhs_update(server);

When you’re done, call yhs_delete_server to free up the server and its resources:

yhs_delete_server(server);
server=NULL;

Adding things to serve

Use yhs_add_res_path_handler to add a callback (see below) for a particular path:

yhs_add_res_path_handler(server,"/res/",&handle_root,NULL);

The argument for context is stored and made available to the callback.

Paths not ending in / are considered files, and their callback will be called when a request is made for that exact path.

Paths ending in / are considered folders, meaning the callback will be called for any file in that folder (at whatever depth), if there isn’t a closer-matching folder or file handler for it.

If there’s no handler added for the root folder /, GET requests for / will be responded to automatically with a contents page.

The server will respond to any other unhandled path with a 404 page.

Serving things

The handler callback has the following signature:

extern "C" typedef void (*yhsResPathHandlerFn)(yhsRequest *re);

re points to the (opaque) request object. There are various functions to get details about the request:

  • yhs_get_path retrieves the specified path, and yhs_get_path_handler_relative retrieves the part that’s relative to the path supplied to yhs_add_res_path_handler.
  • yhs_get_method and yhs_get_method_str retrieve the HTTP method. yhs_get_method returns one of the values from the (not exhaustive) yhsMethod enum, and yhs_get_method_str returns the actual method name string.
  • yhs_find_header_field allows the request header fields to be queried.

You can also use yhs_get_handler_context and yhs_get_handler_path to retrieve the values supplied to yhs_add_res_path_handler.

On entry to the callback, any content is available for reading (if you want it), and the server is ready for your callback to provide a response, as described below.

Once you have sent the response, just return from the callback and appropriate action will be taken automatically. If your callback doesn’t provide any response, the server will automatically provide a 404 page.

(You can respond to a HEAD request in exactly the same way as a GET request. The server checks for HEAD specially, and will discard any response body in that case, leaving just the headers.)

Data response

Use yhs_begin_data_response to start a data response, supplying MIME type of data being sent:

yhs_begin_data_response(re,"text/html");

Then use yhs_text (works like printf) to send raw text:

yhs_text(re,"<html><head><title>Hello</title></head><body><p>%d</p></body></html>",rand());

Also available are yhs_textv (works like vprintf), yhs_text (works like fputs), yhs_data (works a bit like fwrite), and yhs_data_byte (works a bit like fputc).

If you’re responding with HTML, there are a set of convenience functions, yhs_html_text*, which can add in HTML escapes and optionally replace \n with <BR>.

yhs_html_text(re,YHS_HEF_BR,random_text);

These functions perform a bit of buffering, so don’t be afraid to write single bytes or chars.

Between calling yhs_begin_data_response and yhs_text (or similar), you can add extra HTTP header fields to the response using yhs_header_field:

yhs_header_field(re,"X-Powered-By","C");

(yhs_begin_data_response will already have added an appropriate Content-Type field.)

Image response

Use yhs_begin_image_response to start an image response. Supply width, height and bytes per pixel of image:

yhs_begin_image_response(re,256,256,3);

Then for each pixel – and you must supply every pixel – call yhs_pixel to specify red, green, blue and alpha:

for(int y=0;y<256;++y) {
    for(int x=0;x<256;++x)
        yhs_pixel(re,rand()&255,rand()&255,rand()&255,255);
}

Do please note that the PNGs are not compressed.

Between calling yhs_begin_image_response and yhs_text (or similar), you can add extra HTTP header fields to the response using yhs_header_field:

yhs_header_field(re,"X-Powered-By","C");

(yhs_begin_image_response will already have added an appropriate Content-Type field.)

Error response

Call yhs_error_response to generate an HTTP error page. Provide the HTTP status line, e.g., “200 OK”.

303 See Other response

Use yhs_see_other_response to direct the browser to GET a different URL.

Serving a tree of files

The server is primarily designed for serving data using the callbacks, but you can use the supplied yhs_file_server_handler handler to supply a tree of local files. You might use this for icons, say, or Javascript.

When adding the file server handler, supply the local path as the context pointer:

yhs_add_res_path_handler(server,"/resources/",&yhs_file_server_handler,(void *)"./web_resources/");

If a folder is requested rather than a file, the server will respond with a simple files listing page.

Deferred responses

You may want to put off responding to a request, if it can’t be conveniently responded to in the middle of the server update. You can call yhs_defer_response to do this.

Requests with deferred responses are held in a list, so you can work through them later. You can maintain one list of all such requests, or have multiple lists.

Each list is represented by a yhsRequest *, holding a pointer to the head. It should start out NULL.

yhsRequest *list=NULL;

To defer a response, pass the request you’re dealing with, and a pointer to the list head pointer:

yhs_defer_response(re,&list);

This allocates a copy of the current request, adds it to the list, and invalidates *re. (yhs_defer_response may fail and return 0, if the allocation fails; in that case, the list will be unchanged, and the server will end up producing a 404. So most of the time, you probably won’t need to check.)

Then later, work through the list and make progress with each response using the functions above. Then, to advance your current item pointer to the next request in the list, use yhs_next_request_ptr to leave the response in progress or yhs_end_deferred_response to finish it up and remove it from the list.

The expected code is along these lines:

yhsRequest **cur=&list;
while(*cur) {
    /* do stuff to **cur */
    if(/* finished with **cur */)
        yhs_end_deferred_response(cur);
    else
        yhs_next_request_ptr(cur);
}

Content

If the request has content associated with it, use yhs_get_content to retrieve it. Check for associated content by looking for the Content-Length header field by hand, or use yhs_get_content_details to do the check. yhs_get_content_details will retrieve Content-Length as an int, and find any Content-Type field supplied too.

You can retrieve the content all in one go, or in parts.

Forms

Helpers are provided for processing data from POST method forms in application/x-www-form-urlencoded format. (GET forms, and multipart/form-data, are not specifically catered for.)

In the handler, use yhs_read_form_content:

int is_form_data_ok=yhs_read_form_content(re);
if(!is_form_data_ok) {
    /* error (probably unlikely) */
    return;
}

This allocates some memory to save off the form data. This memory is freed automatically when the response finishes.

You can (try to) retrieve a control’s value by control name, using yhs_find_control_value:

const char *value=yhs_find_control_value(re,"value name");

The result is NULL if the value doesn’t exist.

You can also iterate through all the names and values available:

for(size_t i=0;i<yhs_get_num_controls(re);++i) {
    const char *name=yhs_get_control_name(re,i);
    const char *value=yhs_get_control_value(re,i);
}

The pointers point into the data set up by yhs_read_form_content. The pointed-to data must be copied if it is to be kept past the end of the response.

Handler configuration

After adding a handler for a path, you can configure it.

Use yhs_add_to_toc to add the handler to the contents page. A link is provided to the handler’s path; by default, the text of the link is the path too, but you can use yhs_set_handler_description to provide something friendlier.

Use yhs_set_valid_methods to set the valid HTTP methods for the path. The default valid methods are GET and HEAD only. The server will ignore any requests for a path using an invalid method (so that most handlers won’t have to check the method).

The configure functions return the supplied handler, so you can do everything on one line:

yhs_add_to_toc(yhs_set_handler_description("test handler",yhs_add_res_path_handler(server,"/test",&test_func,NULL)));

WebSockets

yhs supports WebSockets as per RFC 6455 (http://tools.ietf.org/html/rfc6455).

yhs passes the AutobahnTestsuite Websocket tests (http://autobahn.ws/testsuite), suggesting that it actually works.

WebSocket connections

To set up a potential WebSocket connection, use yhs_set_valid_methods to add YHS_METHOD_WEBSOCKET as a valid method for the handler.

yhs_set_valid_methods(YHS_METHOD_WEBSOCKET,yhs_add_res_path_handler(server,"/ws",&ws_func,NULL));

In the handler, yhs_get_method will return YHS_METHOD_WEBSOCKET if there is a WebSocket connection attempt being made. Use yhs_accept_websocket to approve it, and put the connection into WebSocket mode.

Once the connection is in WebSocket mode, call yhs_is_websocket_open to see if the connection is still open. The WebSocket MUST (their words, not mine!) be closed at the slightest provocation, so it might become closed unexpectedly.

WebSocket connections are expected to be deferred, but there’s no obligation.

Receiving WebSocket data

To receive data on the WebSocket, or try to, use yhs_begin_recv_websocket_frame. yhs_begin_recv_websocket_frame is non-blocking, and will return 1 if there is data waiting, and optionally set a variable to indicate whether the incoming frame is text or binary.

Once yhs_begin_recv_websocket_frame returns 1, the data is ready for reading, and you are committed to reading it. Use yhs_recv_websocket_data to do this. yhs_recv_websocket_data will attempt to fill a buffer with incoming data, stopping when the buffer is full, the entire frame has been read, or something else happened (some kind of error, or WebSocket closed).

(yhs will automatically handle continuation frames; you can’t detect the fragmentation.)

Once you’ve read the data, call yhs_end_recv_websocket_frame to stop. If there is unread data in the frame, it will be silently discarded.

int is_text;
if(yhs_begin_recv_websocket_frame(re,&is_text)) {
    char buf[1000];
    size_t n;
    if(yhs_recv_websocket_data(re,buf,sizeof buf,&n)) {
        /* stuff */
    }
}

yhs_recv_websocket_data will always fill the entire buffer if there’s data to fill it with, and will block if required. If the read succeeded, and the size read is less than the size of the buffer, all the data in the frame has been read.

Receiving a WebSocket text frame

If the incoming data is text, yhs still allows you to treat it as a sequence of bytes for reading purposes. This means you can read partial UTF-8 byte sequences (e.g., if you’re receiving 1 byte at a time), leaving you with invalid intermediate UTF-8. So take care.

Additionally, the UTF-8 data is validated char-by-char rather than byte-by-byte, so you can receive parts of obviously invalid UTF-8 byte sequences as well, if yhs has yet to see the entire char to validate it. So… take care with that, too.

All in all, if reading a text frame, you’re advised to read the whole thing in before doing anything with it.

Sending WebSocket data

To start sending a frame of data, use yhs_begin_send_websocket_frame, supplying a flag indicating whether the frame is text or binary.

Once the frame is started, use the various data sending functions (yhs_text*, yhs_data*) to send data. (yhs will fragment the frame at its discretion, if necessary.) Then call yhs_end_send_websocket_frame once done.

If sending a text frame, it must be valid UTF-8, but yhs doesn’t check, under the assumption that the client will.

Closing the WebSocket

If the request isn’t deferred, the WebSocket will be closed when the handler returns; if the request is deferred, use yhs_end_deferred_response to close it.

Tweakables

There are some tweakable macros and constants near the top of the .c file. There’s no API for changing these; just edit them using a text editor.

Constants

The main ones:

MAX_REQUEST_SIZE
max supported size of HTTP header included in request. Server will return a 500 Internal Server Error if the client exceeds this.
MAX_TEXT_LEN
size of buffer used for format string expansion. Affects maximum possible length of output from yhs_textf and yhs_textv.
WRITE_BUF_SIZE
size of buffer used when writing, to avoid lots of little send socket calls.

MAX_TEXT_LEN and WRITE_BUF_SIZE contribute to the size of the yhsServer object; MAX_REQUEST_SIZE contributes to the amount of stack required by the yhs_update call.

Memory allocation

There are two malloc macros, MALLOC and FREE, by default wrapping malloc and free respectively.

Logging

There are 3 logging macros, YHS_DEBUG_MSG, YHS_INFO_MSG and YHS_ERR_MSG. These are invoked just like printf, and are assumed to expand to a single statement.

By default, debug and info messages go to stdout, and errors go to stderr.

Notes

  • The server uses blocking sockets and makes blocking socket calls, so yhs_update could take pretty much any amount of time, if there’s something to do. (yhs_update will return 1 if it did anything significant, the idea being that the game avoids playing logic catch-up in this case. No timing is actually performed; this is just a quick hack.)

TODOs

  • 303 would probably be a better default response to a POST than 404.
  • Optional integration with miniz or stb_image_write to serve compressed PNGs
  • Optional integration with miniz for gzip’d transfers
  • Support “Transfer-Encoding: chunked”?
  • Maybe do something nicer about form content?
  • Have the file server handler check for index.html and, if present, respond with its contents rather than the files listing?
  • Currently does a poor job of handling duplicated header lines conveniently; instead of requiring repeated yhs_find_header_field calls to find them all, it should just join them on receipt (see section 4.2) and provide some API for accessing comma-separated lists as a list as well as the string. C strings :(
  • No support for selecting the WebSocket protocol
  • Add mutex as appropriate, so deferred connections can be serviced on other threads (caller is responsible for thread safety of the chain but yhs will have to do the next_deferred/prev_deferred list)
  • Add some kind of context pointer to a yhsRequest when deferring, so the caller can store some action data or something? (Making it easier to have all of them in one big list, so there’s only one mutex for the caller to maintain?)
  • yhs_get_content and yhs_recv_websocket_data should probably be much more similar than they are.
  • Is “yocto” still appropriate?

Other embeddable web serving options

If you disagree with the choices made here, perhaps one of these other offerings will be more to your taste.

mongoose

http://code.google.com/p/mongoose/

libmicrohttpd

http://www.gnu.org/software/libmicrohttpd/

tulrich-testbed

http://tu-testbed.svn.sourceforge.net/viewvc/tu-testbed/trunk/tu-testbed/net/

EasyHTTPD

http://sourceforge.net/projects/ehttpd/

EHS

http://ehs.fritz-elfert.de

poco

http://pocoproject.org/

You might also like...
A C++ header-only HTTP/HTTPS server and client library
A C++ header-only HTTP/HTTPS server and client library

cpp-httplib A C++11 single-file header-only cross platform HTTP/HTTPS library. It's extremely easy to setup. Just include the httplib.h file in your c

Ultra fast and low latency asynchronous socket server & client C++ library with support TCP, SSL, UDP, HTTP, HTTPS, WebSocket protocols and 10K connections problem solution
Ultra fast and low latency asynchronous socket server & client C++ library with support TCP, SSL, UDP, HTTP, HTTPS, WebSocket protocols and 10K connections problem solution

CppServer Ultra fast and low latency asynchronous socket server & client C++ library with support TCP, SSL, UDP, HTTP, HTTPS, WebSocket protocols and

C++ library for creating an embedded Rest HTTP server (and more)

The libhttpserver reference manual Tl;dr libhttpserver is a C++ library for building high performance RESTful web servers. libhttpserver is built upon

Mongoose Embedded Web Server Library - a multi-protocol embedded networking library with TCP/UDP, HTTP, WebSocket,  MQTT built-in protocols, async DNS resolver, and non-blocking API.
Mongoose Embedded Web Server Library - a multi-protocol embedded networking library with TCP/UDP, HTTP, WebSocket, MQTT built-in protocols, async DNS resolver, and non-blocking API.

Mongoose - Embedded Web Server / Embedded Networking Library Mongoose is a networking library for C/C++. It implements event-driven non-blocking APIs

cherry: A Minimal HTTP Server
cherry: A Minimal HTTP Server

cherry: A Minimal HTTP Server Inspired by the Capriccio project and the Zaver HTTP server, cherry started out as an experimental project trying to inc

http server code by c
http server code by c

Lamphttp HTTP服务 Lamphttp是使用c语言实现的http服务,目前市面上有非常多的http服务,比如大名鼎鼎的Nginx 那么对于Lamphttp存在的意义是什么呢?对于Lamphttp主要是为了理解了tcp/ip到http的中间 这一层的实现,说白了就是当作学习用的. 虽然Lam

Phorklift is an HTTP server and proxy daemon, with clear, powerful and dynamic configuration.

Phorklift is an HTTP server and proxy daemon, with clear, powerful and dynamic configuration.

kleinsHTTP: A stupid stupidly simple http server.

kleinsHTTP: A stupid stupidly simple http server. About I wanted to create an api server using C++ as it is my favorite language however i was unable

prometheus exporter using workflow HTTP server
prometheus exporter using workflow HTTP server

wfprometheus This is a light prometheus exporter using workflow HTTP server. This project is currently in the development stage, and the first version

Comments
  • fix build on macOS

    fix build on macOS

    On Xcode Version 9.2 (9C40b) with clang Apple LLVM version 9.0.0 (clang-900.0.39.2) and macOS Sierra Version 10.12.6 (16G29), the build did not work due to some errors. I have corrected them by doing the following:

    • SD_SEND is not defined, the equivalent is SHUT_WR (actually, SD_SEND is typically a re-definition of SHUT_WR). Add a case checking if the compiler is targeting apple devices and use SHUT_WR instead.
    • YHS_PRINTF_LIKE(3,4) was giving me an index out of range error. I looked at the code a few lines up (line 278) and it appears like the correct numbering was YHS_PRINTF_LIKE(2,3) so I changed it to this.

    Tested as working on my setup (listed above) only. Don't think this should affect any other builds.

    opened by ohnx 0
  • Fix deferred request linked list

    Fix deferred request linked list

    yhs_next_request_ptr was a no-op, and yhs_end_deferred_response would move the head of the list when iterating. Simply fixing yhs_next_request_ptr would mean that you had no way to re-iterate the list, as you would have "lost" your head pointer.

    opened by jorgenpt 0
Owner
Tom Seddon
Tom Seddon
Embeddable HTTP server.

yocto HTTP server The yocto HTTP server is a small embeddable web server, with a convenient public domain licence. Use it to add a web server to your

Tom Seddon 75 Dec 17, 2022
Simple embeddable C++11 async tcp,http and websocket serving.

net11 Simple embeddable C++11 async tcp,http and websocket serving. What is it? An easily embeddable C++11 networking library designed to make buildin

Jonas Lund 9 Mar 28, 2020
A collection of C++ HTTP libraries including an easy to use HTTP server.

Proxygen: Facebook's C++ HTTP Libraries This project comprises the core C++ HTTP abstractions used at Facebook. Internally, it is used as the basis fo

Facebook 7.7k Jan 4, 2023
Pushpin is a reverse proxy server written in C++ that makes it easy to implement WebSocket, HTTP streaming, and HTTP long-polling services.

Pushpin is a reverse proxy server written in C++ that makes it easy to implement WebSocket, HTTP streaming, and HTTP long-polling services. The project is unique among realtime push solutions in that it is designed to address the needs of API creators. Pushpin is transparent to clients and integrates easily into an API stack.

Fanout 3.2k Jan 2, 2023
Gromox - Groupware server backend with MAPI/HTTP, RPC/HTTP, IMAP, POP3 and PHP-MAPI support for grommunio

Gromox is the central groupware server component of grommunio. It is capable of serving as a replacement for Microsoft Exchange and compatibles. Conne

grommunio 139 Dec 26, 2022
A project designed for the esp8266 D1 Mini or the esp8266 D1 Mini PRO to provide a wifi http server and dns server.

PS4 Server 9.00 This is a project designed for the esp8266 D1 Mini or the esp8266 D1 Mini PRO to provide a wifi http server and dns server. this is fo

null 14 Nov 28, 2022
tiny HTTP parser written in C (used in HTTP::Parser::XS et al.)

PicoHTTPParser Copyright (c) 2009-2014 Kazuho Oku, Tokuhiro Matsuno, Daisuke Murase, Shigeo Mitsunari PicoHTTPParser is a tiny, primitive, fast HTTP r

H2O 1.6k Jan 1, 2023
cuehttp is a modern c++ middleware framework for http(http/https)/websocket(ws/wss).

cuehttp 简介 cuehttp是一个使用Modern C++(C++17)编写的跨平台、高性能、易用的HTTP/WebSocket框架。基于中间件模式可以方便、高效、优雅的增加功能。cuehttp基于boost.asio开发,使用picohttpparser进行HTTP协议解析。内部依赖了nl

xcyl 29 Dec 17, 2022
Simple, small, C++ embeddable webserver with WebSockets support

Seasocks - A tiny embeddable C++ HTTP and WebSocket server for Linux Features Simple C++ API Serves static content from disk API for building WebSocke

Matt Godbolt 624 Jan 3, 2023
Cross-platform, efficient, customizable, and robust asynchronous HTTP/WebSocket server C++14 library with the right balance between performance and ease of use

What Is RESTinio? RESTinio is a header-only C++14 library that gives you an embedded HTTP/Websocket server. It is based on standalone version of ASIO

Stiffstream 924 Jan 6, 2023