Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

HTTP

Sans-IO HTTP/1.1 and multipart/form-data parsers written in C3. Push bytes in and get callbacks out. The library does not allocate on the heap, uses SIMD, and leaves buffering to the caller.

Overview

http gives you two streaming parsers that never own a buffer and never allocate on the hot path:

  • You feed execute whatever bytes you have; it fires callbacks with slices that point straight into your buffer and returns how many bytes it consumed.
  • Trailing bytes that form an incomplete line (or a partial delimiter) are left unconsumed; you re-present them prepended to the next chunk.
  • The consume / re-present contract works with a raw recv loop, nova::framed's peek/consume, or an in-memory buffer. The library does not need to know which transport you use.

There is no per-message allocation: the parsers are fixed-size values, so the whole pipeline (parser + your read buffer) is constant memory regardless of request or upload size.

Features

  • http::parser: HTTP/1.1 request parser for the request line, headers, and body framing by Content-Length and Transfer-Encoding: chunked (with trailers). The parser supports keep-alive and pipelining.
  • http::multipart: multipart/form-data parser with preamble/epilogue, per-part headers, multiple files, Content-Type per part, and Content-Disposition name / filename / RFC 5987 filename* extraction, all into fixed inline storage.
  • SIMD scanning: http::simd finds line ends, delimiters and tokens with 16-byte (char[<16>]) vectors (SSE2 on x64, NEON on aarch64); no runtime feature detection.
  • Zero heap: neither parser takes an Allocator; the parsers are covered by a TrackingAllocator test. Bodies and file uploads stream through callbacks without buffering, straight to disk if you want.
  • Input checks: rejects Content-Length/Transfer-Encoding smuggling, non-final chunked, whitespace before the header colon, and overflowing Content-Length/chunk sizes; configurable line-size, header-count and part limits.

Example

import std::io;
import http::parser;

fn void on_request_line(parser::Parser* p, char[] method, char[] target, char[] version, void* ud)
{
	(void)io::printfn("%s %s %s", (String)method, (String)target, (String)version);
}

fn void on_header(parser::Parser* p, char[] name, char[] value, void* ud)
{
	(void)io::printfn("  %s: %s", (String)name, (String)value);
}

fn void main()
{
	parser::Settings settings = { .on_request_line = &on_request_line, .on_header = &on_header };
	parser::Parser p;
	p.init(&settings, null);

	String request = "GET /hello HTTP/1.1\r\nHost: example.com\r\n\r\n";
	usz consumed = p.execute((char[])request)!!;
	(void)io::printfn("consumed %d / %d bytes", consumed, request.len);
}

Prints:

GET /hello HTTP/1.1
  Host: example.com
consumed 42 / 42 bytes

For a real transport, call execute repeatedly with whatever bytes arrive and re-present data[result..] on the next call. Streaming a large upload to disk is just writing each on_part_data (or on_body) slice to a file. The parser holds none of the upload.

Design

  • Sans-IO push model. The parser is a state machine over the bytes you hand it; it does no I/O and knows nothing about sockets or event loops.
  • Caller-owned buffering. The parser keeps no buffer of its own. A line's maximum length is whatever you are willing to re-present, and at most delimiter_len - 1 bytes are ever withheld between multipart calls.
  • Coalesced callbacks. Because a line is only emitted once its CRLF is seen, the request-line fields and each header's name/value always arrive together, so they are delivered in one call each (on_request_line, on_header) rather than field-by-field.
  • Zero-allocation hot path. No Allocator in the API; the only memory is fixed inline storage for the multipart boundary and the current part's parsed disposition.
  • Contracts. Caller obligations are @require/@ensure; recoverable failures are fault values via T?; every grammar, framing and limit violation has a named fault.

Install

http is a C3 library bundle (http.c3l/). Put it on a dependency search path and depend on the http library:

{
  "dependency-search-paths": [ "lib" ],
  "dependencies": [ "http" ]
}

(place http.c3l/ under lib/), then in your code:

import http::parser;     // HTTP/1.1 request parser
import http::multipart;  // multipart/form-data parser

For a standalone file: c3c compile-run main.c3 --libdir lib --lib http.

Build and test

Requires the c3c compiler.

c3c test                          # run the test suite (34 tests)
cd examples && c3c run request    # run an example (see examples/)
cd fuzz  && c3c build fuzz  && ./build/fuzz     # robustness fuzzer (no crash)
cd bench && c3c build bench && ./build/bench    # benchmark vs picohttpparser

Examples

Runnable programs in examples/ driving the parsers from an in-memory buffer (the same consume / re-present loop a transport uses):

Target Shows
request parse a request → request line, headers, body
chunked-body stream a chunked body without buffering it
upload multipart upload streamed straight to disk
limits reject abusive requests via Config limits
pipelining keep-alive: many requests on one parser

License

See LICENSE.

About

Sans-IO HTTP/1.1 and multipart/form-data parsers written in C3.

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages