(** 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 ]} *)