sreyansjain

sreyansjain

SSE chunks and compression - responses being sent but without gzip compression

Hi I am trying to use Server Sent Events.
I want to send chunked responses.

I am able to send them like so

conn
      |> put_resp_header("cache-control", "no-cache")
      |> put_resp_header("connection", "keep-alive")
      |> put_resp_header("content-type", "text/event-stream;")
      |> send_chunked(200)
      |> chunk("This is some respsonse")

The responses are being sent, but without gzip compression.
How do i get responses to be sent with gzip compression?

I am using the latest phoenix with latest bandit.
Please advise.

PS If I don’t send chunked response, it gets compressed. For ex -

conn
      |> put_resp_header("cache-control", "no-cache")
      |> put_resp_header("connection", "keep-alive")
      |> put_resp_header("content-type", "text/event-stream;")
      |> text("This would get compressed")

Is some magic happening in the text/2 method?

Marked As Solved

sreyansjain

sreyansjain

Got it to work.
We need to set the content-encoding to gzip
and then manually compress the responses like so

conn
      |> put_resp_header("cache-control", "no-cache")
      |> put_resp_header("connection", "keep-alive")
      |> put_resp_header("content-type", "text/event-stream;")
      |> put_resp_header("content-encoding", "gzip")
      |> send_chunked(200)

And then compress the message manually and send them

defp send_chunk(conn, message) do
    message = :zlib.gzip(message)
    {:ok, conn} =
      conn
      |> prepare_sse()
      |> chunk(message)
    conn
  end

Also Liked

sreyansjain

sreyansjain

SSE compression in long lived connection for streaming updates –

# lib/aet_web/controllers/sse_controller.ex
defmodule AetWeb.SseController do
  use AetWeb, :controller

  def stream(conn, _params) do
    conn =
      conn
      |> put_resp_header("content-type", "text/event-stream")
      |> put_resp_header("content-encoding", "gzip")
      |> put_resp_header("cache-control", "no-cache")
      # |> put_resp_header("connection", "keep-alive")
      |> send_chunked(200)

    # Initialize zlib compression context with gzip wrapper
    z = :zlib.open()
    :ok = :zlib.deflateInit(z, 9, :deflated, 31, 8, :default)  # 31 = gzip format

    # Subscribe to PubSub
    Phoenix.PubSub.subscribe(Aet.PubSub, "events")

    # Start streaming
    try do
      stream_events(conn, z)
    after
      # IO.puts("Finalizing stream...")
      compressed_end = :zlib.deflate(z, "", :finish)
      # IO.inspect(compressed_end, label: "Final chunk")
      Plug.Conn.chunk(conn, compressed_end)
      cleanup_zlib(z)
    end
  end

  defp stream_events(conn, z) do
    receive do
      {:event, data} ->
        # Compress incrementally with sync flush
        compressed_chunk = :zlib.deflate(z, data, :sync)  # :sync flushes immediately

        case Plug.Conn.chunk(conn, compressed_chunk) do
          {:ok, conn} ->
            IO.puts("Chunk sent successfully")
            stream_events(conn, z)
          {:error, :closed} ->
            IO.puts("Client disconnected")
            conn  # Cleanup handled in `after` clause
        end
    after
      1000 ->  # Timeout to prevent infinite loop if no events
        IO.puts("No events received in 1s, continuing...")
        stream_events(conn, z)
    end
  end

  defp cleanup_zlib(z) do
    IO.puts("Cleaning up zlib context...")
    case :zlib.deflateEnd(z) do
      :ok -> :zlib.close(z)
      error ->
        IO.inspect(error, label: "DeflateEnd error")
        :zlib.close(z)
    end
  rescue
    e ->
      IO.inspect(e, label: "Cleanup error")
      :zlib.close(z)
  end
end
hubertlepicki

hubertlepicki

thank you for reporting back, I am sure it’ll be useful to someone

Last Post!

sreyansjain

sreyansjain

SSE compression in long lived connection for streaming updates –

# lib/aet_web/controllers/sse_controller.ex
defmodule AetWeb.SseController do
  use AetWeb, :controller

  def stream(conn, _params) do
    conn =
      conn
      |> put_resp_header("content-type", "text/event-stream")
      |> put_resp_header("content-encoding", "gzip")
      |> put_resp_header("cache-control", "no-cache")
      # |> put_resp_header("connection", "keep-alive")
      |> send_chunked(200)

    # Initialize zlib compression context with gzip wrapper
    z = :zlib.open()
    :ok = :zlib.deflateInit(z, 9, :deflated, 31, 8, :default)  # 31 = gzip format

    # Subscribe to PubSub
    Phoenix.PubSub.subscribe(Aet.PubSub, "events")

    # Start streaming
    try do
      stream_events(conn, z)
    after
      # IO.puts("Finalizing stream...")
      compressed_end = :zlib.deflate(z, "", :finish)
      # IO.inspect(compressed_end, label: "Final chunk")
      Plug.Conn.chunk(conn, compressed_end)
      cleanup_zlib(z)
    end
  end

  defp stream_events(conn, z) do
    receive do
      {:event, data} ->
        # Compress incrementally with sync flush
        compressed_chunk = :zlib.deflate(z, data, :sync)  # :sync flushes immediately

        case Plug.Conn.chunk(conn, compressed_chunk) do
          {:ok, conn} ->
            IO.puts("Chunk sent successfully")
            stream_events(conn, z)
          {:error, :closed} ->
            IO.puts("Client disconnected")
            conn  # Cleanup handled in `after` clause
        end
    after
      1000 ->  # Timeout to prevent infinite loop if no events
        IO.puts("No events received in 1s, continuing...")
        stream_events(conn, z)
    end
  end

  defp cleanup_zlib(z) do
    IO.puts("Cleaning up zlib context...")
    case :zlib.deflateEnd(z) do
      :ok -> :zlib.close(z)
      error ->
        IO.inspect(error, label: "DeflateEnd error")
        :zlib.close(z)
    end
  rescue
    e ->
      IO.inspect(e, label: "Cleanup error")
      :zlib.close(z)
  end
end

Where Next?

Popular in Questions Top

vegabook
I’m brand new to Phoenix and I have stripped one of the demo applications to the bone. I just want to get an svg up on the screen. Here i...
New
vonH
In asking this question I am more interested about the expressiveness of the language itself and less concerned about the availability of...
New
belgoros
I’m not a pro in using Regex and can’t figure out why the following behaviour happens, especially if we take into account the difference ...
New
siddhant3030
Hi, I have to write a raw query for one of my project. But till now I have used ecto queries and don’t have much experience writing raw ...
New
WestKeys
Currently suffering from paralysis by [HTTP client] analysis. This is rather unusual in Elixirland as there tends to be consensus on the ...
New
fayddelight
I tried installing elixir 1.11.2 erlang 23.3.4 via asdf in my zsh shell. Enabled the versions locally and globally. When I list them ...
New
svb
Hi! Currently I want to submit a form by pressing the Enter key. However, since my input field is of type “textarea” this is just adds a...
New

Other popular topics Top

nobody
Hi! In PHP: $_SERVER[‘SERVER_ADDR’] - in Elixir? Searched the docs for ip address and the web, no good results. Thanks!
New
JeremM34
Hello, how can I check the Phoenix version ? Thanks !
New
sen
Hi All, I set a environment variables in dev.exs , like below code. when i start server, how can i set the ${enable} value? thanks. d...
New
sergio_101
I am VERY much an elixir newbie. I have taken one elixir course and one phoenix course on Udemy. During that course, I saw the instructor...
New
Patoshizzle
After calling mix ecto.create I get this error: 17:00:32.162 [error] GenServer #PID<0.412.0> terminating ** (Postgrex.Error) FATAL...
New
TunkShif
This post is an instruction guide to help you setup your Neovim for Elixir development from scratch. It includes general information on h...
274 42533 114
New

We're in Beta

About us Mission Statement