Jetty
  1. Jetty
  2. JETTY-1087

Request chunking not working with HTTPS

    Details

    • Type: Bug Bug
    • Status: Resolved Resolved
    • Priority: Major Major
    • Resolution: Fixed
    • Affects Version/s: None
    • Fix Version/s: 6.1.20
    • Component/s: Security and SSL
    • Labels:
      None
    • Environment:
      Jetty 6.1.18 and jetty-7.
      jdk1.5 and jdk1.6.
    • Number of attachments :
      0

      Description

      Pasted from the Jetty lists:

      I've been looking a problem in our app associated with chunking requests
      from an HTTPS client. When client uses HTTPS and chunking, Jetty gets
      into a state where it just sits waiting for more data from input stream
      on server side after reading the first chunk. It eventually causes the thread to timeout (30s?). However,
      the same thing works when the client is dong HTTP.

      I've recreated the problem with the test program below which exhibits the same behavior.
      When the following program runs with variable https = true (meaning use
      HTTPS from client), Test.doPost() will do one read of 122 bytes from the input stream,
      but on its second read Jetty goes into a wait state seamingly waiting
      for more data.

      Stack:
      Thread [1658468894@qtp0-4] (Suspended)
      Object.wait(long) line: not available [native method] [local variables
      unavailable]
      SslHttpChannelEndPoint(SelectChannelEndPoint).blockReadable(long) line:
      243
      HttpParser$Input.blockForContent() line: 1147
      HttpParser$Input.read(byte[], int, int) line: 1103
      HttpParser$Input(InputStream).read(byte[]) line: 85
      ChunkingTest$Test.doPost(HttpServletRequest, HttpServletResponse) line:
      70
      ChunkingTest$Test(HttpServlet).service(HttpServletRequest,
      HttpServletResponse) line: 760
      ChunkingTest$Test(HttpServlet).service(ServletRequest, ServletResponse)
      line: 853
      ServletHolder.handle(ServletRequest, ServletResponse) line: 502
      ServletHandler.handle(String, HttpServletRequest, HttpServletResponse,
      int) line: 363
      Server(HandlerWrapper).handle(String, HttpServletRequest,
      HttpServletResponse, int) line: 152
      Server.handle(HttpConnection) line: 324
      HttpConnection.handleRequest() line: 534
      HttpConnection$RequestHandler.content(Buffer) line: 879
      HttpParser.parseNext() line: 828
      HttpParser.parseAvailable() line: 207
      HttpConnection.handle() line: 403
      SslHttpChannelEndPoint(SelectChannelEndPoint).run() line: 409
      QueuedThreadPool$PoolThread.run() line: 522

      If the program is run with https = false (meaning use HTTP from client),
      the Test servlet doPost() successfully reads all chunked data and
      then returns.

      I'm at a loss as to why the use of chunking works with a client that is HTTP,
      but doesn't when the client is doing HTTPS. Wireshark shows that
      everything looks ok with the request under HTTP.

      I've tried this program with Jetty 6.1.14 and 6.1.18, with the latest
      JDK 1.5 and 1.6 versions and it behaves the same under all versions.
      I've also tried changing the use of HttpsURLConnection with Apache
      HttpClient, but it has the same problem too.

      Perhaps I'm just missing something unobvious? Anybody have any ideas?

      // Test program
      import java.io.IOException;
      import java.io.InputStream;
      import java.io.OutputStream;
      import java.net.HttpURLConnection;
      import java.net.URL;
      import java.security.KeyManagementException;
      import java.security.NoSuchAlgorithmException;
      import java.security.cert.CertificateException;
      import java.security.cert.X509Certificate;

      import javax.net.ssl.HostnameVerifier;
      import javax.net.ssl.HttpsURLConnection;
      import javax.net.ssl.SSLContext;
      import javax.net.ssl.SSLSession;
      import javax.net.ssl.TrustManager;
      import javax.net.ssl.X509TrustManager;
      import javax.servlet.ServletException;
      import javax.servlet.http.HttpServlet;
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletResponse;

      import org.mortbay.jetty.Server;
      import org.mortbay.jetty.nio.SelectChannelConnector;
      import org.mortbay.jetty.security.SslSelectChannelConnector;
      import org.mortbay.jetty.servlet.ServletHandler;
      import org.mortbay.thread.QueuedThreadPool;

      public class ChunkingTest {
      // With true, Test.doPost() blocks waiting for
      // With false, all chunks are read by Test.doPost().
      static boolean https = false;

      private static final int BODY_SIZE = 300;

      public static final void main(String[] args) throws Exception {

      startServer();

      URL url = new URL((https ? "https" : "http") + "://localhost:8080/test");

      HttpURLConnection conn = (HttpURLConnection) url.openConnection();
      if (conn instanceof HttpsURLConnection) {
      ((HttpsURLConnection) conn)
      .setHostnameVerifier(new HostnameVerifier() {
      public boolean verify(String urlHostName,
      SSLSession session) {
      return true;
      }
      });
      }

      conn.setConnectTimeout(10000);
      conn.setReadTimeout(100000);
      conn.setDoInput(true);
      conn.setDoOutput(true);
      conn.setRequestMethod("POST");
      conn.setRequestProperty("Content-Type", "text/plain"); //$NON-NLS-1$
      conn.setChunkedStreamingMode(128);
      conn.connect();
      byte[] b = new byte[BODY_SIZE];
      for (int i = 0; i < BODY_SIZE; i++) {
      b[i] = 'x';
      }
      OutputStream os = conn.getOutputStream();
      os.write(b);
      os.flush();
      int rc = conn.getResponseCode();

      int len = 0;
      InputStream is = conn.getInputStream();
      while ((len = is.read(b)) > -1)

      { System.out.println("client read: " + len); } is.close(); }

      public static class Test extends HttpServlet {

      @Override
      protected void doPost(HttpServletRequest req, HttpServletResponse resp)
      throws ServletException, IOException {
      resp.setContentType("text/plain");
      resp.setBufferSize(128);
      byte[] b = new byte[BODY_SIZE];
      int len = 0;
      InputStream is = req.getInputStream();

      // !!!! UNDER HTTPS, FIRST CHUNK IS READ HERE BUT THEN SERVER IS WAITING FOR
      // !!!! MORE DATA
      while ((len = is.read(b)) > -1) {
      System.out.println("servlet read chunk: " + len);
      }

      OutputStream os = resp.getOutputStream();
      for (int i = 0; i < BODY_SIZE; i++)

      { b[i] = 'x'; } os.write(b); resp.flushBuffer(); }

      }

      private static void startServer() throws NoSuchAlgorithmException,
      KeyManagementException, Exception {
      Server server = new Server();
      QueuedThreadPool btp = new QueuedThreadPool();
      server.setThreadPool(btp);

      if (https) {
      TrustManager[] tm =

      { new CredulousTM() }

      ;
      SSLContext context = SSLContext.getInstance("SSL"); //$NON-NLS-1$
      context.init(null, tm, null);
      HttpsURLConnection.setDefaultSSLSocketFactory(context
      .getSocketFactory());

      SslSelectChannelConnector httpsConnector = new SslSelectChannelConnector();
      httpsConnector.setPort(8080);
      httpsConnector.setName("JettySSL");
      httpsConnector.setKeystore("keystore");
      httpsConnector.setPassword("password");
      httpsConnector.setKeyPassword("password");
      httpsConnector.setAcceptors(25);
      httpsConnector.setAcceptQueueSize(2000);
      httpsConnector.setAcceptorPriorityOffset(0);
      httpsConnector.setMaxIdleTime(300000);
      server.addConnector(httpsConnector);
      } else {
      SelectChannelConnector connector = new SelectChannelConnector();
      connector.setPort(8080);
      connector.setName("Jetty");
      connector.setAcceptors(25);
      connector.setAcceptQueueSize(2000);
      connector.setMaxIdleTime(300000);
      connector.setLowResourcesConnections(100);
      server.addConnector(connector);
      }
      ServletHandler handler = new ServletHandler();
      handler.addServletWithMapping(Test.class.getName(), "/test");
      server.addHandler(handler);
      server.start();
      }

      private static class CredulousTM implements TrustManager, X509TrustManager {

      public X509Certificate[] getAcceptedIssuers() {
      return null;
      }

      public void checkClientTrusted(X509Certificate[] arg0, String arg1)
      throws CertificateException {
      return;
      }

      public void checkServerTrusted(X509Certificate[] arg0, String arg1)
      throws CertificateException

      { return; } } }

        Issue Links

          Activity

          Hide
          Greg Wilkins added a comment -

          The problem was that some bytes had been read but not processed by the SSL engine.
          Adding the lines to blockforcontent

          if (_endp.isBufferingInput() && _parser.parseNext()>0)
          continue;

          fixes the issue.

          committed and will be in the next release.

          cheers

          Show
          Greg Wilkins added a comment - The problem was that some bytes had been read but not processed by the SSL engine. Adding the lines to blockforcontent if (_endp.isBufferingInput() && _parser.parseNext()>0) continue; fixes the issue. committed and will be in the next release. cheers

            People

            • Assignee:
              Greg Wilkins
              Reporter:
              Jan Bartel
            • Votes:
              0 Vote for this issue
              Watchers:
              0 Start watching this issue

              Dates

              • Created:
                Updated:
                Resolved: