299 lines
9.8 KiB
OCaml
299 lines
9.8 KiB
OCaml
|
|
(** ALPN support.
|
||
|
|
|
||
|
|
[Alpn] depend on [http/af] & [h2] and choose them because they share the
|
||
|
|
same {!Paf.RUNTIME} interface. [Alpn] does not require [ocaml-tls] so it's
|
||
|
|
possible to use OpenSSL. It requires, at least:
|
||
|
|
|
||
|
|
- Something to extract ALPN result from the TLS {i flow}
|
||
|
|
- Something to represent as the string the peer (useful for over-framework)
|
||
|
|
- An injection function (available from [mimic])
|
||
|
|
|
||
|
|
In other words, [Alpn] did the only choice to trust on [http/af] & [h2] to
|
||
|
|
handle HTTP/1.0, HTTP/1.1 and H2 protocols. *)
|
||
|
|
|
||
|
|
module type REQD = sig
|
||
|
|
type t
|
||
|
|
type request
|
||
|
|
type response
|
||
|
|
|
||
|
|
module Body : sig
|
||
|
|
type ro
|
||
|
|
type wo
|
||
|
|
end
|
||
|
|
|
||
|
|
val request : t -> request
|
||
|
|
val request_body : t -> Body.ro
|
||
|
|
val response : t -> response option
|
||
|
|
val response_exn : t -> response
|
||
|
|
val respond_with_string : t -> response -> string -> unit
|
||
|
|
val respond_with_bigstring : t -> response -> Bigstringaf.t -> unit
|
||
|
|
|
||
|
|
val respond_with_streaming :
|
||
|
|
t -> ?flush_headers_immediately:bool -> response -> Body.wo
|
||
|
|
|
||
|
|
val report_exn : t -> exn -> unit
|
||
|
|
val try_with : t -> (unit -> unit) -> (unit, exn) result
|
||
|
|
end
|
||
|
|
|
||
|
|
type http_1_1_protocol =
|
||
|
|
(module REQD
|
||
|
|
with type t = H1.Reqd.t
|
||
|
|
and type request = H1.Request.t
|
||
|
|
and type response = H1.Response.t
|
||
|
|
and type Body.ro = H1.Body.Reader.t
|
||
|
|
and type Body.wo = H1.Body.Writer.t)
|
||
|
|
|
||
|
|
type h2_protocol =
|
||
|
|
(module REQD
|
||
|
|
with type t = H2.Reqd.t
|
||
|
|
and type request = H2.Request.t
|
||
|
|
and type response = H2.Response.t
|
||
|
|
and type Body.ro = H2.Body.Reader.t
|
||
|
|
and type Body.wo = H2.Body.Writer.t)
|
||
|
|
|
||
|
|
type ('reqd, 'headers, 'request, 'response, 'ro, 'wo) protocol =
|
||
|
|
| HTTP_1_1 :
|
||
|
|
http_1_1_protocol
|
||
|
|
-> ( H1.Reqd.t,
|
||
|
|
H1.Headers.t,
|
||
|
|
H1.Request.t,
|
||
|
|
H1.Response.t,
|
||
|
|
H1.Body.Reader.t,
|
||
|
|
H1.Body.Writer.t )
|
||
|
|
protocol
|
||
|
|
| H2 :
|
||
|
|
h2_protocol
|
||
|
|
-> ( H2.Reqd.t,
|
||
|
|
H2.Headers.t,
|
||
|
|
H2.Request.t,
|
||
|
|
H2.Response.t,
|
||
|
|
H2.Body.Reader.t,
|
||
|
|
H2.Body.Writer.t )
|
||
|
|
protocol
|
||
|
|
|
||
|
|
val http_1_1 :
|
||
|
|
( H1.Reqd.t,
|
||
|
|
H1.Headers.t,
|
||
|
|
H1.Request.t,
|
||
|
|
H1.Response.t,
|
||
|
|
H1.Body.Reader.t,
|
||
|
|
H1.Body.Writer.t )
|
||
|
|
protocol
|
||
|
|
|
||
|
|
val h2 :
|
||
|
|
( H2.Reqd.t,
|
||
|
|
H2.Headers.t,
|
||
|
|
H2.Request.t,
|
||
|
|
H2.Response.t,
|
||
|
|
H2.Body.Reader.t,
|
||
|
|
H2.Body.Writer.t )
|
||
|
|
protocol
|
||
|
|
|
||
|
|
type server_error =
|
||
|
|
[ `Bad_gateway | `Bad_request | `Exn of exn | `Internal_server_error ]
|
||
|
|
(** Type of server errors. *)
|
||
|
|
|
||
|
|
type ('flow, 'edn) info = {
|
||
|
|
alpn : 'flow -> string option;
|
||
|
|
peer : 'flow -> 'edn;
|
||
|
|
injection : 'flow -> Mimic.flow;
|
||
|
|
}
|
||
|
|
(** The type of information from a ['flow]:
|
||
|
|
|
||
|
|
- [alpn] is a function which is able to extract the result of the
|
||
|
|
negotiation between the client & the server about which protocol we need
|
||
|
|
to use.
|
||
|
|
- [peer] returns a [string] representation of the given ['flow] to help to
|
||
|
|
print out some logs about this client.
|
||
|
|
- [injection] is the function which wraps the given ['flow] to a
|
||
|
|
[Mimic.flow].
|
||
|
|
|
||
|
|
For the last function, it can be done if you already registered the protocol
|
||
|
|
with [mimic]. In that case, the second value given by [Mimic.register] helps
|
||
|
|
you to {i inject} your flow as a [Mimic.flow]:
|
||
|
|
|
||
|
|
{[
|
||
|
|
let _, protocol = Mimic.register ~name:"my-protocol" (module My_protocol)
|
||
|
|
|
||
|
|
let injection (flow : My_protocol.flow) : Mimic.flow =
|
||
|
|
let module R = (val Mimic.repr protocol) in
|
||
|
|
R.T flow
|
||
|
|
]} *)
|
||
|
|
|
||
|
|
type ('flow, 'edn) server_handler = {
|
||
|
|
error :
|
||
|
|
'reqd 'headers 'request 'response 'ro 'wo.
|
||
|
|
'edn ->
|
||
|
|
('reqd, 'headers, 'request, 'response, 'ro, 'wo) protocol ->
|
||
|
|
?request:'request ->
|
||
|
|
server_error ->
|
||
|
|
('headers -> 'wo) ->
|
||
|
|
unit;
|
||
|
|
request :
|
||
|
|
'reqd 'headers 'request 'response 'ro 'wo.
|
||
|
|
'flow ->
|
||
|
|
'edn ->
|
||
|
|
'reqd ->
|
||
|
|
('reqd, 'headers, 'request, 'response, 'ro, 'wo) protocol ->
|
||
|
|
unit;
|
||
|
|
}
|
||
|
|
(** The type of handler. To be able to handle http/1.1 and h2 requests with the
|
||
|
|
same function, we have chosen to use record with universally quantified
|
||
|
|
types. Such design requires some constraints: 1) [error] and [request]
|
||
|
|
should be defined at top 2) if they requires extra informations (such as the
|
||
|
|
path of file, a value to connect to a database, etc.), they can be used into
|
||
|
|
handlers but the record must contains non-curried version of these handlers.
|
||
|
|
3) you must use type annotation due to the GADT {!type:protocol}
|
||
|
|
|
||
|
|
For instance, we have a value [db] is required by our request handler. You
|
||
|
|
can describe your handler by this way:
|
||
|
|
|
||
|
|
{[
|
||
|
|
let error_handler
|
||
|
|
: type reqd headers request response ro wo.
|
||
|
|
_ -> (reqd, headers, request, response, ro, wo) Alpn.protocol ->
|
||
|
|
?request:request -> _ -> (headers -> wo) -> unit
|
||
|
|
= fun edn protocol ?request error respond ->
|
||
|
|
match protocol with
|
||
|
|
| Alpn.HTTP_1_1 _ ->
|
||
|
|
(* everything is specialized to the [H1] module. You can use
|
||
|
|
[?request] as an [H1.Request.t option] without type error. *)
|
||
|
|
| Alpn.H2 _ ->
|
||
|
|
(* everything is specialized to the [H2] module. *)
|
||
|
|
|
||
|
|
let request_handler
|
||
|
|
: type reqd headers request response ro wo.
|
||
|
|
Database.t -> _ -> _ -> reqd ->
|
||
|
|
(reqd, headers, request, response, ro, wo) Alpn.protocol -> unit
|
||
|
|
= fun db flow edn reqd -> function
|
||
|
|
| Alpn.HTTP_1_1 _ -> ...
|
||
|
|
| Alpn.H2 _ -> ...
|
||
|
|
|
||
|
|
let handler db =
|
||
|
|
{ error= (fun edn protocol ?request error respond ->
|
||
|
|
error_handler edn protocol ?request error respond)
|
||
|
|
; request= (fun flow edn reqd protocol ->
|
||
|
|
request_handler db flow end reqd protocol) }
|
||
|
|
]} *)
|
||
|
|
|
||
|
|
val service :
|
||
|
|
('flow, 'edn) info ->
|
||
|
|
(Mimic.flow, 'edn) server_handler ->
|
||
|
|
('socket -> ('flow, ([> `Closed | `Msg of string ] as 'error)) result Lwt.t) ->
|
||
|
|
('t -> ('socket, ([> `Closed | `Msg of string ] as 'error)) result Lwt.t) ->
|
||
|
|
('t -> unit Lwt.t) ->
|
||
|
|
't Paf.service
|
||
|
|
(** [service info handler connect accept close] creates a new
|
||
|
|
{!type:Paf.service} over the {i socket} ['flow]. From the given
|
||
|
|
implementation of [accept] and [close], we are able to instantiate the
|
||
|
|
{i main loop}. Then, from the given [info], we extract informations such the
|
||
|
|
application layer protocol and choose which protocol we will use. Currently,
|
||
|
|
if [info.alpn] returns:
|
||
|
|
|
||
|
|
- [Some "http/1.0" | Some "http/1.1" | None], we launch an [http/af] service
|
||
|
|
- [Some "h2"], we launch an [h2] service
|
||
|
|
|
||
|
|
The user is able to identify which protocol we launched by
|
||
|
|
{!type:server_handler}. The returned service can be run with {!Paf.serve}.
|
||
|
|
Here is an example with [Lwt_unix.file_descr] and the TCP/IP transmission
|
||
|
|
protocol (without ALPN negotiation):
|
||
|
|
|
||
|
|
{[
|
||
|
|
let _, protocol
|
||
|
|
: Unix.sockaddr Mimic.value
|
||
|
|
* (Unix.sockaddr, Lwt_unix.file_descr) Mimic.protocol
|
||
|
|
= Mimic.register ~name:"lwt-tcp" (module TCP)
|
||
|
|
|
||
|
|
let accept t =
|
||
|
|
Lwt.catch begin fun () ->
|
||
|
|
Lwt_unix.accept >>= fun (socket, _) ->
|
||
|
|
Lwt.return_ok socket
|
||
|
|
end @@ function
|
||
|
|
| Unix.Unix_error (err, f, v) ->
|
||
|
|
Lwt.return_error (`Unix (err, f, v))
|
||
|
|
| exn -> raise exn
|
||
|
|
|
||
|
|
let info =
|
||
|
|
let module R = (val Mimic.register protocol) in
|
||
|
|
{ Alpn.alpn= const None
|
||
|
|
; Alpn.peer= (fun socket ->
|
||
|
|
sockaddr_to_string (Lwt_unix.getpeername socket))
|
||
|
|
; Alpn.injection=
|
||
|
|
(fun socket -> R.T socket) }
|
||
|
|
|
||
|
|
let service = Alpn.service info handler
|
||
|
|
accept Lwt_unix.close
|
||
|
|
|
||
|
|
let fiber =
|
||
|
|
let t = Lwt_unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in
|
||
|
|
Lwt_unix.bind t (Unix.ADDR_INET (Unix.inet_addr_loopback, 8080))
|
||
|
|
>>= fun () ->
|
||
|
|
let `Initialized th = Paf.serve service t in th
|
||
|
|
|
||
|
|
let () = Lwt_main.run fiber
|
||
|
|
]} *)
|
||
|
|
|
||
|
|
type client_error =
|
||
|
|
[ `Exn of exn
|
||
|
|
| `Malformed_response of string
|
||
|
|
| `Invalid_response_body_length_v1 of H1.Response.t
|
||
|
|
| `Invalid_response_body_length_v2 of H2.Response.t
|
||
|
|
| `Protocol_error of H2.Error_code.t * string ]
|
||
|
|
(** Type of client errors. *)
|
||
|
|
|
||
|
|
type 'edn client_handler = {
|
||
|
|
error :
|
||
|
|
'reqd 'headers 'request 'response 'ro 'wo.
|
||
|
|
'edn ->
|
||
|
|
('reqd, 'headers, 'request, 'response, 'ro, 'wo) protocol ->
|
||
|
|
client_error ->
|
||
|
|
unit;
|
||
|
|
response :
|
||
|
|
'reqd 'headers 'request 'response 'ro 'wo.
|
||
|
|
Mimic.flow ->
|
||
|
|
'edn ->
|
||
|
|
'response ->
|
||
|
|
'ro ->
|
||
|
|
('reqd, 'headers, 'request, 'response, 'ro, 'wo) protocol ->
|
||
|
|
unit;
|
||
|
|
}
|
||
|
|
(** The type of client handler. As {!type:server_handler}, we have chosen to use
|
||
|
|
a record with universally quantified types. Please follow the explanation
|
||
|
|
given about {!type:server_handler} to understand how to use it. *)
|
||
|
|
|
||
|
|
type alpn_response =
|
||
|
|
| Response_HTTP_1_1 :
|
||
|
|
(H1.Body.Writer.t * H1.Client_connection.t)
|
||
|
|
-> alpn_response
|
||
|
|
| Response_H2 : H2.Body.Writer.t * H2.Client_connection.t -> alpn_response
|
||
|
|
|
||
|
|
val run :
|
||
|
|
?alpn:string ->
|
||
|
|
'edn client_handler ->
|
||
|
|
'edn ->
|
||
|
|
[ `V1 of H1.Request.t | `V2 of H2.Request.t ] ->
|
||
|
|
Mimic.flow ->
|
||
|
|
(alpn_response, [> `Msg of string ]) result Lwt.t
|
||
|
|
(** [run ?alpn ~client_handler edn req flow] tries communicate to [edn] via
|
||
|
|
[flow] with a certain protocol according to the given [alpn] value and the
|
||
|
|
given request. It returns the body of the request to allow the user to write
|
||
|
|
on it (and communicate then with the server).
|
||
|
|
|
||
|
|
[run] does only the ALPN dispatch. It does not instantiate the connection
|
||
|
|
and it does not try to upgrade the protocol. It just choose the right HTTP
|
||
|
|
protocol according to:
|
||
|
|
|
||
|
|
- the given [alpn] value
|
||
|
|
- the given [request] (if you want to communicate via HTTP/1.1 or H2)
|
||
|
|
|
||
|
|
Here is an example with [mimic]:
|
||
|
|
|
||
|
|
{[
|
||
|
|
let run uri request =
|
||
|
|
let ctx = ctx_of_uri uri in
|
||
|
|
(* See Mimic for more details. *)
|
||
|
|
Mimic.resolve ctx >>= function
|
||
|
|
| Error _ as err -> Lwt.return err
|
||
|
|
| Ok flow -> run ?alpn:None handler uri request flow
|
||
|
|
]} *)
|