Fix git smart-HTTP streaming: apply CGI headers before chunking

5e67fc0 · gmorell · 2026-06-25 15:14

1 files +15 -6
Message
{commit_body(@commit)}

Files changed

modified lib/git_gud_web/plugs/git_smart_http.ex
+15 −6
@@ -78,10 +78,12 @@ defmodule GitGudWeb.Plugs.GitSmartHttp do
78 78 when service in [@upload_pack, @receive_pack] do
79 79 with {:ok, repo} <- find_repo(owner, name),
80 80 {:ok, conn} <- authorize(conn, repo, service) do
81 + # Don't send_chunked here — the CGI emits its own status + headers
82 + # (Content-Type, etc.) which stream_cgi/relay_response apply before
83 + # chunking. Chunking first makes apply_cgi_headers raise
84 + # Plug.Conn.AlreadySentError and resets the HTTP/2 stream.
81 85 conn
82 |> put_resp_content_type("application/x-#{service}-advertisement", nil)
83 86 |> put_resp_header("cache-control", "no-cache")
84 |> send_chunked(200)
85 87 |> stream_cgi(repo, ["http-backend"], cgi_env(conn, repo, service, "GET", true))
86 88 else
87 89 {:error, :not_found} -> send_resp(conn, 404, "")
@@ -95,10 +97,9 @@ defmodule GitGudWeb.Plugs.GitSmartHttp do
95 97 defp handle_rpc(conn, owner, name, service) do
96 98 with {:ok, repo} <- find_repo(owner, name),
97 99 {:ok, conn} <- authorize(conn, repo, service) do
100 + # See handle_info_refs: let the CGI's headers land before chunking.
98 101 conn
99 |> put_resp_content_type("application/x-#{service}-result", nil)
100 102 |> put_resp_header("cache-control", "no-cache")
101 |> send_chunked(200)
102 103 |> stream_cgi(repo, ["http-backend"], cgi_env(conn, repo, service, "POST", false))
103 104 else
104 105 {:error, :not_found} -> send_resp(conn, 404, "")
@@ -318,9 +319,12 @@ defmodule GitGudWeb.Plugs.GitSmartHttp do
318 319 end
319 320
320 321 {^port, {:exit_status, _}} ->
321 conn
322 + # CGI exited before emitting a header block — send a real error
323 + # instead of returning an unsent conn (Bandit would reject it).
324 + send_resp(conn, 500, "git http-backend produced no output")
322 325 after
323 60_000 -> conn
326 + 60_000 ->
327 + send_resp(conn, 504, "git http-backend timed out")
324 328 end
325 329 end
326 330
@@ -338,6 +342,11 @@ defmodule GitGudWeb.Plugs.GitSmartHttp do
338 342 end
339 343
340 344 defp relay_response_send(conn, port, initial) do
345 + # Now that the CGI's status + headers are on the conn, start the
346 + # chunked response (using the CGI status if it set one, else 200),
347 + # then stream the body.
348 + conn = send_chunked(conn, conn.status || 200)
349 +
341 350 conn =
342 351 if byte_size(initial) > 0 do
343 352 {:ok, conn} = Plug.Conn.chunk(conn, initial)

Parents: 636a67b