expect {
bytes = "GET /hello HTTP/1.1\r\nHost: example.com\r\n\r\n".to_utf8()
match HTTP.parse_request(bytes) {
Ok({ request, rest }) => request.method == Get and request.target == "/hello" and rest == []
Err(InvalidHttp(_)) => False
}
}
Message framing (RFC 9112 section 6.3):
Transfer-Encoding: chunked bodies are decoded; chunk extensions and trailer fields are validated and discarded.
Content-Length bodies are exactly that many bytes.
A request with neither has no body. A response with neither runs to the end of the input (the connection close). Responses with a 1xx, 204 or 304 status never have a body. Responses to HEAD and 2xx responses to CONNECT also have no body, but this parser cannot see the request: parse those heads with a status that implies no body or strip the framing fields.
Messages whose framing is ambiguous are rejected rather than guessed at,
because a parser that disagrees with a proxy about where a message ends is
open to request smuggling: both Transfer-Encoding and Content-Length,
conflicting or malformed Content-Length values, any transfer coding other
than a single chunked, Transfer-Encoding in an HTTP/1.0 message, line
folding (obs-fold), whitespace between a field name and its colon, control
characters in field values, and line endings other than CRLF.
HTTP/1.1 requests must have exactly one Host field. Field names keep their
case; field values have surrounding whitespace removed. Field values and
reason phrases must be valid UTF-8 to be represented as Str; other
obs-text bytes are rejected.
It leaves the bytes after the message unconsumed. A failure is a
ParseError whose message starts with invalid HTTP request: and whose
offset is where the problem is.
expect {
text = "GET /hello HTTP/1.1\r\nHost: example.com\r\n\r\n"
match Utf8.parse_str(HTTP.request, text) {
Ok(req) => req.method == Get and HTTP.header(req.headers, "host") == Ok("example.com")
Err(_) => False
}
}
The methods RFC 9110 and RFC 5789 define get their own tags. Any other
method token is kept as Extension(name). Method names are
case-sensitive, so get is Extension("get"), not Get.
One header field: the name as written (case preserved) and the value
with surrounding whitespace removed.
Fields keep their order in the message, and repeated fields are kept.
Use HTTP.header to look one up by name.
Request : {
method : Method,
target : Str,
version : Version,
headers : List(Header),
body : List(U8),
}
A parsed HTTP request message.
target is the request-target exactly as written (it is not decoded or
normalized). body is the message body after any chunked transfer
coding has been removed.
Response : {
version : Version,
status_code : U16,
reason : Str,
headers : List(Header),
body : List(U8),
}
A parsed HTTP response message.
status_code is the three-digit status code and reason the reason
phrase, which may be empty. body is decoded like a request body.