Uploaded image for project: 'RESTEasy'
  1. RESTEasy
  2. RESTEASY-3436

Sending an empty InputStream as a resteasy-client request body cause apache http client to throw ProtocolException: Content-Length header already present

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Done
    • Icon: Major Major
    • 6.2.7.Final, 7.0.0.Alpha1
    • 6.2.6.Final
    • Client
    • None
    • Hide

      This can be reproduced easily in `org.jboss.resteasy.test.client.InputStreamResourceTest`:

      On line 68, post an empty content stream:

      client.postInputStream(new ByteArrayInputStream("".getBytes())); 

      Then run the test (tested with SERVER_VERSION=30.0.1.Final)

      ./mvnw  -U -B  -fae "-Dserver.version=$SERVER_VERSION" -Dtest=InputStreamResourceTest   -Dsurefire.failIfNoSpecifiedTests=false verify 
      Show
      This can be reproduced easily in `org.jboss.resteasy.test.client.InputStreamResourceTest`: On line 68, post an empty content stream: client.postInputStream( new ByteArrayInputStream("".getBytes())); Then run the test (tested with SERVER_VERSION=30.0.1.Final) ./mvnw  -U -B  -fae "-Dserver.version=$SERVER_VERSION" -Dtest=InputStreamResourceTest   -Dsurefire.failIfNoSpecifiedTests= false verify

      When a jax-rs client sends an empty InputStream as a request body; then the apache http client throws:

      org.jboss.resteasy.test.client.InputStreamResourceTest.testClientResponse -- Time elapsed: 73.75 s <<< ERROR!
      jakarta.ws.rs.ProcessingException: RESTEASY004655: Unable to invoke request: org.apache.http.client.ClientProtocolException
          at org.jboss.resteasy.client.jaxrs.engines.ManualClosingApacheHttpClient43Engine.invoke(ManualClosingApacheHttpClient43Engine.java:361)
      
      Caused by: org.apache.http.client.ClientProtocolException
          at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:187)
      
      Caused by: org.apache.http.ProtocolException: Content-Length header already present
          at org.apache.http.protocol.RequestContent.process(RequestContent.java:97)

       
      The cause is the fix for RESTEASY-204, in org.jboss.resteasy.plugins.providers.InputStreamProvider#writeTo:
       

              try {
                  int c = inputStream.read();
                  if (c == -1) {
                      httpHeaders.putSingle(HttpHeaderNames.CONTENT_LENGTH, Integer.toString(0));
                      entityStream.write(new byte[0]); // fix RESTEASY-204
                      return;
                  } else
                      entityStream.write(c);
                  ProviderHelper.writeTo(inputStream, entityStream);
              } finally {
                  inputStream.close();
              } 

      This set the content length header in the request context.
      However, later on, apache http client expects this header to still be unset when processing the request content at org.apache.http.protocol.RequestContent#process:
       

      if (request instanceof HttpEntityEnclosingRequest) {
                  if (this.overwrite) {
                      request.removeHeaders(HTTP.TRANSFER_ENCODING);
                      request.removeHeaders(HTTP.CONTENT_LEN);
                  } else {
                      if (request.containsHeader(HTTP.TRANSFER_ENCODING)) {
                          throw new ProtocolException("Transfer-encoding header already present");
                      }
                      if (request.containsHeader(HTTP.CONTENT_LEN)) {
                          throw new ProtocolException("Content-Length header already present");
                      }
                  } 

       
      Reverting org.jboss.resteasy.plugins.providers.InputStreamProvider#writeTo fixes this issue, but I haven't tested if RESTEASY-204 is still impacted yet.

      EDIT: EmptyInputStreamMultipartProviderTest seems to pass without the workaround implemented, so with this implementation:

      public void writeTo(InputStream inputStream, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType,
              MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException {
          LogMessages.LOGGER.debugf("Provider : %s,  Method : writeTo", getClass().getName());
          try {
              ProviderHelper.writeTo(inputStream, entityStream);
          } finally {
              inputStream.close();
          }
      } 

       
      I think the use cases are not quite common, but an empty InputStream is as valid as one containing content, and having this ProtocolException thrown is quite misleading.
       

            jperkins-rhn James Perkins
            charlyghislain charly ghislain (Inactive)
            Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

              Created:
              Updated:
              Resolved: