file uploads to my origin server using cloudflare workers are failing

233 Views Asked by At

I have a blog hosted on blog.domain.com; I want to switch it from subdomain to subdirectory using Cloudflare workers. everything works except file uploads. when i upload a file , browser sends an xhr post request with formData.

Request URL:
https://www.domain.in/blog/ghost/api/admin/images/upload/
Request Method:
POST
Status Code:
422 Unprocessable Content
Referrer Policy:
strict-origin-when-cross-origin

Content type is set by browser for file uploads

Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryglip7bP33nuVUYAE

Cloudflare worker code :

/**
 * Welcome to Cloudflare Workers! This is your first worker.
 *
 * - Run `npm run dev` in your terminal to start a development server
 * - Open a browser tab at http://localhost:8787/ to see your worker in action
 * - Run `npm run deploy` to publish your worker
 *
 * Learn more at https://developers.cloudflare.com/workers/
 */

export interface Env {
    // Example binding to KV. Learn more at https://developers.cloudflare.com/workers/runtime-apis/kv/
    // MY_KV_NAMESPACE: KVNamespace;
    //
    // Example binding to Durable Object. Learn more at https://developers.cloudflare.com/workers/runtime-apis/durable-objects/
    // MY_DURABLE_OBJECT: DurableObjectNamespace;
    //
    // Example binding to R2. Learn more at https://developers.cloudflare.com/workers/runtime-apis/r2/
    // MY_BUCKET: R2Bucket;
    //
    // Example binding to a Service. Learn more at https://developers.cloudflare.com/workers/runtime-apis/service-bindings/
    // MY_SERVICE: Fetcher;
    //
    // Example binding to a Queue. Learn more at https://developers.cloudflare.com/queues/javascript-apis/
    // MY_QUEUE: Queue;
}

export default {
    async fetch(request : Request){
        async function handleRequest(request : Request) {
            const url = new URL(request.url);
            const originUrl = url.toString().replace("https://www.domain.in", "https://blog.domain.in");
            const cookieHeader = request.headers.get("Cookie");
            const headers = new Headers(request.headers);
        
            if (cookieHeader) {
              headers.append("Cookie", cookieHeader);
            }
        
            let originPage;
        
            if (["POST", "PUT", "DELETE"].includes(request.method)) {
              let contentType = request.headers.get("Content-Type") ;
              if (contentType && contentType.includes("multipart/form-data")) {
                let formData = await request.formData();
                originPage = await fetch(originUrl, {
                  method: request.method,
                  headers,
                  body: formData,
                });
              } else {
                originPage = await fetch(originUrl, {
                  method: request.method,
                  headers,
                  body: await request.text(),
                });
              }
            } else {
              originPage = await fetch(originUrl, {
                method: request.method,
                headers,
              });
            }
        
            const newResponse = new Response(originPage.body, originPage);
            return newResponse;
          };

        const response = await handleRequest(request);
        return response ;
    }
};


Request is reaching the origin Server , but multer is not populating the path. so file upload is getting rejected saying that validation error. Note : uploads are working fine when i upload from blog.domain.in ( i.e no cloudflare worker )

[90mValidationError: Please select an image.
    at uploadValidation (/var/lib/ghost/versions/5.74.0/core/server/web/api/middleware/upload.js:149:25)
    at Layer.handle [as handle_request] (/var/lib/ghost/versions/5.74.0/node_modules/express/lib/router/layer.js:95:5)
    at next (/var/lib/ghost/versions/5.74.0/node_modules/express/lib/router/route.js:144:13)
    at /var/lib/ghost/versions/5.74.0/core/server/web/api/middleware/upload.js:76:9
    at Immediate._onImmediate (/var/lib/ghost/versions/5.74.0/node_modules/multer/lib/make-middleware.js:53:37)
    at process.processImmediate (node:internal/timers:478:21)[39m
[39m
[2023-11-28 04:36:17] [31mERROR[39m The "path" argument must be of type string or an instance of Buffer or URL. Received undefined
[31m
[31mThe "path" argument must be of type string or an instance of Buffer or URL. Received undefined[39m

[1m[37mError Code: [39m[22m
    [90mERR_INVALID_ARG_TYPE[39m

What might be the issue ? thanks in advance

wrangler.toml :

name = "ghost"
main = "src/index.ts"
compatibility_date = "2023-11-21"

route = { pattern = "https://www.domain.in/blog*", zone_name = "domain.in" }

1

There are 1 best solutions below

0
On

(Posting the resolution from my comment above.)

The trouble comes from here:

let formData = await request.formData();
originPage = await fetch(originUrl, {
  method: request.method,
  headers,
  body: formData,
});

This code parses and re-encodes the form data. That means a new boundary string will be chosen. However, the Content-Type header from the original request is being carried over to the subrequest, and it contains the old boundary string, which is not valid for the new encoding. You can solve this by deleting the Content-Type header, in which case the Workers Runtime will automatically regenerate it based on the new FormData encoding:

let formData = await request.formData();
headers.delete("Content-Type");
originPage = await fetch(originUrl, {
  method: request.method,
  headers,
  body: formData,
});