open Lwt.Infix module Http2 = struct open H2 let connection_handler : Httpun.Request.t -> Bigstringaf.t H2.IOVec.t list -> (Server_connection.t, string) result = let request_handler : H2.Server_connection.request_handler = fun request_descriptor -> let request = Reqd.request request_descriptor in match request.meth, request.target with | `GET, "/" | `POST, "/" -> (* This set of routes waits until the entire request body has been read * to produce a response. *) 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 buf = Buffer.create 10 in let rec respond () = Body.Reader.schedule_read request_body ~on_eof:(fun () -> let response = Response.create ~headers: (Headers.of_list [ "content-type", response_content_type ]) `OK in Reqd.respond_with_string request_descriptor response (Buffer.contents buf)) ~on_read:(fun request_data ~off ~len -> let bytes = Bytes.create len in Bigstringaf.blit_to_bytes request_data ~src_off:off ~dst_off:0 ~len bytes; Buffer.add_bytes buf bytes; respond ()) in respond () | _ -> Reqd.respond_with_string request_descriptor (Response.create `Method_not_allowed) "" in let error_handler ?request:_ error start_response = let response_body = start_response Headers.empty in (match error with | `Exn exn -> Body.Writer.write_string response_body (Printexc.to_string exn); Body.Writer.write_string response_body "\n" | #Status.standard as error -> Body.Writer.write_string response_body (Status.default_reason_phrase error)); Body.Writer.close response_body in fun http_request request_body -> let { Httpun.Request.headers; target; meth; _ } = http_request in H2.Server_connection.create_h2c ?config:None ~headers ~target ~meth ~request_body ~error_handler request_handler end let connection_handler = let module Body = Httpun.Body in let module Headers = Httpun.Headers in let module Reqd = Httpun.Reqd in let module Response = Httpun.Response in let module Status = Httpun.Status in let upgrade_handler request upgrade () = let off = 0 in let len = 3 in let body = [ { H2.IOVec.buffer = Bigstringaf.of_string ~off ~len "foo"; off; len } ; { buffer = Bigstringaf.of_string ~off ~len "bar"; off; len } ; { buffer = Bigstringaf.of_string ~off ~len "baz"; off; len } ] in let connection = Stdlib.Result.get_ok (Http2.connection_handler request body) in upgrade (Gluten.make (module H2.Server_connection) connection) in let http_error_handler _client_address ?request:_ error handle = let message = match error with | `Exn exn -> Printexc.to_string exn | (#Status.client_error | #Status.server_error) as error -> Status.to_string error in let body = handle Headers.empty in Body.Writer.write_string body message; Body.Writer.close body in let request_handler _addr (reqd : Httpun.Reqd.t Gluten.reqd) = let { Gluten.reqd; upgrade } = reqd in let headers = Headers.of_list [ "Connection", "Upgrade"; "Upgrade", "h2c" ] in let request = Reqd.request reqd in Reqd.respond_with_upgrade reqd headers (upgrade_handler request upgrade) in Httpun_lwt_unix.Server.create_connection_handler ?config:None ~request_handler ~error_handler:http_error_handler let () = Sys.(set_signal sigpipe Signal_ignore); 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 (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 " echo foo | dune exec examples/lwt/lwt_post.exe\n\n"; flush stdout; Lwt.return_unit); let forever, _ = Lwt.wait () in Lwt_main.run forever