Library
Module
Module type
Parameter
Class
Class type
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:
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;
];
]
The three handler-related types have a vaguely algebraic interpretation:
handler
is an atom.middleware
is for sequential composition (product-like). Dream.no_middleware
is 1.route
is for alternative composition (sum-like). Dream.no_route
is 0.Dream.scope
implements a left distributive law, making Dream a ring-like structure.
and 'a message = 'a Dream_pure.Message.message
and client = Dream_pure.Message.client
and server = Dream_pure.Message.server
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.
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.
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
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
]
Successful (2xx
) status codes. See RFC 7231 §6.3, RFC 7233 §4.1 and MDN. The most common is 200 OK
.
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
| `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
]
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
416 Range Not Satisfiable
.421 Misdirected Request
.425 Too Early
.428 Precondition Required
, 429 Too Many Requests
, and 431 Request
Headers Too Large
.451 Unavailable For Legal Reasons
.type server_error = [
| `Internal_Server_Error
| `Not_Implemented
| `Bad_Gateway
| `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
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
.
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
val client : request -> string
Client sending the request. For example, "127.0.0.1:56001"
.
val tls : request -> bool
Whether the request was sent over a TLS connection.
val target : request -> string
Request target. For example, "/foo/bar"
.
val set_client : request -> string -> unit
Replaces the client. See Dream.client
.
Replaces the method. See Dream.method_
.
val query : request -> string -> string option
First query parameter with the given name. See RFC 3986 §3.4 and example w-query
.
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.
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
.
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
Same as Dream.respond
, but adds Content-Type: application/json
. See Dream.application_json
.
val redirect :
?status:[< redirection ] ->
?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.
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.
Same as Dream.response
with the empty string for a body.
val header : 'a message -> string -> string option
First header with the given name. Header names are case-insensitive. See RFC 7230 §3.2 and MDN.
val headers : 'a message -> string -> 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 : 'a message -> string -> bool
Whether the message has a header with the given name.
val add_header : 'a message -> string -> string -> unit
Appends a header with the given name and value. Does not remove any existing headers with the same name.
val drop_header : 'a message -> string -> unit
Removes all headers with the given name.
val set_header : 'a message -> string -> string -> unit
Equivalent to Dream.drop_header
followed by Dream.add_header
.
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" "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.
val set_cookie :
?prefix:[< `Host | `Secure ] option ->
?encrypt:bool ->
?expires:float ->
?max_age:float ->
?domain:string ->
?path:string option ->
?secure:bool ->
?http_only:bool ->
?same_site:[< `Strict | `Lax | `None ] option ->
response ->
request ->
string ->
string ->
unit
Appends a Set-Cookie:
header to the response
. Infers the most secure defaults from the request
.
Dream.set_cookie request response "my.cookie" "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.
~prefix
sets __Host-
, __Secure-
, or no prefix, from most secure to least. A conforming client will refuse to accept the cookie if ~domain
, ~path
, and ~secure
don't match the constraints implied by the prefix. By default, Dream.set_cookie
chooses the most restrictive prefix based on the other settings and the request
. See RFC 6265bis §4.1.3 and MDN.~encrypt:false
disables cookie encryption. In that case, you must make sure that the cookie value does not contain =
, ;
, or newlines. The easiest way to do so is to pass the value through an encoder like Dream.to_base64url
. See Dream.set_secret
.~expires
sets the Expires=
attribute. The value is compatible with Unix.gettimeofday
. See RFC 6265bis §4.1.2.1 and MDN.~max_age
sets the Max-Age=
attribute. See RFC 6265bis §4.1.2.2 and MDN.~domain
sets the Domain=
attribute. See RFC 6265bis §4.1.2.3 and MDN.~path
sets the Path=
attribute. By default, Path=
set to the site prefix in the request
, which is usually /
. See RFC 6265bis §4.1.2.4 and MDN.~secure
sets the Secure
attribute. By default, Secure
is set if Dream.tls
is true
for the request
. See RFC 6265bis §4.1.2.5 and MDN.~http_only
sets the HttpOnly
attribute. HttpOnly
is set by default. See RFC 6265bis §4.1.2.6 and MDN.~same_site
sets the SameSite=
attribute. SameSite
is set to Strict
by default. See RFC 6265bis §4.1.2.7 and MDN.Dream.to_set_cookie
is a “raw” version of this function that does not do any inference.
val drop_cookie :
?prefix:[< `Host | `Secure ] option ->
?domain:string ->
?path:string option ->
?secure:bool ->
?http_only:bool ->
?same_site:[< `Strict | `Lax | `None ] option ->
response ->
request ->
string ->
unit
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.
val cookie :
?prefix:[< `Host | `Secure ] option ->
?decrypt:bool ->
?domain:string ->
?path:string option ->
?secure:bool ->
request ->
string ->
string option
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.
val set_body : 'a message -> string -> unit
Replaces the body.
val stream :
?status:[< status ] ->
?code:int ->
?headers:(string * string) list ->
?close:bool ->
(stream -> unit promise) ->
response promise
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.
Streams out the string. The promise is fulfilled when the response can accept more writes.
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
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
:
Bigstringaf.t
in bigstringaf.Lwt_bytes.t
in Lwt.Cstruct.buffer
in Cstruct.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
Like ping_stream
, but sends a pong event.
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.
val websocket :
?headers:(string * string) list ->
?close:bool ->
(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!");
Dream.websocket
automatically closes the WebSocket when the callback returns or raises an exception. Pass ~close:false
to suppress this behavior.
See send
and receive_fragment
.
See send
and receive_fragment
.
val send :
?text_or_binary:[< text_or_binary ] ->
?end_of_message:[< end_of_message ] ->
websocket ->
string ->
unit promise
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
.
val receive_fragment :
websocket ->
(string * text_or_binary * end_of_message) option promise
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.
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_referrer_check : middleware
CSRF protection for AJAX requests. Either the method must be `GET
or `HEAD
, or:
Origin:
or Referer:
must be present, andHost:
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 <meta>
tags of a single-page application), and require their presence in an X-CSRF-Token:
header.
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:
<form method="POST" action="/">
<%s! Dream.csrf_tag 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 = [
| `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.
val form : ?csrf:bool -> request -> (string * string) list form_result promise
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
.
Content-Type:
must be application/x-www-form-urlencoded
.dream.csrf
. Dream.csrf_tag
adds such a field.Dream.form
calls Dream.verify_csrf_token
to check the token in dream.csrf
.The call must be done under a session middleware, since each CSRF token is scoped to a session. See Sessions.
CSRF token checking can be bypassed by passing ~csrf:false
.
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
.
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, 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.
val multipart : ?csrf:bool -> request -> multipart_form form_result promise
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
<form method="POST" action="/" enctype="multipart/form-data">
<%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.
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:
Dream.csrf_tag
. Check for `Field ("dream.csrf", token)
during upload and call Dream.verify_csrf_token
.FormData
in the client to submit multipart/form-data
by AJAX, and include a custom header.It's usually not necessary to handle CSRF tokens directly.
Dream.csrf_tag
generates and inserts a CSRF token that Dream.form
and Dream.multipart
transparently verify.Dream.origin_referrer_check
.CSRF functions are exposed for creating custom schemes, and for defense-in-depth purposes. See OWASP Cross-Site Request Forgery Prevention Cheat Sheet.
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.
val verify_csrf_token : request -> string -> csrf_result promise
Checks that the CSRF token is valid for the request
's session.
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...
<
, perhaps with leading whitespace. The line is part of the template.%%
. 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...
%%
.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.%
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 csrf_tag : request -> string
Generates an <input>
tag with a CSRF token, suitable for use with Dream.form
and Dream.multipart
. For example, in a template,
<form method="POST" action="/">
<%s! Dream.csrf_tag request %>
<input name="my.field">
</form>
expands to
<form method="POST" action="/">
<input name="dream.csrf" type="hidden" value="j8vjZ6...">
<input name="my.field">
</form>
It is recommended to put the CSRF tag immediately after the starting <form>
tag, to prevent certain kinds of DOM manipulation-based attacks.
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 livereload : middleware
Adds live reloading to your Dream application.
It works by injecting a script in the HTML pages sent to clients that will initiate a WebSocket.
When the server restarts, the WebSocket connection is lost, at which point, the client will try to reconnect every 500ms for 5s. If within these 5s the client is able to reconnect to the server, it will trigger a reload of the page.
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
When writing a middleware that transforms a request body stream, use server_stream
to retrieve the server's view of the body stream. Create a new transformed stream (note: a function for doing this is not yet exposed), and replace the request's server stream by your transformed stream with set_server_stream
.
When transforming a response stream, replace the client stream instead.
Replaces the stream that the client will use when it receives the response.
Replaces the stream that the server will use when it receives the request.
Creates a router. If none of the routes match the request, the router responds with 404 Not Found
. 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 request "word");
]
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";
]
**
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.
Note: routes that end with /
and routes that don't end with /
are different.
Forwards `GET
requests for the given path to the handler.
Dream.get "/home" home_template
val param : request -> string -> 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_referrer_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_referrer_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;
]
Serves static files from a local directory. See example f-static
.
let () =
Dream.run
@@ Dream.router {
Dream.get "/static/**" @@ Dream.static "www/static";
}
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.
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
.
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.
When a session is at least half-expired, it is automatically refreshed by the next request that it is assigned to.
See example b-session
[playground].
val session_field : request -> string -> string option
Value from the request's session.
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_fields : request -> (string * string) list
Full session dictionary.
Invalidates the request's session, replacing it with a fresh, empty pre-session.
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.
val cookie_sessions : ?lifetime:float -> middleware
Stores sessions in encrypted cookies. Use Dream.set_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 )
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.
Flash messages are short strings which are stored in cookies during one request, to be made available for the next request. The typical use case is to provide form feedback across a redirect. See example w-flash
[playground].
val flash : middleware
Implements storing flash messages in cookies.
val flash_messages : request -> (string * string) list
The request's flash messages.
val add_flash_message : request -> string -> string -> unit
Adds a flash message to the request.
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");
]
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.
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 an introductory 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)
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.
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 = {
error : 'a. ('a, unit) conditional_log;
warning : 'a. ('a, unit) conditional_log;
info : 'a. ('a, unit) conditional_log;
debug : 'a. ('a, unit) conditional_log;
}
Sub-logs. See Dream.sub_log
right below.
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")
?level
sets the log level threshold for this sub-log only. If not provided, falls back to the global log level set by Dream.initialize_log
, unless Dream.set_log_level
is used.
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.
~backtraces:true
, the default, causes Dream to call Printexc.record_backtrace
, which makes exception backtraces available.~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 threshold for the entire binary. The default is `Info
.~enable:false
disables Dream logging completely. This can help sanitize output during testing.val set_log_level : string -> [< log_level ] -> unit
Set the log level threshold of the given sub-log.
Dream passes all errors to a single error handler, including...
4xx
and 5xx
responses from the application, andThis 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 = {
condition : [ `Response of response | `String of string | `Exn of exn ];
layer : [ `App | `HTTP | `HTTP2 | `TLS | `WebSocket ];
caused_by : [ `Server | `Client ];
request : request option;
response : response option;
client : string option;
severity : log_level;
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
.`Error
for `Server
errors and `Warning
for client errors.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
.
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 :
(error -> string -> 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 _error 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
.
500 Internal Server Error
.4xx
or 5xx
response from the application, that response itself is passed to the template.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.~debug_dump
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.
val debug_error_handler : error_handler
An error_handler
for showing extra information about requests and exceptions, for use during development.
val catch : (error -> response promise) -> middleware
Forwards exceptions, rejections, and 4xx
, 5xx
responses from the application to the error handler. See Errors.
val run :
?interface:string ->
?port:int ->
?socket_path:string ->
?stop:unit promise ->
?error_handler:error_handler ->
?tls: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
.~socket_path
is specified, Dream will listen on a Unix domain socket at the given path, and ignore ~interface
and ~port
.~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
.~error_handler
handles all errors, both from the application, and low-level errors. See Errors and example 9-error
[playground]. Dream.debug_error_handler
is a default error handler that can be passed here to help debug Web apps. See example 8-debug
[playground].~tls:true
enables TLS. 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 ~tls
. They are not required for development, but are required for production. Dream will write a warning to the log if you are using ~tls
, 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 ->
?socket_path:string ->
?stop:unit promise ->
?error_handler:error_handler ->
?tls: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 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.catch ~error_handler
@@ 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 with_site_prefix : string -> middleware
Removes the given 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.
This is for applications that are not running at the root (/
) of their domain. The default is "/"
, for no prefix. After with_site_prefix
, routing is done relative to the prefix, and the prefix is also necessary for emitting secure cookies.
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
.
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.
Inverse of Dream.to_base64url
.
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.
Inverse of Dream.to_percent_encoded
.
Inverse of Dream.from_form_urlencoded
. Percent-encodes names and values.
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.
val to_set_cookie :
?expires:float ->
?max_age:float ->
?domain:string ->
?path:string ->
?secure:bool ->
?http_only:bool ->
?same_site:[ `Strict | `Lax | `None ] ->
string ->
string ->
string
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.
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 include query strings (?
), and Dream.from_path
does not treat them specially. Split query strings off with Dream.split_target
first.
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.
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 set_secret : ?old_secrets:string list -> string -> middleware
Sets a key to be used for cryptographic operations, such as signing CSRF tokens and encrypting cookies.
If this middleware is not used, a random secret is generated the first time a secret is needed. The random secret persists for the lifetime of the process. This is useful for quick testing and prototyping, but it means that restarts of the server will not be able to verify tokens or decrypt cookies generated by earlier runs, and multiple servers in a load-balancing arrangement will not accept each others' tokens and cookies.
For production, generate a 256-bit key with
Dream.to_base64url (Dream.random 32)
~old_secrets
is a list of previous secrets that will not be used for encryption or signing, but will still be tried for decryption and verification. This is intended for key rotation. A medium-sized Web app serving 1000 fresh encrypted cookies per second should rotate keys about once a year.
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 set by Dream.set_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, this function first tries to decrypt the string using the main secret set by Dream.set_secret
, and then each of the old secrets passed to Dream.set_secret
in ~old_secrets
.
Dream supports user-defined per-message variables for use by middlewares.
val new_field : ?name:string -> ?show_value:('a -> string) -> unit -> 'a field
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 request :
?method_:[< method_ ] ->
?target:string ->
?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.
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.
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.