#Trying to upload in chunks, blocked by CORS

52 messages ยท Page 1 of 1 (latest)

fast nebula
#

Hi,
For my app I'm trying to upload a videofile in chunks. Main reason is to get around the DigitalOcean 100s timeout for uploads as well as the 500MB nGINX file limit, both of wich you can't change on App Platform.
I have set up a view that allows the upload, I have a view that requests a signed URL to upload it to DO Spaces. I also have the JS needed to get these. However: I get blocked by not having the correct CORS header. In my Spaces settings I have the url for my website with all options allowd (GET, PUT, DLETE, POST). Why is it still getting blocked?
See below for code:

#

and the views:

class UploadView(LoginRequiredMixin,generic.CreateView):
    template_name = "Academy/upload.html"
    model = BlogVideo
    fields = ['file']
 
    def get_success_url(self):
        return reverse("Academy:upload")
 
    def get_context_data(self, **kwargs):
        context = super(UploadView, self).get_context_data(**kwargs)
        context.update({
            "uploads": BlogVideo.objects.all()
        })
        return context

class SignedURLView(LoginRequiredMixin ,generic.View):
    def post(self, request, *args, **kwargs):
        session = boto3.session.Session()
        client = session.client(
            "s3",
            region_name='ams3',
            endpoint_url=os.environ.get('AWS_ENDPOINT_URL'),
            aws_access_key_id=os.environ.get('AWS_ACCESS_KEY_ID'),
            aws_secret_access_key=os.environ.get('AWS_SECRET_ACCESS_KEY')
        )
 
        url = client.generate_presigned_url(
            ClientMethod="put_object",
            Params={
                "Bucket": "barts-academy",
                "Key": f"media/videos/{json.loads(request.body)['fileName']}",
            },
            ExpiresIn=300,
        )
        return JsonResponse({"url": url})```
stable marten
#

Why do you have two domains?

fast nebula
#

What do you mean?

#
hollow star
#

Sounds like you should ad DO support of the headers arr not set?

#

The error is quite clear.

stable marten
#

I mean, why do you use cors in the first place?

#

One project, one domain is the rule of thumb imo

fast nebula
fast nebula
fast nebula
#

Thanks both for looking into this to help me btw

fast nebula
#

When I set the allowed header to * it works, but thats not safe no?

stable marten
fast nebula
#

Yes, I have the domain from my website and the DO Spaces bucket I upload all my media and static files to

cedar rover
fast nebula
cedar rover
#

Then please share the complete error, as well as the relevant headers from the browser's dev tools network tab.

#

Those are request headers... you need to check the response headers from the spaces server.

#

Also, you should probably not be sharing CSRF Tokens on here ๐Ÿ˜…

#

(So I deleted that, though note that there are ways to automatically log these)

fast nebula
#

Thanks

cedar rover
#

Mostly, we care about the access-control-* headers

fast nebula
#

The response header:
cache-control: private cf-cache-status: DYNAMIC cf-ray: 8b1f60e2dd6a663e-AMS content-encoding: br content-type: application/json cross-origin-opener-policy: same-origin date: Mon, 12 Aug 2024 09:18:30 GMT referrer-policy: same-origin server: cloudflare vary: Cookie x-content-type-options: nosniff x-do-app-origin: ad3c407b-c8ee-4aef-8962-d6669aa8d3bc x-do-orig-status: 200 x-frame-options: DENY

cedar rover
#

They don't seem to be set at all ๐Ÿค”

fast nebula
#

Isn't that what this part of the JS code does?:

const getSignedUrls = async () => {
    let urls = [];
    for (let i = 0; i < totalChunks; i++) {
        const chunkStart = i * chunkSize;
        const chunkEnd = Math.min(chunkStart + chunkSize, file.size);
        const chunk = file.slice(chunkStart, chunkEnd);

        const body = {
            fileName: `${file.name}.part${i}`,
            fileType: file.type
        };

        const response = await fetch("{% url 'Academy:signed-url' %}", {
          method: "POST",
          body: JSON.stringify(body),
          headers: { "Content-Type": "application/json", "X-CSRFToken": "{{ csrf_token }}"}
      });
        console.log('got signed url');
        const { url } = await response.json();
        urls.push(url);
    };
    return urls;```
cedar rover
#

No. Headers need to be set on the server, so via the DO Spaces control panel (or whatever it's called).

#

For example, IMDB sets them correctly for api.graphql.imdb.com, as you can see in the dev tools if you open the IMDB landing page.

fast nebula
#

But I don;t know what to put as allowed headers

#

As using a * seems to be unsafe?

cedar rover
#

Yeah, don't use *.

#

https://*.bartsacademy.nl ?

#

This should work for bartsacademy.nl and www.bartsacademy.nl (and any other subdomain).

fast nebula
#

See the last screenshot in the tuutorial to see what I mean

cedar rover
fast nebula
#

Thank. Looks like the upload works now.
However: It feels as if the file is now uploaded twice: Once as chunks and then again as the whole file. The video file now shows up twice in my Spaces Bucket. Did I make a mistake in my view?

cedar rover
#

I don't see anything glaring, but I also haven't worked with boto3 or DO Spaces, so that I can't help with ๐Ÿ˜…

fast nebula
#

No worries, thanks for your help!

cedar rover
#

Nice site though ๐Ÿ˜„ ๐Ÿฅƒ

fast nebula
#

If only I could upload all the videos we made ๐Ÿ˜ฆ

wispy reef
#

So I would do something like this @fast nebula :

  1. Ensure that your CORS configuration in DigitalOcean Spaces includes the necessary headers. Here's an example configuration that you can use: [ { "AllowedHeaders": ["*"], "AllowedMethods": ["GET", "POST", "PUT", "DELETE", "HEAD"], "AllowedOrigins": ["https://bartsacademy.nl"], "ExposeHeaders": [], "MaxAgeSeconds": 3000 } ]

  2. Ensure that the signed URL generation includes the necessary headers. In your SignedURLView, you might need to adjust the parameters to include the required headers:
    ` class SignedURLView(LoginRequiredMixin, generic.View):
    def post(self, request, *args, **kwargs):
    session = boto3.session.Session()
    client = session.client(
    "s3",
    region_name='ams3',
    endpoint_url=os.environ.get('AWS_ENDPOINT_URL'),
    aws_access_key_id=os.environ.get('AWS_ACCESS_KEY_ID'),
    aws_secret_access_key=os.environ.get('AWS_SECRET_ACCESS_KEY')
    )

        url = client.generate_presigned_url(
            ClientMethod="put_object",
            Params={
                "Bucket": "barts-academy",
                "Key": f"media/videos/{json.loads(request.body)['fileName']}",
                "ACL": "public-read"
            },
            ExpiresIn=300,
            HttpMethod="PUT"
        )
        return JsonResponse({"url": url})`
    
#
  1. When you send the request to the signed URL, ensure you include the necessary headers:
    `function uploadChunk() {
    if (currentChunkIndex >= totalChunks) {
    setIsSubmitting(false);
    submitForm();
    return;
    }

    const chunkStart = currentChunkIndex * chunkSize;
    const chunkEnd = Math.min(chunkStart + chunkSize, file.size);
    const chunk = file.slice(chunkStart, chunkEnd);
    const signedUrl = signedUrls[currentChunkIndex];

    var ajax = new XMLHttpRequest();
    ajax.upload.addEventListener("progress", (event) => progressHandler(event, currentChunkIndex), false);
    ajax.addEventListener("load", (event) => completeChunkHandler(event), false);
    ajax.addEventListener("error", errorHandler, false);
    ajax.addEventListener("abort", abortHandler, false);
    ajax.open("PUT", signedUrl);
    ajax.setRequestHeader("Content-Type", file.type);
    ajax.setRequestHeader("x-amz-acl", "public-read");
    ajax.send(chunk);
    console.log('signedURL: ' + signedUrl);
    }
    `

hollow star
#

Is this answer from an AI? If so, please disclose that.