nitro icon indicating copy to clipboard operation
nitro copied to clipboard

Bun request body

Open nooooooooooooooon opened this issue 9 months ago • 3 comments

Environment

bun

Reproduction

https://github.com/b7nan/nitro_bun_body

Describe the bug

In dev body is object, undefined, string, but in prod is ArrayBuffer. That work with composables, but break app that use other node packages or frameworks on top of nitro.


That example show me in dev undefined body (huh in my real app it is an object, another strange thing), in prod show ArrayBuffer what is decoded to object what i send with curl

Additional context

bun run build bun run .output/server/index.mjs

curl -H "Content-Type: application/json" -X POST http://localhost:3000 -d '{"email":"", "password":""}'

or

curl -H "Content-Type: multipart/form-data" -X POST http://localhost:3000 -d '{"email":"", "password":""}'

undefined req.body in dev, array buffer in prod.


I know about readMultipartFormData and readBody, I don't need that, it's not about that.

Logs


nooooooooooooooon avatar Mar 22 '25 08:03 nooooooooooooooon

It's because of body = await req.arrayBuffer(); in build, and not such thing for dev.

const server = Bun.serve({
  port: process.env.NITRO_PORT || process.env.PORT || 3e3,
  websocket: void 0,
  async fetch(req, server2) {
    const url = new URL(req.url);
    let body;
    if (req.body) {
      body = await req.arrayBuffer();
    }
    return nitroApp.localFetch(url.pathname + url.search, {
      host: url.hostname,
      protocol: url.protocol,
      headers: req.headers,
      method: req.method,
      redirect: req.redirect,
      body
    });
  }
});

But if a request has a body, in 99% it will be used in app (api endpoint) and can be decoded here and not in composables like readBody and readMultiparFormData. Two composables for one work (readBody need to be smart)

const server = Bun.serve({
  port: process.env.NITRO_PORT || process.env.PORT || 3e3,
  websocket: void 0,
  async fetch(req, server2) {
    const url = new URL(req.url);
    let body;
    if (req.body) {
      const text = await req.text();
      if (!text) {
        body = undefined;
      } else {
        const contentType = req.headers.get("content-type") || "";
        if (contentType.startsWith("application/x-www-form-urlencoded")) {
          const form = new URLSearchParams(text);
          const EmptyObject = /* @__PURE__ */ (() => {
            const C = function () {};
            C.prototype = Object.create(null);
            return C;
          })();
          const parsedForm = new EmptyObject();
          for (const [key, value] of form.entries()) {
            if (hasProp(parsedForm, key)) {
              if (!Array.isArray(parsedForm[key])) {
                parsedForm[key] = [parsedForm[key]];
              }
              parsedForm[key].push(value);
            } else {
              parsedForm[key] = value;
            }
          }
          body = parsedForm;
        } else {
          try {
            body = JSON.parse(text);
          } catch {
            throw createError({
              statusCode: 400,
              statusMessage: "Bad Request",
              message: "Invalid JSON body",
            });
          }
        }
      }
    }
    return nitroApp.localFetch(url.pathname + url.search, {
      host: url.hostname,
      protocol: url.protocol,
      headers: req.headers,
      method: req.method,
      redirect: req.redirect,
      body
    });
  }
});

nooooooooooooooon avatar Mar 22 '25 08:03 nooooooooooooooon

And that deviation from express/connect/node http or what it is, it's very bad. it is not clear where the body is(for me, int that point of time), in re.body, in event.context in req.rawSomething, in other place. Is object, is undefined, isa array buffer...

nooooooooooooooon avatar Mar 22 '25 09:03 nooooooooooooooon

readMultiparFormData return data in an less util form, that need additional manipulations if i remember correct

nooooooooooooooon avatar Mar 22 '25 09:03 nooooooooooooooon