Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8293786

HttpClient will not send more than 64 kb of data from the 2nd request in http2

XMLWordPrintable

    • b22
    • generic
    • generic
    • Verified

      ADDITIONAL SYSTEM INFORMATION :
      Java 17 Windows 10

      A DESCRIPTION OF THE PROBLEM :
      HttpClient will not send more than 64kb of data in an POST/PUT request from the 2nd request onwards regardless of how many window updates frames we send from the server side.

      It hangs indefinitely after sending 64kb of data without sending any data frames afterwards

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Create an HttpClient which uses http2

      1) Send an initial request of any type[GET/PUT/POST etc] all these requests work on the 1st try so that the server can send back a 101 upgrade response to fully transform the connection to http2

      2) Upload a byte array or anything greater than 64 kb[65535 bytes]

      3) Observe output on server . Server never receives a Data frame with THE END_STREAM flag hence it never processes the full request and hence cannot send a response which causes the client to hang forever


      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      Client should send Data frames till all 100 kb of data is uploaded with the final frame having the END_STREAM flag set
      ACTUAL -
      Client only sends data frames up to the 64kb mark after which it pauses forever

      HEADER TABLE SIZE=16384
      ENABLE PUSH=1
      MAX CONCURRENT STREAMS=100
      INITIAL WINDOW SIZE=16777216
      MAX FRAME SIZE=16384
      ======================
      Data Frame,Stream=3,Size=1000,Total Bytes Received=1000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=2000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=3000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=4000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=5000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=6000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=7000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=8000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=9000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=10000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=11000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=12000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=13000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=14000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=15000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=16000
      Data Frame,Stream=3,Size=384,Total Bytes Received=16384
      Data Frame,Stream=3,Size=616,Total Bytes Received=17000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=18000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=19000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=20000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=21000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=22000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=23000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=24000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=25000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=26000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=27000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=28000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=29000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=30000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=31000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=32000
      Data Frame,Stream=3,Size=768,Total Bytes Received=32768
      Data Frame,Stream=3,Size=232,Total Bytes Received=33000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=34000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=35000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=36000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=37000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=38000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=39000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=40000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=41000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=42000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=43000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=44000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=45000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=46000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=47000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=48000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=49000
      Data Frame,Stream=3,Size=152,Total Bytes Received=49152
      Data Frame,Stream=3,Size=848,Total Bytes Received=50000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=51000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=52000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=53000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=54000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=55000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=56000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=57000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=58000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=59000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=60000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=61000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=62000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=63000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=64000
      Data Frame,Stream=3,Size=1000,Total Bytes Received=65000
      Data Frame,Stream=3,Size=535,Total Bytes Received=65535

      // HANGS HERE DOES NOT SEND ANY MORE DATA FRAMES EVEN THOUGH ACTUAL PAYLOAD IS 100 KB //

      ---------- BEGIN SOURCE ----------
      Source code is divided into 2 applications which needs to be run on separate JVM's

      Server Source Code

      final class PUT_Server
      {
       private static final int
       WINDOW_SIZE=1000, //initial window size sent as setting frame to server
       WINDOW_INC=1000; //make it -1 to make server send window updates equal to data frame payload length
       
       private static byte[] encodeFrame(byte type,byte flags,int streamID,byte[] data)throws Exception
       {
        try(ByteArrayOutputStream output=new ByteArrayOutputStream())
        {
         output.write((byte)((data.length>>16)&0xFF));
         output.write((byte)((data.length>>8)&0xFF));
         output.write((byte)(data.length&0xFF));
         output.write(type);
         output.write(flags);
         output.write((byte)(((streamID>>24)&0xFF)&0b01111111));
         output.write((byte)((streamID>>16)&0xFF));
         output.write((byte)((streamID>>8)&0xFF));
         output.write((byte)(streamID&0xFF));
       
         output.write(data);
         
         return output.toByteArray();
        }
       }

       private static void writeOK(OutputStream output,int streamID)throws Exception
       {
         //we respond with a Header Frame[status=200] &
         output.write
         (
           encodeFrame
           (
            (byte)0x1, //type headers 0x1
            (byte)0x4, //flags = END_HEADERS
            streamID, //stream
            new byte[]{(byte)0b10001000} //indexed header for status 200
           )
         );

         //we respond with a Data Frame with text OK
         output.write
         (
           encodeFrame
           (
            (byte)0x0, //type Data 0x0
            (byte)(0x1), //flags = END_STREAM
            streamID, //stream
            "OK".getBytes() //Payload data
           )
         );
       }
       
       public static void main(String[] args)throws Exception
       {
        try(ServerSocket server=new ServerSocket(1000))
        {
         try(Socket client=server.accept())
         {
          try(InputStream input=client.getInputStream();
              OutputStream output=client.getOutputStream())
          {
           int length,bytesReceived=0,windowInc;
           byte[] data=new byte[100*1024];
           Frame frame=new Frame();
           byte[] connection_preface="PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes("UTF-8");
           
           int stage=1;
           while((length=input.read(data))>0)
           {
            if(stage==1)
            {
             /*
               We dont care about the request client sends
               but respond with an 101 switching protocol
             */
             output.write
             (
              (
               "HTTP/1.1 101 Switching Protocols\r\n"+
               "Connection:Upgrade\r\n"+
               "Upgrade:h2c\r\n\r\n"
              ).getBytes("UTF-8")
             );
             
             //write our custom settings frame with specific initial window size
             output.write
             (
               encodeFrame
               (
                (byte)0x4, //type settings 0x4
                (byte)0 , //no flags
                0, //stream 0
                new byte[]
                {
                 0,
                 (byte)0x4, //setting type initial window
                 (byte)(((WINDOW_SIZE>>24)&0xFF)&0b01111111), //encoded window size
                 (byte)((WINDOW_SIZE>>16)&0xFF),
                 (byte)((WINDOW_SIZE>>8)&0xFF),
                 (byte)(WINDOW_SIZE&0xFF)
                }
               )
             );
             
             //write 200 ok response
             writeOK(output,1);
             
             //stage 2 is where we process the client preface and its setting frame
             stage=2;
            }
            else if(stage==2)
            {
             //decode frames after connection preface
             frame.decode(new ByteArrayInputStream(data,connection_preface.length,length-connection_preface.length));
           
             //settings frame without the ACK flag
             if(frame.type==0x4 && frame.flags==0)
             {
              for(int i=0;i<=frame.payload.length-6;i+=6)
              {
               short option=(short)((frame.payload[i] & 0xFF) << 8 |
                                    (frame.payload[i+1] & 0xFF));
               
               int value=((frame.payload[i+2] & 0xFF) << 24) |
                         ((frame.payload[i+3] & 0xFF) << 16) |
                         ((frame.payload[i+4] & 0xFF) << 8) |
                         (frame.payload[i+5] & 0xFF);
               
               //Initial Window Size
               switch(option)
               {
                case 0x1:System.out.println("HEADER TABLE SIZE="+value);
                break;
                case 0x2:System.out.println("ENABLE PUSH="+value);
                break;
                case 0x3:System.out.println("MAX CONCURRENT STREAMS="+value);
                break;
                case 0x4:System.out.println("INITIAL WINDOW SIZE="+value);
                break;
                case 0x5:System.out.println("MAX FRAME SIZE="+value);
                break;
                case 0x6:System.out.println("MAX HEADER LIST SIZE="+value);
               }
              }
             }
             
             System.out.println("======================");
             
             //decode normally remaining data in the next stage without skipping
             stage=3;
             continue;
            }
            if(stage==3)
            {
              frame.decode(new ByteArrayInputStream(data,0,length));
              if(frame.type==0x0) //Data frame
              {
               bytesReceived+=frame.payload.length;
               System.out.println("Data Frame,Stream="+frame.streamID+",Size="+frame.payload.length+",Total Bytes Received="+bytesReceived);

               //window increment use frame payload length if WINDOW_INC < 0
               windowInc=WINDOW_INC<=0?frame.payload.length:WINDOW_INC;

               //write window update frame
               output.write
               (
                 encodeFrame
                 (
                  (byte)0x8, //type Window Increment 0x8
                  (byte)0, //no flags
                  frame.streamID, //stream
                  new byte[]
                  {
                   (byte)(((windowInc>>24)&0xFF) & 0b01111111), //encoded size[first byte reserved]
                   (byte)((windowInc>>16)&0xFF),
                   (byte)((windowInc>>8)&0xFF),
                   (byte)(windowInc&0xFF)
                  }
                 )
               );
               
               //end of data received test successfull
               if(frame.flags==0x1)
               {
                System.out.println("End Received Test Successfull");
                
                //write 200 ok and end test
                writeOK(output,frame.streamID);
               }
              }
            }
           }
          }
         }
        }
       }
       
       private static class Frame
       {
        private int length,type,flags,streamID;
        private byte[] payload;
        
        private void decode(InputStream input)throws Exception
        {
         //9 bytes containing all header info about the frame
         byte[] frameInfo=new byte[9];
         input.read(frameInfo);
         
         length=((frameInfo[0]&0xFF)<<16) |
                ((frameInfo[1]&0xFF)<<8) |
                ((frameInfo[2]&0xFF));
        
         type=frameInfo[3];
         flags=frameInfo[4];

         streamID=(((frameInfo[5] & 0xFF) & 0b01111111)<<24) |
                  ((frameInfo[6] & 0xFF)<<16) |
                  ((frameInfo[7] & 0xFF)<<8) |
                  (frameInfo[8] & 0xFF);
         
         //payload of frame
         payload=new byte[length];
         input.read(payload);
        }
       }
      }

      Client Source Code

      final class PUT_Client
      {
       private static void debug(HttpResponse response)throws Exception
       {
        System.out.println("==========Status Code=============");
        System.out.println(""+response.statusCode());
        System.out.println("Thread="+Thread.currentThread().getName());
        
        System.out.println("===========Headers=========");
        response.headers().map().forEach((header,values)->System.out.println(header+"=>"+values));
        
        System.out.println("===========Body=============");
        System.out.println(response.body().toString());
       }
       
       public static void main(String[] args)throws Exception
       {
        HttpRequest.Builder builder=HttpRequest.newBuilder();
        
        HttpClient client=HttpClient.newHttpClient();
        
        //1st GET request to allow connection to upgrade o http 2
        HttpResponse response=client.send
        (
          builder.uri(URI.create("http://localhost:1000/Test.txt"))
         .GET()
         .timeout(Duration.ofSeconds(5))
         .version(HttpClient.Version.HTTP_2)
         .build(),
         HttpResponse.BodyHandlers.ofString()
        );
        debug(response);
        
        System.out.println("**********************************************");
        
        //2nd request to put data of size 100kb[it never uploads the full 100kb only upto the 64kb mark]
        byte[] data=new byte[100*1024];
        response=client.send
        (
          builder.uri(URI.create("http://localhost:1000/Test.txt"))
         .PUT(HttpRequest.BodyPublishers.ofByteArray(data))
         .timeout(Duration.ofSeconds(20))
         .version(HttpClient.Version.HTTP_2)
         .build(),
          HttpResponse.BodyHandlers.ofString()
        );
        debug(response);
       }
      }

      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      Upload remaining data as separate requests

      FREQUENCY : always


        1. PutTest.java
          5 kB
        2. PUT_Server.java
          11 kB
        3. PUT_Client.java
          2 kB

            ccleary Conor Cleary (Inactive)
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            8 Start watching this issue

              Created:
              Updated:
              Resolved: