This commit is contained in:
parent
aa2ff7b2f0
commit
2f3113f55d
11742 changed files with 1223940 additions and 0 deletions
192
unikernel/duniverse/paf-le-chien/README.md
Normal file
192
unikernel/duniverse/paf-le-chien/README.md
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
## Paf le chien - A MirageOS compatible layer for [HTTP/AF][httpaf] 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][mimic] and should be the
|
||||
[mirage-tcpip][mirage-tcpip] implementation.
|
||||
|
||||
It does the composition between the TLS encryption layer and the
|
||||
[StackV4V6][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.
|
||||
|
||||
```ocaml
|
||||
module Make (Stack : Mirage_stack.V4V6) = struct
|
||||
module Paf = Paf_mirage.Make (Stack.TCP)
|
||||
|
||||
let start stack =
|
||||
let* t = Paf.init ~port:80 (Stack.tcp 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 (Tcpip_stack_socket.V4V6.TCP)
|
||||
|
||||
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 ((Tcpip_stack_socket.V4V6.tcp 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][mirage-flow] to Paf
|
||||
(client side). The underlying implementation of this /flow/ depends on what the
|
||||
user wants. It can be:
|
||||
- [ocaml-tls][ocaml-tls]
|
||||
- [lwt_ssl][lwt_ssl]
|
||||
- [mirage-tcpip][mirage-tcpip]
|
||||
- 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][mirage-tcpip] and
|
||||
[ocaml-tls][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:
|
||||
|
||||
```ocaml
|
||||
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) 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][letsencrypt] package, Paf provides a process
|
||||
to download a Let's encrypt TLS certificate ready to launch an HTTPS server.
|
||||
|
||||
```ocaml
|
||||
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
|
||||
(fun _ -> LE.request_handler) in
|
||||
let `Initialized th = Paf.serve ~stop service in
|
||||
let fiber =
|
||||
LE.provision_certificate ~production:false cfg
|
||||
(LE.ctx ~gethostbyname ~authenticator) >>= 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][ocaml-tls] or
|
||||
[lwt_ssl][lwt_ssl]. The module `Alpn` is a HTTP service which handles:
|
||||
- HTTP/1.1
|
||||
- H2
|
||||
|
||||
`Alpn` requires:
|
||||
- the `accept` and the `close` function
|
||||
- a way to extract the result of the Application Layer Protocol Negotiation
|
||||
- the Mimic's _injection_
|
||||
- `error_handler` and `request_handler` which handle HTTP/1.0, HTTP/1.1 and
|
||||
H2 requests
|
||||
|
||||
Here is an example with HTTP (without TLS):
|
||||
```ocaml
|
||||
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
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
[httpaf]: https://github.com/inhabitedtype/httpaf
|
||||
[mimic]: https://github.com/mirage/ocaml-git
|
||||
[mirage-tcpip]: https://github.com/mirage/mirage-tcpip
|
||||
[letsencrypt]: https://github.com/mmaker/ocaml-letsencrypt
|
||||
[stackv4v6]: https://github.com/mirage/mirage-stack
|
||||
[ocaml-tls]: https://github.com/mirleft/ocaml-tls
|
||||
[lwt_ssl]: https://github.com/ocsigen/lwt_ssl
|
||||
[mirage-flow]: https://github.com/mirage/mirage-flow
|
||||
[h2]: https://github.com/anmonteiro/ocaml-h2
|
||||
Loading…
Add table
Add a link
Reference in a new issue