From deb44aa80bb5798eedf7e8eb408a128db9f79934 Mon Sep 17 00:00:00 2001 From: Jarred Sumner Date: Sun, 6 Oct 2024 07:11:18 -0700 Subject: [PATCH] okay fastify and express seem to work again --- bench/package.json | 1 + bench/snippets/express-hello.mjs | 13 ++++++++++++ bench/snippets/fastify.mjs | 20 ++++++++++++++++++ packages/bun-uws/src/HttpResponse.h | 13 ++++++++---- src/bun.js/api/server.zig | 13 ++++++------ src/bun.js/bindings/NodeHTTP.cpp | 32 +++++++++++++++++++++++++++++ src/js/node/http.ts | 17 +++++++++++---- 7 files changed, 94 insertions(+), 15 deletions(-) create mode 100644 bench/snippets/express-hello.mjs create mode 100644 bench/snippets/fastify.mjs diff --git a/bench/package.json b/bench/package.json index 11b0ab69bd..69be623fdf 100644 --- a/bench/package.json +++ b/bench/package.json @@ -12,6 +12,7 @@ "eventemitter3": "^5.0.0", "execa": "^8.0.1", "fast-glob": "3.3.1", + "fastify": "^5.0.0", "fdir": "^6.1.0", "mitata": "^0.1.6", "string-width": "7.1.0", diff --git a/bench/snippets/express-hello.mjs b/bench/snippets/express-hello.mjs new file mode 100644 index 0000000000..4c9aea40c7 --- /dev/null +++ b/bench/snippets/express-hello.mjs @@ -0,0 +1,13 @@ +import express from "express"; + +const app = express(); +const port = 3000; + +var i = 0; +app.get("/", (req, res) => { + res.send("Hello World!" + i++); +}); + +app.listen(port, () => { + console.log(`Express app listening at http://localhost:${port}`); +}); diff --git a/bench/snippets/fastify.mjs b/bench/snippets/fastify.mjs new file mode 100644 index 0000000000..576a124558 --- /dev/null +++ b/bench/snippets/fastify.mjs @@ -0,0 +1,20 @@ +import Fastify from "fastify"; + +const fastify = Fastify({ + logger: false, +}); + +fastify.get("/", async (request, reply) => { + return { hello: "world" }; +}); + +const start = async () => { + try { + await fastify.listen({ port: 3000 }); + } catch (err) { + fastify.log.error(err); + process.exit(1); + } +}; + +start(); diff --git a/packages/bun-uws/src/HttpResponse.h b/packages/bun-uws/src/HttpResponse.h index 1632266dae..5cab0fa3d3 100644 --- a/packages/bun-uws/src/HttpResponse.h +++ b/packages/bun-uws/src/HttpResponse.h @@ -440,7 +440,7 @@ public: bool sendTerminatingChunk(bool closeConnection = false) { writeStatus(HTTP_200_OK); HttpResponseData *httpResponseData = getHttpResponseData(); - if (!(httpResponseData->state & HttpResponseData::HTTP_WRITE_CALLED)) { + if (!(httpResponseData->state & (HttpResponseData::HTTP_WRITE_CALLED | HttpResponseData::HTTP_END_CALLED))) { /* Write mark on first call to write */ writeMark(); @@ -469,7 +469,7 @@ public: HttpResponseData *httpResponseData = getHttpResponseData(); - if (!(httpResponseData->state & HttpResponseData::HTTP_WRITE_CALLED)) { + if (!(httpResponseData->state & (HttpResponseData::HTTP_WRITE_CALLED | HttpResponseData::HTTP_END_CALLED))) { /* Write mark on first call to write */ writeMark(); @@ -477,9 +477,14 @@ public: httpResponseData->state |= HttpResponseData::HTTP_WRITE_CALLED; } + Super::write("\r\n", 2); - writeUnsignedHex((unsigned int) data.length()); - Super::write("\r\n", 2); + + // This happens if they call send, include a content-length header + if (!(httpResponseData->state & HttpResponseData::HTTP_END_CALLED)) { + writeUnsignedHex((unsigned int) data.length()); + Super::write("\r\n", 2); + } auto [written, failed] = Super::write(data.data(), (int) data.length()); /* Reset timeout on each sended chunk */ diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig index 1078d33a2e..24d82309d5 100644 --- a/src/bun.js/api/server.zig +++ b/src/bun.js/api/server.zig @@ -5875,7 +5875,7 @@ pub const NodeHTTPResponse = struct { } fn handleEndedIfNecessary(state: uws.State, globalObject: *JSC.JSGlobalObject) bool { - if (state.isHttpEndCalled()) { + if (!state.isResponsePending()) { globalObject.ERR_HTTP_HEADERS_SENT("Stream is already ended", .{}).throw(); return true; } @@ -6110,7 +6110,7 @@ pub const NodeHTTPResponse = struct { } const state = this.response.state(); - if (state.isHttpEndCalled()) { + if (!state.isResponsePending()) { globalObject.ERR_STREAM_WRITE_AFTER_END("Stream already ended", .{}).throw(); return .zero; } @@ -6118,7 +6118,7 @@ pub const NodeHTTPResponse = struct { const input_value = if (arguments.len > 0) arguments[0] else .undefined; var encoding_value = if (arguments.len > 1) arguments[1] else .undefined; const callback_value = brk: { - if (encoding_value != .undefined and encoding_value.isCallable(globalObject.vm())) { + if ((encoding_value != .null and encoding_value != .undefined) and encoding_value.isCallable(globalObject.vm())) { encoding_value = .undefined; break :brk arguments[1]; } @@ -6135,9 +6135,8 @@ pub const NodeHTTPResponse = struct { }; const string_or_buffer: JSC.Node.StringOrBuffer = brk: { - if (input_value == .null) { - globalObject.ERR_STREAM_NULL_VALUES("Cannot write null value to stream", .{}).throw(); - return .zero; + if (input_value == .null or input_value == .undefined) { + break :brk JSC.Node.StringOrBuffer.empty; } var encoding: JSC.Node.Encoding = .utf8; @@ -6177,7 +6176,7 @@ pub const NodeHTTPResponse = struct { } if (is_end and bytes.len == 0 and write_offset == 0) { - this.response.endWithoutBody(state.isHttpConnectionClose()); + this.response.endStream(state.isHttpConnectionClose()); this.onRequestComplete(); return JSC.JSValue.jsNumberFromInt32(0); } diff --git a/src/bun.js/bindings/NodeHTTP.cpp b/src/bun.js/bindings/NodeHTTP.cpp index e0d219827e..7ce532ea42 100644 --- a/src/bun.js/bindings/NodeHTTP.cpp +++ b/src/bun.js/bindings/NodeHTTP.cpp @@ -504,10 +504,42 @@ static void writeFetchHeadersToUWSResponse(WebCore::FetchHeaders& headers, uWS:: } } + auto* data = res->getHttpResponseData(); + for (const auto& header : internalHeaders.commonHeaders()) { + const auto& name = WebCore::httpHeaderNameString(header.key); const auto& value = header.value; + // We have to tell uWS not to automatically insert a TransferEncoding or Date header. + // Otherwise, you get this when using Fastify; + // + // ❯ curl http://localhost:3000 --verbose + // * Trying [::1]:3000... + // * Connected to localhost (::1) port 3000 + // > GET / HTTP/1.1 + // > Host: localhost:3000 + // > User-Agent: curl/8.4.0 + // > Accept: */* + // > + // < HTTP/1.1 200 OK + // < Content-Type: application/json; charset=utf-8 + // < Content-Length: 17 + // < Date: Sun, 06 Oct 2024 13:37:01 GMT + // < Transfer-Encoding: chunked + // < + // + if (header.key == WebCore::HTTPHeaderName::ContentLength) { + if (!(data->state & uWS::HttpResponseData::HTTP_END_CALLED)) { + data->state |= uWS::HttpResponseData::HTTP_END_CALLED; + res->writeMark(); + } + } else if (header.key == WebCore::HTTPHeaderName::TransferEncoding || header.key == WebCore::HTTPHeaderName::Date) { + if (!(data->state & uWS::HttpResponseData::HTTP_WRITE_CALLED)) { + data->state |= uWS::HttpResponseData::HTTP_WRITE_CALLED; + res->writeMark(); + } + } writeResponseHeader(res, name, value); } diff --git a/src/js/node/http.ts b/src/js/node/http.ts index e76c1155bd..bb64936153 100644 --- a/src/js/node/http.ts +++ b/src/js/node/http.ts @@ -1341,10 +1341,13 @@ const ServerResponsePrototype = { return this; } + if (!$isCallable(callback) && callback !== undefined) { + callback = undefined; + } + const handle = this[kHandle]; if (handle) { const headerState = this[headerStateSymbol]; - if (headerState !== NodeHTTPHeaderState.sent) { handle.cork(() => { handle.writeHead(this.statusCode, this.statusMessage, this[headersSymbol]); @@ -1354,11 +1357,12 @@ const ServerResponsePrototype = { this[headerStateSymbol] = NodeHTTPHeaderState.sent; // https://github.com/nodejs/node/blob/2eff28fb7a93d3f672f80b582f664a7c701569fb/lib/_http_outgoing.js#L987 - this._contentLength = handle.end(chunk, encoding, callback); + this._contentLength = handle.end(chunk, encoding); }); } else { - handle.end(chunk, encoding, callback); + handle.end(chunk, encoding); } + this.finished = true; } return this; @@ -1384,6 +1388,10 @@ const ServerResponsePrototype = { let result = 0; + if (!$isCallable(callback)) { + callback = undefined; + } + if (this[headerStateSymbol] !== NodeHTTPHeaderState.sent) { handle.cork(() => { handle.writeHead(this.statusCode, this.statusMessage, this[headersSymbol]); @@ -1407,7 +1415,8 @@ const ServerResponsePrototype = { return false; } - this.emit("drain"); + if (result > 0) this.emit("drain"); + return true; },