aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/deps/uws.zig14
-rw-r--r--src/javascript/jsc/api/server.zig116
-rw-r--r--src/javascript/jsc/webcore/response.zig32
3 files changed, 132 insertions, 30 deletions
diff --git a/src/deps/uws.zig b/src/deps/uws.zig
index 2f590606c..792c11aa2 100644
--- a/src/deps/uws.zig
+++ b/src/deps/uws.zig
@@ -158,13 +158,15 @@ pub const Request = opaque {
var ptr: [*]const u8 = undefined;
return ptr[0..req.uws_req_get_method(&ptr)];
}
- pub fn header(req: *Request, name: []const u8) []const u8 {
+ pub fn header(req: *Request, name: []const u8) ?[]const u8 {
var ptr: [*]const u8 = undefined;
- return ptr[0..req.uws_req_get_header(req, name.ptr, name.len, &ptr)];
+ const len = req.uws_req_get_header(name.ptr, name.len, &ptr);
+ if (len == 0) return null;
+ return ptr[0..len];
}
pub fn query(req: *Request, name: []const u8) []const u8 {
var ptr: [*]const u8 = undefined;
- return ptr[0..req.uws_req_get_query(req, name.ptr, name.len, &ptr)];
+ return ptr[0..req.uws_req_get_query(name.ptr, name.len, &ptr)];
}
pub fn parameter(req: *Request, index: u16) []const u8 {
var ptr: [*]const u8 = undefined;
@@ -173,7 +175,7 @@ pub const Request = opaque {
pub fn cloneHeaders(
req: *Request,
- ctx: *anyopaque,
+ ctx: *anyopaque,
) void {
uws_req_clone_headers(req, ctx);
}
@@ -190,7 +192,7 @@ pub const Request = opaque {
extern fn uws_req_set_field(res: *Request, yield: bool) void;
extern fn uws_req_get_url(res: *Request, dest: *[*]const u8) usize;
extern fn uws_req_get_method(res: *Request, dest: *[*]const u8) usize;
- extern fn uws_req_get_header(res: *Request, lower_case_header: *[*]const u8, lower_case_header_length: usize, dest: *[*]const u8) usize;
+ extern fn uws_req_get_header(res: *Request, lower_case_header: [*]const u8, lower_case_header_length: usize, dest: *[*]const u8) usize;
extern fn uws_req_get_query(res: *Request, key: [*c]const u8, key_length: usize, dest: *[*]const u8) usize;
extern fn uws_req_get_parameter(res: *Request, index: c_ushort, dest: *[*]const u8) usize;
};
@@ -511,7 +513,7 @@ pub fn NewApp(comptime ssl: bool) type {
pub fn onData(
res: *Response,
comptime UserDataType: type,
- comptime handler: fn (*Response, chunk: []const u8, last: bool, UserDataType) void,
+ comptime handler: fn (UserDataType, *Response, chunk: []const u8, last: bool) void,
opcional_data: UserDataType,
) void {
const Wrapper = struct {
diff --git a/src/javascript/jsc/api/server.zig b/src/javascript/jsc/api/server.zig
index d7e7ccd64..f0cb7af03 100644
--- a/src/javascript/jsc/api/server.zig
+++ b/src/javascript/jsc/api/server.zig
@@ -91,7 +91,8 @@ const linux = std.os.linux;
pub const ServerConfig = struct {
port: u16 = 0,
- hostnames: std.ArrayList([*:0]const u8) = .{},
+ hostnames: std.ArrayListUnmanaged([*:0]const u8) = .{},
+ max_request_body_size: usize = 1024 * 1024 * 128,
};
pub fn NewServer(comptime ssl_enabled: bool) type {
@@ -108,7 +109,7 @@ pub fn NewServer(comptime ssl_enabled: bool) type {
globalThis: *JSGlobalObject,
default_server: URL = URL{ .host = "localhost", .port = "3000" },
response_objects_pool: JSC.WebCore.Response.Pool = JSC.WebCore.Response.Pool{},
-
+ config: ServerConfig = ServerConfig{},
request_pool_allocator: std.mem.Allocator = undefined,
pub fn init(port: u16, callback: JSC.JSValue, globalThis: *JSGlobalObject) *ThisServer {
@@ -152,6 +153,8 @@ pub fn NewServer(comptime ssl_enabled: bool) type {
has_sendfile_ctx: bool = false,
sendfile: SendfileContext = undefined,
request_js_object: JSC.C.JSObjectRef = null,
+ request_body_buf: std.ArrayListUnmanaged(u8) = .{},
+
pub threadlocal var pool: *RequestContextStackAllocator = undefined;
pub fn setAbortHandler(this: *RequestContext) void {
@@ -177,7 +180,8 @@ pub fn NewServer(comptime ssl_enabled: bool) type {
}
var response = arguments[0].as(JSC.WebCore.Response) orelse {
- Output.prettyErrorln("Expected serverless to return a Response", .{});
+ Output.prettyErrorln("Expected a Response object", .{});
+ Output.flush();
ctx.req.setYield(true);
ctx.finalize();
return;
@@ -190,12 +194,13 @@ pub fn NewServer(comptime ssl_enabled: bool) type {
_: *JSC.JSGlobalObject,
arguments: []const JSC.JSValue,
) void {
+ JSC.VirtualMachine.vm.defaultErrorHandler(arguments[0], null);
+
if (ctx.aborted) {
ctx.finalize();
return;
}
- JSC.VirtualMachine.vm.defaultErrorHandler(arguments[0], null);
ctx.req.setYield(true);
ctx.finalize();
}
@@ -212,22 +217,31 @@ pub fn NewServer(comptime ssl_enabled: bool) type {
pub fn onAbort(this: *RequestContext, _: *App.Response) void {
this.aborted = true;
- this.req = undefined;
- if (!this.response_jsvalue.isEmpty()) {
- this.server.response_objects_pool.push(this.server.globalThis, this.response_jsvalue);
- this.response_jsvalue = JSC.JSValue.zero;
- }
+ this.finalizeWithoutDeinit();
}
- pub fn finalize(this: *RequestContext) void {
+ // This function may be called multiple times
+ // so it's important that we can safely do that
+ pub fn finalizeWithoutDeinit(this: *RequestContext) void {
this.blob.detach();
+ this.request_body_buf.clearAndFree(bun.default_allocator);
+
if (!this.response_jsvalue.isEmpty()) {
this.server.response_objects_pool.push(this.server.globalThis, this.response_jsvalue);
this.response_jsvalue = JSC.JSValue.zero;
}
if (this.request_js_object != null) {
+ // User called .blob(), .json(), text(), or .arrayBuffer() on the Request object
+ // but we received nothing
if (JSC.JSValue.fromRef(this.request_js_object).as(Request)) |req| {
+ if (req.body == .Locked and req.body.Locked.action != .none and req.body.Locked.promise != null) {
+ var old_body = req.body;
+ req.body = JSC.WebCore.Body.Value.empty;
+ old_body.Locked.callback = null;
+ old_body.resolve(&req.body, this.server.globalThis);
+ VirtualMachine.vm.tick();
+ }
req.uws_request = null;
JSC.C.JSValueUnprotect(this.server.globalThis.ref(), this.request_js_object);
this.request_js_object = null;
@@ -243,6 +257,9 @@ pub fn NewServer(comptime ssl_enabled: bool) type {
this.response_headers.?.deref();
this.response_headers = null;
}
+ }
+ pub fn finalize(this: *RequestContext) void {
+ this.finalizeWithoutDeinit();
this.server.request_pool_allocator.destroy(this);
}
@@ -469,6 +486,76 @@ pub fn NewServer(comptime ssl_enabled: bool) type {
// this.resp.runCorked(*RequestContext, doRender, this);
this.doRender();
}
+
+ pub fn resolveRequestBody(this: *RequestContext) void {
+ if (this.aborted)
+ return;
+ if (JSC.JSValue.fromRef(this.request_js_object).as(Request)) |req| {
+ var bytes = this.request_body_buf.toOwnedSlice(bun.default_allocator);
+ var old = req.body;
+ req.body = .{
+ .Blob = Blob.init(bytes, bun.default_allocator, this.server.globalThis),
+ };
+ old.resolve(&req.body, this.server.globalThis);
+ VirtualMachine.vm.tick();
+ return;
+ }
+ }
+
+ pub fn onBodyChunk(this: *RequestContext, _: *App.Response, chunk: []const u8, last: bool) void {
+ if (this.aborted) return;
+ this.request_body_buf.appendSlice(bun.default_allocator, chunk) catch @panic("Out of memory while allocating request body");
+
+ if (last) {
+ if (JSC.JSValue.fromRef(this.request_js_object).as(Request) != null) {
+ uws.Loop.get().?.nextTick(*RequestContext, this, resolveRequestBody);
+ } else {
+ this.request_body_buf.deinit(bun.default_allocator);
+ this.request_body_buf = .{};
+ }
+ }
+ }
+
+ pub fn onRequestData(this: *RequestContext) void {
+ if (this.req.header("content-length")) |content_length| {
+ const len = std.fmt.parseInt(usize, content_length, 10) catch 0;
+ if (len == 0) {
+ if (JSC.JSValue.fromRef(this.request_js_object).as(Request)) |req| {
+ var old = req.body;
+ old.Locked.callback = null;
+ req.body = .{ .Empty = .{} };
+ old.resolve(&req.body, this.server.globalThis);
+ VirtualMachine.vm.tick();
+ return;
+ }
+ }
+
+ if (len >= this.server.config.max_request_body_size) {
+ if (JSC.JSValue.fromRef(this.request_js_object).as(Request)) |req| {
+ var old = req.body;
+ old.Locked.callback = null;
+ req.body = .{ .Empty = .{} };
+ old.toError(error.RequestBodyTooLarge, this.server.globalThis);
+ VirtualMachine.vm.tick();
+ return;
+ }
+
+ this.resp.writeStatus("413 Request Entity Too Large");
+ this.resp.endWithoutBody();
+ this.finalize();
+ return;
+ }
+
+ this.request_body_buf.ensureTotalCapacityPrecise(bun.default_allocator, len) catch @panic("Out of memory while allocating request body buffer");
+ }
+ this.setAbortHandler();
+
+ this.resp.onData(*RequestContext, onBodyChunk, this);
+ }
+
+ pub fn onRequestDataCallback(this: *anyopaque) void {
+ onRequestData(bun.cast(*RequestContext, this));
+ }
};
pub fn onRequest(this: *ThisServer, req: *uws.Request, resp: *App.Response) void {
@@ -481,6 +568,13 @@ pub fn NewServer(comptime ssl_enabled: bool) type {
.url = JSC.ZigString.init(ctx.url),
.method = ctx.method,
.uws_request = req,
+ .body = .{
+ .Locked = .{
+ .task = ctx,
+ .global = this.globalThis,
+ .onRequestData = RequestContext.onRequestDataCallback,
+ },
+ },
};
// We keep the Request object alive for the duration of the request so that we can remove the pointer to the UWS request object.
var args = [_]JSC.C.JSValueRef{JSC.WebCore.Request.Class.make(this.globalThis.ref(), request_object)};
@@ -494,7 +588,7 @@ pub fn NewServer(comptime ssl_enabled: bool) type {
return;
}
- if (ctx.response_jsvalue.isUndefinedOrNull()) {
+ if (ctx.response_jsvalue.isUndefinedOrNull() or ctx.response_jsvalue.isError() or ctx.response_jsvalue.isAggregateError(this.globalThis) or ctx.response_jsvalue.isException(this.globalThis.vm())) {
req.setYield(true);
ctx.finalize();
return;
diff --git a/src/javascript/jsc/webcore/response.zig b/src/javascript/jsc/webcore/response.zig
index b9cfdc415..407d1fc6f 100644
--- a/src/javascript/jsc/webcore/response.zig
+++ b/src/javascript/jsc/webcore/response.zig
@@ -4111,10 +4111,24 @@ pub const Body = struct {
promise: ?JSValue = null,
global: *JSGlobalObject,
task: ?*anyopaque = null,
+ /// runs after the data is available.
callback: ?fn (ctx: *anyopaque, value: *Value) void = null,
+ /// conditionally runs when requesting data
+ /// used in HTTP server to ignore request bodies unless asked for it
+ onRequestData: ?fn (ctx: *anyopaque) void = null,
deinit: bool = false,
action: Action = Action.none,
+ pub fn setPromise(value: *PendingValue, globalThis: *JSC.JSGlobalObject, action: Action) void {
+ value.action = action;
+ var promise = JSC.JSPromise.create(globalThis);
+ value.promise = promise.asValue(globalThis);
+ if (value.onRequestData) |onRequestData| {
+ value.onRequestData = null;
+ onRequestData(value.task.?);
+ }
+ }
+
pub const Action = enum {
none,
getText,
@@ -4150,6 +4164,7 @@ pub const Body = struct {
}
if (locked.promise) |promise| {
+ locked.promise = null;
var blob = new.use();
switch (locked.action) {
@@ -4177,7 +4192,6 @@ pub const Body = struct {
},
}
JSC.C.JSValueUnprotect(global.ref(), promise.asObjectRef());
- locked.promise = null;
}
}
}
@@ -4718,9 +4732,7 @@ fn BlobInterface(comptime Type: type) type {
) js.JSValueRef {
var value = this.getBodyValue();
if (value.* == .Locked) {
- value.Locked.action = .getText;
- var promise = JSC.JSPromise.create(ctx.ptr());
- value.Locked.promise = promise.asValue(ctx.ptr());
+ value.Locked.setPromise(ctx.ptr(), .getText);
return value.Locked.promise.?.asObjectRef();
}
@@ -4738,9 +4750,7 @@ fn BlobInterface(comptime Type: type) type {
) js.JSValueRef {
var value = this.getBodyValue();
if (value.* == .Locked) {
- value.Locked.action = .getJSON;
- var promise = JSC.JSPromise.create(ctx.ptr());
- value.Locked.promise = promise.asValue(ctx.ptr());
+ value.Locked.setPromise(ctx.ptr(), .getJSON);
return value.Locked.promise.?.asObjectRef();
}
@@ -4758,9 +4768,7 @@ fn BlobInterface(comptime Type: type) type {
var value = this.getBodyValue();
if (value.* == .Locked) {
- value.Locked.action = .getArrayBuffer;
- var promise = JSC.JSPromise.create(ctx.ptr());
- value.Locked.promise = promise.asValue(ctx.ptr());
+ value.Locked.setPromise(ctx.ptr(), .getArrayBuffer);
return value.Locked.promise.?.asObjectRef();
}
@@ -4778,9 +4786,7 @@ fn BlobInterface(comptime Type: type) type {
) js.JSValueRef {
var value = this.getBodyValue();
if (value.* == .Locked) {
- value.Locked.action = .getBlob;
- var promise = JSC.JSPromise.create(ctx.ptr());
- value.Locked.promise = promise.asValue(ctx.ptr());
+ value.Locked.setPromise(ctx.ptr(), .getBlob);
return value.Locked.promise.?.asObjectRef();
}