Let me tell you a story about a large company I worked for.
This company (that shall not be named) has a large Node codebase that was built up over the past many years. The codebase is a bit of a disaster, especially given its reliance on third-party vendors running code on unseen servers.
I've been slowly migrating our company codebase to more modern javascript, so I decided to try out running it on Deno instead. It worked! First try! Except that every chat message sent through the Deno system was in uppercase. Weird.
I kept combing through the code to see what could possibly be causing this discrepancy. It made no sense. How could a different javascript runtime affect how the application specific code was sending it's message? I was stumped.
Until I found it.
The component of this codebase that is relevant to this short story interacts with a third-party chat server. The integration on our side has been rewritten many times, but the TLDR is that the code looks something like this:
async function sendMessage(message: string) {
// ... some code
const response = await fetch("[SOME_IP]", {
method: message,
// ... some more code
});
// ... some more code
}
The code is a bit of a mess, but the important part is that the message
parameter is used as the METHOD, and not the BODY for the fetch
request. I have stared at this code for too long to understand its original
purpose. I have no idea why it was implemented like this. Even if I wanted to
change it, I have no idea what code is running on that foreign IP address or who
to contact to change it.
I decided to run a little experiment. I wrote a small script that looked like this:
await fetch("http://localhost:8000/post", {
method: "Hello!",
});
const data = await fetch("http://localhost:8000/get");
console.log(await data.text());
Here's the server code:
let data: string;
export default {
fetch: (req: Request) => {
const pathname = new URL(req.url).pathname;
if (pathname === "/post") {
data = req.method;
return new Response(null);
} else if (pathname === "/get") {
return new Response(data);
} else {
return new Response(null);
}
},
};
And now for the experiment:
$ deno run --allow-net fetch.js
HELLO!
$ node server.js
Hello!
And for fun (because I know the runtime never quite gives the results a normal person would expect), I tried to run the same script with Bun:
$ bun run fetch.js
GET
Of course Bun. Don't change.
Bun, from my very first usage of it, to this day, has been broken in my hands. It's a bit of a running joke at this point.
The first time I tried to use Bun, it segfaulted on a hello world console.log example.
The issues I've gotten from it since then have been increasingly ridiculous. Everything from, "fetch is not a function", when it *was* a function, to crashing the MacOS kernel.
I should write a blog post about it.
I talked to a fetch
spec author and they were kind enough to inform me that
this is a hole in the current specification. Both Node and Deno's
implementations are likely spec-complaint. Bun's on the other hand... well, it's
Bun.
I posted this in a forum with a few members of Bun's team and they got back to me pretty quickly informing me that, yes, this is a bug.
Here's the code for the curious ones:
It's a little more complex than that, but it's like this in a few places. It's unlikely to be fixed soon (if ever) given how low priority it is, but it's good to know that it's on their radar.
While this was meant to be a goofy little story, there is actually modern relevance to assuming that the method of an http request is just in an enum. The http QUERY method is getting dangerously close to being standardized. In a world with that, Bun's implementation would just blow up.
Please code with the future in mind people. I beg of you.
Also, for the love of god, PLEASE just use body. It's there for a reason.
Comments