Skip to content

Conversation

@edwintorok
Copy link

I was trying to use a persistent HTTP/1.1 connection, but I kept getting "unexpected eof" errors, and strace was showing that shutdown got called on the client socket. I haven't found a way to make this work without patching httpaf, but perhaps I missed something.

The following program is then able to use persistent connections when talking to nginx on localhost:8000:

open Lwt.Syntax
open Httpaf
open Httpaf_lwt_unix

let repeat = 100000

let config =
  let open Httpaf.Config in
  {default with response_body_buffer_size= 16384}

let rec perform n =
  let socket = Lwt_unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in
  let* () =
    Lwt_unix.connect socket (Unix.ADDR_INET (Unix.inet_addr_loopback, 8000))
  in
  let headers = Httpaf.Headers.of_list [("host", "127.0.0.1")] in
  let req = Request.create ~headers `GET "/" in
  let rec loop n =
    let finished, notify_finished = Lwt.wait () in
    let error_handler error =
      Printexc.print_backtrace stderr ;
      let exn =
        match error with
        | `Malformed_response err ->
            Failure ("Malformed response: " ^ err)
        | `Invalid_response_body_length r ->
            Failure
              (Format.asprintf "Invalid response length: %a" Response.pp_hum r)
        | `Exn exn ->
            exn
      in
      Printexc.to_string exn |> prerr_endline ;
      raise exn
    in
    let keepalive = ref true in
    let response_handler response body =
      keepalive := Response.persistent_connection response ;
      (* Format.eprintf "Keepalive: %b, Response: %a@." (!keepalive) Response.pp_hum response ; *)
      let on_eof () =
        Body.Reader.close body ;
        Lwt.wakeup notify_finished ()
      in
      let rec on_read bs ~off ~len =
        Body.Reader.schedule_read body ~on_read ~on_eof
      in
      Body.Reader.schedule_read body ~on_read ~on_eof
    in
    let request_body =
      Client.request ~config ~error_handler ~response_handler socket req
    in
    Body.Writer.flush request_body ignore ;
    let* () = finished in
    if n < repeat then if !keepalive then loop (n + 1) else perform n
    else
      let* () = Lwt_unix.close socket in
      Lwt.return n
  in
  loop n

let main =
  let+ lst = List.init 6 (fun _ -> 0) |> Lwt_list.map_p perform in
  List.fold_left ( + ) 0 lst

let () =
  let t0 = Unix.gettimeofday () in
  let n = Lwt_main.run main in
  let t1 = Unix.gettimeofday () in
  Printf.printf "%d req: %g req/s\n" n (float n /. (t1 -. t0))

@edwintorok
Copy link
Author

Note: the client still needs to be careful not to accidentally close the request body writer, because that'd prevent further requests from being sent (whereas closing the reader is fine), perhaps similar treatment is needed for the writer?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant