How can I add a custom header via PutObjectRequestBuilder in AWS Java SDK v2?

I'm using PutObjectRequestBuilder in AWS Java SDK v2 ( https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/model/PutObjectRequest.Builder.html ) to transfer a file from my computer to a publicly accessible AWS S3 bucket from where other people and software will eventually download the file. PutObjectRequestBuilder lets me set various things that will be put in the header of the HTTP response sent to those people, e.g., contentType(String contentType) lets me specify the MIME type that will be specified in the Content-Type tag in the header in the response. But I also need to specify some non-standard items for the header (e.g., "Content-Description"="dods-das") which is needed for some client software that will be downloading these files. Unfortunately, I don't see a method in PutObjectRequestBuilder to add a custom header. Is there a way to specify a custom header item?

Upvotes: 0

Views: 2982

Answers (1)

stdunbar
stdunbar

Reputation: 17475

Not totally custom but you can add meta tags to content. Something like:

Map<String, String> metadata = new HashMap<>();
metadata.put("x-amz-meta-my-cool-header", "woohoo");

PutObjectRequest putObjectRequest = PutObjectRequest.builder()
        .bucket(bucketName)
        .key(fileObjKeyName)
        .metadata(metadata)
        .build();

Results in: enter image description here

The metadata key must start with x-amz-meta-. See Working with object metadata for a bit more details.

UPDATE

Since you have to have the header fixed, one way is to have a Lambda and API Gateway in front of S3. It acts as a proxy. This will be a bit more expensive in that now you're reading from S3, firing up a Lambda, and going through API Gateway. But I was able to get some code to work. The important parts (a Java Lambda as that's what you started with). This uses JsonPath to read the file name from the request:

public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context) throws IOException {
    LambdaLogger logger = context.getLogger();

    String fileName = JsonPath.read(inputStream, "$.pathParameters.fileName");

    try {
        GetObjectRequest getObjectRequest = GetObjectRequest.builder()
                .bucket(bucketName)
                .key(fileName)
                .build();

        APIGatewayResponse apiGatewayResponse = new APIGatewayResponse();
        apiGatewayResponse.setBase64Encoded(Boolean.TRUE);

        ResponseBytes<GetObjectResponse> getObjectResponseResponseBytes = s3Client.getObject(getObjectRequest, ResponseTransformer.toBytes());
        apiGatewayResponse.setBody(Base64.getEncoder().encodeToString(getObjectResponseResponseBytes.asByteArray()));
        apiGatewayResponse.setStatusCode(200);
        apiGatewayResponse.addHeader("Content-Type", getObjectResponseResponseBytes.response().contentType());

        if( getObjectResponseResponseBytes.response().metadata().get("my-cool-header") != null ) {
            apiGatewayResponse.addHeader("Content-Description", getObjectResponseResponseBytes.response().metadata().get("my-cool-header"));
        }

        String outputString = objectMapper.writeValueAsString(apiGatewayResponse);

        logger.log("output is \"" + outputString + "\"");

        outputStream.write(outputString.getBytes(StandardCharsets.UTF_8));
    }
    catch( ...

It reads the specified file from S3 and, if a particular header exists, it sends that back as a API gateway header. Note that even though you have to store the header on the object in S3 starting with a x-amz-meta- it comes back without that.

I put an API gateway in front of this and the important part is:

"/s3proxy/{fileName}" : {
  "get" : {
    "responses" : {
      "default" : {
        "description" : "Default response for GET /s3proxy/{fileName}"
      }
    },
    "x-amazon-apigateway-integration" : {
      "payloadFormatVersion" : "2.0",
      "type" : "aws_proxy",
      "httpMethod" : "POST",
      "uri" : "arn:aws:apigateway:us-west-2:lambda:path/2015-03-31/functions/arn:aws:lambda:us-west-2:1234567890:function:s3proxy/invocations",
      "connectionType" : "INTERNET"
    }
  }
}

I will say that API Gateway is about 80% magic to me and I very well may have missed something. For example, I have this setup as a GET and the export says POST.

Obviously there is work to do here and Java may not be the best solution for a file server service as a cold start takes about 9 seconds on a 512MB Lambda and about 4 on a 2048MB one. But it does what you want and it was a good distraction this afternoon :)

Upvotes: 2

Related Questions