Skip to content

Latest commit

 

History

History
132 lines (122 loc) · 7.16 KB

README.md

File metadata and controls

132 lines (122 loc) · 7.16 KB

nomad

Nomad is an HTTP server for Trail apps inspired by Bandit.

Nomad is written entirely in OCaml and is built atop Atacama. It aims to be an Application-layer for Trail, implementing: HTTP/1.x, HTTP/2, and WebSockets. It is written with a big focus on clarity.

Correctness

Nomad aims to be correct and so we're testing against the Bandit HTTP/1.1 test-bed, h2spec, and Autobahn

HTTP/1.1

  • invalid requests
    • returns a 400 if the request cannot be parsed
    • returns a 400 if the request has an invalid http version
  • keepalive requests
    • closes connection after max_requests is reached
    • idle keepalive connections are closed after read_timeout
    • unread content length bodies are read before starting a new request
    • unread chunked bodies are read before starting a new request
  • origin-form request target (RFC9112§3.2.1)
    • derives scheme from underlying transport
    • derives host from host header
    • returns 400 if no host header set in HTTP/1.1
    • sets a blank host if no host header set in HTTP/1.0
    • derives port from host header
    • derives host from host header with ipv6 host
    • derives host and port from host header with ipv6 host
    • returns 400 if port cannot be parsed from host header
    • derives port from schema default if no port specified in host header
    • derives port from schema default if no host header set in HTTP/1.0
    • sets path and query string properly when no query string is present
    • sets path and query string properly when query string is present
    • ignores fragment when no query string is present
    • ignores fragment when query string is present
    • handles query strings with question mark characters in them
    • returns 400 if a non-absolute path is send
    • returns 400 if path has no leading slash
  • absolute-form request target (RFC9112§3.2.2)
    • uses request-line scheme even if it does not match the transport
    • derives host from the URI, even if it differs from host header
    • derives ipv6 host from the URI, even if it differs from host header
    • does not require a host header set in HTTP/1.1 (RFC9112§3.2.2)
    • derives port from the URI, even if it differs from host header
    • derives port from schema default if no port specified in the URI
    • sets path and query string properly when no query string is present
    • sets path and query string properly when query string is present
    • ignores fragment when no query string is present
    • ignores fragment when query string is present
    • handles query strings with question mark characters in them
  • authority-form request target (RFC9112§3.2.3)
    • returns 400 for authority-form / CONNECT requests
  • asterisk-form request target (RFC9112§3.2.4)
    • parse global OPTIONS path correctly
  • request line limits
    • returns 414 for request lines that are too long
  • request headers
    • reads headers properly
    • returns 431 for header lines that are too long
    • returns 431 for too many header lines
  • content-length request bodies
    • reads a zero length body properly
    • reads a content-length encoded body properly
    • reads a content-length with multiple content-lengths encoded body properly
    • rejects a request with non-matching multiple content lengths
    • rejects a request with negative content-length
    • rejects a request with non-integer content length
    • handles the case where we ask for less than is already in the buffer
    • handles the case where we ask for more than is already in the buffer
    • handles the case where we read from the network in smaller chunks than we return
    • handles the case where the declared content length is longer than what is sent
    • handles the case where the declared content length is less than what is sent
    • reading request body multiple times works as expected
  • chunked request bodies
    • reads a chunked body properly
  • upgrade handling
    • raises an ArgumentError on unsupported upgrades
    • returns a 400 and errors loudly in cases where an upgrade is indicated but the connection is not a GET
    • returns a 400 and errors loudly in cases where an upgrade is indicated but upgrade header is incorrect
    • returns a 400 and errors loudly in cases where an upgrade is indicated but connection header is incorrect
    • returns a 400 and errors loudly in cases where an upgrade is indicated but key header is incorrect
    • returns a 400 and errors loudly in cases where an upgrade is indicated but version header is incorrect
    • returns a 400 and errors loudly if websocket support is not enabled
  • response headers
    • writes out a response with a valid date header
    • returns user-defined date header instead of internal version
  • response body
    • writes out a response with deflate encoding if so negotiated
    • writes out a response with gzip encoding if so negotiated
    • writes out a response with x-gzip encoding if so negotiated
    • uses the first matching encoding in accept-encoding
    • falls back to no encoding if no encodings provided
    • does no encoding if content-encoding header already present in response
    • does no encoding if a strong etag is present in the response
    • does content encoding if a weak etag is present in the response
    • does no encoding if cache-control: no-transform is present in the response
    • falls back to no encoding if no encodings match
    • falls back to no encoding if compression is disabled
    • sends expected content-length but no body for HEAD requests
    • replaces any incorrect provided content-length headers
    • writes out a response with no content-length header or body for 204 responses
    • writes out a response with no content-length header or body for 304 responses
    • writes out a response with zero content-length for 200 responses
    • writes out a response with zero content-length for 301 responses
    • writes out a response with zero content-length for 401 responses
    • writes out a chunked response
    • does not write out a body for a chunked response to a HEAD request
    • returns socket errors on chunk calls
    • writes out a sent file for the entire file with content length
    • writes out headers but not body for files requested via HEAD request
    • does not write out a content-length header or body for files on a 204
    • does not write out a content-length header or body for files on a 304
    • writes out a sent file for parts of a file with content length
  • sending informational responses
  • does not send informational responses to HTTP/1.0 clients
  • reading HTTP version
  • reading peer data

HTTP/2

WebSockets