Uploaded image for project: 'Undertow'
  1. Undertow
  2. UNDERTOW-850

HTTP/2, Proxying and chunked responses

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Done
    • Icon: Major Major
    • 2.0.0.Beta1, 1.4.4.Final
    • None
    • Proxy
    • None

      I think I have identified a bug in the HTTP/2 handling, or in the proxy code - not sure which.

      To reproduce it, I modified the io.undertow.examples.http2.HttpServer slightly to force chunked encoding - once I do this, every requests using chrome fails with ERR_SPDY_PROTOCOL_ERROR within chrome when I use SSL - https://localhost:8444

      /*
       * JBoss, Home of Professional Open Source.
       * Copyright 2014 Red Hat, Inc., and individual contributors
       * as indicated by the @author tags.
       *
       * Licensed under the Apache License, Version 2.0 (the "License");
       * you may not use this file except in compliance with the License.
       * You may obtain a copy of the License at
       *
       *     http://www.apache.org/licenses/LICENSE-2.0
       *
       *  Unless required by applicable law or agreed to in writing, software
       *  distributed under the License is distributed on an "AS IS" BASIS,
       *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
       *  See the License for the specific language governing permissions and
       *  limitations under the License.
       */
      
      package io.undertow.examples.http2;
      
      import static io.undertow.Handlers.predicate;
      import static io.undertow.Handlers.resource;
      import static io.undertow.predicate.Predicates.secure;
      
      import java.io.InputStream;
      import java.net.URI;
      import java.nio.file.Files;
      import java.nio.file.Paths;
      import java.security.KeyStore;
      
      import javax.net.ssl.KeyManager;
      import javax.net.ssl.KeyManagerFactory;
      import javax.net.ssl.SSLContext;
      import javax.net.ssl.TrustManager;
      import javax.net.ssl.TrustManagerFactory;
      
      import io.undertow.Handlers;
      import io.undertow.Undertow;
      import io.undertow.UndertowOptions;
      import io.undertow.attribute.ExchangeAttributes;
      import io.undertow.examples.UndertowExample;
      import io.undertow.protocols.ssl.UndertowXnioSsl;
      import io.undertow.server.HttpHandler;
      import io.undertow.server.HttpServerExchange;
      import io.undertow.server.handlers.LearningPushHandler;
      import io.undertow.server.handlers.ResponseCodeHandler;
      import io.undertow.server.handlers.proxy.LoadBalancingProxyClient;
      import io.undertow.server.handlers.proxy.ProxyHandler;
      import io.undertow.server.handlers.resource.PathResourceManager;
      import io.undertow.server.session.InMemorySessionManager;
      import io.undertow.server.session.SessionAttachmentHandler;
      import io.undertow.server.session.SessionCookieConfig;
      import io.undertow.util.Headers;
      import io.undertow.util.StatusCodes;
      import org.xnio.OptionMap;
      import org.xnio.Xnio;
      
      /**
       * @author Stuart Douglas
       */
      @UndertowExample(value = "HTTP2", location = "https://localhost:8443")
      public class Http2Server {
      
          private static final char[] STORE_PASSWORD = "password".toCharArray();
      
          public static class ForceChunkedHandler implements HttpHandler {
          	private HttpHandler next;
          	
      		public ForceChunkedHandler(HttpHandler next) {
      			this.next = next;
      			
      		}
      		@Override
      		public void handleRequest(HttpServerExchange exchange) throws Exception {
      			exchange.getResponseHeaders().put(Headers.TRANSFER_ENCODING, Headers.CHUNKED.toString());
      			next.handleRequest(exchange);
      		}    	
          }
        
          public static void main(final String[] args) throws Exception {
              String version = System.getProperty("java.version");
              System.out.println("Java version " + version);
              if(version.charAt(0) == '1' && Integer.parseInt(version.charAt(2) + "") < 8 ) {
                  System.out.println("This example requires Java 1.8 or later");
                  System.out.println("The HTTP2 spec requires certain cyphers that are not present in older JVM's");
                  System.out.println("See section 9.2.2 of the HTTP2 specification for details");
                  System.exit(1);
              }
              String bindAddress = System.getProperty("bind.address", "localhost");
              SSLContext sslContext = createSSLContext(loadKeyStore("server.keystore"), loadKeyStore("server.truststore"));
              
      
              Undertow server = Undertow.builder()
                      .setServerOption(UndertowOptions.ENABLE_HTTP2, false)
                      .addHttpListener(8080, bindAddress)
                      .setHandler(new ForceChunkedHandler(new SessionAttachmentHandler(new LearningPushHandler(100, -1, Handlers.header(resource(new PathResourceManager(Paths.get(System.getProperty("example.directory", System.getProperty("user.home"))), 100))
                              .setDirectoryListingEnabled(true), "x-undertow-transport", ExchangeAttributes.transportProtocol())), new InMemorySessionManager("test"), new SessionCookieConfig()))).build();
              server.start();
      
              SSLContext clientSslContext = createSSLContext(loadKeyStore("client.keystore"), loadKeyStore("client.truststore"));
              LoadBalancingProxyClient proxy = new LoadBalancingProxyClient()
                     .addHost(new URI("http://localhost:8080"))
                     .setConnectionsPerThread(20);
      
              Undertow reverseProxy = Undertow.builder()
                      .setServerOption(UndertowOptions.ENABLE_HTTP2, true)
                      .addHttpListener(8081, bindAddress)
                      .addHttpsListener(8444, bindAddress, sslContext)
                      .setHandler(new ProxyHandler(proxy, 30000, ResponseCodeHandler.HANDLE_404, true, false))
                      .build();
              reverseProxy.start();
      
          }
      
          private static KeyStore loadKeyStore(String name) throws Exception {
              String storeLoc = System.getProperty(name);
              final InputStream stream;
              if(storeLoc == null) {
                  stream = Http2Server.class.getResourceAsStream(name);
              } else {
                  stream = Files.newInputStream(Paths.get(storeLoc));
              }
      
              try(InputStream is = stream) {
                  KeyStore loadedKeystore = KeyStore.getInstance("JKS");
                  loadedKeystore.load(is, password(name));
                  return loadedKeystore;
              }
          }
      
          static char[] password(String name) {
              String pw = System.getProperty(name + ".password");
              return pw != null ? pw.toCharArray() : STORE_PASSWORD;
          }
      
      
          private static SSLContext createSSLContext(final KeyStore keyStore, final KeyStore trustStore) throws Exception {
              KeyManager[] keyManagers;
              KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
              keyManagerFactory.init(keyStore, password("key"));
              keyManagers = keyManagerFactory.getKeyManagers();
      
              TrustManager[] trustManagers;
              TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
              trustManagerFactory.init(trustStore);
              trustManagers = trustManagerFactory.getTrustManagers();
      
              SSLContext sslContext;
              sslContext = SSLContext.getInstance("TLS");
              sslContext.init(keyManagers, trustManagers, null);
      
              return sslContext;
          }
      
      }
      

      If I change this line: .setServerOption(UndertowOptions.ENABLE_HTTP2, true) from true to false, then it works - thats why I am not sure if the issue is in the proxy or in the HTTP2 handling.

      EDIT: Just managed to try it out with MSIE - and there it works, so it definately seems to be chrome related - might it be a bug in chrome ?

            sdouglas1@redhat.com Stuart Douglas
            kimras_jira Kim Rasmussen (Inactive)
            Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

              Created:
              Updated:
              Resolved: