diff --git a/.travis.yml b/.travis.yml index 6b6736de..65d409ab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,10 +12,22 @@ env: - PACKAGE="httpaf-async" DISTRO="ubuntu-16.04" OCAML_VERSION="4.07.1" - PACKAGE="httpaf" DISTRO="ubuntu-16.04" OCAML_VERSION="4.06.0" - PACKAGE="httpaf-async" DISTRO="ubuntu-16.04" OCAML_VERSION="4.06.0" - - PACKAGE="httpaf-lwt" DISTRO="ubuntu-16.04" OCAML_VERSION="4.06.0" + - | + PRE_INSTALL_HOOK="sudo apt-get install -y libgmp-dev; opam install tls" + PACKAGE="httpaf-lwt" + DISTRO="ubuntu-16.04" + OCAML_VERSION="4.06.0" - PACKAGE="httpaf" DISTRO="ubuntu-16.04" OCAML_VERSION="4.04.2" - PACKAGE="httpaf-async" DISTRO="ubuntu-16.04" OCAML_VERSION="4.04.2" - - PACKAGE="httpaf-lwt" DISTRO="ubuntu-16.04" OCAML_VERSION="4.04.2" + - | + PRE_INSTALL_HOOK="sudo apt-get install -y libgmp-dev; opam install tls" + PACKAGE="httpaf-lwt" + DISTRO="ubuntu-16.04" + OCAML_VERSION="4.04.2" - PACKAGE="httpaf" DISTRO="debian-unstable" OCAML_VERSION="4.03.0" - PACKAGE="httpaf-async" DISTRO="debian-unstable" OCAML_VERSION="4.03.0" - - PACKAGE="httpaf-lwt" DISTRO="debian-unstable" OCAML_VERSION="4.03.0" + - | + PRE_INSTALL_HOOK="sudo apt-get install -y libgmp-dev; opam install tls" + PACKAGE="httpaf-lwt" + DISTRO="debian-unstable" + OCAML_VERSION="4.03.0" diff --git a/certificates/server.key b/certificates/server.key new file mode 100644 index 00000000..5a92851d --- /dev/null +++ b/certificates/server.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXQIBAAKBgQC2QEje5rwhlD2iq162+Ng3AH9BfA/jNJLDqi9VPk1eMUNGicJv +K+aOANKIsOOr9v4RiEXZSYmFEvGSy+Sf1bCDHwHLLSdNs6Y49b77POgatrVZOTRE +BE/t1soVT3a/vVJWCLtVCjm70u0S5tcfn4S6IapeIYAVAmcaqwSa+GQNoQIDAQAB +AoGAd/CShG8g/JBMh9Nz/8KAuKHRHc2BvysIM1C62cSosgaFmdRrazJfBrEv3Nlc +2/0uc2dVYIxuvm8bIFqi2TWOdX9jWJf6oXwEPXCD0SaDbJTaoh0b+wjyHuaGlttY +Ztvmf8mK1BOhyl3vNMxh/8Re0dGvGgPZHpn8zanaqfGVz+ECQQDngieUpwzxA0QZ +GZKRYhHoLEaPiQzBaXphqWcCLLN7oAKxZlUCUckxRRe0tKINf0cB3Kr9gGQjPpm0 +YoqXo8mNAkEAyYgdd+JDi9FH3Cz6ijvPU0hYkriwTii0V09+Ar5DvYQNzNEIEJu8 +Q3Yte/TPRuK8zhnp97Bsy9v/Ji/LSWbtZQJBAJe9y8u3otfmWCBLjrIUIcCYJLe4 +ENBFHp4ctxPJ0Ora+mjkthuLF+BfdSZQr1dBcX1a8giuuvQO+Bgv7r9t75ECQC7F +omEyaA7JEW5uGe9/Fgz0G2ph5rkdBU3GKy6jzcDsJu/EC6UfH8Bgawn7tSd0c/E5 +Xm2Xyog9lKfeK8XrV2kCQQCTico5lQPjfIwjhvn45ALc/0OrkaK0hQNpXgUNFJFQ +tuX2WMD5flMyA5PCx5XBU8gEMHYa8Kr5d6uoixnbS0cZ +-----END RSA PRIVATE KEY----- diff --git a/certificates/server.pem b/certificates/server.pem new file mode 100644 index 00000000..22a82fb7 --- /dev/null +++ b/certificates/server.pem @@ -0,0 +1,15 @@ +-----BEGIN CERTIFICATE----- +MIICYzCCAcwCCQDLbE6ES1ih1DANBgkqhkiG9w0BAQUFADB2MQswCQYDVQQGEwJB +VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 +cyBQdHkgTHRkMRUwEwYDVQQDDAxZT1VSIE5BTUUhISExGDAWBgkqhkiG9w0BCQEW +CW1lQGJhci5kZTAeFw0xNDAyMTcyMjA4NDVaFw0xNTAyMTcyMjA4NDVaMHYxCzAJ +BgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5l +dCBXaWRnaXRzIFB0eSBMdGQxFTATBgNVBAMMDFlPVVIgTkFNRSEhITEYMBYGCSqG +SIb3DQEJARYJbWVAYmFyLmRlMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC2 +QEje5rwhlD2iq162+Ng3AH9BfA/jNJLDqi9VPk1eMUNGicJvK+aOANKIsOOr9v4R +iEXZSYmFEvGSy+Sf1bCDHwHLLSdNs6Y49b77POgatrVZOTREBE/t1soVT3a/vVJW +CLtVCjm70u0S5tcfn4S6IapeIYAVAmcaqwSa+GQNoQIDAQABMA0GCSqGSIb3DQEB +BQUAA4GBAIo4ZppIlp3JRyltRC1/AyCC0tsh5TdM3W7258wdoP3lEe08UlLwpnPc +aJ/cX8rMG4Xf4it77yrbVrU3MumBEGN5TW4jn4+iZyFbp6TT3OUF55nsXDjNHBbu +deDVpGuPTI6CZQVhU5qEMF3xmlokG+VV+HCDTglNQc+fdLM0LoNF +-----END CERTIFICATE----- diff --git a/examples/lwt/jbuild b/examples/lwt/jbuild index b21fe052..962ef8e9 100644 --- a/examples/lwt/jbuild +++ b/examples/lwt/jbuild @@ -1,5 +1,5 @@ (jbuild_version 1) (executables - ((names (lwt_get lwt_post lwt_echo_server)) + ((names (lwt_get lwt_post lwt_echo_server lwt_https_get lwt_https_server)) (libraries (httpaf httpaf-lwt lwt lwt.unix)))) diff --git a/examples/lwt/lwt_echo_server.ml b/examples/lwt/lwt_echo_server.ml index 1f2bc386..966dae07 100644 --- a/examples/lwt/lwt_echo_server.ml +++ b/examples/lwt/lwt_echo_server.ml @@ -5,7 +5,7 @@ let connection_handler : Unix.sockaddr -> Lwt_unix.file_descr -> unit Lwt.t = let module Response = Httpaf.Response in let module Status = Httpaf.Status in - let request_handler : Unix.sockaddr -> _ Reqd.t -> unit = + let request_handler : Unix.sockaddr -> Reqd.t -> unit = fun _client_address request_descriptor -> let request = Reqd.request request_descriptor in diff --git a/examples/lwt/lwt_https_get.ml b/examples/lwt/lwt_https_get.ml new file mode 100644 index 00000000..46636668 --- /dev/null +++ b/examples/lwt/lwt_https_get.ml @@ -0,0 +1,73 @@ +module Body = Httpaf.Body + +let response_handler notify_response_received response response_body = + let module Response = Httpaf.Response in + match Response.(response.status) with + | `OK -> + let rec read_response () = + Body.schedule_read + response_body + ~on_eof:(fun () -> Lwt.wakeup_later notify_response_received ()) + ~on_read:(fun response_fragment ~off ~len -> + let response_fragment_string = Bytes.create len in + Lwt_bytes.blit_to_bytes + response_fragment off + response_fragment_string 0 + len; + print_string (Bytes.unsafe_to_string response_fragment_string); + + read_response ()) + in + read_response () + + | _ -> + Format.fprintf Format.err_formatter "%a\n%!" Response.pp_hum response; + exit 1 + +let error_handler _ = + assert false + +open Lwt.Infix + +let () = + let host = ref None in + let port = ref 443 in + + Arg.parse + ["-p", Set_int port, " Port number (443 by default)"] + (fun host_argument -> host := Some host_argument) + "lwt_https_get.exe [-p N] HOST"; + + let host = + match !host with + | None -> failwith "No hostname provided" + | Some host -> host + in + + Lwt_main.run begin + Lwt_unix.getaddrinfo host (string_of_int !port) [Unix.(AI_FAMILY PF_INET)] + >>= fun addresses -> + + let socket = Lwt_unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in + Lwt_unix.connect socket (List.hd addresses).Unix.ai_addr + >>= fun () -> + + let request_headers = + Httpaf.Request.create + `GET "/" ~headers:(Httpaf.Headers.of_list ["Host", host]) + in + + let response_received, notify_response_received = Lwt.wait () in + let response_handler = response_handler notify_response_received in + + let request_body = + Httpaf_lwt.Client.request_tls + socket + request_headers + ~error_handler + ~response_handler + in + Body.close_writer request_body; + + response_received + end diff --git a/examples/lwt/lwt_https_server.ml b/examples/lwt/lwt_https_server.ml new file mode 100644 index 00000000..490c50af --- /dev/null +++ b/examples/lwt/lwt_https_server.ml @@ -0,0 +1,106 @@ +let connection_handler : Unix.sockaddr -> Lwt_unix.file_descr -> unit Lwt.t = + let module Body = Httpaf.Body in + let module Headers = Httpaf.Headers in + let module Reqd = Httpaf.Reqd in + let module Response = Httpaf.Response in + let module Status = Httpaf.Status in + + let request_handler : Unix.sockaddr -> Reqd.t -> unit = + fun _client_address request_descriptor -> + + let request = Reqd.request request_descriptor in + match request.meth with + | `POST -> + let request_body = Reqd.request_body request_descriptor in + + let response_content_type = + match Headers.get request.headers "Content-Type" with + | Some request_content_type -> request_content_type + | None -> "application/octet-stream" + in + + let response = + Response.create + ~headers:(Headers.of_list [ + "Content-Type", response_content_type; + "Connection", "close"; + ]) + `OK + in + + let response_body = + Reqd.respond_with_streaming request_descriptor response in + + let rec respond () = + Body.schedule_read + request_body + ~on_eof:(fun () -> Body.close_writer response_body) + ~on_read:(fun request_data ~off ~len -> + Body.write_bigstring response_body request_data ~off ~len; + respond ()) + in + respond () + + | _ -> + Reqd.respond_with_string + request_descriptor (Response.create `Method_not_allowed) "" + in + + let error_handler : + Unix.sockaddr -> + ?request:Httpaf.Request.t -> + _ -> + (Headers.t -> [`write] Body.t) -> + unit = + fun _client_address ?request:_ error start_response -> + + let response_body = start_response Headers.empty in + + begin match error with + | `Exn exn -> + Body.write_string response_body (Printexc.to_string exn); + Body.write_string response_body "\n"; + + | #Status.standard as error -> + Body.write_string response_body (Status.default_reason_phrase error) + end; + + Body.close_writer response_body + in + + let certfile = "./certificates/server.pem" in + let keyfile = "./certificates/server.key" in + Httpaf_lwt.Server.create_tls_connection_handler + ?server:None + ~certfile + ~keyfile + ?config:None + ~request_handler + ~error_handler + + + +let () = + let open Lwt.Infix in + + let port = ref 8080 in + Arg.parse + ["-p", Arg.Set_int port, " Listening port number (8080 by default)"] + ignore + "Echoes POST requests. Runs forever."; + + let listen_address = Unix.(ADDR_INET (inet_addr_loopback, !port)) in + + Lwt.async begin fun () -> + Lwt_io.establish_server_with_client_socket + listen_address connection_handler + >>= fun _server -> + Printf.printf "Listening on port %i and echoing POST requests.\n" !port; + print_string "To send a POST request, try\n\n"; + print_string " curl https://localhost:8080 -k -X POST -d foo\n\n"; + flush stdout; + Lwt.return_unit + end; + + let forever, _ = Lwt.wait () in + Lwt_main.run forever diff --git a/lwt/buffer.ml b/lwt/buffer.ml new file mode 100644 index 00000000..10a45a03 --- /dev/null +++ b/lwt/buffer.ml @@ -0,0 +1,37 @@ +open Lwt.Infix + +(* Based on the Buffer module in httpaf_async.ml. *) +type t = + { buffer : Lwt_bytes.t + ; mutable off : int + ; mutable len : int } + +let create size = + let buffer = Lwt_bytes.create size in + { buffer; off = 0; len = 0 } + +let compress t = + if t.len = 0 + then begin + t.off <- 0; + t.len <- 0; + end else if t.off > 0 + then begin + Lwt_bytes.blit t.buffer t.off t.buffer 0 t.len; + t.off <- 0; + end + +let get t ~f = + let n = f t.buffer ~off:t.off ~len:t.len in + t.off <- t.off + n; + t.len <- t.len - n; + if t.len = 0 + then t.off <- 0; + n + +let put t ~f = + compress t; + f t.buffer ~off:(t.off + t.len) ~len:(Lwt_bytes.length t.buffer - t.len) + >>= fun n -> + t.len <- t.len + n; + Lwt.return n diff --git a/lwt/buffer.mli b/lwt/buffer.mli new file mode 100644 index 00000000..a98dcdae --- /dev/null +++ b/lwt/buffer.mli @@ -0,0 +1,6 @@ +type t + +val create : int -> t + +val get : t -> f:(Lwt_bytes.t -> off:int -> len:int -> int) -> int +val put : t -> f:(Lwt_bytes.t -> off:int -> len:int -> int Lwt.t) -> int Lwt.t diff --git a/lwt/httpaf_lwt.ml b/lwt/httpaf_lwt.ml index 8764f55b..8b7f6e1a 100644 --- a/lwt/httpaf_lwt.ml +++ b/lwt/httpaf_lwt.ml @@ -1,52 +1,6 @@ open Lwt.Infix - -(* Based on the Buffer module in httpaf_async.ml. *) -module Buffer : sig - type t - - val create : int -> t - - val get : t -> f:(Lwt_bytes.t -> off:int -> len:int -> int) -> int - val put : t -> f:(Lwt_bytes.t -> off:int -> len:int -> int Lwt.t) -> int Lwt.t -end = struct - type t = - { buffer : Lwt_bytes.t - ; mutable off : int - ; mutable len : int } - - let create size = - let buffer = Lwt_bytes.create size in - { buffer; off = 0; len = 0 } - - let compress t = - if t.len = 0 - then begin - t.off <- 0; - t.len <- 0; - end else if t.off > 0 - then begin - Lwt_bytes.blit t.buffer t.off t.buffer 0 t.len; - t.off <- 0; - end - - let get t ~f = - let n = f t.buffer ~off:t.off ~len:t.len in - t.off <- t.off + n; - t.len <- t.len - n; - if t.len = 0 - then t.off <- 0; - n - - let put t ~f = - compress t; - f t.buffer ~off:(t.off + t.len) ~len:(Lwt_bytes.length t.buffer - t.len) - >>= fun n -> - t.len <- t.len + n; - Lwt.return n -end - let read fd buffer = Lwt.catch (fun () -> @@ -67,7 +21,6 @@ let read fd buffer = Lwt.return (`Ok bytes_read) - let shutdown socket command = try Lwt_unix.shutdown socket command with Unix.Unix_error (Unix.ENOTCONN, _, _) -> () @@ -75,118 +28,148 @@ let shutdown socket command = module Config = Httpaf.Config module Server = struct + module Server_connection = Httpaf.Server_connection + + let start_read_write_loops + ?(readf=read) + ?(writev=Faraday_lwt_unix.writev_of_fd) + ~config + ~socket + connection = + let read_buffer = Buffer.create config.Config.read_buffer_size in + let read_loop_exited, notify_read_loop_exited = Lwt.wait () in + + let rec read_loop () = + let rec read_loop_step () = + match Server_connection.next_read_operation connection with + | `Read -> + readf socket read_buffer >>= begin function + | `Eof -> + Buffer.get read_buffer ~f:(fun bigstring ~off ~len -> + Server_connection.read_eof connection bigstring ~off ~len) + |> ignore; + read_loop_step () + | `Ok _ -> + Buffer.get read_buffer ~f:(fun bigstring ~off ~len -> + Server_connection.read connection bigstring ~off ~len) + |> ignore; + read_loop_step () + end + + | `Yield -> + Server_connection.yield_reader connection read_loop; + Lwt.return_unit + + | `Close -> + Lwt.wakeup_later notify_read_loop_exited (); + if not (Lwt_unix.state socket = Lwt_unix.Closed) then begin + shutdown socket Unix.SHUTDOWN_RECEIVE + end; + Lwt.return_unit + in + + Lwt.async (fun () -> + Lwt.catch + read_loop_step + (fun exn -> + Server_connection.report_exn connection exn; + Lwt.return_unit)) + in + + + let writev = writev socket in + let write_loop_exited, notify_write_loop_exited = Lwt.wait () in + + let rec write_loop () = + let rec write_loop_step () = + match Server_connection.next_write_operation connection with + | `Write io_vectors -> + writev io_vectors >>= fun result -> + Server_connection.report_write_result connection result; + write_loop_step () + + | `Yield -> + Server_connection.yield_writer connection write_loop; + Lwt.return_unit + + | `Close _ -> + Lwt.wakeup_later notify_write_loop_exited (); + if not (Lwt_unix.state socket = Lwt_unix.Closed) then begin + shutdown socket Unix.SHUTDOWN_SEND + end; + Lwt.return_unit + in + + Lwt.async (fun () -> + Lwt.catch + write_loop_step + (fun exn -> + Server_connection.report_exn connection exn; + Lwt.return_unit)) + in + + + read_loop (); + write_loop (); + Lwt.join [read_loop_exited; write_loop_exited] >>= fun () -> + + if Lwt_unix.state socket <> Lwt_unix.Closed then + Lwt.catch + (fun () -> Lwt_unix.close socket) + (fun _exn -> Lwt.return_unit) + else + Lwt.return_unit + let create_connection_handler ?(config=Config.default) ~request_handler ~error_handler = fun client_addr socket -> - let module Server_connection = Httpaf.Server_connection in let connection = Server_connection.create ~config ~error_handler:(error_handler client_addr) (request_handler client_addr) in - - let read_buffer = Buffer.create config.read_buffer_size in - let read_loop_exited, notify_read_loop_exited = Lwt.wait () in - - let rec read_loop () = - let rec read_loop_step () = - match Server_connection.next_read_operation connection with - | `Read -> - read socket read_buffer >>= begin function - | `Eof -> - Buffer.get read_buffer ~f:(fun bigstring ~off ~len -> - Server_connection.read_eof connection bigstring ~off ~len) - |> ignore; - read_loop_step () - | `Ok _ -> - Buffer.get read_buffer ~f:(fun bigstring ~off ~len -> - Server_connection.read connection bigstring ~off ~len) - |> ignore; - read_loop_step () - end - - | `Yield -> - Server_connection.yield_reader connection read_loop; - Lwt.return_unit - - | `Close -> - Lwt.wakeup_later notify_read_loop_exited (); - if not (Lwt_unix.state socket = Lwt_unix.Closed) then begin - shutdown socket Unix.SHUTDOWN_RECEIVE - end; - Lwt.return_unit - in - - Lwt.async (fun () -> - Lwt.catch - read_loop_step - (fun exn -> - Server_connection.report_exn connection exn; - Lwt.return_unit)) - in - - - let writev = Faraday_lwt_unix.writev_of_fd socket in - let write_loop_exited, notify_write_loop_exited = Lwt.wait () in - - let rec write_loop () = - let rec write_loop_step () = - match Server_connection.next_write_operation connection with - | `Write io_vectors -> - writev io_vectors >>= fun result -> - Server_connection.report_write_result connection result; - write_loop_step () - - | `Yield -> - Server_connection.yield_writer connection write_loop; - Lwt.return_unit - - | `Close _ -> - Lwt.wakeup_later notify_write_loop_exited (); - if not (Lwt_unix.state socket = Lwt_unix.Closed) then begin - shutdown socket Unix.SHUTDOWN_SEND - end; - Lwt.return_unit - in - - Lwt.async (fun () -> - Lwt.catch - write_loop_step - (fun exn -> - Server_connection.report_exn connection exn; - Lwt.return_unit)) + start_read_write_loops ~config ~socket connection + + let create_tls_connection_handler + ?server + ?certfile + ?keyfile + ?(config=Config.default) + ~request_handler + ~error_handler = + fun client_addr socket -> + let connection = + Server_connection.create + ~config + ~error_handler:(error_handler client_addr) + (request_handler client_addr) in - - - read_loop (); - write_loop (); - Lwt.join [read_loop_exited; write_loop_exited] >>= fun () -> - - if Lwt_unix.state socket <> Lwt_unix.Closed then - Lwt.catch - (fun () -> Lwt_unix.close socket) - (fun _exn -> Lwt.return_unit) - else - Lwt.return_unit + Tls_impl.make_server ?server ?certfile ?keyfile socket >>= fun tls_server -> + let readf = Tls_impl.readf tls_server in + let writev = Tls_impl.writev tls_server in + start_read_write_loops ~config ~readf ~writev ~socket connection + >>= Lwt.return end module Client = struct - let request ?(config=Config.default) socket request ~error_handler ~response_handler = - let module Client_connection = Httpaf.Client_connection in - let request_body, connection = - Client_connection.request ~config request ~error_handler ~response_handler in - - - let read_buffer = Buffer.create config.read_buffer_size in + module Client_connection = Httpaf.Client_connection + + let start_read_write_loops + ?(readf=read) + ?(writev=Faraday_lwt_unix.writev_of_fd) + ~config + ~socket + connection = + let read_buffer = Buffer.create config.Config.read_buffer_size in let read_loop_exited, notify_read_loop_exited = Lwt.wait () in let read_loop () = let rec read_loop_step () = match Client_connection.next_read_operation connection with | `Read -> - read socket read_buffer >>= begin function + readf socket read_buffer >>= begin function | `Eof -> Buffer.get read_buffer ~f:(fun bigstring ~off ~len -> Client_connection.read_eof connection bigstring ~off ~len) @@ -216,7 +199,7 @@ module Client = struct in - let writev = Faraday_lwt_unix.writev_of_fd socket in + let writev = writev socket in let write_loop_exited, notify_write_loop_exited = Lwt.wait () in let rec write_loop () = @@ -233,9 +216,6 @@ module Client = struct | `Close _ -> Lwt.wakeup_later notify_write_loop_exited (); - if not (Lwt_unix.state socket = Lwt_unix.Closed) then begin - shutdown socket Unix.SHUTDOWN_SEND - end; Lwt.return_unit in @@ -259,7 +239,28 @@ module Client = struct (fun () -> Lwt_unix.close socket) (fun _exn -> Lwt.return_unit) else - Lwt.return_unit); + Lwt.return_unit) + let request ?(config=Config.default) socket request ~error_handler ~response_handler = + let request_body, connection = + Client_connection.request ~config request ~error_handler ~response_handler + in + + start_read_write_loops ~config ~socket connection; + request_body + + let request_tls ?client ?(config=Config.default) socket request ~error_handler ~response_handler = + let request_body, connection = + Client_connection.request ~config request ~error_handler ~response_handler + in + + Lwt.async(fun () -> + Tls_impl.make_client ?client socket >|= fun tls_client -> + let readf = Tls_impl.readf tls_client in + let writev = Tls_impl.writev tls_client in + + start_read_write_loops ~config ~readf ~writev ~socket connection); request_body end + +module Tls_impl = Tls_impl diff --git a/lwt/httpaf_lwt.mli b/lwt/httpaf_lwt.mli index 2967abad..420cca61 100644 --- a/lwt/httpaf_lwt.mli +++ b/lwt/httpaf_lwt.mli @@ -12,15 +12,40 @@ module Server : sig -> Unix.sockaddr -> Lwt_unix.file_descr -> unit Lwt.t + + val create_tls_connection_handler + : ?server : Tls_impl.server + -> ?certfile : string + -> ?keyfile : string + -> ?config : Config.t + -> request_handler : (Unix.sockaddr -> Server_connection.request_handler) + -> error_handler : (Unix.sockaddr -> Server_connection.error_handler) + -> Unix.sockaddr + -> Lwt_unix.file_descr + -> unit Lwt.t end (* For an example, see [examples/lwt_get.ml]. *) module Client : sig val request - : ?config : Httpaf.Config.t + : ?config : Config.t -> Lwt_unix.file_descr -> Request.t -> error_handler : Client_connection.error_handler -> response_handler : Client_connection.response_handler - -> [`write] Httpaf.Body.t + -> [`write] Body.t + + val request_tls + : ?client : Tls_impl.client + -> ?config : Config.t + -> Lwt_unix.file_descr + -> Request.t + -> error_handler : Client_connection.error_handler + -> response_handler : Client_connection.response_handler + -> [`write] Body.t +end + +module Tls_impl : sig + type client = Tls_impl.client + type server = Tls_impl.server end diff --git a/lwt/jbuild b/lwt/jbuild index 179e7f9a..2d31f29e 100644 --- a/lwt/jbuild +++ b/lwt/jbuild @@ -3,5 +3,9 @@ (library ((name httpaf_lwt) (public_name httpaf-lwt) - (libraries (faraday-lwt-unix httpaf lwt.unix)) + (libraries (faraday-lwt-unix httpaf lwt.unix + (select tls_impl.ml from + (tls.lwt -> tls_io.ml) + (lwt_ssl -> ssl_io.ml)))) + (modules (buffer httpaf_lwt tls_impl)) (flags (:standard -safe-string)))) diff --git a/lwt/ssl_io.ml b/lwt/ssl_io.ml new file mode 100644 index 00000000..bdd01475 --- /dev/null +++ b/lwt/ssl_io.ml @@ -0,0 +1,63 @@ +open Lwt.Infix + +let () = Ssl.init () + +let readf socket = + fun _fd buffer -> + Lwt.catch + (fun () -> + Buffer.put buffer ~f:(fun bigstring ~off ~len -> + Lwt_unix.blocking (Lwt_ssl.get_fd socket) >>= fun _ -> + Lwt_ssl.read_bytes socket bigstring off len)) + (function + | Unix.Unix_error (Unix.EBADF, _, _) as exn -> + Lwt.fail exn + | exn -> + Lwt.async (fun () -> + Lwt_ssl.ssl_shutdown socket >>= fun () -> + Lwt_ssl.close socket); + Lwt.fail exn) + >>= fun bytes_read -> + if bytes_read = 0 then + Lwt.return `Eof + else + Lwt.return (`Ok bytes_read) + +let writev socket _fd = + fun iovecs -> + Lwt.catch + (fun () -> + Lwt_list.fold_left_s (fun acc {Faraday.buffer; off; len} -> + Lwt_ssl.write_bytes socket buffer off len + >|= fun written -> acc + written) 0 iovecs + >|= fun n -> `Ok n) + (function + | Unix.Unix_error (Unix.EBADF, "check_descriptor", _) -> + Lwt.return `Closed + | exn -> + Lwt.fail exn) + +type client = Lwt_ssl.socket +type server = Lwt_ssl.socket + +let make_client ?client socket = + match client with + | Some client -> Lwt.return client + | None -> + let client_ctx = Ssl.create_context Ssl.SSLv23 Ssl.Client_context in + Ssl.disable_protocols client_ctx [Ssl.SSLv23]; + Ssl.honor_cipher_order client_ctx; + Lwt_ssl.ssl_connect socket client_ctx + +let make_server ?server ?certfile ?keyfile socket + = + match server, certfile, keyfile with + | Some server, _, _ -> Lwt.return server + | None, Some cert, Some priv_key -> + let server_ctx = Ssl.create_context Ssl.SSLv23 Ssl.Server_context in + Ssl.disable_protocols server_ctx [Ssl.SSLv23]; + Ssl.use_certificate server_ctx cert priv_key; + Lwt_ssl.ssl_accept socket server_ctx + | _ -> + Lwt.fail (Invalid_argument "Certfile and Keyfile required when server isn't provided") + diff --git a/lwt/tls_io.ml b/lwt/tls_io.ml new file mode 100644 index 00000000..7ad2674f --- /dev/null +++ b/lwt/tls_io.ml @@ -0,0 +1,67 @@ +open Lwt.Infix + +let _ = Nocrypto_entropy_lwt.initialize () + +let readf tls = + fun _fd buffer -> + Lwt.catch + (fun () -> + Buffer.put buffer ~f:(fun bigstring ~off ~len -> + Tls_lwt.Unix.read_bytes tls bigstring off len)) + (function + | Unix.Unix_error (Unix.EBADF, _, _) as exn -> + Lwt.fail exn + | exn -> + Lwt.async (fun () -> + Tls_lwt.Unix.close tls); + Lwt.fail exn) + >>= fun bytes_read -> + if bytes_read = 0 then + Lwt.return `Eof + else + Lwt.return (`Ok bytes_read) + +let writev tls _fd = + fun iovecs -> + Lwt.catch + (fun () -> + let cstruct_iovecs = List.map (fun {Faraday.len; buffer; off} -> + Cstruct.of_bigarray ~off ~len buffer) + iovecs + in + Tls_lwt.Unix.writev tls cstruct_iovecs + >|= fun () -> + `Ok (Cstruct.lenv cstruct_iovecs)) + (function + | Unix.Unix_error (Unix.EBADF, "check_descriptor", _) -> + Lwt.return `Closed + | exn -> Lwt.fail exn) + +type client = Tls_lwt.Unix.t +type server = Tls.Config.server + +let make_client ?client socket = + match client with + | Some client -> Lwt.return client + | None -> + X509_lwt.authenticator `No_authentication_I'M_STUPID >>= fun authenticator -> + let config = Tls.Config.client ~authenticator () in + Tls_lwt.Unix.client_of_fd config socket + +let make_server ?server ?certfile ?keyfile socket + = + let config = match server, certfile, keyfile with + | Some server, _, _ -> Lwt.return server + | None, Some cert, Some priv_key -> + X509_lwt.private_of_pems ~cert ~priv_key >>= fun certificate -> + X509_lwt.authenticator `No_authentication_I'M_STUPID >|= fun authenticator -> + Tls.Config.server + ~certificates:(`Single certificate) + ~authenticator + () + | _ -> + Lwt.fail (Invalid_argument "Certfile and Keyfile required when server isn't provided") + in + config >>= fun config -> Tls_lwt.Unix.server_of_fd config socket + +