Webs_bazaar.Kurl
Kinds of URL requests and their formatters.
This module provides support to describe and handle URL requests by multiple client-defined OCaml types in a bidirectional and modular manner.
A kind value represents a subset of bare URL requests by values of a custom request OCaml type. Kinds are then used:
TODO.
bare
type or on formatting functions ? Likely not on bare
, you never get fragments to decode so it seems a bit OT.URL
able (well with Hc
every aspect becomes URL
able). Bare could embody Webs.Http.Request.t
.Kurl.kind
The type for bare URL requests. This embodies an HTTP method, an URL path, an URL query and an optional URL path file extension used by some URL formatting modes.
val bare :
?ext:string ->
?query:Webs.Http.Query.t ->
Webs.Http.Method.t ->
Webs.Http.Path.t ->
bare
bare m p ~query ~ext
is an URL request to path p
with method m
, query query
(defaults to Webs.Http.Query.empty
) and URL path file extension ext
(defaults to ""
).
module Bare : sig ... end
Bare URL requests.
type 'a enc = 'a -> bare
The type for encoding an URL request value of type 'a
to a bare URL request.
type 'a dec = bare -> ('a option, Webs.Http.Response.t) Stdlib.result
The type for decoding a bare URL request to a request value of type 'a
. The path in bare
url is relative to the service tree binding point.
The function returns:
Ok (Some r)
, if the request belongs to the kind of URL requests with value r
.Ok None
, if the request does not belong to the kind of URL requests. This lets other services attached to tree consider the prefix or an extension of it.Error r
, if the request was meant to belong to the kind of URL requests but directly responding with r
at that point was deemed appropriate. Examples are bad request, not allowed, etc. Possibly not found but if you want to let other subtrees consider the prefix use Ok None
TODO.
TODO. Provide easy redirect.
val allow :
'a Webs.Http.Method.constraint' list ->
bare ->
('a, Webs.Http.Response.t) Stdlib.result
meths ms u
is:
Ok (Bare.meth u)
if List.mem (Bare.meth u, Bare.meth u) ms
Error _
with a 405 not allowed response otherwise.val kind :
?root_is:[ `Dir of string option | `File ] ->
?name:string ->
'a enc ->
'a dec ->
'a kind
kind ?root_is ?name enc dec
is a kind using enc
and dec
to codec bare URL request values. name
is a name used for debugging purposes (defaults to ""
).
root_is
defines formatting behaviour of the root path ([""]
) relative to the service binding path. With:
`File
the binding path encodes the root. Note that since the binding path must be well-formed it is guaranteed to lack a trailing slash. If extension formatting is requested and there is one in the encoded bare, it is appended to it. For example if the URL kind is bound to path ["mykind"]
and enc
encodes a bare URL with path [""]
the formatted URL will be "/mykind"
.`Dir seg
the binding path with a trailing slash is the resulting path. If extension formatting is requested and seg
is Some seg
then both segment seg
(e.g. "index") and the extension are appended, otherwise the path is left as is. For example if the URL kind is bound to path ["mykind"]
and enc
encodes a bare URL with path [""]
the formatted URL will be "/mykind/"
or /mykind/seg
if extension formatting is requested.TODO. Should root_is
also affect decoding ?
IMPORTANT. Kinds have a notion of identity and they can only be bound once in a tree.
module Kind : sig ... end
Kinds of URL requests
any
is Kind.bare ~name:"any" ~root_is:(`Dir (Some "index")) ()
. This is a catch-all bare request kind defined. It is always added at the root of URL formatters.
In conjunction with Bare.of_req_referer
it allows for example to easily format relative URLs for requests performed by HTML page components or for request that do not exist in the service space like 404.
You can also bind it at the root of your service tree as the last service. It will provide you with a catch all handler.
The type for services handling URL requests with a value of type 'a
, see service
.
service k f
is a service handling URL requests of kind k
with f
.
The return type of 'a
is common to all services. Usually this is a function which when given arguments common to all services effectively handles the request. The service type simply hides the first, request kind specific, argument of these functions.
map_service f s
applies f
to the service of s
.
The type for service trees binding constant HTTP paths to services of type 'a
.
val empty : unit -> 'a tree
empty ()
is an empty service tree.
val find_service :
'a tree ->
bare ->
('a option, Webs.Http.Response.t) Stdlib.result
find_service t u
finds the service for handling u
's Webs.Http.Request.path
. Ok None
is returned if no URL request kind matched. Error _
is returned if a URL request kind matched but errored.
val bind : Webs.Http.Path.t -> 'a service -> 'a tree -> 'a tree
bind p s t
is t
with well-formed path p
bound to s
. Note shorter bindings to p
in t
take precedence, but s
takes precedence over existing bindings at p
. More precisely:
s'
on a prefix of p
in t
, s
will only be invoked for those requests for which the kind of s'
decodes to Ok None
.s'
at p
in t
, s'
will only be invoked for those requests for which the kind of s
decodes Ok None
.Raises Invalid_argument
if s
is already bound in t
or if p
is not well-formed.
unbind s t
is t
with the binding of s
removed (if any).
val unbind_path : Webs.Http.Path.t -> 'a tree -> 'a tree
unbind_path p t
is t
with all services bound to p
removed (if any).
val service_path : 'a service -> 'a tree -> Webs.Http.Path.t option
service_path s t
is the path to which s
is bound in t
(if any).
val path_services : Webs.Http.Path.t -> 'a tree -> 'a service list
path_services p t
are the services bound to p
in t
(if any).
val fold_paths :
(Webs.Http.Path.t -> 'a service list -> 'b -> 'b) ->
'a tree ->
'b ->
'b
fold_paths f t acc
folds f
and acc
over all paths of t
in lexicographic order. This includes paths on which the service list is empty.
module Fmt : sig ... end
URL request formatters.
HTTP path handling is a mess.
For this module a well-formed path is either the root path [""]
or a non-empty Webs.Http.Request.path
devoid of empty segments (including trailing ones).
To lessen the mess, in Kurl
you can only bind to well-formed paths in service trees and URL formatters. Note that this doesn't mean that your kinds can't produce non well-formed paths.
It's not mandatory but also a good idea to use Webs.Http.Request.clean_path
before handing your requests to find_service
.
HTTP clients makes no difference between:
https://example.org = https://example.org/
but they do between:
https://example.org/a ≠ https://example.org/a/
This leads to all sorts of difficulties for abitrarily rebinding services, since the root is treated differently.
For this reason in tree services we never give an empty path to decode. That is if a service is bound on /a/b
both requests with path /a/b/
and /a/b
will result in path /
("root" of the service) in the bare URL to decode. For this reason it's a good idea to canonicalize requests before with Webs.Http.Request.clean_path
.
Todo describe Kind.root_is
.