Hacktoberfest 2026: những issue maintainer đã đánh dấu cho tháng Mười, đang mở và phù hợp người mới. Xem issue Hacktoberfest

HTTP/2 client ALPN fallback needs a handshake-complete signal that the TLS stage does not emit

Đang mở
#1,249 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Maintainer thường phản hồi trong vòng 1 ngày

Chưa có ai nhận issue này.

Đánh giá

Độ khó
5/5
Thời gian dự kiến
Hơn một tuần
Mức phù hợp với người mới
35/100
Loại issue
Tính năng
Độ rõ ràng
Đặc tả rõ ràng
Mức độ hoạt động
Sôi nổi
Công nghệ
java, scala

Hướng nghiên cứu

Bắt đầu với Http2AlpnSupport.scala:74 và Http2.scala:241,275, sau đó lần theo ProtocolSwitch.scala và Http2Blueprint.scala:146,327. Triển khai tín hiệu engine cho mỗi materialization và switch 4 cổng phía client được mô tả trong issue, bao gồm việc buffering các phần tử inbound đến sớm. Hoàn tất có nghĩa là cung cấp các protocol được chỉ định và chọn stack HTTP/1.1 hoặc HTTP/2 đã đàm phán mà không xảy ra deadlock.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Mô tả

Split out from #483 (client-side HTTP/1.1 vs HTTP/2 protocol negotiation), which needs this solved first.

Code anchors below are against main @ 85d7243e7.

The blocker

The server already negotiates via ALPN and switches stacks: Http2Ext.httpsWithAlpn (Http2.scala:241) parks the chosen protocol in a mutable cell from setHandshakeApplicationProtocolSelector, and ProtocolSwitch (ProtocolSwitch.scala) installs either the HTTP/1.1 or HTTP/2 stack.

ProtocolSwitch makes that decision on the first inbound SessionBytes. That is sound server-side, because in HTTP the client always speaks first — there is always an inbound byte to trigger on.

Mirroring it on the client deadlocks. If negotiation lands on http/1.1, the server sends nothing until it receives a request, and the switch is sitting on that request waiting to learn the protocol. Neither side moves.

So the client needs a different trigger: a "handshake complete, ALPN result known" signal, delivered without depending on inbound application bytes.

Why the obvious signals don't work

  • Pekko's TLS stage emits no handshake-complete event. SslTlsInbound is only ever SessionBytes or SessionTruncated, and SessionBytes only arrives when there is application data.
  • SSLSession (carried by SessionBytes) does not expose the negotiated ALPN protocol. Only SSLEngine.getApplicationProtocol does.
  • SSLEngine.setHandshakeApplicationProtocolSelector is only meaningful for the peer that selects the protocol, i.e. the server.
  • HandshakeCompletedListener is SSLSocket-only; SSLEngine has no equivalent.

Proposed fix

Wrap the engine returned by HttpsConnectionContext.engineCreator in a delegating SSLEngine that, on the first wrap/unwrap after which getApplicationProtocol goes non-null, completes a Promise[String]. The client-side switch stage installs the chosen layer from an AsyncCallback hung off that promise.

Notes on shape:

  • Per-materialization state is required — PersistentConnection.managedConnection re-materializes the connection flow on every reconnect, so the httpsWithAlpn approach of a closed-over var plus an "is not reusable" guard is not viable here. Building the stack inside Flow.fromMaterializer gives a fresh promise and engine per materialization.
  • The switch stage must buffer inbound elements that arrive before the async callback is delivered (with h2, the server's SETTINGS frame can land first) and replay them on install.
  • Types line up favourably: Http.ClientLayer (Http.scala:794) is BidiFlow[HttpRequest, SslTlsOutbound, SslTlsInbound, HttpResponse, NotUsed], and Http2Blueprint.clientStack (Http2Blueprint.scala:146) .atop(unwrapTls) (:327) has that same shape. Both carry NotUsed, so the client switch needs none of the ServerTerminator plumbing ProtocolSwitch carries — but it is a 4-port BidiFlow stage rather than the server's 2-port Flow.

The delegation is ~30 mechanical methods, which is the main cost of this approach.

Alternative considered

A TimerGraphStageLogic polling engine.getApplicationProtocol is a few lines instead of a delegating engine, but puts a poll loop in the connection setup path. Recorded here as the fallback if the wrapper turns out to interact badly with the TLS stage.

Prerequisite bug

Http2JDKAlpnSupport.clientSetApplicationProtocols (Http2AlpnSupport.scala:74) ignores its protocols parameter and hardcodes Array("h2"):

def clientSetApplicationProtocols(engine: SSLEngine, protocols: Array[String]): Unit = {
  val params = engine.getSSLParameters
  params.setApplicationProtocols(Array("h2"))   // should be `protocols`
  engine.setSSLParameters(params)
}

Harmless today because the only caller (Http2.scala:275) passes Array("h2"), but this is exactly the seam negotiation needs — offering ["h2", "http/1.1"] is step one.

Ngôn ngữ chính
Scala
Star
196
Fork
56
Merge trung bình
1 ngày 18 giờ
Pull request đã merge (30 ngày)
43

Chuẩn bị môi trường

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Issue khác của apache/pekko-http

Tất cả issue của apache/pekko-http

Issue tương tự

Thêm issue về Scala

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.