let reporter ppf = let report src level ~over k msgf = let k _ = over () ; k () in let with_metadata header _tags k ppf fmt = Format.kfprintf k ppf ("%a[%a]: " ^^ fmt ^^ "\n%!") Logs_fmt.pp_header (level, header) Fmt.(styled `Magenta string) (Logs.Src.name src) in msgf @@ fun ?header ?tags fmt -> with_metadata header tags k ppf fmt in { Logs.report } let apply v f = f v let sigpipe = 13 let () = Mirage_crypto_rng_unix.use_default () let () = Printexc.record_backtrace true (* let () = Fmt_tty.setup_std_outputs ~style_renderer:`Ansi_tty ~utf_8:true () let () = Logs.set_reporter (reporter Fmt.stdout) let () = Logs.set_level ~all:true (Some Logs.Debug) *) let () = Sys.set_signal sigpipe Sys.Signal_ignore let src = Logs.Src.create "simple-server" module Log = (val Logs.src_log src : Logs.LOG) module P = Paf_mirage.Make (Tcpip_stack_socket.V4V6.TCP) module Ke = Ke.Rke let getline queue = let exists ~predicate queue = let pos = ref 0 and res = ref (-1) in Ke.iter (fun chr -> if predicate chr then res := !pos ; incr pos) queue ; if !res = -1 then None else Some !res in let blit src src_off dst dst_off len = Bigstringaf.blit_to_bytes src ~src_off dst ~dst_off ~len in match exists ~predicate:(( = ) '\n') queue with | Some pos -> let tmp = Bytes.create pos in Ke.N.keep_exn queue ~blit ~length:Bytes.length ~off:0 ~len:pos tmp ; Ke.N.shift_exn queue (pos + 1) ; Some (Bytes.unsafe_to_string tmp) | None -> None let http_large filename ?shutdown:_ (_ip, _port) ic oc = let open H1 in Body.Reader.close ic ; let ic = open_in filename in let tp = Bytes.create 0x1000 in let rec go () = match input ic tp 0 (Bytes.length tp) with | 0 -> Body.Writer.close oc | len -> Body.Writer.write_string oc (Bytes.sub_string tp 0 len) ; go () | exception End_of_file -> Body.Writer.close oc in go () ; close_in ic let http_ping_pong ?shutdown:_ (_ip, _port) ic oc = let open H1 in let open Lwt.Infix in let closed = ref false and queue = Ke.create ~capacity:0x1000 Bigarray.char in let blit src src_off dst dst_off len = Bigstringaf.blit src ~src_off dst ~dst_off ~len in let on_eof () = closed := true in let rec on_read buf ~off ~len = Ke.N.push queue ~blit ~length:Bigstringaf.length buf ~off ~len ; Body.Reader.schedule_read ic ~on_eof ~on_read in Body.Reader.schedule_read ic ~on_eof ~on_read ; let rec go () = match (!closed, getline queue) with | false, None -> Lwt.pause () >>= go | false, Some "ping" -> Body.Writer.write_string oc "pong\n" ; go () | false, Some "pong" -> Body.Writer.write_string oc "ping\n" ; go () | false, Some _line -> Body.Writer.close oc ; Lwt.return_unit | true, _ -> Body.Writer.close oc ; Lwt.return_unit in Lwt.async go let request_handler large ?shutdown _flow (ip, port) reqd = let open H1 in let request = Reqd.request reqd in match request.Request.target with | "/" -> let headers = Headers.of_list [ ("transfer-encoding", "chunked") ] in let response = Response.create ~headers `OK in let oc = Reqd.respond_with_streaming reqd response in http_ping_pong ?shutdown (ip, port) (Reqd.request_body reqd) oc | "/ping" -> let headers = Headers.of_list [ ("content-length", "4") ] in let response = Response.create ~headers `OK in Reqd.respond_with_string reqd response "pong" ; Option.iter (apply ()) shutdown | "/pong" -> let headers = Headers.of_list [ ("content-length", "4") ] in let response = Response.create ~headers `OK in Reqd.respond_with_string reqd response "ping" ; Option.iter (apply ()) shutdown | "/large" -> let headers = Headers.of_list [ ("transfer-encoding", "chunked") ] in let response = Response.create ~headers `OK in let oc = Reqd.respond_with_streaming reqd response in http_large large ?shutdown (ip, port) (Reqd.request_body reqd) oc | _ -> assert false let error_handler _ ?request:_ error _respond = match error with | `Exn _exn -> Printexc.print_backtrace stderr | `Bad_gateway -> Fmt.epr "Got a bad gateway error.\n%!" | `Bad_request -> Fmt.epr "Got a bad request error.\n%!" | `Internal_server_error -> Fmt.epr "Got an internal server error.\n%!" let ( <.> ) f g x = f (g x) open Lwt.Infix let ( >>? ) x f = x >>= function Ok x -> f x | Error _ as err -> Lwt.return err let fd_8080 = Unix.openfile "lock.8080" Unix.[ O_CREAT; O_RDWR ] 0o644 let () = at_exit (fun () -> try Unix.close fd_8080 with _exn -> ()) let fd_4343 = Unix.openfile "lock.4343" Unix.[ O_CREAT; O_RDWR ] 0o644 let () = at_exit (fun () -> try Unix.close fd_4343 with _exn -> ()) let unlock fd = Unix.lockf fd Unix.F_ULOCK 0 let server_http large stack = P.init ~port:8080 stack >>= fun service -> let http = P.http_service ~error_handler (request_handler large) in let (`Initialized th) = P.serve http service in unlock fd_8080 ; Log.debug (fun m -> m "HTTP server initialized.") ; th let load_file filename = let ic = open_in filename in let ln = in_channel_length ic in let rs = Bytes.create ln in really_input ic rs 0 ln ; close_in ic ; Bytes.unsafe_to_string rs let server_https cert key large stack = let cert = load_file cert in let key = load_file key in match (X509.Certificate.decode_pem_multiple cert, X509.Private_key.decode_pem key) with | Ok certs, Ok (`RSA key) -> let tls = Result.get_ok (Tls.Config.server ~certificates:(`Single (certs, `RSA key)) ()) in P.init ~port:4343 stack >>= fun service -> let https = P.https_service ~tls ~error_handler (request_handler large) in let (`Initialized th) = P.serve https service in unlock fd_4343 ; th | _ -> invalid_arg "Invalid certificate or key" let stack = Tcpip_stack_socket.V4V6.UDP.connect ~ipv4_only:false ~ipv6_only:false Ipaddr.V4.Prefix.global None >>= fun udpv4 -> Tcpip_stack_socket.V4V6.TCP.connect ~ipv4_only:false ~ipv6_only:false Ipaddr.V4.Prefix.global None >>= fun tcpv4 -> Tcpip_stack_socket.V4V6.connect udpv4 tcpv4 let run_http large = stack >|= Tcpip_stack_socket.V4V6.tcp >>= server_http large let run_https cert key large = stack >|= Tcpip_stack_socket.V4V6.tcp >>= server_https cert key large let () = match Sys.argv with | [| _; "--with-tls"; cert; key; large |] -> Lwt_main.run (run_https cert key large) | [| _; large |] -> Lwt_main.run (run_http large) | _ -> Fmt.epr "%s [--with-tls cert key] large\n%!" Sys.argv.(0)