HTTP/2 client ALPN fallback needs a handshake-complete signal that the TLS stage does not emit
メンテナーはふだん 1 日以内に返信
まだ誰も着手していません。
評価
- 難易度
- 5/5
- 見積もり時間
- 1週間以上
- 初心者へのやさしさ
- 35/100
- issue の種類
- 機能追加
- 明瞭さ
- 明確に書かれている
- 活発さ
- 活発
調査の方向性
Http2AlpnSupport.scala:74 と Http2.scala:241,275 から始め、次に ProtocolSwitch.scala と Http2Blueprint.scala:146,327 を追跡します。issue に記載されている materialization ごとの engine シグナルとクライアント側の 4 ポート switch を、初期の inbound 要素のバッファリングも含めて実装します。提供されたプロトコルを提示し、ネゴシエートされた HTTP/1.1 または HTTP/2 のスタックを deadlock なしで選択できれば完了です。
索引モデルが issue の本文から書いたものです。
説明
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
TLSstage emits no handshake-complete event.SslTlsInboundis only everSessionBytesorSessionTruncated, andSessionBytesonly arrives when there is application data. SSLSession(carried bySessionBytes) does not expose the negotiated ALPN protocol. OnlySSLEngine.getApplicationProtocoldoes.SSLEngine.setHandshakeApplicationProtocolSelectoris only meaningful for the peer that selects the protocol, i.e. the server.HandshakeCompletedListenerisSSLSocket-only;SSLEnginehas 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.managedConnectionre-materializes the connection flow on every reconnect, so thehttpsWithAlpnapproach of a closed-overvarplus an "is not reusable" guard is not viable here. Building the stack insideFlow.fromMaterializergives 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) isBidiFlow[HttpRequest, SslTlsOutbound, SslTlsInbound, HttpResponse, NotUsed], andHttp2Blueprint.clientStack(Http2Blueprint.scala:146).atop(unwrapTls)(:327) has that same shape. Both carryNotUsed, so the client switch needs none of theServerTerminatorplumbingProtocolSwitchcarries — but it is a 4-portBidiFlowstage rather than the server's 2-portFlow.
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.
- 主要言語
- Scala
- スター
- 196
- フォーク
- 56
- 平均マージ
- 1日 18時間
- マージ済み PR(30日)
- 43
環境構築
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートなし
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
apache/pekko-http のほかの issue
-
dependencies
難易度 1/5 1時間未満 初心者へのやさしさ 78/100
apache/pekko-http#1354 ·
メンテナーはふだん 1 日以内に返信
-
難易度 5/5 1週間以上 初心者へのやさしさ 45/100
apache/pekko-http#1273 ·
メンテナーはふだん 1 日以内に返信
-
enhancement
難易度 5/5 1週間以上 初心者へのやさしさ 35/100
apache/pekko-http#1248 ·
メンテナーはふだん 1 日以内に返信
-
難易度 4/5 3〜5日 初心者へのやさしさ 42/100
apache/pekko-http#1180 ·
メンテナーはふだん 1 日以内に返信
-
難易度 5/5 1週間以上 初心者へのやさしさ 30/100
apache/pekko-http#1112 · コメント 1 件 · リアクション 1 件 ·
メンテナーはふだん 1 日以内に返信
apache/pekko-http の issue をすべて見る
似ている issue
-
Bound wrong-magic P2P diagnostics to parsed header bytes対応中かも このイシューにリンクされたプルリクエストがオープン中、またはマージ済みです。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
ergoplatform/ergo#2736 ·
メンテナーはふだん 1 日以内に返信
-
area:shuffle enhancement good first issue performance requires-triage
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
apache/datafusion-comet#7072 ·
メンテナーはふだん 1 日以内に返信
-
bug 🐞 CI/CD ⚒️
難易度 2/5 1〜3時間 初心者へのやさしさ 78/100
valkey-io/valkey-glide#7370 ·
メンテナーはふだん 1 日以内に返信
-
Improve UI text on confirmation page (at registration)対応中かも @Arghyann が今日担当しました。 オープンgood first issue improvement
難易度 1/5 1時間未満 初心者へのやさしさ 72/100
lichess-org/lila#22126 · コメント 2 件 ·
メンテナーはふだん 1 日以内に返信
-
[VL] Remove the unused ShuffleSchema.h対応中かも このイシューにリンクされたプルリクエストがオープン中、またはマージ済みです。 オープン
難易度 1/5 1時間未満 初心者へのやさしさ 82/100
メンテナーはふだん 2 日以内に返信