Unable to load file and send http request when using eio

Hello together,

I am trying (in my journey to learn ocaml) to use eio for sending an http request. I am using piaf and for a simple request I am successful.

Now I want to load an api key from a file before sending then request:

open Piaf

let get ~sw env =
  let ( / ) = Eio.Path.( / ) in
  let path = Eio.Stdenv.cwd env / "key" in
  let key = Eio.Path.load path in
  print_endline key;
  let headers = [ ("Authorization", Format.sprintf "Bearer %s" key) ] in
  Client.Oneshot.get ~sw env (Uri.of_string "https://example.com") ~headers

let () =
  Eio_main.run @@ fun env ->
  Eio.Switch.run (fun sw ->
      let _ = get env ~sw in
      print_endline "done")

If I do this, I get this output:

<content-of-./key>

and than the application just hangs. Doing printf debugging I found out, that it seems to hang in Client.Oneshot.get. But if I remove the file system access again, it works.

What am I no understanding?

Thank you!

Just a beginner myself and have no familiarity with piaf, but I noticed in its dependencies “lwt” which is its own concurrency library. I’ve only played with “eio” but I do remember seeing something about interoperability. Maybe this: GitHub - ocaml-multicore/lwt_eio: Use Lwt libraries from within Eio ?

Ah - it was this bit of the main docs: https://github.com/ocaml-multicore/eio/blob/main/README.md#lwt

1 Like

I can’t fully reproduce this particular example (for me it prints done as well and only then it hangs), and I don’t know why reading a file would change the behavior, but one thing to know about piaf is that the switch won’t exit unless you read (or drain) the response body.

Ergo, you should handle the response instead of ignoring it, like in this example.

2 Likes

Good catch. Looks like you can also drain the response body: https://github.com/anmonteiro/piaf/blob/5c9561ba81ba3d3883cf4959541f5e2675516f88/lib_test/test_client.ml#L258

1 Like

Hey,

That seems really strange! I modified the example:

open Piaf

let get ~sw env =
  let ( / ) = Eio.Path.( / ) in
  let path = Eio.Stdenv.cwd env / "key" in
  let key = Eio.Path.load path in
  print_endline key;
  let headers = [ ("Authorization", Format.sprintf "Bearer %s" key) ] in
  Client.Oneshot.get ~sw env (Uri.of_string "https://example.com") ~headers

let () =
  Eio_main.run @@ fun env ->
  Eio.Switch.run (fun sw ->
      let resp = get env ~sw in
      print_endline "done";
      let body =
        resp |> Result.get_ok
        |> (fun r -> Body.to_string r.body)
        |> Result.get_ok
      in
      print_endline body)

But when I run it, it still hangs (after printing the contents of key).
Could you do me a favor and test again if this now works in your machine?

The example you linked to works. but that does not include any file reading. And my example continues to work for me if I remove the file reading…

This is indeed a bit odd. I’ve tried your reproduction, and it fails on macOS (M2) but works on Linux. Could you reproduce the bad behavior on Linux?

I’ve confirmed that this is a bug in h2 / hpack.

The bug I’ve uncovered is related to sending a header value that ends in a newline character. If that’s what you’re also facing, I suspect you want to trim your bearer token string after reading it.

3 Likes

You are amazing!

Final working verison:

open Piaf

let get ~sw env =
  let ( / ) = Eio.Path.( / ) in
  let path = Eio.Stdenv.cwd env / "key" in
  let key = String.trim @@ Eio.Path.load path in
  print_endline key;
  let headers = [ ("Authorization", Format.sprintf "Bearer %s" key) ] in
  Client.Oneshot.get ~sw env (Uri.of_string "https://example.com") ~headers

let () =
  Eio_main.run @@ fun env ->
  Eio.Switch.run (fun sw ->
      let resp = get env ~sw in
      print_endline "done";
      let body =
        resp |> Result.get_ok
        |> (fun r -> Body.to_string r.body)
        |> Result.get_ok
      in
      print_endline body)

1 Like