A few conventions and recipes to handle requests.
HTTP request processing entails a lot of error handling. Webs encourages you to deal with it using Stdlib.result
values in which the error case is an HTTP error response.
By using the result
binding operators, combinators in Webs.Http.Request
and your own, you can mostly highlight the "happy" path in your code while being extremely accurate and correct in your HTTP error handling.
For example:
open Result.Syntax
open Webs
let respond : Http.Request.t -> (Http.Response.t, Http.Response.t) result =
fun request ->
(* Make sure we have no empty or trailing segments *)
let* () = Http.Request.clean_path request in
(* Make sure that's only a GET or POST request *) in
let* meth = Http.Request.allow Http.Method.[get,post] request in
(* Extract the query *)
let* q = Http.Request.to_query request in
…
In the code above:
Webs.Http.Request.clean_path
errors with an appropriate Webs.Http.Status.moved_permanently_301
to the canonical path if the request path has empty segments.Webs.Http.Request.allow
path with makes sure HTTP requests whose methods are not allowed are responded with a Webs.Http.Status.method_not_allowed_405
response which includes a proper Webs.Http.Header.allow
header.Webs.Http.Request.to_query
decodes the request query regardless of where it's located, that is in the request path for GET and in the body for POST and returns appropriate HTTP errors in case of errorsWhen it's time to response distinguishing between the Ok _
and Error _
responses is no longer relevant so you can simply Result.retract
the result type:
let service request = Result.retract (respond request)
let main () = Webs_quick.serve service
let () = if !Sys.interactive then () else exit (main ())
The service path is not a concept you find in HTTP. It is added by Webs to allow your service to be attached at arbitrary point of a server's path hierarchy. It assumes that your service gets attached to a root of the path hierarchy, typically you specify this root to the connector that runs your service. This root is made available in the request value as Http.Request.service_path
.
Connectors are in charge of stripping the service path from the raw request path and provide the resulting path as the Webs.Http.Request.path
property in the request values they hand you out.
The Webs.Http.Request.clean_path
allows to clean paths. Note that this function does not handle dot segments and the resulting paths remain dangerious for file seving. See serving_files
for more details.
TODO. What's the idea with not handling dot segments ?
The Webs.Http.Request.raw_path
property of a request has the path and query as found in the HTTP request. Note that using this property is not a good idea as it makes your responses sensitive to where your service is attached.
The Webs.Http.Request.raw_path
property of a request has the path and query as found in the HTTP request. The Webs.Http.Request.query
function detaches the query from the path.
The Webs.Http.Request.to_query
parses a query regardless of the request method and thus regardless of whether the request is in the body (POST method) or in the request path (GET method).
TODO say something about file and multiparts and that it may not be a good idea in these cases.
The Webs.Http.Request.allow
combinator allows you to constrain and match the allowed methods of a request. For examples:
open Result.Syntax
open Webs
let respond : Http.Request.t -> (Http.Response.t, Http.Response.t) result =
fun request ->
let* meth = Http.Request.allow Http.Method.[get,head] request in
match meth with
| `GET -> …
| `HEAD -> …
If the request has not the right method a suitable Webs.Http.Status.method_not_allowed_405
response which includes a proper Webs.Http.Headers.allow
header is returned in the Error _
case.