diff options
author | 2023-05-08 18:10:40 -0300 | |
---|---|---|
committer | 2023-05-08 14:10:40 -0700 | |
commit | c6c21eeba749a5ebbc7a3f9dc3a0f7e5a702e0da (patch) | |
tree | a6f7b5b616e492ace2439dfca9544f157e2fcb03 | |
parent | 14597dbcdc318439d1ba3a7bdbf20d7b5d3c51ef (diff) | |
download | bun-c6c21eeba749a5ebbc7a3f9dc3a0f7e5a702e0da.tar.gz bun-c6c21eeba749a5ebbc7a3f9dc3a0f7e5a702e0da.tar.zst bun-c6c21eeba749a5ebbc7a3f9dc3a0f7e5a702e0da.zip |
added some improvements on server (#2803)
* added some improvements on server
* undo unintended change
* clean data handler before end calls
* refactor
* make ctx.resp nullable
---------
Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
-rw-r--r-- | src/bun.js/api/server.zig | 372 | ||||
-rw-r--r-- | src/bun.js/webcore/blob.zig | 1 | ||||
-rw-r--r-- | src/bun.js/webcore/body.zig | 3 | ||||
-rw-r--r-- | src/deps/libuwsockets.cpp | 16 | ||||
-rw-r--r-- | src/deps/uws.zig | 4 | ||||
-rw-r--r-- | src/url.zig | 14 |
6 files changed, 266 insertions, 144 deletions
diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig index f8be42e79..762d35bb6 100644 --- a/src/bun.js/api/server.zig +++ b/src/bun.js/api/server.zig @@ -912,7 +912,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp pub const shim = JSC.Shimmer("Bun", name, @This()); server: *ThisServer, - resp: *App.Response, + resp: ?*App.Response, /// thread-local default heap allocator /// this prevents an extra pthread_getspecific() call which shows up in profiling allocator: std.mem.Allocator, @@ -948,6 +948,9 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp /// Used to avoid looking at the uws.Request struct after it's been freed is_transfer_encoding: bool = false, + /// Used to identify if request can be safely deinitialized + is_waiting_body: bool = false, + /// Used in renderMissing in debug mode to show the user an HTML page /// Used to avoid looking at the uws.Request struct after it's been freed is_web_browser_navigation: if (debug_mode) bool else void = if (debug_mode) false else {}, @@ -971,8 +974,10 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp pub fn setAbortHandler(this: *RequestContext) void { if (this.has_abort_handler) return; - this.has_abort_handler = true; - this.resp.onAborted(*RequestContext, RequestContext.onAbort, this); + if (this.resp) |resp| { + this.has_abort_handler = true; + resp.onAborted(*RequestContext, RequestContext.onAbort, this); + } } pub fn onResolve(_: *JSC.JSGlobalObject, callframe: *JSC.CallFrame) callconv(.C) JSValue { @@ -1042,7 +1047,12 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp } fn handleReject(ctx: *RequestContext, value: JSC.JSValue) void { - const has_responded = ctx.resp.hasResponded(); + if (ctx.resp == null) { + ctx.finalizeForAbort(); + return; + } + const resp = ctx.resp.?; + const has_responded = resp.hasResponded(); if (!has_responded) ctx.runErrorHandler( value, @@ -1059,39 +1069,43 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp return; } - if (!ctx.resp.hasResponded() and !ctx.has_marked_pending) { + if (!resp.hasResponded() and !ctx.has_marked_pending) { ctx.renderMissing(); return; } } pub fn renderMissing(ctx: *RequestContext) void { - ctx.resp.runCorkedWithType(*RequestContext, renderMissingCorked, ctx); + if (ctx.resp) |resp| { + resp.runCorkedWithType(*RequestContext, renderMissingCorked, ctx); + } ctx.finalize(); } pub fn renderMissingCorked(ctx: *RequestContext) void { - if (comptime !debug_mode) { - if (!ctx.has_written_status) - ctx.resp.writeStatus("204 No Content"); - ctx.has_written_status = true; - ctx.resp.end("", ctx.shouldCloseConnection()); - } else { - if (ctx.is_web_browser_navigation) { - ctx.resp.writeStatus("200 OK"); + if (ctx.resp) |resp| { + if (comptime !debug_mode) { + if (!ctx.has_written_status) + resp.writeStatus("204 No Content"); ctx.has_written_status = true; + ctx.end("", ctx.shouldCloseConnection()); + } else { + if (ctx.is_web_browser_navigation) { + resp.writeStatus("200 OK"); + ctx.has_written_status = true; + + resp.writeHeader("content-type", MimeType.html.value); + resp.writeHeader("content-encoding", "gzip"); + resp.writeHeaderInt("content-length", welcome_page_html_gz.len); + ctx.end(welcome_page_html_gz, ctx.shouldCloseConnection()); + return; + } - ctx.resp.writeHeader("content-type", MimeType.html.value); - ctx.resp.writeHeader("content-encoding", "gzip"); - ctx.resp.writeHeaderInt("content-length", welcome_page_html_gz.len); - ctx.resp.end(welcome_page_html_gz, ctx.shouldCloseConnection()); - return; + if (!ctx.has_written_status) + resp.writeStatus("200 OK"); + ctx.has_written_status = true; + ctx.end("Welcome to Bun! To get started, return a Response object.", ctx.shouldCloseConnection()); } - - if (!ctx.has_written_status) - ctx.resp.writeStatus("200 OK"); - ctx.has_written_status = true; - ctx.resp.end("Welcome to Bun! To get started, return a Response object.", ctx.shouldCloseConnection()); } } @@ -1105,9 +1119,10 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp ) void { if (!this.has_written_status) { this.has_written_status = true; - - this.resp.writeStatus("500 Internal Server Error"); - this.resp.writeHeader("content-type", MimeType.html.value); + if (this.resp) |resp| { + resp.writeStatus("500 Internal Server Error"); + resp.writeHeader("content-type", MimeType.html.value); + } } const allocator = this.allocator; @@ -1139,7 +1154,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp @TypeOf(bb_writer), bb_writer, ) catch unreachable; - if (this.resp.tryEnd(bb.items, bb.items.len, this.shouldCloseConnection())) { + if (this.resp == null or this.resp.?.tryEnd(bb.items, bb.items.len, this.shouldCloseConnection())) { bb.clearAndFree(); this.finalizeWithoutDeinit(); return; @@ -1147,49 +1162,82 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp this.has_marked_pending = true; this.response_buf_owned = std.ArrayListUnmanaged(u8){ .items = bb.items, .capacity = bb.capacity }; - this.resp.onWritable(*RequestContext, onWritableCompleteResponseBuffer, this); + + if (this.resp) |resp| { + resp.onWritable(*RequestContext, onWritableCompleteResponseBuffer, this); + } this.setAbortHandler(); } pub fn renderResponseBuffer(this: *RequestContext) void { - this.resp.onWritable(*RequestContext, onWritableResponseBuffer, this); + if (this.resp) |resp| { + resp.onWritable(*RequestContext, onWritableResponseBuffer, this); + } } /// Render a complete response buffer pub fn renderResponseBufferAndMetadata(this: *RequestContext) void { - this.renderMetadata(); + if (this.resp) |resp| { + this.renderMetadata(); - if (!this.resp.tryEnd( - this.response_buf_owned.items, - this.response_buf_owned.items.len, - this.shouldCloseConnection(), - )) { - this.has_marked_pending = true; - this.resp.onWritable(*RequestContext, onWritableCompleteResponseBuffer, this); - this.setAbortHandler(); - return; + if (!resp.tryEnd( + this.response_buf_owned.items, + this.response_buf_owned.items.len, + this.shouldCloseConnection(), + )) { + this.has_marked_pending = true; + resp.onWritable(*RequestContext, onWritableCompleteResponseBuffer, this); + this.setAbortHandler(); + return; + } } this.finalize(); } /// Drain a partial response buffer pub fn drainResponseBufferAndMetadata(this: *RequestContext) void { - this.renderMetadata(); - this.setAbortHandler(); - - _ = this.resp.write( - this.response_buf_owned.items, - ); + if (this.resp) |resp| { + this.renderMetadata(); + this.setAbortHandler(); + _ = resp.write( + this.response_buf_owned.items, + ); + } this.response_buf_owned.items.len = 0; } - pub fn renderResponseBufferAndMetadataCorked(this: *RequestContext) void { - this.resp.runCorkedWithType(*RequestContext, renderResponseBufferAndMetadata, this); + pub fn end(this: *RequestContext, data: []const u8, closeConnection: bool) void { + if (this.resp) |resp| { + if (this.is_waiting_body) { + this.is_waiting_body = false; + resp.clearOnData(); + } + resp.end(data, closeConnection); + this.resp = null; + } } - pub fn drainResponseBufferAndMetadataCorked(this: *RequestContext) void { - this.resp.runCorkedWithType(*RequestContext, drainResponseBufferAndMetadata, this); + pub fn endStream(this: *RequestContext, closeConnection: bool) void { + if (this.resp) |resp| { + if (this.is_waiting_body) { + this.is_waiting_body = false; + resp.clearOnData(); + } + resp.endStream(closeConnection); + this.resp = null; + } + } + + pub fn endWithoutBody(this: *RequestContext, closeConnection: bool) void { + if (this.resp) |resp| { + if (this.is_waiting_body) { + this.is_waiting_body = false; + resp.clearOnData(); + } + resp.endWithoutBody(closeConnection); + this.resp = null; + } } pub fn onWritableResponseBuffer(this: *RequestContext, _: c_ulong, resp: *App.Response) callconv(.C) bool { @@ -1198,7 +1246,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp this.finalizeForAbort(); return false; } - resp.end("", this.shouldCloseConnection()); + this.end("", this.shouldCloseConnection()); this.finalize(); return false; } @@ -1217,7 +1265,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp } if (this.method == .HEAD) { - resp.end("", this.shouldCloseConnection()); + this.end("", this.shouldCloseConnection()); this.finalize(); return false; } @@ -1400,6 +1448,14 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp this.allocator.free(bun.constStrToU8(this.pathname)); this.pathname = ""; } + + // if we are waiting for the body yet and the request was not aborted we can safely clear the onData callback + if (this.resp) |resp| { + if (this.is_waiting_body and this.aborted == false) { + resp.clearOnData(); + this.is_waiting_body = false; + } + } } pub fn finalize(this: *RequestContext) void { ctxLog("finalize", .{}); @@ -1424,6 +1480,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp _ = body.unref(); this.request_body = null; } + server.request_pool_allocator.destroy(this); } @@ -1434,7 +1491,9 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp headers.fastRemove(.ContentLength); headers.fastRemove(.TransferEncoding); if (!ssl_enabled) headers.fastRemove(.StrictTransportSecurity); - headers.toUWSResponse(ssl_enabled, this.resp); + if (this.resp) |resp| { + headers.toUWSResponse(ssl_enabled, resp); + } } pub fn writeStatus(this: *RequestContext, status: u16) void { @@ -1442,16 +1501,20 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp std.debug.assert(!this.has_written_status); this.has_written_status = true; - if (HTTPStatusText.get(status)) |text| { - this.resp.writeStatus(text); - } else { - this.resp.writeStatus(std.fmt.bufPrint(&status_text_buf, "{d} HM", .{status}) catch unreachable); + if (this.resp) |resp| { + if (HTTPStatusText.get(status)) |text| { + resp.writeStatus(text); + } else { + resp.writeStatus(std.fmt.bufPrint(&status_text_buf, "{d} HM", .{status}) catch unreachable); + } } } fn cleanupAndFinalizeAfterSendfile(this: *RequestContext) void { - this.resp.overrideWriteOffset(this.sendfile.offset); - this.resp.endWithoutBody(this.shouldCloseConnection()); + if (this.resp) |resp| { + resp.overrideWriteOffset(this.sendfile.offset); + this.endWithoutBody(this.shouldCloseConnection()); + } // use node syscall so that we don't segfault on BADF if (this.sendfile.auto_close) _ = JSC.Node.Syscall.close(this.sendfile.fd); @@ -1465,10 +1528,11 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp }}; pub fn onSendfile(this: *RequestContext) bool { - if (this.aborted) { + if (this.aborted or this.resp == null) { this.cleanupAndFinalizeAfterSendfile(); return false; } + const resp = this.resp.?; const adjusted_count_temporary = @min(@as(u64, this.sendfile.remain), @as(u63, std.math.maxInt(u63))); // TODO we should not need this int cast; improve the return type of `@min` @@ -1522,11 +1586,11 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp if (!this.sendfile.has_set_on_writable) { this.sendfile.has_set_on_writable = true; this.has_marked_pending = true; - this.resp.onWritable(*RequestContext, onWritableSendfile, this); + resp.onWritable(*RequestContext, onWritableSendfile, this); } this.setAbortHandler(); - this.resp.markNeedsMore(); + resp.markNeedsMore(); return true; } @@ -1555,7 +1619,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp return true; } else { this.has_marked_pending = true; - this.resp.onWritable(*RequestContext, onWritableBytes, this); + resp.onWritable(*RequestContext, onWritableBytes, this); return true; } } @@ -1569,7 +1633,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp this.finalize(); } else { this.has_marked_pending = true; - this.resp.onWritable(*RequestContext, onWritableCompleteResponseBuffer, this); + resp.onWritable(*RequestContext, onWritableCompleteResponseBuffer, this); } return true; @@ -1582,6 +1646,9 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp // We tried open() in another thread for this // it was not faster due to the mountain of syscalls pub fn renderSendFile(this: *RequestContext, blob: JSC.WebCore.Blob) void { + if (this.resp == null) return; + const resp = this.resp.?; + this.blob = .{ .Blob = blob }; const file = &this.blob.store().?.data.file; var file_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; @@ -1661,7 +1728,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp .remain = this.blob.Blob.offset + original_size, .offset = this.blob.Blob.offset, .auto_close = auto_close, - .socket_fd = if (!this.aborted) this.resp.getNativeHandle() else -999, + .socket_fd = if (!this.aborted) resp.getNativeHandle() else -999, }; // if we are sending only part of a file, include the content-range header @@ -1677,7 +1744,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp this.sendfile.remain = @min(@max(this.sendfile.remain, this.sendfile.offset), stat_size) -| this.sendfile.offset; } - this.resp.runCorkedWithType(*RequestContext, renderMetadataAndNewline, this); + resp.runCorkedWithType(*RequestContext, renderMetadataAndNewline, this); if (this.sendfile.remain == 0 or !this.method.hasBody()) { this.cleanupAndFinalizeAfterSendfile(); @@ -1688,8 +1755,10 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp } pub fn renderMetadataAndNewline(this: *RequestContext) void { - this.renderMetadata(); - this.resp.prepareForSendfile(); + if (this.resp) |resp| { + this.renderMetadata(); + resp.prepareForSendfile(); + } } pub fn doSendfile(this: *RequestContext, blob: Blob) void { @@ -1711,7 +1780,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp } pub fn onReadFile(this: *RequestContext, result: Blob.Store.ReadFile.ResultType) void { - if (this.aborted) { + if (this.aborted or this.resp == null) { this.finalizeForAbort(); return; } @@ -1747,7 +1816,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp }; this.response_buf_owned = .{ .items = result.result.buf, .capacity = result.result.buf.len }; - this.renderResponseBufferAndMetadataCorked(); + this.resp.?.runCorkedWithType(*RequestContext, renderResponseBufferAndMetadata, this); } } @@ -1776,6 +1845,12 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp fn doRenderStream(pair: *StreamPair) void { var this = pair.this; var stream = pair.stream; + if (this.resp == null or this.aborted) { + stream.value.unprotect(); + this.finalizeForAbort(); + return; + } + const resp = this.resp.?; // uWS automatically adds the status line if needed // we want to batch network calls as much as possible @@ -1788,7 +1863,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp var response_stream = this.allocator.create(ResponseStream.JSSink) catch unreachable; response_stream.* = ResponseStream.JSSink{ .sink = .{ - .res = this.resp, + .res = resp, .allocator = this.allocator, .buffer = bun.ByteList{}, }, @@ -1816,7 +1891,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp std.debug.assert(!signal.isDead()); if (comptime Environment.allow_assert) { - if (this.resp.hasResponded()) { + if (resp.hasResponded()) { streamLog("responded", .{}); } } @@ -1825,7 +1900,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp if (assignment_result.toError()) |err_value| { streamLog("returned an error", .{}); - if (!this.aborted) this.resp.clearAborted(); + if (!this.aborted) resp.clearAborted(); response_stream.detach(); this.sink = null; response_stream.sink.destroy(); @@ -1835,12 +1910,12 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp if (response_stream.sink.done or // TODO: is there a condition where resp could be freed before done? - this.resp.hasResponded()) + resp.hasResponded()) { - if (!this.aborted) this.resp.clearAborted(); + if (!this.aborted) resp.clearAborted(); const wrote_anything = response_stream.sink.wrote > 0; streamLog("is done", .{}); - const responded = this.resp.hasResponded(); + const responded = resp.hasResponded(); response_stream.detach(); this.sink = null; @@ -1849,7 +1924,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp this.renderMissing(); return; } else if (wrote_anything and !responded and !this.aborted) { - this.resp.endStream(this.shouldCloseConnection()); + this.endStream(this.shouldCloseConnection()); } this.finalize(); @@ -2075,9 +2150,10 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp response_value.then(this.globalThis, ctx, RequestContext.onResolve, RequestContext.onReject); return; } - - // The user returned something that wasn't a promise or a promise with a response - if (!ctx.resp.hasResponded() and !ctx.has_marked_pending) ctx.renderMissing(); + if (ctx.resp) |resp| { + // The user returned something that wasn't a promise or a promise with a response + if (!resp.hasResponded() and !ctx.has_marked_pending) ctx.renderMissing(); + } } pub fn handleResolveStream(req: *RequestContext) void { @@ -2105,20 +2181,21 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp streamLog("onResolve({any})", .{wrote_anything}); //aborted so call finalizeForAbort - if (req.aborted) { + if (req.aborted or req.resp == null) { req.finalizeForAbort(); return; } + const resp = req.resp.?; - const responded = req.resp.hasResponded(); + const responded = resp.hasResponded(); if (!responded and !wrote_anything) { - req.resp.clearAborted(); + resp.clearAborted(); req.renderMissing(); return; } else if (!responded and wrote_anything) { - req.resp.clearAborted(); - req.resp.endStream(req.shouldCloseConnection()); + resp.clearAborted(); + req.endStream(req.shouldCloseConnection()); } req.finalize(); @@ -2176,7 +2253,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp req.handleReject(err); return; } else if (wrote_anything) { - req.resp.endStream(true); + req.endStream(true); if (comptime debug_mode) { if (!err.isEmptyOrUndefinedOrNull()) { var exception_list: std.ArrayList(Api.JsException) = std.ArrayList(Api.JsException).init(req.allocator); @@ -2247,8 +2324,10 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp .Blob, .File => unreachable, .JavaScript, .Direct => { - var pair = StreamPair{ .stream = stream, .this = this }; - this.resp.runCorkedWithType(*StreamPair, doRenderStream, &pair); + if (this.resp) |resp| { + var pair = StreamPair{ .stream = stream, .this = this }; + resp.runCorkedWithType(*StreamPair, doRenderStream, &pair); + } return; }, @@ -2258,6 +2337,12 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp stream.detach(this.server.globalThis); + if (this.resp == null) { + byte_stream.parent().deinit(); + return; + } + const resp = this.resp.?; + // If we've received the complete body by the time this function is called // we can avoid streaming it and just send it all at once. if (byte_stream.has_received_last_chunk) { @@ -2277,10 +2362,10 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp // if we've received metadata and part of the body, send everything we can and drain if (this.response_buf_owned.items.len > 0) { - this.drainResponseBufferAndMetadataCorked(); + resp.runCorkedWithType(*RequestContext, drainResponseBufferAndMetadata, this); } else { // if we only have metadata to send, send it now - this.resp.runCorkedWithType(*RequestContext, renderMetadata, this); + resp.runCorkedWithType(*RequestContext, renderMetadata, this); } this.setAbortHandler(); return; @@ -2313,26 +2398,27 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp } } - if (this.aborted) { + if (this.aborted or this.resp == null) { this.finalizeForAbort(); return; } + const resp = this.resp.?; const chunk = stream.slice(); // on failure, it will continue to allocate // we can't do buffering ourselves here or it won't work // uSockets will append and manage the buffer // so any write will buffer if the write fails - if (this.resp.write(chunk)) { + if (resp.write(chunk)) { if (stream.isDone()) { - this.resp.endStream(this.shouldCloseConnection()); + this.endStream(this.shouldCloseConnection()); this.finalize(); } } else { // when it's the last one, we just want to know if it's done if (stream.isDone()) { this.has_marked_pending = true; - this.resp.onWritable(*RequestContext, onWritableResponseBuffer, this); + resp.onWritable(*RequestContext, onWritableResponseBuffer, this); } } } @@ -2344,7 +2430,9 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp // We are not streaming // This is an important performance optimization if (this.has_abort_handler and this.blob.size() < 16384 - 1024) { - this.resp.runCorkedWithType(*RequestContext, doRenderBlobCorked, this); + if (this.resp) |resp| { + resp.runCorkedWithType(*RequestContext, doRenderBlobCorked, this); + } } else { this.doRenderBlobCorked(); } @@ -2367,26 +2455,26 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp } pub fn renderProductionError(this: *RequestContext, status: u16) void { - switch (status) { - 404 => { - if (!this.has_written_status) { - this.resp.writeStatus("404 Not Found"); - this.has_written_status = true; - } - - this.resp.endWithoutBody(this.shouldCloseConnection()); - }, - else => { - if (!this.has_written_status) { - this.resp.writeStatus("500 Internal Server Error"); - this.resp.writeHeader("content-type", "text/plain"); - this.has_written_status = true; - } + if (this.resp) |resp| { + switch (status) { + 404 => { + if (!this.has_written_status) { + resp.writeStatus("404 Not Found"); + this.has_written_status = true; + } + this.endWithoutBody(this.shouldCloseConnection()); + }, + else => { + if (!this.has_written_status) { + resp.writeStatus("500 Internal Server Error"); + resp.writeHeader("content-type", "text/plain"); + this.has_written_status = true; + } - this.resp.end("Something went wrong!", true); - }, + this.end("Something went wrong!", this.shouldCloseConnection()); + }, + } } - this.finalize(); } @@ -2409,7 +2497,10 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp } pub inline fn shouldCloseConnection(this: *const RequestContext) bool { - return this.resp.state().isHttpConnectionClose(); + if (this.resp) |resp| { + return resp.state().isHttpConnectionClose(); + } + return false; } fn finishRunningErrorHandler(this: *RequestContext, value: JSC.JSValue, status: u16) void { @@ -2466,12 +2557,15 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp status: u16, ) void { JSC.markBinding(@src()); - if (this.resp.hasResponded()) return; + if (this.resp == null or this.resp.?.hasResponded()) return; runErrorHandlerWithStatusCodeDontCheckResponded(this, value, status); } pub fn renderMetadata(this: *RequestContext) void { + if (this.resp == null) return; + const resp = this.resp.?; + var response: *JSC.WebCore.Response = this.response_ptr.?; var status = response.statusCode(); var needs_content_range = this.needs_content_range and this.sendfile.remain < this.blob.size(); @@ -2532,7 +2626,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp // we may not know the content-type when streaming (!this.blob.isDetached() or content_type.value.ptr != MimeType.other.value.ptr)) { - this.resp.writeHeader("content-type", content_type.value); + resp.writeHeader("content-type", content_type.value); } // automatically include the filename when: @@ -2546,7 +2640,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp if (basename.len > 0) { var filename_buf: [1024]u8 = undefined; - this.resp.writeHeader( + resp.writeHeader( "content-disposition", std.fmt.bufPrint(&filename_buf, "filename=\"{s}\"", .{basename[0..@min(basename.len, 1024 - 32)]}) catch "", ); @@ -2557,14 +2651,14 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp } if (this.needs_content_length) { - this.resp.writeHeaderInt("content-length", size); + resp.writeHeaderInt("content-length", size); this.needs_content_length = false; } if (needs_content_range) { var content_range_buf: [1024]u8 = undefined; - this.resp.writeHeader( + resp.writeHeader( "content-range", std.fmt.bufPrint( &content_range_buf, @@ -2583,17 +2677,18 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp // copy it to stack memory to prevent aliasing issues in release builds const blob = this.blob; const bytes = blob.slice(); - - if (!this.resp.tryEnd( - bytes, - bytes.len, - this.shouldCloseConnection(), - )) { - this.has_marked_pending = true; - this.resp.onWritable(*RequestContext, onWritableBytes, this); - // given a blob, we might not have set an abort handler yet - this.setAbortHandler(); - return; + if (this.resp) |resp| { + if (!resp.tryEnd( + bytes, + bytes.len, + this.shouldCloseConnection(), + )) { + this.has_marked_pending = true; + resp.onWritable(*RequestContext, onWritableBytes, this); + // given a blob, we might not have set an abort handler yet + this.setAbortHandler(); + return; + } } this.finalize(); @@ -2608,9 +2703,11 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp pub fn onBufferedBodyChunk(this: *RequestContext, resp: *App.Response, chunk: []const u8, last: bool) void { ctxLog("onBufferedBodyChunk {} {}", .{ chunk.len, last }); + std.debug.assert(this.resp == resp); - if (this.aborted) return; + this.is_waiting_body = last == false; + if (this.aborted or this.has_marked_complete) return; if (this.request_body != null) { var body = this.request_body.?; @@ -4287,13 +4384,14 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type { } var upgrader = bun.cast(*RequestContext, request.upgrader.?); - if (upgrader.aborted) { + if (upgrader.aborted or upgrader.resp == null) { return JSC.jsBoolean(false); } if (upgrader.upgrade_context == null or @ptrToInt(upgrader.upgrade_context) == std.math.maxInt(usize)) { return JSC.jsBoolean(false); } + const resp = upgrader.resp.?; var ctx = upgrader.upgrade_context.?; var sec_websocket_key_str = ZigString.Empty; @@ -4374,8 +4472,8 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type { // TODO: should we cork? // we must write the status first so that 200 OK isn't written - upgrader.resp.writeStatus("101 Switching Protocols"); - fetch_headers_to_use.toUWSResponse(comptime ssl_enabled, upgrader.resp); + resp.writeStatus("101 Switching Protocols"); + fetch_headers_to_use.toUWSResponse(comptime ssl_enabled, resp); } } } @@ -4387,7 +4485,8 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type { upgrader.upgrade_context = @intToPtr(*uws.uws_socket_context_s, std.math.maxInt(usize)); request.upgrader = null; - upgrader.resp.clearAborted(); + resp.clearAborted(); + var ws = this.vm.allocator.create(ServerWebSocket) catch return .zero; ws.* = .{ .handler = &this.config.websocket.?.handler, @@ -4399,7 +4498,7 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type { var sec_websocket_extensions_str = sec_websocket_extensions.toSlice(bun.default_allocator); defer sec_websocket_extensions_str.deinit(); - upgrader.resp.upgrade( + resp.upgrade( *ServerWebSocket, ws, sec_websocket_key_str.slice(), @@ -4957,6 +5056,7 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type { .onStartStreaming = RequestContext.onStartStreamingRequestBodyCallback, }, }; + ctx.is_waiting_body = true; resp.onData(*RequestContext, RequestContext.onBufferedBodyChunk, ctx); } } diff --git a/src/bun.js/webcore/blob.zig b/src/bun.js/webcore/blob.zig index 5723a36b4..f0692d482 100644 --- a/src/bun.js/webcore/blob.zig +++ b/src/bun.js/webcore/blob.zig @@ -3109,7 +3109,6 @@ pub const Blob = struct { } var view_ = this.sharedView(); - bloblog("sharedView {d}", .{view_.len}); if (view_.len == 0) return JSC.ArrayBuffer.create(global, "", .ArrayBuffer); diff --git a/src/bun.js/webcore/body.zig b/src/bun.js/webcore/body.zig index a4446446b..b39fb1761 100644 --- a/src/bun.js/webcore/body.zig +++ b/src/bun.js/webcore/body.zig @@ -269,6 +269,7 @@ pub const Body = struct { var promise = JSC.JSPromise.create(globalThis); const promise_value = promise.asValue(globalThis); value.promise = promise_value; + promise_value.protect(); if (value.onStartBuffering) |onStartBuffering| { value.onStartBuffering = null; @@ -290,6 +291,7 @@ pub const Body = struct { /// This is a duplex stream! pub const Value = union(Tag) { + const log = Output.scoped(.BodyValue, false); Blob: Blob, /// Single-use Blob /// Avoids a heap allocation. @@ -636,6 +638,7 @@ pub const Body = struct { } pub fn resolve(to_resolve: *Value, new: *Value, global: *JSGlobalObject) void { + log("resolve", .{}); if (to_resolve.* == .Locked) { var locked = &to_resolve.Locked; if (locked.readable) |readable| { diff --git a/src/deps/libuwsockets.cpp b/src/deps/libuwsockets.cpp index cbf399f20..1533787ee 100644 --- a/src/deps/libuwsockets.cpp +++ b/src/deps/libuwsockets.cpp @@ -1261,14 +1261,22 @@ extern "C" if (ssl) { uWS::HttpResponse<true> *uwsRes = (uWS::HttpResponse<true> *)res; - uwsRes->onData([handler, res, opcional_data](auto chunk, bool is_end) - { handler(res, chunk.data(), chunk.length(), is_end, opcional_data); }); + if (handler) { + uwsRes->onData([handler, res, opcional_data](auto chunk, bool is_end) + { handler(res, chunk.data(), chunk.length(), is_end, opcional_data); }); + } else { + uwsRes->onData(nullptr); + } } else { uWS::HttpResponse<false> *uwsRes = (uWS::HttpResponse<false> *)res; - uwsRes->onData([handler, res, opcional_data](auto chunk, bool is_end) - { handler(res, chunk.data(), chunk.length(), is_end, opcional_data); }); + if (handler) { + uwsRes->onData([handler, res, opcional_data](auto chunk, bool is_end) + { handler(res, chunk.data(), chunk.length(), is_end, opcional_data); }); + } else { + uwsRes->onData(nullptr); + } } } diff --git a/src/deps/uws.zig b/src/deps/uws.zig index 2dce774ef..7a4ae7b86 100644 --- a/src/deps/uws.zig +++ b/src/deps/uws.zig @@ -1448,6 +1448,10 @@ pub fn NewApp(comptime ssl: bool) type { uws_res_on_aborted(ssl_flag, res.downcast(), null, null); } + pub fn clearOnData(res: *Response) void { + uws_res_on_data(ssl_flag, res.downcast(), null, null); + } + pub fn onData( res: *Response, comptime UserDataType: type, diff --git a/src/url.zig b/src/url.zig index a2d67d93d..1e1b284b5 100644 --- a/src/url.zig +++ b/src/url.zig @@ -856,6 +856,7 @@ pub const PercentEncoding = struct { pub const FormData = struct { fields: Map, buffer: []const u8, + const log = Output.scoped(.FormData, false); pub const Map = std.ArrayHashMapUnmanaged( bun.Semver.String, @@ -907,6 +908,7 @@ pub const FormData = struct { pub fn toJS(this: *AsyncFormData, global: *bun.JSC.JSGlobalObject, data: []const u8, promise: bun.JSC.AnyPromise) void { if (this.encoding == .Multipart and this.encoding.Multipart.len == 0) { + log("AsnycFormData.toJS -> promise.reject missing boundary", .{}); promise.reject(global, bun.JSC.ZigString.init("FormData missing boundary").toErrorInstance(global)); return; } @@ -916,10 +918,10 @@ pub const FormData = struct { data, this.encoding, ) catch |err| { + log("AsnycFormData.toJS -> failed ", .{}); promise.reject(global, global.createErrorInstance("FormData {s}", .{@errorName(err)})); return; }; - promise.resolve(global, js_value); } }; @@ -976,7 +978,10 @@ pub const FormData = struct { ) !bun.JSC.JSValue { const form_data_value = bun.JSC.DOMFormData.create(globalThis); form_data_value.ensureStillAlive(); - var form = bun.JSC.DOMFormData.fromJS(form_data_value).?; + var form = bun.JSC.DOMFormData.fromJS(form_data_value) orelse { + log("failed to create DOMFormData.fromJS", .{}); + return error.@"failed to parse multipart data"; + }; const Wrapper = struct { globalThis: *bun.JSC.JSGlobalObject, form: *bun.JSC.DOMFormData, @@ -1024,7 +1029,10 @@ pub const FormData = struct { .form = form, }; - try forEachMultipartEntry(input, boundary, *Wrapper, &wrap, Wrapper.onEntry); + forEachMultipartEntry(input, boundary, *Wrapper, &wrap, Wrapper.onEntry) catch |err| { + log("failed to parse multipart data", .{}); + return err; + }; } return form_data_value; |