3

I'm using Elixir/Phoenix and I have an endpoint that returns a chunked response like say a never ending stream of log lines. The log lines, however, come from another service A which also returns a chunked response. I want my endpoint to read the chunked response from service A and pass them through to the client also in chunks. In essence, it's just a proxy for service A, but I can't let the client connect directly to service A because I need to perform some authentication.

Afshin Moazami
  • 2,092
  • 5
  • 33
  • 55
Jesse Shieh
  • 4,660
  • 5
  • 34
  • 49
  • You should use any HTTP client and make authentication on your own. The only issue I see here is the size of chunked data, but propably using Stream / Flow might help. – PatNowak May 15 '17 at 05:37

1 Answers1

8

Here's an example of sending chunk data with Phoenix:

  def test_1(conn, _params) do
    conn = conn
    |> put_resp_content_type("text/event-stream")
    |> send_chunked(200)

    conn |> chunk("a")
    conn |> chunk("b")
    conn |> chunk("c")

    # send data five times, once per second, to simulate a log
    for n <- 1..5 do
      conn |> chunk(Integer.to_string(n))
      Process.sleep(1000)
    end

    conn
  end

There are a few http libraries available for Elixir/Erlang; I like HTTPoison.

Here is an example that reads chunks from a url, and sends them off as they come in:

  def test_2(conn, _params) do
    url = "http://localhost:4000/test_1"
    %HTTPoison.AsyncResponse{id: id} = HTTPoison.get!(url, %{}, stream_to: self())

    conn = conn
    |> put_resp_content_type("text/event-stream")
    |> send_chunked(200)

    process_httpoison_chunks(conn, id)
  end

  def process_httpoison_chunks(conn, id) do
    receive do
      %HTTPoison.AsyncStatus{id: ^id} ->
        # TODO handle status
        process_httpoison_chunks(conn, id)
      %HTTPoison.AsyncHeaders{id: ^id, headers: %{"Connection" => "keep-alive"}} ->
        # TODO handle headers
        process_httpoison_chunks(conn, id)
      %HTTPoison.AsyncChunk{id: ^id, chunk: chunk_data} ->
        conn |> chunk(chunk_data)
        process_httpoison_chunks(conn, id)
      %HTTPoison.AsyncEnd{id: ^id} ->
        conn
    end
  end

Some references:

stoodfarback
  • 1,299
  • 9
  • 12
  • Wow! Thanks for the great answer and references! – Jesse Shieh May 16 '17 at 14:17
  • 2
    I just implemented this, but noticed that the connection is dropped when I don't receive another chunk for 5 seconds or so. Do you happen to know of a way to keep the tcp connection alive? – Jesse Shieh May 18 '17 at 21:56
  • 2
    @JesseShieh Ah, yep, there's a `recv_timeout` option for `HTTPoison` which defaults to `5000`ms. You can set it to something large, or `:infinity`. Something like: `HTTPoison.get!(url, %{}, stream_to: self(), recv_timeout: :infinity)` – stoodfarback May 19 '17 at 00:08
  • 1
    `recv_timeout: :infinity` is quite dangerous usually. Better to set the timeout to something long and let it fail eventually. – Jason G May 20 '17 at 02:26