Bug 62605

Summary: Async servlet over HTTP/2 setReadListener does not work if post request data arrives much later than headers
Product: Tomcat 9 Reporter: Dapeng Zhang <zdapeng>
Component: ServletAssignee: Tomcat Developers Mailing List <dev>
Status: RESOLVED FIXED    
Severity: normal    
Priority: P2    
Version: 9.0.10   
Target Milestone: -----   
Hardware: PC   
OS: Linux   

Description Dapeng Zhang 2018-08-07 21:05:10 UTC
[Previously I filed an invalid bug https://bz.apache.org/bugzilla/show_bug.cgi?id=62569 , which was not producible with telnet client over HTTP/1.1, sorry about that]

The issue is reproducible when using client over HTTP/2

Steps to reproduce:

1. Enable HTTP/2 for Tomcat
<Connector port="8080" protocol="HTTP/1.1">
    <UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol"/>
</Connector>

2. Deploy the following Servlet (basically the same as the tutorial example https://javaee.github.io/tutorial/servlets013.html with more debug info and fixing reading EOS problem)

@WebServlet(urlPatterns={"/asyncioservlet"}, asyncSupported=true)
public class AsyncIOServlet extends HttpServlet {
  @Override
  public void doPost(HttpServletRequest request,
      HttpServletResponse response)
      throws IOException {
    final AsyncContext acontext = request.startAsync();
    final ServletInputStream input = request.getInputStream();

    System.out.println("Set listener");
    input.setReadListener(
        new ReadListener() {
          byte buffer[] = new byte[4 * 1024];
          StringBuilder sbuilder = new StringBuilder();

          @Override
          public void onDataAvailable() {
            System.out.println("onDataAvailable");
            try {
              do {
                int length = input.read(buffer);
                System.out.println("length = " + length);
                if (length == -1) {
                  return;
                }
                sbuilder.append(new String(buffer, 0, length));
              } while (input.isReady());
            } catch (IOException ex) {
              ex.printStackTrace();
            } catch (RuntimeException e) {
              e.printStackTrace();
            }
          }

          @Override
          public void onAllDataRead() {
            System.out.println("onAllDataRead");
            try {
              acontext.getResponse().getWriter().write("...the response...");
            } catch (IOException ex) {
              ex.printStackTrace();
            }
            acontext.complete();
          }

          @Override
          public void onError(Throwable t) {
            t.printStackTrace();
          }
        });
    System.out.println("Set listener - done");
  }

3. Make sure the curl command supports HTTP/2
$ curl --version
curl 7.60.0
Features: AsynchDNS IDN IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz TLS-SRP HTTP2 UnixSockets HTTPS-proxy PSL

4. Run the following curl command 
$ (sleep 2s; echo "hello world") | curl --http2 --http2-prior-knowledge http://localhost:8080/asyncioservlet -H "content-type: text/plain" -T - -X POST

(for TLS use the following command, I didn't check the TLS case though)
$ (sleep 2s; echo "hello world") | curl --http2 --http2-prior-knowledge https://localhost:8443/asyncioservlet -H "content-type: text/plain" -T - -X POST -k

5. The client will hang, and check the server log "logs/catalina.out"
Set listener
Set listener - done

no sign of ReadListener callbacks invoked.

6. Without the sleep, the client does not hang, and ReadListener callbacks get invoked.

Deploying the same servlet to Glassfish/Undertow/Jetty will not see this problem.
Comment 1 Mark Thomas 2018-08-08 09:43:04 UTC
Fixed. See also bug 61719.

Fixed in:
- trunk for 9.0.11 onwards
- 8.5.x for 8.5.33 onwards

Thanks for the test case. It make tracking down the root cause very simple.
Comment 2 Dapeng Zhang 2018-08-08 17:55:35 UTC
Thanks for the fix! 

I also noticed in some cases WriteListener callbacks are not invoked, not clear if its my fault yet, still investigating.
Comment 3 Dapeng Zhang 2018-08-10 00:25:24 UTC

(In reply to Mark Thomas from comment #1)
> Fixed. See also bug 61719.
> 
> Fixed in:
> - trunk for 9.0.11 onwards
> - 8.5.x for 8.5.33 onwards
> 
> Thanks for the test case. It make tracking down the root cause very simple.

Mark, thanks a lot for the fix. I verified the fix works. An new issue on WriteListener over HTTP/2 is filed:
https://bz.apache.org/bugzilla/show_bug.cgi?id=62614