HTTP parser

August 23, 2026 · View on GitHub

proto/http.nim is an incremental, zero-copy HTTP/1.1 request parser. It materializes strings lazily from byte offsets — nothing is copied until you ask for it — and it drives streaming bodies, chunked transfer encoding and multipart.

Most users never touch the parser directly: the HTTP server feeds it for you and hands you an HttpRequest (see requests). This page is for understanding it, or for using it standalone.

Creating a parser

var p = newHttpParser(initialBufSize = 4096)

Public parser fields: buf, bufLen, maxBodySize, maxStreamBodySize, headerEnd, contentLength, connectionClose, expectContinue, onBodyData (streaming callback), methodCache, phase.

Feeding bytes

let phase = p.feed(data)          # openArray[byte] or string

The parser is incremental: feed as many or as few bytes as arrive. It tracks a ParsePhase: PhaseRequestLine, PhaseHeaders, PhaseBody, PhaseComplete, PhaseError.

p.isComplete()          # reached PhaseComplete
p.isError()             # reached PhaseError
p.error()               # the HttpCode that caused the error
p.phase()               # current phase
p.setError(Http400)     # force an error state (rejects the request)

reset clears the parser; resetForNext keeps reusable state for the next request on a keep-alive connection.

Zero-copy request fields

Before the request is complete you can peek:

p.peekMethod()            # HttpMethod
p.peekPath()              # lent string
p.peekContentType()       # lent string

Once complete, materialize a request object:

let req = p.getRequest()      # HttpRequest — see requests.md

getRemainingData(p) returns bytes beyond the current request (pipelining).

Streaming bodies

p.onBodyData = proc(data: openArray[byte]; done: bool) {.closure.} is invoked as body bytes are parsed; done signals the final chunk. Set it before feeding body bytes.

p.onBodyData = proc(data: openArray[byte]; done: bool) =
  write(f, data)          # stream to disk without buffering the whole body

Limits

Constants enforced by the parser/server:

ConstantValue
MaxHeaderSize8192
MaxRequestLine8192
MaxHeaders100
DefaultBodyBuf65536
MaxStreamBodySize512 MB

See security for how these protect against DoS.

Chunked trailers

Chunked bodies may carry trailers — Name: value lines after the zero-size chunk (RFC 9112 §7.1.3). The parser accepts them incrementally, enforces the same strictness as headers (CRLF only, colon required, no obs-fold, MaxHeaderSize per section, MaxHeaders per count; violations are 400/431), and exposes them once complete:

if parser.isComplete():
  if parser.hasTrailers():
    for (name, value) in parser.getTrailers():
      echo name, ": ", value
  let crc = req.getTrailer("X-Crc32")   # case-insensitive lookup, "" if absent

Body streaming on requests

For fully parsed requests, the BodyStream API in requests is the higher-level way to consume bodies in chunks.

API reference

Full signatures: HTTP parser API. Related: requests, server.