Search results

There are no results.

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 code
Hide 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 code
Hide 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 code
Hide 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 code
Hide 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 code
Hide 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 code
Hide source code
fn pub method -> Method {
  @data.method
}
fn pub method -> Method

Returns the request method.

multipart_form

Show source code
Hide 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:

  1. The Content-Type header is not set to multipart/form-data; boundary=X where X is the boundary delimiter
  2. The Content-Type header specifies the charset parameter with a value other than utf-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 code
Hide 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 code
Hide 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 code
Hide 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 code
Hide 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:

  1. The Content-Type header isn't set to application/x-www-form-urlencoded
  2. The Content-Type header specifies the charset parameter with a value other than utf-8
  3. The form data in the body uses invalid syntax
  4. Reading the body fails, such as due to a network error or when the HTTP body can't be parsed

version

Show source code
Hide source code
fn pub version -> Version {
  @data.version
}
fn pub version -> Version

Returns the HTTP version used by the request.