Skip to content

lsfratel/http.c3l

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

HTTP

Sans-IO HTTP/1.1 and multipart/form-data parsers written in C3. Push bytes in, get callbacks out — zero heap allocation, SIMD-accelerated, with all buffering owned by 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.
  • That single consume / re-present contract is the seam any transport plugs into — a raw recv loop, nova::framed's peek/consume, or an in-memory buffer — with no transport knowledge baked into the library.

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.

Feature highlights

  • http::parser — HTTP/1.1 request parser: request line, headers, and body framing by Content-Length and Transfer-Encoding: chunked (with trailers). Keep-alive and pipelining out of the box.
  • http::multipart — multipart/form-data parser: 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 scanninghttp::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; verified by a TrackingAllocator test. Bodies and file uploads stream through callbacks without buffering, straight to disk if you want.
  • Hardened — 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 it.

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 & 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

License

Stars

Watchers

Forks

Releases

No releases published

Contributors

Languages