aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Ciro Spaciari <ciro.spaciari@gmail.com> 2023-05-08 18:10:40 -0300
committerGravatar GitHub <noreply@github.com> 2023-05-08 14:10:40 -0700
commitc6c21eeba749a5ebbc7a3f9dc3a0f7e5a702e0da (patch)
treea6f7b5b616e492ace2439dfca9544f157e2fcb03
parent14597dbcdc318439d1ba3a7bdbf20d7b5d3c51ef (diff)
downloadbun-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.zig372
-rw-r--r--src/bun.js/webcore/blob.zig1
-rw-r--r--src/bun.js/webcore/body.zig3
-rw-r--r--src/deps/libuwsockets.cpp16
-rw-r--r--src/deps/uws.zig4
-rw-r--r--src/url.zig14
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;