kwhitley / itty-router

A little router.
MIT License
1.8k stars 78 forks source link

withContent middleware fails on BunJS #244

Open lucas-pelton opened 6 months ago

lucas-pelton commented 6 months ago

Describe the Issue

Per https://github.com/oven-sh/bun/issues/1381 the BunJS runtime does not support .clone()

The withContent() middleware relies on .clone().json() to throw an error if the body is not parsed as JSON (and subsequently as formData). Since .clone() does not properly clone the request body, all inbuilt parsings fail, resulting in the post body being returned by .text()

Example Router Code

router.post('/path',withContent, req=>{return typeof req.content}) // "string" no matter if the post body is JSON or formData

Request Details

This code is executed on BunJS runtime bun run app.js

Steps to Reproduce

Steps to reproduce the behavior:

  1. Post a request to an endpoint that's defined as above running on BunJS

Expected Behavior

withContent should parse the post body, but it can't because there is none after .clone()

Actual Behavior

the .catch() is executed every time because .json() and .formData() are being passed empty data due to .clone() failing to clone the post body

Environment (please complete the following information):

Additional Context

Maybe this is just something that goes into the ity-router documentation. I'm only reporting it because itty-router is so often linked with BunJS, it seems relevant to be published somewhere that this is a known limitation.

I ended up writing my own withContent middleware, but it's cast more in the approach of the pre-v4.2 to parsing post body. It's not itty, but it works.

const withContent = async t => {
    if (!t.body) throw new StatusError(400, "No body in the request");

    const contentType = t.headers.get('Content-Type');
    try {
        if (contentType.includes('json')) {
            t.content = await t.json();
        } else if (contentType.includes('form-data')) {
            t.content = Object.fromEntries([...(await t.formData()).entries()]);
        } else {
            t.content = await t.text();
            if (contentType.includes("x-www-form-urlencoded")) {
                t.content = Object.fromEntries(new URLSearchParams(t.content).entries());
            }
        }
    } catch (e) {
        throw new StatusError(400, e.message);
    }
};
kwhitley commented 6 months ago

Fantastic writeup - I'm considering adding environment-specific exports (e.g. itty-router/bun) for overrides that may require more verbosity... or just forcing withContent to check/honor content-type settings so it doesn't attempt more than one...