aws-sdk-java icon indicating copy to clipboard operation
aws-sdk-java copied to clipboard

Test to skip the Content-Length in ApacheHttpRequestFactory should be case insensitive

Open outsideMyBox opened this issue 4 years ago • 9 comments

There is a test in ApacheHttpRequestFactory to skip the Content-Length and avoid the HttpClient4 to complain if it's already present. The problem is that the test is case sensitive whereas headers are not according to the rfc2616 standard. For example the header 'content-length' is not skipped

Describe the bug

The test should be case insensitive.

Expected Behavior

Skip any header in the list independently of their case.

Current Behavior

A request with 'content-length' leads to an exception:

[...]
Caused by: org.apache.http.ProtocolException: Content-Length header already present
[...]

Your Environment

  • AWS Java SDK version used: 1.11.873
  • JDK version used: 11
  • Operating System and version: Ubuntu

outsideMyBox avatar Feb 10 '21 09:02 outsideMyBox

@outsideMyBox can you tell us more about your use case? How is the content-length header being added?

debora-ito avatar Feb 11 '21 03:02 debora-ito

Hi @debora-ito , it happens when copying an object with its metadata from a source to another destination. Interestingly it works on AWS S3 (as far as I remember, the Content-Length was not already present in the source's metadata), but it doesn't on our client's premises, who uses an alternative S3 provider and other settings (e.g. load balancers). A first investigation showed that a load balancer returns a lowercase header 'content-length'.

outsideMyBox avatar Feb 11 '21 08:02 outsideMyBox

Understood. We don't guarantee that SDK operations will work with third party S3 providers, we need to guarantee that it is working with S3 directly, which it is. But I'll bring this up to the team, and will change to a feature request.

debora-ito avatar Feb 12 '21 00:02 debora-ito

Quick update: we've added this task to the our backlog.

debora-ito avatar Feb 17 '21 01:02 debora-ito

@outsideMyBox have you tried using Java SDK 2.x? It changed the way it handles request headers and you can plug in different http clients.

Although this was added to the backlog, the team focus is in releasing features for 2.x and this would have a very low priority.

debora-ito avatar Feb 26 '21 04:02 debora-ito

@debora-ito Thanks for the hint. Unfortunately, switching to the SDK 2.0 would involve too much refactoring on our side relative to the time we can spend at the moment.

outsideMyBox avatar Mar 01 '21 09:03 outsideMyBox

@outsideMyBox run into a similar issue when testing. A workaround was to configure the client to fix that header using a RequestHandler2

AmazonS3ClientBuilder builder = AmazonS3ClientBuilder.standard();
builder.withRequestHandlers(new RequestHandler2() {
      public void beforeRequest(Request<?> request) {
        Map<String, String> headers = request.getHeaders();
        if (!StringUtils.isAllLowerCase(HttpHeaders.CONTENT_LENGTH) && headers.containsKey(HttpHeaders.CONTENT_LENGTH)) {
          headers.remove(HttpHeaders.CONTENT_LENGTH.toLowerCase());
        }
        super.beforeRequest(request);
      }
    });

mosche avatar Dec 08 '21 12:12 mosche

Hi, @mosche I encountered the same issue when running the below code.

public static class MinioS3ClientBuilderFactory extends DefaultS3ClientBuilderFactory {
  @Override
  public AmazonS3ClientBuilder createBuilder(S3Options s3Options) {
    AmazonS3ClientBuilder builder = super.createBuilder(s3Options);
    builder.withPathStyleAccessEnabled(true);
    return builder;
  }
}

// START MinIO configurations        
options.as(AwsOptions.class).setAwsServiceEndpoint("http://minio-client.test.svc.cluster.local:9000");
AWSCredentialsProvider credentialsProvider = new AWSStaticCredentialsProvider(new BasicAWSCredentials("minio", "miniopassword"));
options.as(AwsOptions.class).setAwsCredentialsProvider(credentialsProvider);
Class<? extends DefaultS3ClientBuilderFactory> builderFactory = MinioS3ClientBuilderFactory.class;
options.as(S3Options.class).setS3ClientFactoryClass(builderFactory);
// END MinIO configurations

Pipeline p = Pipeline.create(options);
p.apply(TextIO.read().from("s3://jin/alarm_monitor_4.csv"))
 .apply(ParDo.of(new filterData()))
 .apply(ParDo.of(new responseApiFn()))
 .apply(TextIO.write().to("s3://jin/apiIdResponse").withSuffix(".csv").withoutSharding());

Error:

[ERROR] Failed to execute goal org.codehaus.mojo:exec-maven-plugin:3.0.0:java (default-cli) on project api: An exception occured while executing the Java class. java.io.IOException: com.amazonaws.SdkClientException: Unable to execute HTTP request: null: ClientProtocolException: Content-Length header already present -> [Help 1]

Seems It could upload the temporary files (something like s3://jin/.temp-beam), but couldn't move it to s3://jin/apiIdResponse.csv

And, after adding builder.withRequestHandlers, it got another error complaining that it want a Content-Length in http header.

[ERROR] Failed to execute goal org.codehaus.mojo:exec-maven-plugin:3.0.0:java (default-cli) on project api: An exception occured while executing the Java class. java.util.concurrent.ExecutionException: java.io.IOException: Failed closing channel to s3://jin/.temp-beam-1c3bc976-129b-47d2-a9d7-9be7fe835cd6/6dce7af262ab539b-c7b3-4720-8e28-582cf801cf72: com.amazonaws.services.s3.model.AmazonS3Exception: You must provide the Content-Length HTTP header. (Service: Amazon S3; Status Code: 411; Error Code: MissingContentLength; Request ID: 1716BF6FFB6436AE; S3 Extended Request ID: cebfa887-476d-4883-b6c5-2724abc0b4a9; Proxy: null), S3 Extended Request ID: cebfa887-476d-4883-b6c5-2724abc0b4a9 -> [Help 1]

Related libraries:

<dependency>
    <groupId>com.amazonaws</groupId>
    <artifactId>aws-java-sdk</artifactId>
    <version>1.12.305</version>
</dependency>

<dependency>
    <groupId>org.apache.commons</groupId>
      <artifactId>commons-lang3</artifactId>
      <version>3.12.0</version>
  </dependency>

<dependency>
  <groupId>org.apache.httpcomponents</groupId>
  <artifactId>httpclient</artifactId>
  <version>4.5.13</version>
</dependency>

<dependency>
    <groupId>org.apache.beam</groupId>
    <artifactId>beam-sdks-java-io-amazon-web-services</artifactId>
    <version>2.40.0</version>
</dependency>

Do you have anything idea about how to fix it?

wyljpn avatar Sep 21 '22 02:09 wyljpn

I added a condition and it works! Remove the "content-length" only when it equals "0"

if (!StringUtils.isAllLowerCase(HttpHeaders.CONTENT_LENGTH) && headers.containsKey(HttpHeaders.CONTENT_LENGTH) && headers.get("content-length").equals("0")) {
                    headers.remove(HttpHeaders.CONTENT_LENGTH.toLowerCase());
                }

wyljpn avatar Sep 21 '22 03:09 wyljpn