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:

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

HTTP responses, such as 200 OK. See Responses.

The remaining three types are for building up Web apps.

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!")

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

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

'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

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 client message and server message. client and server 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_ = [
  | `GET
  | `POST
  | `PUT
  | `DELETE
  | `HEAD
  | `CONNECT
  | `OPTIONS
  | `TRACE
  | `PATCH
  | `Method of string
]

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.

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

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

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

Status codes

type informational = [
  | `Continue
  | `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 = [
  | `OK
  | `Created
  | `Accepted
  | `Non_Authoritative_Information
  | `No_Content
  | `Reset_Content
  | `Partial_Content
]

type redirection = [
  | `Multiple_Choices
  | `Moved_Permanently
  | `Found
  | `See_Other
  | `Not_Modified
  | `Temporary_Redirect
  | `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 = [
  | `Bad_Request
  | `Unauthorized
  | `Payment_Required
  | `Forbidden
  | `Not_Found
  | `Method_Not_Allowed
  | `Not_Acceptable
  | `Proxy_Authentication_Required
  | `Request_Timeout
  | `Conflict
  | `Gone
  | `Length_Required
  | `Precondition_Failed
  | `Payload_Too_Large
  | `URI_Too_Long
  | `Unsupported_Media_Type
  | `Range_Not_Satisfiable
  | `Expectation_Failed
  | `Misdirected_Request
  | `Too_Early
  | `Upgrade_Required
  | `Precondition_Required
  | `Too_Many_Requests
  | `Request_Header_Fields_Too_Large
  | `Unavailable_For_Legal_Reasons
]

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 = [
  | `Internal_Server_Error
  | `Not_Implemented
  | `Bad_Gateway
  | `Service_Unavailable
  | `Gateway_Timeout
  | `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.

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

val is_redirection : [< status ] -> bool

val is_client_error : [< status ] -> bool

val is_server_error : [< 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

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

Dream.normalize_status (`Status 404) = `Not_Found

Requests

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

Whether the request was sent over a TLS connection.

Request method. For example, `GET.

Request target. For example, "/foo/bar".

val set_client : request -> string -> unit

val queries : request -> string -> 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

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.

As your Web app develops, consider adding Content-Security-Policy headers, as described in example w-content-security-policy. These headers are completely optional, but they can provide an extra layer of defense for a mature app.

val json :
  ?status:[< status ] ->
  ?code:int ->
  ?headers:(string * string) list ->
    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.

If you use ~code, be sure the number follows the pattern 3xx, or most browsers and other clients won’t actually perform a redirect.

The request is used for retrieving the site prefix, if the string is an absolute path. Most applications don’t have a site prefix.

Response status. For example, `OK.

Sets the response status.

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

All headers with the given name.

Entire header set as name-value list.

Whether the message has a header with the given name.

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

Removes all headers with the given name.

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 response request "my.cookie"http://aantron.github.io/"foo"
Dream.cookie request "my.cookie"

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 request response "my.cookie"http://aantron.github.io/"value"

Use the Dream.set_secret middleware, 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.

Deletes the given cookie.

This function works by calling Dream.set_cookie, and setting the cookie to expire in the past. Pass all the same optional values that you would pass to Dream.set_cookie to make sure that the same cookie is deleted.

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

Dream.cookie request "my.cookie"

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 set_body : 'a message -> string -> unit

Replaces the body.

Streams

Gradual reading of request bodies or gradual writing of response bodies.

val body_stream : request -> stream

A stream that can be used to gradually read the request’s body.

Creates a response with a stream open for writing, and passes the stream to the callback when it is ready. See example j-stream.

fun request ->
  Dream.stream (fun stream ->
    Dream.write stream "foo")

Dream.stream automatically closes the stream when the callback returns or raises an exception. Pass ~close:false to suppress this behavior.

Retrieves a body chunk. See example j-stream.

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

Flushes the stream’s write buffer. Data is sent to the client.

Low-level streaming

Note: this part of the API is still a work in progress.

type buffer =
  (char, Bigarray.int8_unsigned_elt, Bigarray.c_layout)
    Bigarray.Array1.t

val read_stream :
  stream ->
  data:(buffer -> int -> int -> bool -> bool -> unit) ->
  flush:(unit -> unit) ->
  ping:(buffer -> int -> int -> unit) ->
  pong:(buffer -> int -> int -> unit) ->
  close:(int -> unit) ->
  exn:(exn -> unit) ->
    unit

Waits for the next stream event, and calls:

  • ~data with an offset and length, if a buffer is received, ~ ~flush if a flush request is received,
  • ~ping if a ping is received (WebSockets only),
  • ~pong if a pong is received (WebSockets only),
  • ~close if the stream is closed, and
  • ~exn to report an exception.

val write_stream :
  stream ->
  buffer -> int -> int ->
  bool -> bool ->
  close:(int -> unit) ->
  exn:(exn -> unit) ->
  (unit -> unit) ->
    unit

Writes a buffer into the stream:

write_stream stream buffer offset length binary fin ~close ~exn callback

write_stream calls one of its three callback functions, depending on what happens with the write:

  • ~close if the stream is closed before the write completes,
  • ~exn to report an exception during or before the write,
  • callback to report that the write has succeeded and the stream can accept another write.

binary and fin are for WebSockets only. binary marks the stream as containing binary (non-text) data, and fin sets the FIN bit, indicating the end of a message. These two parameters are ignored by non-WebSocket streams.

val flush_stream :
  stream ->
  close:(int -> unit) ->
  exn:(exn -> unit) ->
  (unit -> unit) ->
    unit

Requests the stream be flushed. The callbacks have the same meaning as in write_stream.

val ping_stream :
  stream ->
  buffer -> int -> int ->
  close:(int -> unit) ->
  exn:(exn -> unit) ->
  (unit -> unit) ->
    unit

Sends a ping frame on the WebSocket stream. The buffer is typically empty, but may contain up to 125 bytes of data.

val pong_stream :
  stream ->
  buffer -> int -> int ->
  close:(int -> unit) ->
  exn:(exn -> unit) ->
  (unit -> unit) ->
    unit

val close_stream : stream -> int -> unit

Closes the stream. The integer parameter is a WebSocket close code, and is ignored by non-WebSocket streams.

val abort_stream : stream -> exn -> unit

Aborts the stream, causing all readers and writers to receive the given exception.

WebSockets

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!");

Dream.websocket automatically closes the WebSocket when the callback returns or raises an exception. Pass ~close:false to suppress this behavior.

type text_or_binary = [ `Text | `Binary ]

type end_of_message = [ `End_of_message | `Continues ]

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

With ~text_or_binary:`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 ~text_or_binary:`Binary, the message will be received unmodified, as either a Blob or an ArrayBuffer. See MDN, WebSocket.binaryType.

~end_of_message is ignored for now, as the WebSocket library underlying Dream does not support sending message fragments yet.

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

Receives a single fragment of a message, streaming it.

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

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.

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_referrer_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 tags of a single-page application), and require their presence in an X-CSRF-Token: header.

Forms

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

<%s! Dream.csrf_tag request %>

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 = [
  | `Ok            of 'a
  | `Expired       of 'a * float
  | `Wrong_session of 'a
  | `Invalid_token of 'a
  | `Missing_token of 'a
  | `Many_tokens   of 'a
  | `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.

Parses the request body as a form. Performs CSRF checks. Use Dream.csrf_tag in a form 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.

The returned form fields are sorted in alphabetical order for reliable pattern matching. This is because browsers can transmit the form fields in a different order from how they appear in the HTML:

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,

. For example, if a form


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 "http://aantron.github.io/"). 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 "http://aantron.github.io/"; "http://aantron.github.io/"]. Dream.multipart replaces this with "field-name", []. Use the advanced interface, Dream.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 CSRF token can be generated in a template with


  <%s! Dream.csrf_tag request %>

See 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.

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.

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

  • Generate a CSRF token with Dream.csrf_tag. Check for `Field ("dream.csrf", token) during upload and call Dream.verify_csrf_token.
  • Use FormData in the client to submit multipart/form-data by AJAX, and include a custom header.

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 = [
  | `Ok
  | `Expired of float
  | `Wrong_session
  | `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.set_secret. ~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.

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 =
  
    
      

The message is <%s message %>!

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