std.net.http.server.Request
type pub Request
An 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: Address
The 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: Request
The request data for which a response is to be produced.
path
let pub @path: Path
The 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 = []
let mut parsed = 0
for val in headers.get_all(Header.accept) {
for chunk in val.split(',') {
if parsed == 16 { break }
match Mime.parse(chunk.trim) {
case Some(v) -> mimes.push(v)
case _ -> {}
}
parsed += 1
}
}
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.
Any invalid values are ignored. If none of the values are valid or none are
specified, an empty Array
is returned.
To guard against unreasonably large Accept
header values (in addition to
the limits applied when parsing requests), this method only parses the first
16 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 Body
Returns 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 HeaderMap
Returns 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 -> Method
Returns 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-Type
header is not set tomultipart/form-data; boundary=X
whereX
is the boundary delimiter - The
Content-Type
header specifies thecharset
parameter 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.
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 _ -> {
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 same_origin? -> Bool
Returns 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 Uri
Returns 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-Type
header isn't set toapplication/x-www-form-urlencoded
- The
Content-Type
header specifies thecharset
parameter 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 -> Version
Returns the HTTP version used by the request.