Skip to content

Instantly share code, notes, and snippets.

@ezekg
Forked from jeremywall/readme.md
Created November 10, 2022 13:41
Show Gist options
  • Save ezekg/c1bef30421137d4da38a9622760bb29c to your computer and use it in GitHub Desktop.
Save ezekg/c1bef30421137d4da38a9622760bb29c to your computer and use it in GitHub Desktop.
R2, CORS, Presigned Put Object URLs, and uploading directly from browser side JavaScript

First and foremost you need to set the CORS configuration for your bucket. I'm a Java developer so here's the code I wrote using the AWS Java SDK v2 to define the CORS configuration for my R2 bucket. Alternatively you can also edit your bucket CORS configuration using Postman following the tutorial at https://kian.org.uk/configuring-cors-on-cloudflare-r2/

// update the config section here with your information first
String bucketName = "YOUR_BUCKET";
String accessKeyId = "YOUR_R2_ACCESS_KEY_ID";
String secretAccessKey = "YOUR_R2_SECRET_ACCESS_KEY";
String accountId = "YOUR_ACCOUNT_ID";
URI uri = new URI("https://" + accountId + ".r2.cloudflarestorage.com");

// create an S3 client
S3Client s3Client = S3Client.builder()
  .credentialsProvider(
    StaticCredentialsProvider.create(
      AwsBasicCredentials.create(
        accessKeyId,
        secretAccessKey
      )
    )
  )
  .endpointOverride(uri)
  .build();

// create a CORS rule
CORSRule corsRule = CORSRule.builder()
  .allowedHeaders("content-type") // DO NOT SET TO "*", SET TO "content-type"
  .allowedMethods("PUT")
  .allowedOrigins("*")
  .build();

// update the bucket with the CORS rule
s3Client.putBucketCors(
  // create a put bucket cors request
  PutBucketCorsRequest.builder()
    .bucket(bucketName)
    .corsConfiguration(
      CORSConfiguration.builder()
        .corsRules(corsRule)
        .build()
    )
    .build()
);

Now it's time to generate a Presigned Put Object URL and use that URL from the browser side JavaScript to upload a file without an HTML Form. In my test I was uploading JPG images and I was generating my object key name based on the current timestamp with the ".jpg" file type suffix.

// create the presigner
S3Presigner presigner = S3Presigner.builder()
  .credentialsProvider(
    StaticCredentialsProvider.create(
      AwsBasicCredentials.create(
        accessKeyId,
        secretAccessKey
      )
    )
  )
  .endpointOverride(uri)
  .build();

// presign a out object request
PresignedPutObjectRequest presignedPutObjectRequest = presigner.presignPutObject(
  PutObjectPresignRequest.builder()
    .putObjectRequest(
      PutObjectRequest.builder()
        .bucket(bucketName)
        .key(System.currentTimeMillis() + ".jpg")
        .build()
    )
    .signatureDuration(Duration.ofSeconds(3600))
    .build()
);

// get the final presigend put object request url
String presignedUrl = presignedPutObjectRequest.url().toString();

Once I had the presigned URL I generated a HTML page that has a file input field and a button to upload the file. The upload is done using jQuery when the button is clicked. The presigned URL is injected into the HTML content as it is served by my web server and the URL is placed where the {{PRESIGNED_PUT_OBJECT_URL}} placeholder is.

<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.1/jquery.min.js" integrity="sha512-aVKKRRi/Q/YV+4mjoKBsE4x3H+BkegoM/em46NNlCqNTmUYADjBbeNefNxYV7giUp0VxICtqdrbqU7iVaeZNXA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script>
$(document).ready(function(){
  $("#upload").click(function(){
    var file = $("#file")[0].files[0];
    $.ajax(
      "{{PRESIGNED_PUT_OBJECT_URL}}",
      {
        method: "PUT",
        data: file,
        contentType: "image/jpeg",
        processData: false
      }
    );
  });
});
</script>
<head>
<body>
<input type="file" id="file">
<button id="upload">Upload</button>
</body>
</html>

After many hours of tinkering and experimenting this is what is currently working for us.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment