deniz.in

Piyasalar

Hava durumu

Hava durumu yükleniyor

· kaynak dev.to (home feed)

Go'da SSE stream'leri üretimde neden kırılıyor: geçersiz HTTP/2 header'ları ve varsayılan write timeout'ları

Bir dev.to yazısı, Go'daki iki üretim SSE arızasını ele alıyor: HTTP/2'de yasak olan bir hop-by-hop header ve stream'leri 30 saniyede kesen varsayılan sunucu timeout'ları.

Go'da SSE stream'leri üretimde neden kırılıyor: geçersiz HTTP/2 header'ları ve varsayılan write timeout'ları

Stream, çok yavaş bir istektir

Jules Robineau'nun dev.to'daki yazısına göre bir Server-Sent Events endpoint'i HTTP sunucusu için özel bir durum değildir: hiç bitmeyen bir istektir. Sunucudaki her varsayılan koruma mekanizması — write timeout'ları, context deadline'leri, idle timeout'lar — uzayıp giden istekleri öldürmek için vardır. Meşru bir stream, tam olarak bu ayarların sonlandırmak için yazıldığı patolojinin kopyası gibi görünür.

Yazar üretimde iki SSE endpoint'i çalıştırıyor: Kubernetes üzerinde reverse proxy arkasında bir Go bildirim servisi ve sayfa yenilemeden güncellenen dahili bir dashboard. İkisi de farklı yerlerde kırıldı ama görünür belirti aynıydı: istemci sonsuz bir döngüde yeniden bağlanıyordu.

HTTP/2 stream'lerini sıfırlayan Connection header'ı

İlk olayda endpoint 200 döndü ve tarayıcı ardından net::ERR_HTTP2_PROTOCOL_ERROR bildirdi. Sebep, eski bir SSE tutorial'ından kopyalanmış tek bir satırdı: yanıta Connection: keep-alive eklenmesi.

Connection bir hop-by-hop header'dır; yani uçtan uca değil tek bir ağ sekmesi için geçerlidir ve RFC 9113 bölüm 8.2.2 bu tür header'ları HTTP/2'de yasaklar. Tarayıcı ingress ile HTTP/2 konuşur, ingress yanıtı yeniden iletir ve yasadışı header, status line'ın hemen ardından stream'i sıfırlar.

Yazının da belirttiği gibi bu header hiçbir zaman işe yaramamıştır: HTTP/2 tasarımı gereği kalıcı ve çoklanabilir (multiplexed), HTTP/1.1 ise zaten varsayılan olarak keep-alive kullanır. Önerilen set yalnızca üç header'dır — Content-Type: text/event-stream, Cache-Control: no-cache ve nginx için X-Accel-Buffering: no — buna karşılık Connection, Keep-Alive, Transfer-Encoding veya Upgrade asla ayarlanmamalıdır.

Varsayılan timeout'lar stream'i 30 saniyede öldürüyor

Günler sonra ikinci bir arıza ortaya çıktı. Yazarın HTTP sunucularını kuran paylaşımlı dahili paket, ReadTimeout'u 15 saniye, WriteTimeout'u 30 saniye, IdleTimeout'u 60 saniye olarak ayarlıyordu; ayrıca 30 saniye sonra istek context'ini iptal eden bir middleware vardı.

Bu değerlerden ikisi SSE'yi kırıyor. WriteTimeout, handler hâlâ yazarken bağlantıyı kapatıyor ve middleware context'i iptal ediyor. Stream tam 30 saniyede ölüyor ve aynı HTTP/2 hatası olarak ortaya çıktığından, belirti protokolü suçlarken sebep konfigürasyonda gizli kalıyor. Düzeltme hem stream path'inin timeout middleware'inden muaf tutulmasını hem de o route için WriteTimeout'un sıfıra ayarlanmasını gerektirdi; tek başına yapılan herhangi bir değişiklik yetmedi.

Yazıya göre diğer iki timeout bu kurulumda zararsız: ReadTimeout hiç tetiklenmiyor çünkü istemci ilk istekten sonra hiçbir şey göndermiyor; IdleTimeout ise yazmalar, süresi dolma aralığından daha sık gerçekleştiği sürece güvenli — yazar 60 saniyelik limite karşı her 30 saniyede bir heartbeat kullanıyor.

HTTP/1.1'de kalıcı bir stream sayfayı donduruyor

Dahili dashboard'daki ayrı bir olayda istekler hiç dönmediği için butonlar dönmeye devam etti. Tarayıcılar HTTP/1.1'i origin başına kabaca altı bağlantıyla sınırlar ve bir SSE stream'i bu bağlantılardan birini kalıcı olarak işgal eder; ikinci bir sekme havuzu tüketebilir. HTTP/2, her isteği tek bir bağlantı üzerinden çoklayarak bu sorunu ortadan kaldırır.

Yazarın önlemi, stream'i yalnızca istek HTTPS ön yüzünden geldiğinde servis etmek: X-Forwarded-Proto header'ını kontrol edip aksi durumda 404 döndürmek; böylece düz HTTP/1.1 origin'ine gelen istemciler hiç stream açamaz.

Go aynı deseni kendi kütüphanesinde yamaladı

Yazıya göre 13 Ağustos 2026'da Go ekibi, on güvenlik sorununun düzeltmesiyle 1.26.6 ve 1.25.13 sürümlerini yayınladı. Bunlardan biri olan GO-2026-6089 (CVE-2026-56853 olarak da takip ediliyor), şifrelenmemiş HTTP/2 bağlantıları kontrolü sırasında ReadHeaderTimeout'un uygulanmamasını ele aldı. Bir istemci, timeout'u hiç ödemeden bağlantıları açık tutabiliyordu; bu, kaynak tükenmesi yoluyla bir hizmet dışı bırakma (denial of service) durumuydu. Düzeltme go1.25.13, go1.26.6 ve go1.27.0-rc.3'e girdi; Go 1.27 ise 19 Ağustos'ta yayınlandı. Yazarın çıkarımı yinelenen bir desen: timeout vardı ama basitçe HTTP/2 yolunu kapsamıyordu — uygulama düzeyindeki konfigürasyonla aynı arıza modu.

Yayına almadan önce bir kontrol listesi

Yazı, üretim öncesi bir listeyle kapanıyor: handler'da Connection, Keep-Alive, Transfer-Encoding veya Upgrade header'ları olmamalı; WriteTimeout devre dışı bırakılmalı ve stream path'inde timeout middleware atlanmalı; IdleTimeout'tan daha sık atan bir heartbeat olmalı; stream yalnızca bir HTTP/2 ön yüzünün arkasında servis edilmeli; yanıtın proxy tarafından buffer'lanması engellenmeli; istemci yeniden bağlanmaları sayılmalı; ve güncel bir Go sürümü, standard library dahil, kullanılmalı.

Neden önemli

Streaming yanıtlar artık rutin — canlı dashboard'lar, bildirimler, AI token stream'leri — ve Go'nun varsayılanları sıradan istek-yanıt API'lerine göre ayarlanmıştır. Burada anlatılan arıza modları sessiz ya da aktif şekilde yanıltıcıdır: tarayıcı, gerçek sebep kalıtsal bir tutorial header'ı ya da 30 saniyelik bir write timeout iken HTTP/2 protokol hatası bildirir. Ağustos 2026 yaması, standard library'nin kendisinin de timeout kör noktalarına karşı bağışık olmadığını gösteriyor; dolayısıyla eski Go sürümlerindeki ekiplerin, operasyonel gerekçelere ek olarak go.mod dosyalarını kontrol etmesi için somut bir güvenlik sebebi var.

  • #go
  • #http2
  • #server-sent-events
  • #timeouts
  • #streaming