std.net.http.server.Request
type pub RequestAn HTTP request.
This type is a wrapper around std.net.http.Request and provides additional
data useful when writing a server, such as the socket address.
Fields
address
let pub @address: AddressThe raw client address of the connection.
This address isn't necessarily accurate. For example, if your application sits behind an HTTP proxy then this value will be the address of the proxy and not the client. A client may also be able to spoof the address.
data
let pub @data: RequestThe request data for which a response is to be produced.
path
let pub @path: PathThe parsed request URI.
We parse the path once and store it here as it's likely to be used frequently, removing the need for parsing it every time.
If the requested path is not an absolute path, the components list is empty.
Instance methods
accepted_mime_types
Show source codeHide source code
fn pub accepted_mime_types -> Array[Mime] {
let mimes = headers
.get_all(Header.accept)
.flat_map(fn (v) -> Stream[Mime] { Mime.parse_list(v) })
.take(16)
.to_array
mimes.sort_by(fn (a, b) {
let mut a_val = quality_value(a)
let mut b_val = quality_value(b)
match b_val.cmp(a_val) {
case Equal if a.type.equals?(b.type) -> {
# `a/b, a/b;foo=bar` results in `a/b;foo=bar, a/b`
if a.subtype.equals?(b.subtype) {
return b.parameters.size.cmp(a.parameters.size)
}
# `text/*, text/plain` results in `text/plain, text/*`
match (a.subtype, b.subtype) {
case ('*', _) -> Ordering.Greater
case (_, '*') -> Ordering.Less
case _ -> b.parameters.size.cmp(a.parameters.size)
}
}
case ord -> ord
}
})
mimes
}fn pub accepted_mime_types -> Array[Mime]Parses the request's Accept header values (if any) into a list of
std.mime.Mime values.
When encountering an invalid MIME type, it and any remaining MIME types are ignored.
To guard against unreasonably large Accept header values (in addition to
the limits applied when parsing requests), this method only parses up to 16
valid MIME types.
Quality values
The returned Array is sorted such that the most preferred formats come
first, using the q parameter if present (aka the "quality value").
While RFC 9110 states the q parameter is case-insensitive, this
implementation treats it as case-sensitive (i.e. it doesn't support Q). To
our knowledge nobody uses Q instead of q so this shouldn't pose any
issues.
If the value of a q parameter is invalid, it's treated as a weight of
0.0. If the value is absent, the weight defaults to 1.0.
body
Show source codeHide source code
fn pub mut body -> mut RequestBody {
@data.body
}fn pub mut body -> mut BodyReturns a mutable borrow of the request body.
content_type
Show source codeHide source code
fn pub content_type -> Option[Mime] {
match headers.get(Header.content_type) {
case Ok(v) -> Mime.parse(v)
case _ -> Option.None
}
}fn pub content_type -> Option[Mime]Parses the request's Content-Type header into a Mime value.
This method always parses the first Content-Type header, should multiple
such headers be provided.
headers
Show source codeHide source code
fn pub headers -> ref HeaderMap {
@data.headers
}fn pub headers -> ref HeaderMapReturns an mutable borrow of the request headers.
host
Show source codeHide source code
fn pub host -> (Slice[String], Option[Int]) {
# Per RFC 9110, the Host header is required and requests without it are
# rejected, so this won't panic.
let raw = headers.get(Header.host).get
match raw.split_once(':') {
case Some((host, port)) -> (host, Int.parse(port, int.Format.Decimal))
case _ -> (raw.to_slice, Option.None)
}
}fn pub host -> (Slice[String], Option[Int])Parses the Host header into a (host, port) tuple.
method
Show source codeHide source code
fn pub method -> Method {
@data.method
}fn pub method -> MethodReturns the request method.
multipart_form
Show source codeHide source code
fn pub mut multipart_form -> Result[
multipart.Multipart[mut RequestBody, ParseError],
FormError,
] {
let mime = match content_type {
case Some(v) -> v
case _ -> throw FormError.InvalidContentType
}
let boundary = match (mime.type, mime.subtype, mime.charset) {
case ('multipart', 'form-data', Some('utf-8') or None) -> {
match mime.get('boundary') {
case Ok(v) if v.size > 0 -> v.to_string
case _ -> throw FormError.InvalidContentType
}
}
case _ -> throw FormError.InvalidContentType
}
let mp = multipart.Multipart.new(body, boundary)
mp.header_size = @limits.header_size
Result.Ok(mp)
}fn pub mut multipart_form -> Result[Multipart[mut Body, ParseError], FormError]Parses a multipart form request.
This method doesn't care about the request method used, only that the
Content-Type header is set to the correct value.
For more information, refer to the documentation of the
std.multipart.Multipart type.
Error handling
This method returns an error if any of the following is true:
- The
Content-Typeheader is not set tomultipart/form-data; boundary=XwhereXis the boundary delimiter - The
Content-Typeheader specifies thecharsetparameter with a value other thanutf-8
Note that while this method enforces the charset parameter to either be
set to utf-8 or be absent, individual form fields may still specify a
different charset parameter as part of their Content-Type headers.
Due to the streaming nature of multipart forms, other errors may be produced when reading fields.
origin_matches_host?
Show source codeHide source code
fn pub origin_matches_host? -> Bool {
match headers.get(Header.origin) {
case Ok(origin) -> {
match (Uri.parse(origin), host) {
case (Ok(o), (h, p)) -> o.host == Host.new(h) and o.port == p
case _ -> false
}
}
case _ -> true
}
}fn pub origin_matches_host? -> BoolReturns true if the value of the Origin header matches the value of the
Host header.
If the Origin header is missing then the return value is true.
same_origin?
Show source codeHide source code
fn pub same_origin? -> Bool {
if method.safe? { return true }
match headers.get(Header.sec_fetch_site) {
case Ok('same-origin' or 'none') -> true
case Ok(_) -> false
case _ -> origin_matches_host?
}
}fn pub same_origin? -> BoolReturns true if self is a request for which the origin and target are
the same (i.e. it's not a cross-site request).
This method uses the Sec-Fetch-Site header. If this header is missing the
Origin and Host headers are compared instead.
If all these headers are missing then the return value is true instead of
false, as this means the browser in question is severely out of date.
target
Show source codeHide source code
fn pub target -> ref Array[Slice[String]] {
@path.components
}fn pub target -> ref Array[Slice[String]]Returns the components of the request path.
This differs from Request.path in that it returns the components such that
they can be pattern matched against, instead of returning a Path.
Examples
import std.net.http (Status)
import std.net.http.server (Handle, Request, Response)
type App {}
impl Handle for App {
fn pub mut handle(request: mut Request) -> Response {
match request.target {
case [] -> Response.new.string('home')
case ['about'] -> Response.new.string('about')
case _ -> Response.new.status(Status.not_found)
}
}
}
uri
Show source codeHide source code
fn pub uri -> ref Uri {
@data.uri
}fn pub uri -> ref UriReturns an immutable borrow of the request URI.
url_encoded_form
Show source codeHide source code
fn pub mut url_encoded_form -> Result[Values, FormError] {
match method {
case Get or Head -> return Result.Ok(uri.query.parse)
case _ -> {
let typ = match content_type {
case Some(v) -> v
case _ -> throw FormError.InvalidContentType
}
match (typ.type, typ.subtype, typ.charset) {
case ('application', 'x-www-form-urlencoded', Some('utf-8') or None)
-> {}
case _ -> throw FormError.InvalidContentType
}
}
}
let buf = ByteArray.new
try body.read_all(buf).map_error(fn (e) { FormError.Read(e) })
match Values.parse(buf) {
case Some(v) -> Result.Ok(v)
case _ -> Result.Error(FormError.InvalidSyntax)
}
}fn pub mut url_encoded_form -> Result[Values, FormError]Parses a form request into a std.uri.Values.
For GET and HEAD requests, this method parses the query string. For other
request methods, this method requires the Content-Type header to be set to
application/x-www-form-urlencoded and consumes the request body.
If the query string component of the URI is empty (for GET and HEAD
requests), or the body is empty (for all other request methods), an empty
Values is returned. In this case the body isn't consumed.
Memory usage
When parsing the body as a form, the body is read into memory. For large
payloads (e.g. when uploading files), use Request.parse_multipart instead
as that allows for streaming of key-value pairs.
The size limit on the body imposed by the Limits type applies to the
encoded body size, i.e. for a%20b the body size is considered to be 5
and not 3 bytes.
Errors
For GET and HEAD requests no error is ever returned, as all the necessary data is already parsed and validated at this point. For other requests an error is returned if any of the following is true:
- The
Content-Typeheader isn't set toapplication/x-www-form-urlencoded - The
Content-Typeheader specifies thecharsetparameter with a value other thanutf-8 - The form data in the body uses invalid syntax
- Reading the body fails, such as due to a network error or when the HTTP body can't be parsed
version
Show source codeHide source code
fn pub version -> Version {
@data.version
}fn pub version -> VersionReturns the HTTP version used by the request.