jetty.project icon indicating copy to clipboard operation
jetty.project copied to clipboard

Firefox WebSocket Client does not seem to work using HTTP/2 or HTTP/3

Open minduch opened this issue 2 years ago • 26 comments

Jetty version(s) 11.0.8

Java version/vendor (use: java -version) Temurin 11.0.14.1, but also in Java 17.x

OS type/version Windows 11, but also Linux in various versions

Description Embedded Jetty WebSocket connections fails when HTTP/2 or HTTP/3 is enabled, regardless of Conscrypt usage.

How to reproduce? Embedded Jetty WebSockets always fails in browsers (latest Chrome, Chrome Canary and Firefox) with the code to create the HTTPS connector:

  /**
   * Creates a new connector for HTTPS.
   *
   * @param server      Server instance.
   * @param port        The secure port.
   * @param keystore    The KeyStore.
   * @param pw          The password.
   * 
   * @return The connector.
   */
  private static ServerConnector createHttpsConnector(Server server,int port,KeyStore keyStore,String pw)
    {
    // SSL Context Factory for HTTPS
    SslContextFactory.Server sslContextFactory=new SslContextFactory.Server();
    sslContextFactory.setTrustAll(true);
    sslContextFactory.setKeyStore(keyStore);
    sslContextFactory.setKeyStorePassword(pw);
    
    // Use Conscrypt?
    if ( useConscrypt ) // Global flag.
      sslContextFactory.setProvider("Conscrypt");
    
    // Secure SSL Context: remove renegotiation
    sslContextFactory.setRenegotiationAllowed(false);
    
    // Configure comparator for HTTP/2 ciphers.
    sslContextFactory.setCipherComparator(HTTP2Cipher.COMPARATOR);
    sslContextFactory.setUseCipherSuitesOrder(true);
    
    // HTTP Configuration
    HttpConfiguration https_config=new HttpConfiguration();
    https_config.setOutputBufferSize(32768);
    https_config.setSendServerVersion(false);
    https_config.setSendXPoweredBy(false);
    https_config.setSecureScheme("https");
    https_config.setSecurePort(port);
    
    // HTTPS Configuration
    SecureRequestCustomizer src=new SecureRequestCustomizer(); // Include SNI = multiple domains.
    https_config.addCustomizer(src);
    HttpConnectionFactory http=new HttpConnectionFactory(https_config);
    HTTP2ServerConnectionFactory h2=(useHTTP2)? // useHTTP2 is a global flag.
        new HTTP2ServerConnectionFactory(https_config):
        null;
    HTTP3ServerConnectionFactory h3=(useHTTP3)? // useHTTP3 is a global flag.
        new HTTP3ServerConnectionFactory(https_config):
        null;

    // HTTP(s) connectors use ALPN
    ServerConnector httpsConnector;
    String httpProtocol=http.getProtocol();
    if ( h2!=null && h3!=null )
      {
      // HTTP/3 and HTTP/2.
      String protocol=h3.getProtocol();
      ALPNServerConnectionFactory alpn=new ALPNServerConnectionFactory(protocol,h2.getProtocol(),httpProtocol);
      alpn.setDefaultProtocol(protocol);
      SslConnectionFactory ssl=new SslConnectionFactory(sslContextFactory,alpn.getProtocol());
      httpsConnector=new ServerConnector(server,ssl,alpn,h3,h2,http);
      }
    else
    if ( h3!=null && h2==null )
      {
      // Only HTTP/3 and *NO* HTTP/2. 
      String protocol=h3.getProtocol();
      ALPNServerConnectionFactory alpn=new ALPNServerConnectionFactory(protocol,httpProtocol);
      alpn.setDefaultProtocol(protocol);
      SslConnectionFactory ssl=new SslConnectionFactory(sslContextFactory,alpn.getProtocol());
      httpsConnector=new ServerConnector(server,ssl,alpn,h3,http);
      }
    else
    if ( h2!=null )
      {
      // Only HTTP/2.
      String protocol=h2.getProtocol();
      ALPNServerConnectionFactory alpn=new ALPNServerConnectionFactory(protocol,httpProtocol);
      alpn.setDefaultProtocol(protocol);
      SslConnectionFactory ssl=new SslConnectionFactory(sslContextFactory,alpn.getProtocol());
      httpsConnector=new ServerConnector(server,ssl,alpn,h2,http);
      }
    else
      {
      // *NO* HTTP/3 nor HTTP/2, just plain HTTP/1.x using SSL (TLS).
      SslConnectionFactory ssl=new SslConnectionFactory(sslContextFactory,httpProtocol);
      httpsConnector=new ServerConnector(server,ssl,http);
      }
 
    httpsConnector.setPort(port);
    ..........

The websockets are implemented in our own code (we would be happy to share as required), but it doesn't seem to reach our code -- all this is done in Jetty.

To be clear: this happens when the global flag useHTTP2 or useHTTP3 are true, no matter which one. When they are both false it works just fine.

Please find the attached log file produced from our server where "ocj." stands for "org.eclipse.jetty." when logging is output.

Let me know if there is anything else you need from us.

server.log

minduch avatar Mar 14 '22 23:03 minduch

To see our server in action using Jetty 11.0.8 WebSockets with Conscrypt and without HTTP/2 and HTTP/3, open a browser (e.g. Chrome) at iizi Demo Server at mt.iizi.co, then click on "...everything starts here...". You can e.g. open the app "CarRental" -> "start", it will be running using Jetty 11.0.8 embedded version with the code above, but then Conscrypt is used and HTTP/2 and HTTP/3 are disabled.

When we turn on the flags useHTTP2 or useHTTP3 for our code above, the connection fails for all browsers.

Actually, this is not a new problem to Jetty 11. We were using Jetty 9.x (late versions) with HTTP/2 support, and it failed there too.

minduch avatar Mar 14 '22 23:03 minduch

Perhaps I need to be a little more specific: the WebSocket is configured with the code above, then referencing the Java class attached. The websocket is configured with the code below:

  /**
   * Configure the JettyWebSocketServletFactory for this servlet instance by setting default
   * configuration (which may be overriden by annotations) and mapping {@link JettyWebSocketCreator}s.
   * This method assumes a single {@link FrameHandlerFactory} will be available as a bean on the
   * {@link ContextHandler}, which in practice will mostly the the Jetty WebSocket API factory.
   *
   * @param factory the JettyWebSocketServletFactory
   */
  @Override
  protected void configure(JettyWebSocketServletFactory factory)
    {
    factory.setCreator(new WSCreator(acceptor));
 
    factory.setIdleTimeout(TIMEOUT); // Set 10 seconds upgrade to websocket timeout.
    
    // Set maximum message sizes: 10 MB for binary, 15 MB for text (not actually used).
    factory.setMaxBinaryMessageSize(MAX_BIN_MSG);

    factory.setMaxTextMessageSize  (MAX_TXT_MSG);
    factory.setAutoFragment(true); // Enable auto-fragmentation to respect frame size.
    }

The WSCreator class is attached: WSCreator.java.txt

minduch avatar Mar 15 '22 00:03 minduch

@minduch can you try setting your alpn default protocol to HTTP/1.1.

WebSocket upgrade comes as an HTTP/1.1 request, with the exception of RFC8441 where a single HTTP/2 stream is upgraded to websocket and this is not widely supported yet.

I was able to test your example code and changing alpn.setDefaultProtocol(protocol) to alpn.setDefaultProtocol(httpProtocol) worked for me.

lachlan-roberts avatar Mar 22 '22 06:03 lachlan-roberts

Yes we know this works - i.e. that using only HTTP/1 and disabling HTTP/2 and HTTP/3 works with WebSockets. This, however, turns our Jetty-based server into an "OLD" HTTP/1.1 server that cannot serve the new protocols HTTP/2 and HTTP/3. About 99% of the requests are the new protocols, and 1% at maxiumum for WebSockets. So the "bug" according to us, is the WebSocket upgrade process that should - for now - take care of upgrading to use HTTP/1.1 for itself, and not the other 99% of the requests, until RFC8441 is widely supported.

Or is there a mechanism by means of API to let the WebSocket upgrade process use HTTP/1.1 instead of newer protocols?

What does @joakime say about this?

minduch avatar Mar 22 '22 13:03 minduch

I am reading about Chrome's support for WebSockets over HTTP/2. Then there are stuff written for Bootstrapping WebSockets with HTTP/2 and for Bootstrapping WebSockets with HTTP/3. Is there no way that Jetty performs Bootstrapping WebSockets to "upgrade" the connection to "HTTP/1.1"?

minduch avatar Mar 22 '22 13:03 minduch

Couldn't the upgrade process for WebSocket look like somthing described below in order for WebSockets to be able to function in a HTTP/3, HTTP/2 and HTTP/1 environment with security enabled (HTTPS or TLS).

The handshake from the client looks as follows:

 GET /chat HTTP/2    _or HTTP/3 (but I don't know how it looks like_
 Host: server.example.com
 Upgrade: websocket
 Connection: Upgrade
 Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
 Origin: http://example.com
 Sec-WebSocket-Protocol: chat, superchat
 Sec-WebSocket-Version: 13

The handshake from the Jetty would look like:

 HTTP/1.1 101 Switching Protocols
 Upgrade: websocket
 Connection: Upgrade
 Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
 Sec-WebSocket-Protocol: chat

minduch avatar Mar 22 '22 13:03 minduch

HTTP/3 support in Jetty is currently experimental and is not ready for production.

Now, with HTTP/2, we support RFC8441: Bootstrapping WebSockets with HTTP/2, so using HTTP/2 for WebSocket should work on compatible browsers.

  • Chrome 91+ supports it - https://chromestatus.com/feature/6251293127475200
  • Firefox 65 was the first one with support, but it currently has problems with proxies - https://bugzilla.mozilla.org/show_bug.cgi?id=1434137

I'm not aware of an equivalent spec for Websocket over HTTP/3.

joakime avatar Mar 22 '22 13:03 joakime

 GET /chat HTTP/2    _or HTTP/3 (but I don't know how it looks like_
 Host: server.example.com
 Upgrade: websocket
 Connection: Upgrade
 Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
 Origin: http://example.com
 Sec-WebSocket-Protocol: chat, superchat
 Sec-WebSocket-Version: 13

That is not an HTTP/2 request. That's an HTTP/1.1 request attempting to use HTTP/2 or HTTP/3. The existence of the Host header is the clue (that only exists on HTTP/1.1).

The HTTP/2 support for WebSocket uses the spec defined CONNECT + "websocket" protocol technique.

Also, the Upgrade, Connection headers are not permitted on HTTP/2, and the Sec-WebSocket-Key is not used when upgrading to websocket on HTTP/2.

joakime avatar Mar 22 '22 13:03 joakime

Jetty 10+ does support the HTTP/2 WebSocket upgrade, but it is a CONNECT request and only upgrades a single HTTP/2 stream not the entire connection. Jetty does not support WebSocket over HTTP/3 and I have not seen any spec for this either.

@minduch I wasn't suggesting you disable HTTP/2 and HTTP/3, if you set the alpn default protocol to HTTP/1.1 doesn't turn Jetty into a OLD HTTP/1.1 server, it just sets the default protocol to use in case there is no negotiation. If you load up a page in the browser it will still upgrade to use HTTP/2.

lachlan-roberts avatar Mar 22 '22 22:03 lachlan-roberts

@lachlan-roberts @joakime

Just to be sure: calling alpn.setDefaultProtocol(httpProtocol), where httpProtocol is the String returned by the HttpConnectionFactory will allow HTTP/3, HTTP/2 and HTTP/1.1 (perhaps also HTTP/1.0)?

To be really clear, if the client (browser/WebView) connects using HTTP/3 or 2, then that protocol will be used, regardless of the alpn.setDefaultProtocol(...) setting (turning Jetty into a OLD HTTP/1.1 server)?

A follow-up question would be: what is then the purpose of the method alpn.setDefaultProtocol(...)?

minduch avatar Mar 24 '22 23:03 minduch

@lachlan-roberts @joakime When would Jetty support HTTP/2 (and/or 3 [I know 3 is "hardly RFC" yet]) WebSocket Bootstrapping? Will it be announced somewhere so I can keep track of it?

minduch avatar Mar 24 '22 23:03 minduch

@lachlan-roberts @joakime Sorry: forgot some questions, what does the browsers or WebViews request as default protocol below:

  1. Chrome?
  2. MS IE 11?
  3. MS Edge?
  4. macOS/iOS Safari?

Please specify the (browser/WebView major) version numbers where changes may occur, if any (or the browser/WebView userAgent string).

minduch avatar Mar 24 '22 23:03 minduch

@lachlan-roberts @joakime When would Jetty support HTTP/2 (and/or 3 [I know 3 is "hardly RFC" yet]) WebSocket Bootstrapping? Will it be announced somewhere so I can keep track of it?

WebSocket over HTTP/2 is currently supported in Jetty 10.0.8 and 11.0.8

WebSocket over HTTP/3 is not (yet).

joakime avatar Mar 25 '22 02:03 joakime

@lachlan-roberts @joakime Our product IIZI (accessible using https://mt.iizi.co), is currently using Jetty 11.0.8 with HTTP/1.1 (with the code attached in this case, plus much more).

However, turning on HTTP/2 like said in the discussion above to set ALNP default protocol to HTTP/1.1, our server causes Chrome Version 99.0.4844.84 (Official Build) (64-bit) to not display the "home page", but rather just produce and download a download file that is attached here in the zip file.

Our server has been configured to omit Conscrypt, and:

  1. WORKING: use only HTTP/1.1 attached as log file http1.log, or
  2. FAILING: both HTTP/1.1 and and HTTP/2 (but HTTP/1.1 as default protocol for ALPN) , attached as hptt2.log.

Both these log files are in the attached zip file. Please let me know what I should do to assist you further.

jetty-http-1-2.zip

minduch avatar Mar 25 '22 21:03 minduch

@minduch now I don't think the ALPN default protocol is actually related to your issue.

I can actually run your code without modifications and connect to WebSocket over HTTP/2. Here is a simplified version of your sample code which does work with WebSocket.

Try running this and connecting with https://lachlan-roberts.github.io/util/websocket.html. (I needed to visit https://localhost:8443/ in browser first to avoid issue with self signed cert)

@Test
public void test() throws Exception
{
    Server server = new Server();

    // SSL Context Factory for HTTPS
    SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
    sslContextFactory.setKeyStorePath("src/test/resources/keystore.p12");
    sslContextFactory.setKeyStorePassword("storepwd");
    sslContextFactory.setTrustAll(true);
    sslContextFactory.setRenegotiationAllowed(false);
    sslContextFactory.setCipherComparator(HTTP2Cipher.COMPARATOR);
    sslContextFactory.setUseCipherSuitesOrder(true);

    // HTTP Configuration
    HttpConfiguration https_config = new HttpConfiguration();

    // HTTPS Configuration
    SecureRequestCustomizer src = new SecureRequestCustomizer(); // Include SNI = multiple domains.
    https_config.addCustomizer(src);
    HttpConnectionFactory http = new HttpConnectionFactory(https_config);
    HTTP2ServerConnectionFactory h2 = new HTTP2ServerConnectionFactory(https_config);

    // Only HTTP/2.
    String protocol = h2.getProtocol();
    ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory(protocol, http.getProtocol());
    alpn.setDefaultProtocol(protocol);
    SslConnectionFactory ssl = new SslConnectionFactory(sslContextFactory, alpn.getProtocol());

    // HTTP(s) connectors use ALPN.
    ServerConnector httpsConnector = new ServerConnector(server, ssl, alpn, h2, http);
    httpsConnector.setPort(8443);
    server.addConnector(httpsConnector);

    // Configure WebSocket.
    ServletContextHandler contextHandler = new ServletContextHandler();
    server.setHandler(contextHandler);
    JettyWebSocketServletContainerInitializer.configure(contextHandler, null);
    contextHandler.addServlet(new ServletHolder(new JettyWebSocketServlet() {
        @Override
        protected void configure(JettyWebSocketServletFactory factory)
        {
            factory.addMapping("/", (req, resp) -> new ProtocolEchoSocket());
        }
    }), "/");

    server.start();
    server.join();
}

@WebSocket
public static class ProtocolEchoSocket
{
    @OnWebSocketConnect
    public void onOpen(Session session) throws IOException
    {
        String protocol = session.getUpgradeRequest().getHttpVersion();
        session.getRemote().sendString("Upgraded over " + protocol);
    }

    @OnWebSocketMessage
    public void onMessage(Session session, String message) throws IOException
    {
        session.getRemote().sendString(message);
    }
}

I get Received: "Upgraded over HTTP/2.0" message when connecting, which indicates that the HTTP/2 websocket upgrade is working as expected.

lachlan-roberts avatar Mar 28 '22 13:03 lachlan-roberts

Hi,

We are experiences the same issue. The code @lachlan-roberts offered above does not work in our setup. If run as it is with JDK16.0.2 (azul build) and Jetty 10.0.9 (jvm launched with -Dorg.eclipse.jetty.LEVEL=DEBUG) we receive the following exception:

java.lang.IllegalStateException: Connection rejected: No ALPN Processor for sun.security.ssl.SSLEngineImpl from [org.eclipse.jetty.alpn.conscrypt.server.ConscryptServerALPNProcessor@5fd4f8f5]
	at org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory.newServerConnection(ALPNServerConnectionFactory.java:106)
	at org.eclipse.jetty.server.NegotiatingServerConnectionFactory.newConnection(NegotiatingServerConnectionFactory.java:103)
	at org.eclipse.jetty.server.SslConnectionFactory.newConnection(SslConnectionFactory.java:162)
	at org.eclipse.jetty.server.ServerConnector$ServerConnectorManager.newConnection(ServerConnector.java:618)
	at org.eclipse.jetty.io.ManagedSelector.createEndPoint(ManagedSelector.java:384)
	at org.eclipse.jetty.io.ManagedSelector$Accept.run(ManagedSelector.java:889)
	at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:894)
	at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1038)
	at java.base/java.lang.Thread.run(Thread.java:831)

image

In order to bypass this we had to add 'sslContextFactory.setProvider("Conscrypt");' to the code but now there is another exception in the logs:

2022-06-14 12:08:43.181:DEBUG:oejas.ALPNServerConnection:qtp306980751-18: Could not negotiate protocol from client[http/1.1] and server[h2, HTTP/1.1] on DecryptedEndPoint@37059dc1[{l=/192.168.250.103:8443,r=/192.168.250.103:59020,OPEN,fill=-,flush=-,to=43/30000}]
2022-06-14 12:08:43.186:DEBUG:oejis.SslConnection:qtp306980751-18: DecryptedEndPoint@37059dc1[{l=/192.168.250.103:8443,r=/192.168.250.103:59020,OPEN,fill=-,flush=-,to=49/30000}] stored fill exception
java.lang.IllegalStateException
	at org.eclipse.jetty.alpn.server.ALPNServerConnection.select(ALPNServerConnection.java:81)
	at org.eclipse.jetty.alpn.conscrypt.server.ConscryptServerALPNProcessor$ALPNCallback.selectApplicationProtocol(ConscryptServerALPNProcessor.java:83)
	at org.conscrypt.ApplicationProtocolSelectorAdapter.selectApplicationProtocol(ApplicationProtocolSelectorAdapter.java:66)
	at org.conscrypt.ConscryptEngine.selectApplicationProtocol(ConscryptEngine.java:1780)
	at org.conscrypt.NativeCrypto.ENGINE_SSL_read_direct(Native Method)
	at org.conscrypt.NativeSsl.readDirectByteBuffer(NativeSsl.java:567)
	at org.conscrypt.ConscryptEngine.readPlaintextDataDirect(ConscryptEngine.java:1099)
	at org.conscrypt.ConscryptEngine.readPlaintextDataHeap(ConscryptEngine.java:1119)
	at org.conscrypt.ConscryptEngine.readPlaintextData(ConscryptEngine.java:1091)
	at org.conscrypt.ConscryptEngine.unwrap(ConscryptEngine.java:880)
	at org.conscrypt.ConscryptEngine.unwrap(ConscryptEngine.java:751)
	at org.conscrypt.ConscryptEngine.unwrap(ConscryptEngine.java:716)
	at org.conscrypt.Java8EngineWrapper.unwrap(Java8EngineWrapper.java:236)
	at org.eclipse.jetty.io.ssl.SslConnection.unwrap(SslConnection.java:398)
	at org.eclipse.jetty.io.ssl.SslConnection$DecryptedEndPoint.fill(SslConnection.java:693)
	at org.eclipse.jetty.server.NegotiatingServerConnection.fill(NegotiatingServerConnection.java:147)
	at org.eclipse.jetty.server.NegotiatingServerConnection.onFillable(NegotiatingServerConnection.java:92)
	at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:319)
	at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:100)
	at org.eclipse.jetty.io.ssl.SslConnection$DecryptedEndPoint.onFillable(SslConnection.java:530)
	at org.eclipse.jetty.io.ssl.SslConnection.onFillable(SslConnection.java:379)
	at org.eclipse.jetty.io.ssl.SslConnection$2.succeeded(SslConnection.java:146)
	at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:100)
	at org.eclipse.jetty.io.SelectableChannelEndPoint$1.run(SelectableChannelEndPoint.java:53)
	at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:894)
	at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1038)
	at java.base/java.lang.Thread.run(Thread.java:831)

The client displays the same messages as in the first run.

We were able to connect with a jetty websocket client but not using the native Websocket from the browser (tried with chrome and FF, the exceptions are the same - oejas.ALPNServerConnection:qtp306980751-18: Could not negotiate protocol from client[http/1.1] and server[h2, HTTP/1.1] on DecryptedEndPoint@37059dc1).

image

When we connected successfully from the jetty client the logs show: "oejas.ALPNServerConnection:qtp1361960727-27: Protocol selected h2 from client[h2] and server[h2, HTTP/1.1] on DecryptedEndPoint@585c4583"

What are we doing wrong ?

Thanks

horiavmuntean avatar Jun 14 '22 09:06 horiavmuntean

@horiavmuntean can you try running this project (websocket-http2-embedded-example) and tell me if that works for you.

I have not tried on your JDK but this does work fine for me on Temurin-17.0.3+7.

lachlan-roberts avatar Jun 14 '22 11:06 lachlan-roberts

Hi,

The https://github.com/lachlan-roberts/websocket-http2-embedded-example project works as expected but I want to make further comments about what happened in our environment because in certain conditions the RFC 8441 handshake can be broken and it does not work anymore.

At first 'websocket-http2-embedded-example' worked with no problem on my PC but it didn't work on other PCs and laptops showing the same exception 'Could not negotiate protocol from client[http/1.1] and server[h2, HTTP/1.1]' . Then I changed the port on my PC and it stopped working on my PC also, same exception.

The culprit seems to be the ESET antivirus and it's web protocol filtering function. It acts as a man-in-the-middle on every connection and it seems it is altering this wss over h2 handshake. (why on one PC it worked even with ESET on but only on port 8443 - I can't explain). Maybe ESET does not transmit SETTINGS_ENABLE_CONNECT_PROTOCOL and the browser tries to connect to websockets over http/1.1 and so the server rejects it.

Anyway after disabling the ESET web protocol filtering the project worked on every PC. Also our original code started to work.

Also the @Test code from https://github.com/eclipse/jetty.project/issues/7740#issuecomment-1080663449 I said does not work, works in fact. Here it was another problem - I changed the code to use a keystore with a valid certificate and I didn't load first in a browser tab the main URL as @lachlan-roberts did (I needed to visit https://localhost:8443/ in browser first to avoid issue with self signed cert); but this is needed not only to bypass self signed cert issues but to have an h2 connection to be used by websockets to piggy back on - without this h2 connection, the wss session will not work.

After I loaded the main URL first, the websockets session succeeded.

Thanks @lachlan-roberts for your support.

PS. I was looking for a tool that shows the SETTINGS of a http connection (to make sure the a server advertises RFC 8441 support) but I was not able to find one (I tried with https://github.com/fstab/h2c but it's rather old and seems it does not understand this new flag - crashes with 'Error while reading next frame: Unknown setting in SETTINGS frame' when run against our servers). What tools are you using for this (other than Wireshark) ?

horiavmuntean avatar Jun 15 '22 08:06 horiavmuntean

Hi,

I tested further https://github.com/lachlan-roberts/websocket-http2-embedded-example with FF 101.0.1 on Windows 10 64 bit and unfortunately does no work, same 'Could not negotiate protocol from client[http/1.1] and server[h2, HTTP/1.1] on DecryptedEndPoint@5fde3509' exception.

Tested browsers on a Windows 10 64 bit PC:

  • Chrome 102.0.5005.115 - OK
  • Opera 88.0.4412.40 - OK
  • Edge 102.0.1245.41 - OK
  • Firefox 101.0.1 - Error

Thanks

horiavmuntean avatar Jun 15 '22 09:06 horiavmuntean

Have you considered opening an issue to Firefox? Seems to me that on the Jetty side all is working properly, so if you're satisfied, feel free to close the issue.

sbordet avatar Jun 15 '22 14:06 sbordet

I am not the reporter of this issue, I can't close it. I will try to report this to FF and come back here with any results.

horiavmuntean avatar Jun 16 '22 08:06 horiavmuntean

However I found a situation where the websocket session does not work even with Chrome: load index.html in the browser, wait few minutes (until h2 connection closes) and now try to open the wss session (press 'Join') - it fails with the same exception on the server side "Could not negotiate protocol from client[http/1.1] and server[h2, HTTP/1.1] on DecryptedEndPoint".

Is this the expected behavior ?

horiavmuntean avatar Jun 16 '22 08:06 horiavmuntean

It is working as expected, in the sense that if the client only offers http/1.1, the server cannot decide to use h2.

I am guessing it is some logic inside the browsers that makes them think the site does not support anymore h2, even though they were able to connect with h2 just few minutes before. 🤷🏼‍♂️

sbordet avatar Jun 16 '22 09:06 sbordet

This seems to put constraints for web apps communicating with h2 only servers - in order to make sure they can open wss over h2 at any time they should request some h2 resource first to have the underlying connection available.

This is unexpected for me !

horiavmuntean avatar Jun 16 '22 09:06 horiavmuntean

Hopefully something good will come out of this: https://bugzilla.mozilla.org/show_bug.cgi?id=1774572

horiavmuntean avatar Jun 16 '22 10:06 horiavmuntean

In the meantime there is RFC9220 Bootstrapping WebSockets with HTTP/3.

Horcrux7 avatar Sep 14 '22 13:09 Horcrux7

This issue has been automatically marked as stale because it has been a full year without activity. It will be closed if no further activity occurs. Thank you for your contributions.

github-actions[bot] avatar Sep 15 '23 00:09 github-actions[bot]

Closing, as Jetty does support WebSocket over HTTP/2, but in Firefox this has been disabled as per the linked Firefox bug.

sbordet avatar Sep 29 '23 10:09 sbordet