package paf
Install
Dune Dependency
Authors
Maintainers
Sources
sha256=320406348df093126842abc2d68324ce7091736b7fb8cb3645968168de3ae642
sha512=c067e933bc67a9e658e4e64897911d2118fa4b73a2ae73749108b6fc2fe6cb927ef158c48d86e5d076cf1f2e0e83959fc82ca79a412f672cd62e03a3a6f5271d
Description
A compatible layer for HTTP/AF and MirageOS.
Published: 22 May 2022
README
Paf le chien - A MirageOS compatible layer for HTTP/AF and [H2][h2]
This library wants to provide an easy way to use HTTP/AF & H2 into a unikernel. It implements the global /loop/ with a protocol implementation. The code, due to the compatibility with MirageOS, can work for unix
too.
The protocol implementation is given by mimic and should be the mirage-tcpip implementation.
It does the composition between the TLS encryption layer and the StackV4V6 implementation to provide a way to initiate a TLS server. Via mimic
, it still keeps the abstraction of the underlying TCP/IP connection used.
module Make (Time : Mirage_time.S) (Stack : Mirage_stack.V4V6) = struct
module Paf = Paf_mirage.Make(Time)(Stack)
let start stack =
let* t = Paf.init ~port:80 stack in
let service = Paf.http_service ~error_handler request_handler in
let `Initialized th = Paf.serve service t in
th
end
(* For UNIX with mirage-time-unix & tcpip.stack-socket *)
include Make (Time) (Tcpip_stack_socket.V4V6)
let stack () =
let open Tcpip_stack_socket.V4V6 in
TCP.connect ~ipv4_only:false ~ipv6_only:false
Ipaddr.V4.Prefix.global None
let () = Lwt_main.run (stack () >>= start)
It also provides a client-side with the logic of mimic and let the user to implement the resolution process to determine if the connection needs the TLS encryption (and how) layer or not.
Mimic
Paf wants to provide an agnostic implementation of HTTP with the ability to launch a server or a client from an user-defined context: a Mimic.ctx
. It does not exist one and unique way to use Paf because the context can be:
a MirageOS
a simple executable
something else like a JavaScript script (with
js_of_ocaml
)
Mimic ensures the ability to gives a Mirage_flow.S to Paf (client side). The underlying implementation of this /flow/ depends on what the user wants. It can be:
The host TCP/IP stack (see the
Unix
module)
All of these choices is not done by Paf but must be defined by the user. Then, the CoHTTP layer trusts on mirage-tcpip and ocaml-tls to easily communicate with a peer from a given Uri.t
. Even if it seems to be the easy way to do HTTP requests (over TLS or not), the user is able to choose some others possibilities/paths.
For example, the user is able to start a connection with an Unix domain socket:
module Unix_domain_socket : Mimic.Mirage_protocol.S
with type flow = Unix.file_descr
and type endpoint = Fpath.t
let unix_domain_socket =
Mimic.register ~name:"unix-domain-socket" (module Unix_domain_socket)
let ctx =
Mimic.add unix_domain_socket
(Fpath.v "/var/my_domain.sock") Mimic.empty
let run =
Mimic.resolve ~ctx >>= function
| Error _ as err -> Lwt.return err
| Ok flow ->
let body, conn = Httpaf.Client_connection.request ?config:None req
~error_handler ~response_handler in
Paf.run (module Httpaf.Client_connection) ~sleep conn flow >>= fun () ->
Lwt.return_ok body
CoHTTP layer
Paf comes with a not-fully-implemented compatible layer with CoHTTP. From this sub-package and the letsencrypt package, Paf provides a process to download a Let's encrypt TLS certificate ready to launch an HTTPS server.
let cfg =
{ LE.email= Result.to_option (Emile.of_string "romain@x25519.net")
; LE.account_seed= None
; LE.account_key_type= `ED25519
; LE.account_key_bits= None
; LE.certificate_seed= None
; LE.certificate_key_type= `ED25519
; LE.certificate_key_bits= None
; LE.hostname= Domain_name.(host_exn (of_string_exn "x25519.net")) }
let ctx = ... (* see [mimic] *)
module Paf = Paf_mirage.Make (Time) (Tcpip_stack_socket.V4V6)
let get_tls_certificate stack =
Lwt_switch.with_switch @@ fun stop ->
let* t = Paf.init ~port:80 stack in
let service = Paf.http_service
~error_handler
LE.request_handler in
let `Initialized th = Paf.serve ~stop service in
let fiber =
LE.provision_certificate ~production:false cfg ctx >>= fun res ->
Lwt_switch.turn_off stop >>= fun () -> Lwt.return res in
Lwt.both (th, fiber) >>= fun (_, tls) -> Lwt.return tls
Application Layer Protocol Negotiation
Paf provides the logic behind ALPN negotiation according a certain TLS/SSL implementation. In other words, Paf is able to correctly dispatch which protocol the client wants without a requirement of ocaml-tls or lwt_ssl. The module [Alpn] is a HTTP service which handles:
HTTP/1.1
H2
[Alpn] requires:
the
accept
and theclose
functiona way to extract the result of the Application Layer Protocol Negotiation
the Mimic's injection
error_handler
andrequest_handler
which handle HTTP/1.0, HTTP/1.1 and H2 requests
Here is an example with HTTP (without TLS):
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
~error_handler
~request_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
~sleep:(Lwt_unix.sleep <.> Int64.to_float)
service t in th
let () = Lwt_main.run fiber
Tests & Benchmark
The distribution comes with a tool which launch several clients to communicate with a server. We record the time spent for each request and show as the result the histogram of them. It's not really a benchmark as is but it a good stress-test and we check that we don't have failure from the server.
Dependencies (13)
-
cstruct
>= "6.0.0"
-
tls
>= "0.15.0" & < "1.0.0"
-
faraday
>= "0.7.2"
-
h2
>= "0.7.0" & < "0.9.0"
-
httpaf
>= "0.7.1"
-
bigstringaf
>= "0.7.0"
-
ke
>= "0.4"
- mimic
-
tls-mirage
>= "0.15.0" & < "1.0.0"
-
mirage-time
>= "2.0.0"
-
tcpip
>= "7.0.0"
-
dune
>= "2.0.0"
-
ocaml
>= "4.08.0"
Dev Dependencies (9)
-
alcotest-lwt
with-test
-
uri
with-test
-
ptime
with-test
-
mirage-time-unix
with-test
-
mirage-crypto-rng
with-test & < "0.11.0"
-
fmt
with-test
-
logs
with-test
-
base-unix
with-test
-
lwt
with-test
Used by (3)
-
git-paf
>= "3.5.0" & < "3.10.0"
-
paf-cohttp
= "0.0.8-1"
-
paf-le
= "0.0.8-1"
Conflicts
None