Hacktoberfest 2026: los issues que los mantenedores marcaron para octubre, abiertos y aptos para principiantes. Explorar issues de Hacktoberfest

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

Abierto
#1,249 0 comentarios 0 reacciones 0 asignados Ver en GitHub

Los mantenedores suelen responder en 1 día

Nadie ha tomado este issue todavía.

Evaluación

Dificultad
5/5
Tiempo estimado
Más de una semana
Aptitud para principiantes
35/100
Tipo de issue
Nueva funcionalidad
Claridad
Bien especificado
Estado de actividad
Activo
Stack tecnológico
java, scala

Línea de trabajo

Empieza por Http2AlpnSupport.scala:74 y Http2.scala:241,275; después sigue el recorrido por ProtocolSwitch.scala y Http2Blueprint.scala:146,327. Implementa la señal del engine por materialización y el switch de 4 puertos del lado del cliente descritos en el issue, incluido el almacenamiento en búfer de los elementos entrantes tempranos. Se considera terminado cuando se ofrecen los protocolos proporcionados y se selecciona el stack HTTP/1.1 o HTTP/2 negociado sin deadlock.

Escrito por el modelo de indexación a partir del texto del issue.

Descripción

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.

Lenguaje dominante
Scala
Estrellas
196
Forks
57
Merge medio
1 d 20 h
PR fusionados (30 d)
40

Preparar el entorno

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Más de apache/pekko-http

Todos los issues de apache/pekko-http

Issues similares

Más issues de Scala

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.