Sane C++ Libraries
C++ Platform Abstraction Libraries
Loading...
Searching...
No Matches
Http

🟥 HTTP parser, client and server

SaneCppHttp.h is a library implementing a hand-written http 1.1 parser, client and server.

Dependencies

Dependency Graph

Features

  • HTTP 1.1 Parser
  • HTTP 1.1 Client
  • HTTP 1.1 Server

Status

🟥 Draft
In current state the library is able to host simple static website but it cannot be used for any internet facing application.
Additionally its API will be changing heavily as it's undergoing major re-design to make it fully allocation free and extend it to support more of the HTTP 1.1 standard.

Description

The HTTP parser is an incremental parser, that will emit events as soon as a valid element has been successfully parsed. This allows handling incomplete responses without needing holding it entirely in memory.

The HTTP client and server are for now just some basic implementations and are missing many important features.

Videos

This is the list of videos that have been recorded showing some of the internal thoughts that have been going into this library:

Blog

Some relevant blog posts are:

HttpServer

Async Http server.

Usage:

See also
SC::HttpWebServer
// constexpr int NUM_CLIENTS = 3;
// constexpr int CLIENT_HEADERS = 8 * 1024;
Buffer headersMemory;
SC_TEST_EXPECT(headersMemory.resize(NUM_CLIENTS * CLIENT_HEADERS));
GrowableBuffer<Buffer> headers = {headersMemory};
HttpServer::Memory memory = {headers, clients};
HttpAsyncServer server;
SC_TEST_EXPECT(server.start(eventLoop, "127.0.0.1", 6152, memory));
struct ServerContext
{
int numRequests;
} serverContext = {0};
server.httpServer.onRequest = [this, &serverContext](HttpRequest& request, HttpResponse& response)
{
if (request.getParser().method != HttpParser::Method::HttpGET)
{
SC_TEST_EXPECT(response.startResponse(405));
SC_TEST_EXPECT(response.sendHeaders());
SC_TEST_EXPECT(response.end());
return;
}
if (request.getURL() != "/index.html" and request.getURL() != "/")
{
SC_TEST_EXPECT(response.startResponse(404));
SC_TEST_EXPECT(response.sendHeaders());
SC_TEST_EXPECT(response.end());
return;
}
serverContext.numRequests++;
SC_TEST_EXPECT(response.startResponse(200));
SC_TEST_EXPECT(response.addHeader("Connection", "Closed"));
SC_TEST_EXPECT(response.addHeader("Content-Type", "text/html"));
SC_TEST_EXPECT(response.addHeader("Server", "SC"));
SC_TEST_EXPECT(response.addHeader("Date", "Mon, 27 Aug 2023 16:37:00 GMT"));
SC_TEST_EXPECT(response.addHeader("Last-Modified", "Wed, 27 Aug 2023 16:37:00 GMT"));
const char sampleHtml[] = "<html>\r\n"
"<body bgcolor=\"#000000\" text=\"#ffffff\">\r\n"
"<h1>This is a title {}!</h1>\r\n"
"We must start from somewhere\r\n"
"</body>\r\n"
"</html>\r\n";
String content;
SC_TEST_EXPECT(StringBuilder::format(content, sampleHtml, serverContext.numRequests));
SmallString<16> contentLength;
SC_TEST_EXPECT(StringBuilder::format(contentLength, "{}", content.view().sizeInBytes()));
SC_TEST_EXPECT(response.addHeader("Content-Length", contentLength.view()));
SC_TEST_EXPECT(response.sendHeaders());
SC_TEST_EXPECT(response.getWritableStream().write(move(content)));
SC_TEST_EXPECT(response.end());
};

HttpWebServer

Http web server helps statically serves files from a directory.


It can be used in conjunction with SC::HttpServer, by calling SC::HttpWebServer::serveFile inside the SC::HttpServer::onRequest callback to statically serve files.

See also
SC::HttpServer
constexpr int NUM_CLIENTS = 16;
constexpr int REQUEST_SLICES = 2;
constexpr int CLIENT_REQUEST = 1024;
Buffer headersMemory;
SC_TEST_EXPECT(headersMemory.resize(NUM_CLIENTS * 8 * CLIENT_REQUEST));
Buffer requestsMemory;
SC_TEST_EXPECT(requestsMemory.resize(NUM_CLIENTS * CLIENT_REQUEST * 2));
HttpServerClient clients[NUM_CLIENTS];
GrowableBuffer<Buffer> headers = {headersMemory};
HttpServer::Memory serverMemory = {headers, clients};
AsyncBufferView buffers[NUM_CLIENTS * (REQUEST_SLICES + 2)]; // +2 to accomodate some slots for external bufs
AsyncReadableStream::Request readQueue[NUM_CLIENTS * REQUEST_SLICES];
AsyncWritableStream::Request writeQueue[NUM_CLIENTS * REQUEST_SLICES];
Span<char> requestsSpan = requestsMemory.toSpan();
for (size_t idx = 0; idx < NUM_CLIENTS; ++idx)
{
for (size_t slice = 0; slice < REQUEST_SLICES; ++slice)
{
Span<char> memory;
const size_t offset = idx * CLIENT_REQUEST + slice * CLIENT_REQUEST / REQUEST_SLICES;
SC_TEST_EXPECT(requestsSpan.sliceStartLength(offset, CLIENT_REQUEST / REQUEST_SLICES, memory));
buffers[idx * REQUEST_SLICES + slice] = memory;
buffers[idx * REQUEST_SLICES + slice].setReusable(true); // We want to recycle these buffers
}
}
HttpAsyncServer asyncServer;
HttpWebServer webServer;
// Creates an HttpServer that serves files from application root directory
SC_TEST_EXPECT(asyncServer.start(eventLoop, "127.0.0.1", 8090, serverMemory));
SC_TEST_EXPECT(webServer.init(report.applicationRootDirectory.view()));
asyncServer.setupStreamsMemory(readQueue, writeQueue, buffers);
asyncServer.httpServer.onRequest = [&](HttpRequest& req, HttpResponse& res) { webServer.serveFile(req, res); };

HttpClient

Http async client.

Examples

Blog

Some relevant blog posts are:

Roadmap

🟨 MVP

  • Server+Client: Support mostly used HTTP verbs / methods
  • Server+Client: HTTP 1.1 Chunked Encoding

🟩 Usable Features:

🟦 Complete Features:

  • HTTPS
  • Support all HTTP verbs / methods

💡 Unplanned Features:

  • Http 2.0
  • Http 3.0

Statistics

Type Lines Of Code Comments Sum
Headers 282 216 498
Sources 1530 283 1813
Sum 1812 499 2311