package dream

  1. Overview
  2. Docs

Types

Dream is built on just five types. The first two are the data types of Dream. Both are abstract, even though they appear to have definitions:

type request = incoming message

HTTP requests, such as GET /something HTTP/1.1. See Requests.

and response = outgoing message

HTTP responses, such as 200 OK. See Responses.

The remaining three types are for building up Web apps.

and handler = request -> response promise

Handlers are asynchronous functions from requests to responses. Example 1-hello [playground] shows the simplest handler, an anonymous function which we pass to Dream.run. This creates a complete Web server! You can also see the Reason version in example r-hello.

let () =
  Dream.run (fun _ ->
    Dream.html "Good morning, world!")
and middleware = handler -> handler

Middlewares are functions that take a handler, and run some code before or after — producing a “bigger” handler. Example 2-middleware inserts the Dream.logger middleware into a Web app:

let () =
  Dream.run
  @@ Dream.logger
  @@ fun _ -> Dream.html "Good morning, world!"

Examples 4-counter [playground] and 5-promise show user-defined middlewares:

let count_requests inner_handler request =
  count := !count + 1;
  inner_handler request

In case you are wondering why the example middleware count_requests takes two arguments, while the type says it should take only one, it's because:

middleware
  = handler -> handler
  = handler -> (request -> response promise)
  = handler -> request -> response promise
and route

Routes tell Dream.router which handler to select for each request. See Routing and example 3-router [playground]. Routes are created by helpers such as Dream.get and Dream.scope:

Dream.router [
  Dream.scope "/admin" [Dream.memory_sessions] [
    Dream.get "/" admin_handler;
    Dream.get "/logout" admin_logout_handler;
  ];
]

Algebra

The three handler-related types have a vaguely algebraic interpretation:

Dream.scope implements a left distributive law, making Dream a ring-like structure.

Helpers

and 'a message

'a message, pronounced “any message,” allows some functions to take either request or response as arguments, because both are defined in terms of 'a message. For example, in Headers:

val Dream.header : string -> 'a message -> string option
and incoming
and outgoing

Type parameters for message for request and response, respectively. These are “phantom” types. They have no meaning other than they are different from each other. Dream only ever creates incoming message and outgoing message. incoming and outgoing are never mentioned again in the docs.

and 'a promise = 'a Lwt.t

Dream uses Lwt for promises and asynchronous I/O. See example 5-promise [playground].

Use raise to reject promises. If you are writing a library, you may prefer using Lwt.fail in some places, in order to avoid clobbering your user's current exception backtrace — though, in most cases, you should still extend it with raise and let%lwt, instead.

Methods

type method_ = [
  1. | `GET
  2. | `POST
  3. | `PUT
  4. | `DELETE
  5. | `HEAD
  6. | `CONNECT
  7. | `OPTIONS
  8. | `TRACE
  9. | `PATCH
  10. | `Method of string
]

HTTP request methods. See RFC 7231 §4.2, RFC 5789 §2, and MDN.

val method_to_string : method_ -> string

Evaluates to a string representation of the given method. For example, `GET is converted to "GET".

val string_to_method : string -> method_

Evaluates to the method_ corresponding to the given method string.

val methods_equal : method_ -> method_ -> bool

Compares two methods, such that equal methods are detected even if one is represented as a string. For example,

Dream.methods_equal `GET (`Method "GET") = true
val normalize_method : method_ -> method_

Converts methods represented as strings to variants. Methods generated by Dream are always normalized.

Dream.normalize_method (`Method "GET") = `GET

Status codes

type informational = [
  1. | `Continue
  2. | `Switching_Protocols
]

Informational (1xx) status codes. See RFC 7231 §6.2 and MDN. 101 Switching Protocols is generated internally by Dream.websocket. It is usually not necessary to use it directly.

type successful = [
  1. | `OK
  2. | `Created
  3. | `Accepted
  4. | `Non_Authoritative_Information
  5. | `No_Content
  6. | `Reset_Content
  7. | `Partial_Content
]

Successful (2xx) status codes. See RFC 7231 §6.3, RFC 7233 §4.1 and MDN. The most common is 200 OK.

type redirection = [
  1. | `Multiple_Choices
  2. | `Moved_Permanently
  3. | `Found
  4. | `See_Other
  5. | `Not_Modified
  6. | `Temporary_Redirect
  7. | `Permanent_Redirect
]

Redirection (3xx) status codes. See RFC 7231 §6.4 and RFC 7538 §3, and MDN. Use 303 See Other to direct clients to follow up with a GET request, especially after a form submission. Use 301 Moved Permanently for permanent redirections.

type client_error = [
  1. | `Bad_Request
  2. | `Unauthorized
  3. | `Payment_Required
  4. | `Forbidden
  5. | `Not_Found
  6. | `Method_Not_Allowed
  7. | `Not_Acceptable
  8. | `Proxy_Authentication_Required
  9. | `Request_Timeout
  10. | `Conflict
  11. | `Gone
  12. | `Length_Required
  13. | `Precondition_Failed
  14. | `Payload_Too_Large
  15. | `URI_Too_Long
  16. | `Unsupported_Media_Type
  17. | `Range_Not_Satisfiable
  18. | `Expectation_Failed
  19. | `Misdirected_Request
  20. | `Too_Early
  21. | `Upgrade_Required
  22. | `Precondition_Required
  23. | `Too_Many_Requests
  24. | `Request_Header_Fields_Too_Large
]

Client error (4xx) status codes. The most common are 400 Bad Request, 401 Unauthorized, 403 Forbidden, and, of course, 404 Not Found.

See MDN, and

type server_error = [
  1. | `Internal_Server_Error
  2. | `Not_Implemented
  3. | `Bad_Gateway
  4. | `Service_Unavailable
  5. | `Gateway_Timeout
  6. | `HTTP_Version_Not_Supported
]

Server error (5xx) status codes. See RFC 7231 §6.6 and MDN. The most common of these is 500 Internal Server Error.

Sum of all the status codes declared above.

type status = [
  1. | standard_status
  2. | `Status of int
]

Status codes, including codes directly represented as integers. See the types above for the full list and references.

val status_to_string : status -> string

Evaluates to a string representation of the given status. For example, `Not_Found and `Status 404 are both converted to "Not Found". Numbers are used for unknown status codes. For example, `Status 567 is converted to "567".

val status_to_reason : status -> string option

Converts known status codes to their string representations. Evaluates to None for unknown status codes.

val status_to_int : status -> int

Evaluates to the numeric value of the given status code.

val int_to_status : int -> status

Evaluates to the symbolic representation of the status code with the given number.

val is_informational : status -> bool

Evaluates to true if the given status is either from type Dream.informational, or is in the range `Status 100`Status 199.

val is_successful : status -> bool

Like Dream.is_informational, but for type Dream.successful and numeric codes 2xx.

val is_redirection : status -> bool

Like Dream.is_informational, but for type Dream.redirection and numeric codes 3xx.

val is_client_error : status -> bool

Like Dream.is_informational, but for type Dream.client_error and numeric codes 4xx.

val is_server_error : status -> bool

Like Dream.is_informational, but for type Dream.server_error and numeric codes 5xx.

val status_codes_equal : status -> status -> bool

Compares two status codes, such that equal codes are detected even if one is represented as a number. For example,

Dream.status_codes_equal `Not_Found (`Status 404) = true
val normalize_status : status -> status

Converts status codes represented as numbers to variants. Status codes generated by Dream are always normalized.

Dream.normalize_status (`Status 404) = `Not_Found

Requests

val client : request -> string

Client sending the request. For example, "127.0.0.1:56001".

val https : request -> bool

Whether the request was sent over HTTPS.

val method_ : request -> method_

Request method. For example, `GET.

val target : request -> string

Request target. For example, "/foo/bar". See Dream.path.

val path : request -> string list

Parsed request path. For example, "foo"; "bar".

val version : request -> int * int

Protocol version. (1, 1) for HTTP/1.1 and (2, 0) for HTTP/2.

val with_client : string -> request -> request

Replaces the client. See Dream.client.

val with_method_ : method_ -> request -> request

Replaces the method. See Dream.method_.

val with_path : string list -> request -> request

Replaces the path. See Dream.path.

val with_version : (int * int) -> request -> request

Replaces the version. See Dream.version.

val query : string -> request -> string option

First query parameter with the given name. See RFC 3986 §3.4 and example w-query.

val queries : string -> request -> string list

All query parameters with the given name.

val all_queries : request -> (string * string) list

Entire query string as a name-value list.

Responses

val response : ?status:status -> ?code:int -> ?headers:(string * string) list -> string -> response

Creates a new response with the given string as body. ~code and ~status are two ways to specify the status code, which is 200 OK by default. The headers are empty by default.

Note that browsers may interpret lack of a Content-Type: header as if its value were application/octet-stream or text/html; charset=us-ascii, which will prevent correct interpretation of UTF-8 strings. Either add a Content-Type: header using ~headers or Dream.add_header, or use a wrapper like Dream.html. The modern Content-Type: for HTML is text/html; charset=utf-8. See Dream.text_html.

val respond : ?status:status -> ?code:int -> ?headers:(string * string) list -> string -> response promise

Same as Dream.response, but the new response is wrapped in a promise.

val html : ?status:status -> ?code:int -> ?headers:(string * string) list -> string -> response promise

Same as Dream.respond, but adds Content-Type: text/html; charset=utf-8. See Dream.text_html.

val json : ?status:status -> ?code:int -> ?headers:(string * string) list -> string -> response promise

Same as Dream.respond, but adds Content-Type: application/json. See Dream.application_json.

val redirect : ?status:status -> ?code:int -> ?headers:(string * string) list -> request -> string -> response promise

Creates a new response. Adds a Location: header with the given string. The default status code is 303 See Other, for a temporary redirection. Use ~status:`Moved_Permanently or ~code:301 for a permanent redirection. The request is used for retrieving the site prefix, if the string is an absolute path. Most applications don't have a site prefix.

val empty : ?headers:(string * string) list -> status -> response promise

Same as Dream.response with the empty string for a body.

val stream : ?status:status -> ?code:int -> ?headers:(string * string) list -> (response -> unit promise) -> response promise

Same as Dream.respond, but calls Dream.with_stream internally to prepare the response for stream writing, and then runs the callback asynchronously to do it. See example j-stream.

fun request ->
  Dream.stream (fun response ->
    let%lwt () = Dream.write response "foo" in
    Dream.close_stream response)
val status : response -> status

Response status. For example, `OK.

Headers

val header : string -> 'a message -> string option

First header with the given name. Header names are case-insensitive. See RFC 7230 §3.2 and MDN.

val headers : string -> 'a message -> string list

All headers with the given name.

val all_headers : 'a message -> (string * string) list

Entire header set as name-value list.

val has_header : string -> 'a message -> bool

Whether the message has a header with the given name.

val add_header : string -> string -> 'a message -> 'a message

Appends a header with the given name and value. Does not remove any existing headers with the same name.

val drop_header : string -> 'a message -> 'a message

Removes all headers with the given name.

val with_header : string -> string -> 'a message -> 'a message

Equivalent to Dream.drop_header followed by Dream.add_header.

Cookies

Dream.set_cookie and Dream.cookie are designed for round-tripping secure cookies. The most secure settings applicable to the current server are inferred automatically. See example c-cookie [playground].

Dream.set_cookie "my.cookie" "foo" request response
Dream.cookie "my.cookie" request

The Dream.cookie call evaluates to Some "foo", but the actual cookie that is exchanged may look like:

__Host-my.cookie=AL7NLA8-so3e47uy0R5E2MpEQ0TtTWztdhq5pTEUT7KSFg; \
  Path=/; Secure; HttpOnly; SameSite=Strict

Dream.set_cookie has a large number of optional arguments for tweaking the inferred security settings. If you use them, pass the same arguments to Dream.cookie to automatically undo the result.

Appends a Set-Cookie: header to the response. Infers the most secure defaults from the request.

Dream.set_cookie "my.cookie" "value" request response

Specify Dream.run argument ~secret, or the Web app will not be able to decrypt cookies from prior starts.

See example c-cookie.

Most of the optional arguments are for overriding inferred defaults. ~expires and ~max_age are independently useful. In particular, to delete a cookie, use ~expires:0.

Dream.to_set_cookie is a “raw” version of this function that does not do any inference.

First cookie with the given name. See example c-cookie.

Dream.cookie "my.cookie" request

Pass the same optional arguments as to Dream.set_cookie for the same cookie. This will allow Dream.cookie to infer the cookie name prefix, implementing a transparent cookie round trip with the most secure attributes applicable.

val all_cookies : request -> (string * string) list

All cookies, with raw names and values.

Bodies

val body : 'a message -> string promise

Retrieves the entire body. Retains a reference to the body, so Dream.body can be used multiple times. See example 6-echo.

val with_body : string -> response -> response

Replaces the body.

Streaming

val read : request -> string option promise

Retrieves a body chunk. The chunk is not buffered, thus it can only be read once. See example j-stream.

val with_stream : response -> response

Makes the response ready for stream writing with Dream.write. You should return it from your handler soon after — only one call to Dream.write will be accepted before then. See Dream.stream for a more convenient wrapper.

val write : response -> string -> unit promise

Streams out the string. The promise is fulfilled when the response can accept more writes.

val flush : response -> unit promise

Flushes write buffers. Data is sent to the client.

val close_stream : response -> unit promise

Finishes the response stream.

Low-level streaming

Byte arrays in the C heap. See Bigarray.Array1. This type is also found in several libraries installed by Dream, so their functions can be used with Dream.buffer:

val next : buffer:(buffer -> int -> int -> unit) -> close:(unit -> unit) -> exn:(exn -> unit) -> request -> unit

Waits for the next stream event, and calls:

  • ~buffer with an offset and length, if a buffer is written,
  • ~close if close is requested, and
  • ~exn to report an exception.
val write_buffer : ?offset:int -> ?length:int -> response -> buffer -> unit promise

Streams out the buffer slice. ~offset defaults to zero. ~length defaults to the length of the buffer, minus ~offset.

JSON

Dream presently recommends using Yojson. See also ppx_yojson_conv for generating JSON parsers and serializers for OCaml data types.

See example e-json.

val origin_referer_check : middleware

CSRF protection for AJAX requests. Either the method must be `GET or `HEAD, or:

  • Origin: or Referer: must be present, and
  • their value must match Host:

Responds with 400 Bad Request if the check fails. See example e-json.

Implements the OWASP Verifying Origin With Standard Headers CSRF defense-in-depth technique, which is good enough for basic usage. Do not allow `GET or `HEAD requests to trigger important side effects if relying only on Dream.origin_referer_check.

Future extensions to this function may use X-Forwarded-Host or host whitelists.

For more thorough protection, generate CSRF tokens with Dream.csrf_token, send them to the client (for instance, in <meta> tags of a single-page application), and require their presence in an X-CSRF-Token: header.

Forms

Dream.form_tag and Dream.form round-trip secure forms. Dream.form_tag is used inside a template to generate a form header with a CSRF token:

<%s! Dream.form_tag ~action:"/" request %>
  <input name="my.field">
</form>

Dream.form recieves the form and checks the CSRF token:

match%lwt Dream.form request with
| `Ok ["my.field", value] -> (* ... *)
| _ -> Dream.empty `Bad_Request

See example d-form [playground].

type 'a form_result = [
  1. | `Ok of 'a
  2. | `Expired of 'a * float
  3. | `Wrong_session of 'a
  4. | `Invalid_token of 'a
  5. | `Missing_token of 'a
  6. | `Many_tokens of 'a
  7. | `Wrong_content_type
]

Form CSRF checking results, in order from least to most severe. See Dream.form and example d-form.

The first three constructors, `Ok, `Expired, and `Wrong_session can occur in regular usage.

The remaining constructors, `Invalid_token, `Missing_token, `Many_tokens, `Wrong_content_type correspond to bugs, suspicious activity, or tokens so old that decryption keys have since been rotated on the server.

val form : request -> (string * string) list form_result promise

Parses the request body as a form. Performs CSRF checks. Use Dream.form_tag in a template to transparently generate forms that will pass these checks. See Templates and example d-form.

The call must be done under a session middleware, since each CSRF token is scoped to a session. See Sessions.

Form fields are sorted for easy pattern matching:

match%lwt Dream.form request with
| `Ok ["email", email; "name", name] -> (* ... *)
| _ -> Dream.empty `Bad_Request

To recover from conditions like expired forms, add extra cases:

match%lwt Dream.form request with
| `Ok      ["email", email; "name", name] -> (* ... *)
| `Expired ["email", email; "name", name] -> (* ... *)
| _ -> Dream.empty `Bad_Request

It is recommended not to mutate state or send back sensitive data in the `Expired and `Wrong_session cases, as they may indicate an attack against a client.

The remaining cases, including unexpected field sets and the remaining constructors of Dream.form_result, usually indicate either bugs or attacks. It's usually fine to respond to all of them with 400 Bad Request.

Upload

type multipart_form = (string * (string option * string) list) list

Submitted file upload forms, <form enctype="multipart/form-data">. For example, if a form

<input name="files" type="file" multiple>
<input name="text">

is submitted with two files and a text value, it will be received by Dream.multipart as

[
  "files", [
    Some "file1.ext", "file1-content";
    Some "file2.ext", "file2-content";
  ];
  "text", [
    None, "text-value"
  ];
]

See example g-upload [playground] and RFC 7578.

Note that clients such as curl can send files with no filename (None), though most browsers seem to insert at least an empty filename (Some ""). Don't use use the presence of a filename to determine if the field value is a file. Use the field name and knowledge about the form instead.

If a file field has zero files when submitted, browsers send "field-name", [Some ""; ""]. Dream.multipart replaces this with "field-name", []. Use the advanced interface upload for the raw behavior.

Non-file fields always have one value, which might be the empty string.

See OWASP File Upload Cheat Sheet for security precautions for upload forms.

Like Dream.form, but also reads files, and Content-Type: must be multipart/form-data. The <form> tag and CSRF token can be generated in a template with

<%s! Dream.form_tag ~action:"/"
       ~enctype:`Multipart_form_data request %>

See Dream.form_tag, section Templates, and example g-upload.

Note that, like Dream.form, this function sorts form fields by field name.

Dream.multipart reads entire files into memory, so it is only suitable for prototyping, or with yet-to-be-added file size and count limits. See Dream.upload below for a streaming version.

Streaming uploads

type part = string option * string option * (string * string) list

Upload form parts.

A value Some (name, filename, headers) received by Dream.upload begins a part in the stream. A part represents either a form field, or a single, complete file.

Note that, in the general case, filename and headers are not reliable. name is the form field name.

val upload : request -> part option promise

Retrieves the next upload part.

Upon getting Some (name, filename, headers) from this function, the user should call Dream.upload_part to stream chunks of the part's data, until that function returns None. The user should then call Dream.upload again. None from Dream.upload indicates that all parts have been received.

upload does not verify a CSRF token. There are several ways to add CSRF protection for an upload stream, including:

val upload_part : request -> string option promise

Retrieves a part chunk.

CSRF tokens

It's usually not necessary to handle CSRF tokens directly.

CSRF functions are exposed for creating custom schemes, and for defense-in-depth purposes. See OWASP Cross-Site Request Forgery Prevention Cheat Sheet.

type csrf_result = [
  1. | `Ok
  2. | `Expired of float
  3. | `Wrong_session
  4. | `Invalid
]

CSRF token verification outcomes.

`Expired and `Wrong_session can occur in normal usage, when a user's form or session expire, respectively. However, they can also indicate attacks, including stolen tokens, stolen tokens from other sessions, or attempts to use a token from an invalidated pre-session after login.

`Invalid indicates a token with a bad signature, a payload that was not generated by Dream, or other serious errors that cannot usually be triggered by normal users. `Invalid usually corresponds to bugs or attacks. `Invalid can also occur for very old tokens after old keys are no longer in use on the server.

val csrf_token : ?valid_for:float -> request -> string

Returns a fresh CSRF token bound to the given request's and signed with the ~secret given to Dream.run. ~valid_for is the token's lifetime, in seconds. The default value is one hour (3600.). Dream uses signed tokens that are not stored server-side.

val verify_csrf_token : request -> string -> csrf_result promise

Checks that the CSRF token is valid for the request's session.

Templates

Dream includes a template preprocessor that allows interleaving OCaml and HTML in the same file:

let render message =
  <html>
    <body>
      <p>The message is <b><%s message %></b>!</p>
    </body>
  </html>

See examples 7-template [playground] and r-template [playground].

There is also a typed alternative, provided by an external library, TyXML. It is shown in example w-tyxml [playground]. If you are using Reason syntax, TyXML can be used with server-side JSX. See example r-tyxml [playground].

To use the built-in templates, add this to dune:

(rule
 (targets template.ml)
 (deps template.eml.ml)
 (action (run dream_eml %{deps} --workspace %{workspace_root})))

A template begins...

  • Implicitly on a line that starts with <, perhaps with leading whitespace. The line is part of the template.
  • Explicitly after a line that starts with %%. The %% line is not part of the template.

A %% line can also be used to set template options. The only option supported presently is %% response for streaming the template using Dream.write, to a response that is in scope. This is shown in examples w-template-stream and r-template-stream.

A template ends...

  • Implicitly, when the indentation level is less than that of the beginning line.
  • Explicitly on a line that starts with another %%.

Everything outside a template is ordinary OCaml code.

OCaml code can also be inserted into a template:

  • <%s code %> expects code to evaluate to a string, and inserts the string into the template.
  • A line that begins with % in the first column is OCaml code inside the template. Its value is not inserted into the template. Indeed, it can be fragments of control-flow constructs.
  • <% code %> is a variant of % that can be used for short snippets within template lines.

The s in <%s code %> is actually a Printf-style format specification. So, for example, one can print two hex digits using <%02X code %>.

<%s code %> automatically escapes the result of code using Dream.html_escape. This can be suppressed with !. <%s! code %> prints the result of code literally. Dream.html_escape is only safe for use in HTML text and quoted attribute values. It does not offer XSS protection in unquoted attribute values, CSS in <style> tags, or literal JavaScript in <script> tags.

val form_tag : ?enctype:[ `Multipart_form_data ] -> action:string -> request -> string

Generates a <form> tag and an <input> tag with a CSRF token, suitable for use with Dream.form and Dream.multipart. For example, in a template,

<%s! Dream.form_tag ~action:"/" request %>
  <input name="my.field">
</form>

expands to

<form method="POST" action="/">
  <input name="dream.csrf" type="hidden" value="a-token">
  <input name="my.field">
</form>

Pass ~enctype:`Multipart_form_data for a file upload form.

Middleware

Interesting built-in middlewares are scattered throughout the various sections of these docs, according to where they are relevant. This section contains only generic middleware combinators.

val no_middleware : middleware

Does nothing but call its inner handler. Useful for disabling middleware conditionally during application startup:

if development then
  my_middleware
else
  Dream.no_middleware
val pipeline : middleware list -> middleware

Combines a sequence of middlewares into one, such that these two lines are equivalent:

Dream.pipeline [middleware_1; middleware_2] @@ handler
               middleware_1 @@ middleware_2 @@ handler

Routing

val router : route list -> middleware

Creates a router. Besides interpreting routes, a router is a middleware which calls its next handler if none of its routes match the request. Route components starting with : are parameters, which can be retrieved with Dream.param. See example 3-router [playground].

let () =
  Dream.run
  @@ Dream.router [
    Dream.get "/echo/:word" @@ fun request ->
      Dream.html (Dream.param "word" request);
  ]
  @@ Dream.not_found

Dream.scope is the main form of site composition. However, Dream also supports full subsites with **:

let () =
  Dream.run
  @@ Dream.router [
    Dream.get "/static/**" @@ Dream.static "www/static";
  ]
  @@ Dream.not_found

** causes the request's path to be trimmed by the route prefix, and the request's prefix to be extended by it. It is mainly useful for “mounting” Dream.static as a subsite.

It can also be used as an escape hatch to convert a handler, which may include its own router, into a subsite. However, it is better to compose sites with routes and Dream.scope rather than opaque handlers and **, because, in the future, it may be possible to query routes for site structure metadata.

val get : string -> handler -> route

Forwards `GET requests for the given path to the handler.

Dream.get "/home" home_template
val post : string -> handler -> route
val put : string -> handler -> route
val delete : string -> handler -> route
val head : string -> handler -> route
val connect : string -> handler -> route
val options : string -> handler -> route
val trace : string -> handler -> route
val patch : string -> handler -> route

Like Dream.get, but for each of the other methods.

val any : string -> handler -> route

Like Dream.get, but does not check the method.

val not_found : handler

Always responds with 404 Not Found.

val param : string -> request -> string

Retrieves the path parameter. If it is missing, Dream.param raises an exception — the program is buggy.

val scope : string -> middleware list -> route list -> route

Groups routes under a common path prefix and middlewares. Middlewares are run only if a route matches.

Dream.scope "/api" [Dream.origin_referer_check] [
  Dream.get  "/widget" get_widget_handler;
  Dream.post "/widget" set_widget_handler;
]

To prefix routes without applying any more middleware, use the empty list:

Dream.scope "/api" [] [
  (* ...routes... *)
]

To apply middleware without prefixing the routes, use "/":

Dream.scope "/" [Dream.origin_referer_check] [
  (* ...routes... *)
]

Scopes can be nested.

val no_route : route

A dummy value of type route that is completely ignored by the router. Useful for disabling routes conditionally during application start:

Dream.router [
  if development then
    Dream.get "/graphiql" (Dream.graphiql "/graphql")
  else
    Dream.no_route;
]

Static files

val static : ?loader:(string -> string -> handler) -> string -> handler

Serves static files from a local directory. See example f-static.

let () =
  Dream.run
  @@ Dream.router {
    Dream.get "/static/**" @@ Dream.static "www/static";
  }
  @@ Dream.not_found

Dream.static local_directory validates the path substituted for ** by checking that it is (1) relative, (2) does not contain parent directory references (..), and (3) does not contain separators (/) within components. If these checks fail, Dream.static responds with 404 Not Found.

If the checks succeed, Dream.static calls ~loader local_directory path request, where

  • local_directory is the same directory that was passed to Dream.static.
  • path is what was substituted for **.

The default loader is Dream.from_filesystem. See example w-one-binary for a loader that serves files from memory instead.

val from_filesystem : string -> string -> handler

Dream.from_filesystem local_directory path request responds with a file from the file system found at local_directory ^ "/" ^ path. If such a file does not exist, it responds with 404 Not Found.

To serve single files like sitemap.xml from the file system, use routes like

Dream.get "/sitemap.xml" (Dream.from_filesystem "assets" "sitemap.xml")

Dream.from_filesystem calls Dream.mime_lookup to guess a Content-Type: based on the file's extension.

val mime_lookup : string -> (string * string) list

Returns a Content-Type: header based on the given filename. This is mostly a wrapper around magic-mime. However, if the result is text/html, Dream.mime_lookup replaces it with text/html; charset=utf-8, so as to match Dream.html.

Sessions

Dream's default sessions contain string-to-string dictionaries for application data. For example, a logged-in session might have

[
  "user", "someone";
  "lang", "ut-OP";
]

Sessions also have three pieces of metadata:

There are several back ends, which decide where the sessions are stored:

All requests passing through session middleware are assigned a session, either an existing one, or a new empty session, known as a pre-session.

See example b-session [playground].

val session : string -> request -> string option

Value from the request's session.

val put_session : string -> string -> request -> unit promise

Mutates a value in the request's session. The back end may commit the value to storage immediately, so this function returns a promise.

val all_session_values : request -> (string * string) list

Full session dictionary.

val invalidate_session : request -> unit promise

Invalidates the request's session, replacing it with a fresh, empty pre-session.

Back ends

val memory_sessions : ?lifetime:float -> middleware

Stores sessions in server memory. Passes session IDs to clients in cookies. Session data is lost when the server process exits.

Stores sessions in encrypted cookies. Pass Dream.run ~secret to be able to decrypt cookies from previous server runs.

val sql_sessions : ?lifetime:float -> middleware

Stores sessions in an SQL database. Passes session IDs to clients in cookies. Must be used under Dream.sql_pool. Expects a table

CREATE TABLE dream_session (
  id TEXT PRIMARY KEY,
  label TEXT NOT NULL,
  expires_at REAL NOT NULL,
  payload TEXT NOT NULL
)

Metadata

val session_id : request -> string

Secret value used to identify a client.

val session_label : request -> string

Tracing label suitable for printing to logs.

val session_expires_at : request -> float

Time at which the session will expire.

WebSockets

type websocket

A WebSocket connection. See RFC 6455 and MDN.

val websocket : ?headers:(string * string) list -> (websocket -> unit promise) -> response promise

Creates a fresh 101 Switching Protocols response. Once this response is returned to Dream's HTTP layer, the callback is passed a new websocket, and the application can begin using it. See example k-websocket [playground].

let my_handler = fun request ->
  Dream.websocket (fun websocket ->
    let%lwt () = Dream.send websocket "Hello, world!" in
    Dream.close_websocket websocket);
val send : ?kind:[ `Text | `Binary ] -> websocket -> string -> unit promise

Sends a single message. The WebSocket is ready another message when the promise resolves.

With ~kind:`Text, the default, the message is interpreted as a UTF-8 string. The client will receive it transcoded to JavaScript's UTF-16 representation.

With ~kind:`Binary, the message will be received unmodified, as either a Blob or an ArrayBuffer. See MDN, WebSocket.binaryType.

val receive : websocket -> string option promise

Retrieves a message. If the WebSocket is closed before a complete message arrives, the result is None.

val close_websocket : ?code:int -> websocket -> unit promise

Closes the WebSocket. ~code is usually not necessary, but is needed for some protocols based on WebSockets. See RFC 6455 §7.4.

GraphQL

Dream integrates ocaml-graphql-server. See examples:

If you are also writing a client in a flavor of OCaml, consider graphql-ppx for generating GraphQL queries.

See OWASP GraphQL Cheat Sheet for an overview of security topics related to GraphQL.

val graphql : (request -> 'a promise) -> 'a Graphql_lwt.Schema.schema -> handler

Dream.graphql make_context schema serves the GraphQL schema.

let () =
  Dream.run
  @@ Dream.router [
    Dream.any "/graphql"  (Dream.graphql Lwt.return schema);
    Dream.get "/graphiql" (Dream.graphiql "/graphql");
  ]
  @@ Dream.not_found

make_context is called by Dream.graphql on every request to create the context, a value that is passed to each resolver from the schema. Passing Lwt.return, the same as

fun request -> Lwt.return request

causes the request itself to be used as the context:

field "name"
  ~doc:"User name"
  ~typ:(non_null string)
  ~args:Arg.[]
  ~resolve:(fun info user ->
    (* The context is in info.ctx *)
    user.name);
val graphiql : ?default_query:string -> string -> handler

Serves GraphiQL, a GraphQL query editor. The string gives the GraphQL endpoint that the editor will work with.

~default_query sets the query that appears upon the first visit to the endpoint. It is empty by default. The string is pasted literally into the content of a JavaScript string, between its quotes, so it must be escaped manually.

Dream's build of GraphiQL is found in the src/graphiql directory. If you have the need, you can use it as the starting point for your own customized GraphiQL.

Use Dream.no_route to disable GraphiQL conditionally outside of development.

SQL

Dream provides thin convenience functions over Caqti, an SQL interface with several back ends. See example h-sql [playground].

Dream installs the core caqti package, but you should also install at least one of:

They are separated because each has its own system library dependencies. Regardless of which you install, usage on the OCaml level is the same. The differences are in SQL syntax, and in external SQL server or file setup. See

For a superficial overview of database security, see OWASP Database Security Cheat Sheet.

val sql_pool : ?size:int -> string -> middleware

Makes an SQL connection pool available to its inner handler.

val sql : request -> (Caqti_lwt.connection -> 'a promise) -> 'a promise

Runs the callback with a connection from the SQL pool. See example h-sql.

let () =
  Dream.run
  @@ Dream.sql_pool "sqlite3://db.sqlite"
  @@ fun request ->
    Dream.sql request (fun db ->
      (* ... *) |> Dream.html)

Logging

Dream uses the Logs library internally, and integrates with all other libraries in your project that are also using it. Dream provides a slightly simplified interface to Logs.

All log output is written to stderr.

See OWASP Logging Cheat Sheet for a survey of security topics related to logging.

val logger : middleware

Logs and times requests. Time spent logging is included. See example 2-middleware [playground].

val log : ('a, Format.formatter, unit, unit) format4 -> 'a

Formats a message and logs it. Disregard the obfuscated type: the first argument is a format string as described in the standard library modules Printf and Format. The rest of the arguments are determined by the format string. See example a-log [playground].

Dream.log "Counter is now: %i" counter;
Dream.log "Client: %s" (Dream.client request);
type ('a, 'b) conditional_log = ((?request:request -> ('a, Format.formatter, unit, 'b) format4 -> 'a) -> 'b) -> unit

Loggers. This type is difficult to read — instead, see Dream.error for usage.

type log_level = [
  1. | `Error
  2. | `Warning
  3. | `Info
  4. | `Debug
]

Log levels, in order from most urgent to least.

val error : ('a, unit) conditional_log

Formats a message and writes it to the log at level `Error. The inner formatting function is called only if the current log level is `Error or higher. See example a-log.

Dream.error (fun log ->
  log ~request "My message, details: %s" details);

Pass the optional argument ~request to Dream.error to associate the message with a specific request. If not passed, Dream.error will try to guess the request. This usually works, but not always.

val warning : ('a, unit) conditional_log
val info : ('a, unit) conditional_log
val debug : ('a, unit) conditional_log

Like Dream.error, but for each of the other log levels.

type sub_log = {
  1. error : 'a. ('a, unit) conditional_log;
  2. warning : 'a. ('a, unit) conditional_log;
  3. info : 'a. ('a, unit) conditional_log;
  4. debug : 'a. ('a, unit) conditional_log;
}

Sub-logs. See Dream.sub_log right below.

val sub_log : string -> sub_log

Creates a new sub-log with the given name. For example,

let log = Dream.sub_log "myapp.ajax"

...creates a logger that can be used like Dream.error and the other default loggers, but prefixes "myapp.ajax" to each log message.

log.error (fun log -> log ~request "Validation failed")

See README of example a-log.

val initialize_log : ?backtraces:bool -> ?async_exception_hook:bool -> ?level:log_level -> ?enable:bool -> unit -> unit

Initializes Dream's log with the given settings.

Dream initializes its logging back end lazily. This is so that if a Dream Web app is linked into a larger binary, it does not affect that binary's runtime unless the Web app runs.

This also allows the Web app to give logging settings explicitly by calling Dream.initialize_log early in program execution.

  • ~async_exception_hook:true, the default, causes Dream to set Lwt.async_exception_hook so as to forward all asynchronous exceptions to the logger, and not terminate the process.
  • ~level sets the log level threshould for the entire binary. The default is `Info.
  • ~enable:false disables Dream logging completely. This can help sanitize output during testing.

Errors

Dream passes all errors to a single error handler, including...

  • exceptions and rejected promises from the application,
  • 4xx and 5xx responses from the application, and
  • lower-level errors, such as TLS handshake failures and malformed HTTP requests.

This allows customizing error handling in one place. Including low-level errors prevents leakage of strings in automatic responses not under the application's control, for full internationalization.

Use Dream.error_template and pass the result to Dream.run ~error_handler to customize the error template.

The default error handler logs errors and its template generates completely empty responses, to avoid internationalization issues. In addition, this conforms to the recommendations in OWASP Error Handling Cheat Sheet.

For full control over error handling, including logging, you can define an error_handler directly.

type error = {
  1. condition : [ `Response of response | `String of string | `Exn of exn ];
  2. layer : [ `App | `HTTP | `HTTP2 | `TLS | `WebSocket ];
  3. caused_by : [ `Server | `Client ];
  4. request : request option;
  5. response : response option;
  6. client : string option;
  7. severity : log_level;
  8. debug : bool;
  9. will_send_response : bool;
}

Detailed errors. Ignore this type if only using Dream.error_template.

  • condition describes the error itself.

    • `Response is a 4xx or 5xx response.
    • `String is an error that has only an English-language description.
    • `Exn is a caught exception.

    The default error handler logs `Exn and `Strings, but not `Response. `Response is assumed to be deliberate, and already logged by Dream.logger.

  • layer is which part of the Dream stack detected the error.

    • `App is for application exceptions, rejections, and 4xx, 5xx responses.
    • `HTTP and `HTTP2 are for low-level HTTP protocol errors.
    • `TLS is for low-level TLS errors.
    • `WebSocket is for WebSocket errors.

    The default error handler uses this to just prepend a prefix to its log messages.

  • caused_by is the party likely to have caused the error.

    • `Server errors suggest bugs, and correspond to 5xx responses.
    • `Client errors suggest user errors, network failure, buggy clients, and sometimes attacks. They correspond to 4xx responses.
  • request is a request associated with the error, if there is one.

    As examples, a request might not be available if the error is a failure to parse an HTTP/1.1 request at all, or failure to perform a TLS handshake.

    In case of a `WebSocket error, the request is the client's original request to establish the WebSocket connection.

  • response is a response that was either generated by the application, or suggested by the error context.

    In case of a `WebSocket error, the response is the application's original connection agreement response created by Dream.websocket.

    See Dream.error_template.

  • client is the client's address, if available. For example, 127.0.0.1:56001.
  • Suggested log level for the error. Usually `Error for `Server errors and `Warning for client errors.
  • debug is true if Dream.run was called with ~debug.

    If so, the default error handler gathers various fields from the current request, formats the error condition, and passes the resulting string to the template as debug_dump.

    The default template shows this string in its repsonse, instead of returning a response with no body.

  • will_send_response is true in error contexts where Dream will still send a response.

    The default handler calls the error template only if will_send_response is true.

type error_handler = error -> response option promise

Error handlers log errors and convert them into responses. Ignore if using Dream.error_template.

If the error has will_send_response = true, the error handler must return a response. Otherwise, it should return None.

If an error handler raises an exception or rejects, Dream logs this secondary failure. If the error context needs a response, Dream responds with an empty 500 Internal Server Error.

The behavior of Dream's default error handler is described at Dream.error.

val error_template : (string option -> response -> response promise) -> error_handler

Builds an error_handler from a template. See example 9-error [playground].

let my_error_handler =
  Dream.error_template (fun ~debug_dump suggested_response ->
    let body =
      match debug_dump with
      | Some string -> Dream.html_escape string
      | None -> Dream.status_to_string (Dream.status suggested_response)
    in

    suggested_response
    |> Dream.with_body body
    |> Lwt.return)

The error's context suggests a response. Usually, its only valid field is Dream.status.

  • If the error is an exception or rejection from the application, the status is usually 500 Internal Server Error.
  • In case of a 4xx or 5xx response from the application, that response itself is passed to the template.
  • For low-level errors, the status is typically either 400 Bad Request if the error was likely caused by the client, and 500 Internal Server Error if the error was likely caused by the server.

If ~debug was passed to Dream.run, ~debug_dump will be Some info, where info is a multi-line string containing an error description, stack trace, request state, and other information.

When an error occurs in a context where a response is not possible, the template is not called. In some contexts where the template is called, the status code is hardcoded, but the headers and body from the template's response will still be used.

If the template itself raises an exception or rejects, an empty 500 Internal Server Error will be sent in contexts that require a response.

Servers

val run : ?interface:string -> ?port:int -> ?stop:unit promise -> ?debug:bool -> ?error_handler:error_handler -> ?secret:string -> ?old_secrets:string list -> ?prefix:string -> ?https:bool -> ?certificate_file:string -> ?key_file:string -> ?builtins:bool -> ?greeting:bool -> ?adjust_terminal:bool -> handler -> unit

Runs the Web application represented by the handler, by default at http://localhost:8080.

This function calls Lwt_main.run internally, so it is intended to be the main loop of a program. Dream.serve is a version that does not call Lwt_main.run.

  • ~interface is the network interface to listen on. Defaults to "localhost". Use "0.0.0.0" to listen on all interfaces.
  • ~port is the port to listen on. Defaults to 8080.
  • ~stop is a promise that causes the server to stop accepting new requests, and Dream.run to return. Requests that have already entered the Web application continue to be processed. The default value is a promise that never resolves. However, see also ~stop_on_input.
  • ~debug:true enables debug information in error templates. See Dream.error_template. The default is false, to prevent accidental deployment with debug output turned on. See example 8-debug [playground].
  • ~error_handler handles all errors, both from the application, and low-level errors. See Errors and example 9-error [playground].
  • ~secret is a key to be used for cryptographic operations, such as signing CSRF tokens. By default, a random secret is generated on each call to Dream.run. For production, generate a 256-bit key with

    Dream.to_base64url (Dream.random 32)

    and load it from file. A medium-sized Web app serving 1000 fresh encrypted cookies per second should rotate keys about once a year. See argument ~old_secrets below for key rotation. See Dream.encrypt for cipher information.

  • ~old_secrets is a list of previous secrets that can still be used for decryption, but not for encryption. This is intended for key rotation.
  • ~prefix is a site prefix for applications that are not running at the root (/) of their domain. The default is "/", for no prefix.
  • ~https:true enables HTTPS. You should also specify ~certificate_file and ~key_file. However, for development, Dream includes an insecure compiled-in localhost certificate. Enabling HTTPS also enables transparent upgrading of connections to HTTP/2. See example l-https.
  • ~certificate_file and ~key_file specify the certificate and key file, respectively, when using ~https. They are not required for development, but are required for production. Dream will write a warning to the log if you are using ~https, don't provide ~certificate_file and ~key_file, and ~interface is not "localhost".
  • ~builtins:false disables Built-in middleware.

The remaining arguments can be used to gradually disable convenience features of Dream.run. Once both are disabled, you may want to switch to using Dream.serve.

  • ~greeting:false disables the start-up log message that prints a link to the Web application.
  • ~adjust_terminal:false disables adjusting the terminal to disable echo and line wrapping.
val serve : ?interface:string -> ?port:int -> ?stop:unit promise -> ?debug:bool -> ?error_handler:error_handler -> ?secret:string -> ?old_secrets:string list -> ?prefix:string -> ?https:bool -> ?certificate_file:string -> ?key_file:string -> ?builtins:bool -> handler -> unit promise

Like Dream.run, but returns a promise that does not resolve until the server stops listening, instead of calling Lwt_main.run.

This function is meant for integrating Dream applications into larger programs that have their own procedures for starting and stopping the Web server.

All arguments have the same meanings as they have in Dream.run.

Built-in middleware

Built-in middleware is Dream functionality that is implemented as middleware for maintainability reasons. It is necessary for Dream to work correctly. However, because it is middleware, Dream allows replacing it with Dream.run ~builtins:false. The middleware is applied in documented order, so

Dream.run my_app

is the same as

Dream.run ~builtins:false
@@ Dream.lowercase_headers
@@ Dream.content_length
@@ Dream.catch (* ... *)
@@ Dream.assign_request_id
@@ Dream.chop_site_prefix
@@ my_app

The middleware can be replaced with work-alikes, or omitted to use Dream as a fairly raw abstraction layer over low-level HTTP libraries.

val lowercase_headers : middleware

Lowercases response headers for HTTP/2 requests.

val content_length : middleware

If the request has Dream.version (1, _), then...

  • if the response does not have Content-Length: and the body is a string, sets Content-Length: to the string's length, or
  • if the response does not have Transfer-Encoding: and the body is a stream, sets Transfer-Encoding: chunked.

This is built in because an application cannot be expected to decide including these headers in the face of transparent HTTP/2 upgrades. The headers are necessary in HTTP/1, and forbidden or redundant and difficult to use in HTTP/2.

val catch : (error -> response promise) -> middleware

Forwards exceptions, rejections, and 4xx, 5xx responses from the application to the error handler. See Errors.

val assign_request_id : middleware

Assigns an id to each request.

val chop_site_prefix : string -> middleware

Removes Dream.run ~prefix from the path in each request, and adds it to the request prefix. Responds with 502 Bad Gateway if the path does not have the expected prefix.

Web formats

val html_escape : string -> string

Escapes a string so that it is suitable for use as text inside HTML elements and quoted attribute values. Implements OWASP Cross-Site Scripting Prevention Cheat Sheet RULE #1.

This function is not suitable for use with unquoted attributes, inline scripts, or inline CSS. See Security in example 7-template.

val to_base64url : string -> string

Converts the given string its base64url encoding, as specified in RFC 4648 §5, using a Web-safe alphabet and no padding. The resulting string can be used without escaping in URLs, form data, cookies, HTML content, attributes, and JavaScript code. For more options, see the Base64 library.

val from_base64url : string -> string option

Inverse of Dream.to_base64url.

val to_percent_encoded : ?international:bool -> string -> string

Percent-encodes a string for use inside a URL.

~international is true by default, and causes non-ASCII bytes to be preserved. This is suitable for display to users, including in <a href=""> attributes, which are displayed in browser status lines. See RFC 3987.

Use ~international:false for compatibility with legacy systems, or when constructing URL fragments from untrusted input that may not match the interface language(s) the user expects. In the latter case, similar letters from different writing scripts can be used to mislead users about the targets of links.

val from_percent_encoded : string -> string
val to_form_urlencoded : (string * string) list -> string

Inverse of Dream.from_form_urlencoded. Percent-encodes names and values.

val from_form_urlencoded : string -> (string * string) list

Converts form data or a query string from application/x-www-form-urlencoded format to a list of name-value pairs. See RFC 1866 §8.2.1. Reverses the percent-encoding of names and values.

Converts a Cookie: header value to key-value pairs. See RFC 6265bis §4.2.1. Does not apply any decoding to names and values.

Dream.to_set_cookie name value formats a Set-Cookie: header value. The optional arguments correspond to the attributes specified in RFC 6265bis §5.3, and are documented at Dream.set_cookie.

Does not apply any encoding to names and values. Be sure to encode so that names and values cannot contain `=`, `;`, or newline characters.

val split_target : string -> string * string

Splits a request target into a path and a query string.

val from_path : string -> string list

Splits the string into components on / and percent-decodes each component. Empty components are dropped, except for the last. This function does not distinguish between absolute and relative paths, and is only meant for routes and request targets. So,

  • Dream.from_path "" becomes [].
  • Dream.from_path "/" becomes [""].
  • Dream.from_path "abc" becomes ["abc"].
  • Dream.from_path "/abc" becomes ["abc"].
  • Dream.from_path "abc/" becomes ["abc"; ""].
  • Dream.from_path "a%2Fb" becomes ["a/b"].
  • Dream.from_path "a//b" becomes ["a"; "b"].

This function is not for use on full targets, because they may incldue query strings (?), and Dream.from_path does not treat them specially. Split query strings off with Dream.split_target first.

val to_path : ?relative:bool -> ?international:bool -> string list -> string

Percent-encodes a list of path components and joins them with / into a path. Empty components, except for the last, are removed. The path is absolute by default. Use ~relative:true for a relative path. Dream.to_path uses an IRI-friendly percent encoder, which preserves UTF-8 bytes in unencoded form. Use ~international:false to percent-encode those bytes as well, for legacy protocols that require ASCII URLs.

val drop_trailing_slash : string list -> string list

Changes the representation of path abc/ to the representation of abc by checking if the last element in the list is "", and, if it is, dropping it.

val text_html : string

The string "text/html; charset=utf-8" for Content-Type: headers.

val application_json : string

The string "application/json" for Content-Type: headers.

Cryptography

val random : int -> string

Generates the requested number of bytes using a cryptographically secure random number generator.

val encrypt : ?associated_data:string -> request -> string -> string

Signs and encrypts the string using the ~secret in the request. See Dream.run for setting ~secret.

~associated_data is included when computing the signature, but not included in the ciphertext. It can be used like a “salt,” to force ciphertexts from different contexts to be distinct, and dependent on the context.

For example, when Dream.set_cookie encrypts cookie values, it internally passes the cookie names in the associated data. This makes it impossible (or impractical) to use the ciphertext from one cookie as the value of another. The associated data will not match, and the value will be recognized as invalid.

The cipher presently used by Dream is AEAD_AES_256_GCM. It will be replaced by AEAD_AES_256_GCM_SIV as soon as the latter is available. The upgrade will be transparent, because Dream includes a cipher rotation scheme.

The cipher is suitable for encrypted transmissions and storing data other than credentials. For password or other credential storage, see package argon2. See OWASP Cryptographic Storage Cheat Sheet and OWASP Password Storage Cheat Sheet.

val decrypt : ?associated_data:string -> request -> string -> string option

Reverses Dream.encrypt.

To support secret rotation, the decryption secrets with which decryption is attempted are (~secret)::(~old_secrets). See the descriptions of ~secret and ~old_secrets in Dream.run.

Variables

Dream provides two variable scopes for use by middlewares.

type 'a local

Per-message variable.

type 'a global

Per-server variable.

val new_local : ?name:string -> ?show_value:('a -> string) -> unit -> 'a local

Declares a variable of type 'a in all messages. The variable is initially unset in each message. The optional ~name and ~show_value are used by Dream.run ~debug to show the variable in debug dumps.

val local : 'a local -> 'b message -> 'a option

Retrieves the value of the per-message variable.

val with_local : 'a local -> 'a -> 'b message -> 'b message

Sets the per-message variable to the value.

val new_global : ?name:string -> ?show_value:('a -> string) -> (unit -> 'a) -> 'a global

Declares a variable of type 'a in all servers. The first time the variable is accessed, the given initializer function is called to get its value. Global variables cannot be changed. So, they are typically refs or other mutable data structures, such as hash tables.

val global : 'a global -> request -> 'a

Retrieves the value of the per-server variable.

Testing

val request : ?client:string -> ?method_:method_ -> ?target:string -> ?version:(int * int) -> ?headers:(string * string) list -> string -> request

Dream.request body creates a fresh request with the given body for testing. The optional arguments set the corresponding request fields.

val test : ?prefix:string -> handler -> request -> response

Dream.test handler runs a handler the same way the HTTP server (Dream.run) would — assigning it a request id and noting the site root prefix, which is used by routers. Dream.test calls Lwt_main.run internally to await the response, which is why the response returned from the test is not wrapped in a promise. If you don't need these facilities, you can test handler by calling it directly with a request.

val first : 'a message -> 'a message

Dream.first message evaluates to the original request or response that message is immutably derived from. This is useful for getting the original state of requests especially, when they were first created inside the HTTP server (Dream.run).

val last : 'a message -> 'a message

Dream.last message evaluates to the latest request or response that was derived from message. This is most useful for obtaining the state of requests at the time an exception was raised, without having to instrument the latest version of the request before the exception.

val sort_headers : (string * string) list -> (string * string) list

Sorts headers by name. Headers with the same name are not sorted by value or otherwise reordered, because order is significant for some headers. See RFC 7230 §3.2.2 on header order. This function can help sanitize output before comparison.

val echo : handler

Responds with the request body.