Skip to content

HTTP/1.1 conformance

This page lists the HTTP/1.1 behaviors aeronet implements (RFC 9110 semantics, RFC 9112 message syntax), the status codes the server produces by itself, and the protocol behaviors an application can configure. HTTP/2 is covered in HTTP/2.

Message parsing

Behavior Status
Request line parsing (method, target, version) Supported
Header fields, looked up case-insensitively. Obsolete line folding is not accepted. Supported
Method tokens are case-sensitive (RFC 9110 §9.1): GET is a method, get is an unknown method answered 501 Supported
A bare CR in a request head is rejected (400) as soon as the byte following it is received Supported
Content-Length and Transfer-Encoding together are rejected (400), as is Transfer-Encoding on an HTTP/1.0 request Supported
Pipelined requests, processed one after the other Supported
Duplicate request headers merged, overridden, or rejected depending on the header (details) Supported
Optional stricter policy failing on unknown duplicate headers Not implemented

Connections

Behavior Status
Persistent connections: default for HTTP/1.1, opt-in with Connection: keep-alive for HTTP/1.0 Supported
HTTP/1.0 requests answered with HTTP/1.0 responses (no silent upgrade) Supported
Connection: close from either side Supported
Requests per connection limit (maxRequestsPerConnection) and idle timeout (keepAliveTimeout) Supported
Closing in stages a connection whose client may still be sending (RFC 9112 §9.6) Supported, see Lingering close
Header read timeout against slow clients (Slowloris), disabled by default Supported
Partial writes and backpressure, bounded by maxOutboundBufferBytes Supported
CONNECT tunneling Supported, see CONNECT tunneling
Upgrade to WebSocket Supported, see WebSocket
Upgrade to cleartext HTTP/2 (Upgrade: h2c) Ignored, as RFC 9113 deprecated it

Request bodies

Behavior Status
Content-Length bodies, bounded by maxBodyBytes Supported
Chunked transfer coding, with chunk extensions and trailers (details) Supported
Request trailers carrying forbidden fields rejected Supported
Trailers exposed by HttpRequestView::trailers(), identically for HTTP/1.1 and HTTP/2 Supported
Request body decompression (Content-Encoding: gzip, deflate, br, zstd, stacked codings), with size and ratio limits (details) Supported, opt-in
multipart/form-data parsing helpers (details) Supported
Expect: 100-continue, and custom expectations through a handler (see below) Supported

Responses

Behavior Status
Date, Content-Length, Connection, Transfer-Encoding, Trailer, TE, and Upgrade response headers managed by the server (details) Supported
Content-Length: 0 on empty responses, except for 1xx, 204, 304, HEAD, file, streaming, and direct-compression responses Supported
1xx, 204, 304 responses ending at the empty line after their headers: body, trailers and Content-Length / Transfer-Encoding dropped (details) Supported
HEAD answered by the GET handler, without body and with the GET length Supported
Chunked streaming responses, with trailers Supported
Buffered responses with trailers, sent chunked Supported
Response compression (gzip, deflate, br, zstd) negotiated from Accept-Encoding with q-values (details) Supported
406 Not Acceptable when the client refuses identity and no offered coding is available Supported
Range and conditional requests for files (details) Supported

Status codes produced by the server

The server answers these errors itself, before or instead of calling a handler:

Status Cause
400 Bad Request Malformed request, conflicting framing headers, Transfer-Encoding on HTTP/1.0, invalid CONNECT target, failed WebSocket handshake.
403 Forbidden CONNECT target not in the allowlist, request denied by a CORS policy.
404 Not Found No route matches the path.
405 Method Not Allowed The path exists but not for this method, with an Allow header. Also TRACE when disabled.
406 Not Acceptable No acceptable content coding.
413 Payload Too Large Body larger than maxBodyBytes.
415 Unsupported Media Type Unsupported Content-Encoding on a request body, when decompression is enabled.
417 Expectation Failed Expect token other than 100-continue without expectation handler.
431 Request Header Fields Too Large Request head larger than maxHeaderBytes.
500 Internal Server Error Handler exception, or an invalid interim status returned by an expectation handler.
501 Not Implemented Unknown method, or unsupported Transfer-Encoding.
502 Bad Gateway CONNECT target unreachable.
505 HTTP Version Not Supported Well-formed version of a major version other than 1, such as HTTP/2.0. A higher minor version, such as HTTP/1.3, is processed and answered as HTTP/1.1 (RFC 9110 §6.2). Any other version token, such as http/1.1 (the name is case-sensitive) or HTTP/1.10, is a 400.

aeronet does not validate the Content-Type of request bodies: a 415 based on the media type is the application's decision, for instance in a request middleware.

Configurable behaviors

OPTIONS

OPTIONS * is answered 200 with an Allow header listing the methods the router supports. OPTIONS on a path is a CORS preflight when the route has a CORS policy, and is routed like any other method otherwise.

TRACE

TRACE echoes the received request back to the client, with Content-Type: message/http (RFC 9110 §9.3.8). Because the echo includes every header, cookies and credentials included, it is disabled by default:

HttpServerConfig::TraceMethodPolicy Effect
Disabled (default) TRACE is answered 405.
EnabledPlainAndTLS TRACE is echoed on every connection.
EnabledPlainOnly TRACE is echoed on plaintext connections and answered 405 on TLS connections.
HttpServerConfig config;
config.withTracePolicy(HttpServerConfig::TraceMethodPolicy::EnabledPlainOnly);

Expect

For Expect: 100-continue, the server sends 100 Continue when it starts reading the body, so a client waiting for it does not send a body that would be refused. The token is recognized in a comma-separated list, with surrounding whitespace.

Other expectation tokens go to the expectation handler, when one is installed. Without a handler, they are answered 417 Expectation Failed. The handler receives the request and the token, and returns an ExpectationResult:

ExpectationResultKind Effect
Continue Process the request normally.
Interim Send the informational response interimStatus (a 1xx status, such as 102), then continue.
FinalResponse Send finalResponse and skip the body and the handler. When the request announces a body (Content-Length other than 0, or Transfer-Encoding), the response carries Connection: close and the connection closes: the client may send the body anyway, which could not be told apart from the next request. Otherwise the requests pipelined behind it are served.
Reject Answer 417 Expectation Failed.
#include <string_view>

SingleHttpServer server(HttpServerConfig{}.withPort(8080));
server.setExpectationHandler([](const HttpRequestView& /*request*/, std::string_view token) {
  SingleHttpServer::ExpectationResult result;
  if (token == "x-quota-check") {
    result.kind = SingleHttpServer::ExpectationResultKind::Interim;
    result.interimStatus = 102;
  } else {
    result.kind = SingleHttpServer::ExpectationResultKind::Reject;
  }
  return result;
});

The handler runs on the event-loop thread, so it must be fast. An Interim result with a status outside 1xx is a programming error, answered 500. MultiHttpServer::setExpectationHandler() installs a handler on every worker.

Tests