aboutsummaryrefslogtreecommitdiff
path: root/src/javascript/jsc/api/server.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/javascript/jsc/api/server.zig')
-rw-r--r--src/javascript/jsc/api/server.zig230
1 files changed, 210 insertions, 20 deletions
diff --git a/src/javascript/jsc/api/server.zig b/src/javascript/jsc/api/server.zig
index 8c64c112a..7e2520b99 100644
--- a/src/javascript/jsc/api/server.zig
+++ b/src/javascript/jsc/api/server.zig
@@ -79,6 +79,12 @@ const IOTask = JSC.IOTask;
const is_bindgen = JSC.is_bindgen;
const uws = @import("uws");
+const SendfileContext = struct {
+ fd: i32,
+ remain: u32 = 0,
+ offset: i64 = 0,
+};
+
pub fn NewServer(comptime ssl_enabled: bool) type {
return struct {
const ThisServer = @This();
@@ -133,9 +139,17 @@ pub fn NewServer(comptime ssl_enabled: bool) type {
blob: JSC.WebCore.Blob = JSC.WebCore.Blob{},
promise: ?*JSC.JSValue = null,
response_headers: ?*JSC.WebCore.Headers.RefCountedHeaders = null,
-
+ has_abort_handler: bool = false,
+ has_sendfile_ctx: bool = false,
+ sendfile: SendfileContext = undefined,
pub threadlocal var pool: *RequestContextStackAllocator = undefined;
+ pub fn setAbortHandler(this: *RequestContext) void {
+ if (this.has_abort_handler) return;
+ this.has_abort_handler = true;
+ this.resp.onAborted(*RequestContext, RequestContext.onAbort, this);
+ }
+
pub fn onResolve(
ctx: *RequestContext,
_: *JSC.JSGlobalObject,
@@ -215,30 +229,206 @@ pub fn NewServer(comptime ssl_enabled: bool) type {
this.server.request_pool_allocator.destroy(this);
}
+ fn writeHeaders(
+ this: *RequestContext,
+ headers_: *Headers.RefCountedHeaders,
+ ) void {
+ var headers: *JSC.WebCore.Headers = headers_.get();
+ if (headers.getHeaderIndex("content-length")) |index| {
+ headers.entries.orderedRemove(index);
+ }
+ defer headers_.deref();
+ var entries = headers.entries.slice();
+ const names = entries.items(.name);
+ const values = entries.items(.value);
+
+ this.resp.writeHeaderInt("content-length", this.blob.size);
+ this.resp.writeHeaders(names, values, headers.buf.items);
+ }
+
+ pub fn writeStatus(this: *RequestContext, status: u16) void {
+ var status_text_buf: [48]u8 = undefined;
+
+ if (status == 302) {
+ this.resp.writeStatus("302 Found");
+ } else {
+ this.resp.writeStatus(std.fmt.bufPrint(&status_text_buf, "{d} HM", .{status}) catch unreachable);
+ }
+ }
+
+ fn cleanupAfterSendfile(this: *RequestContext) void {
+ this.resp.endWithoutBody();
+ std.os.close(this.sendfile.fd);
+ this.sendfile = undefined;
+ this.finalize();
+ }
+
+ pub fn onSendfile(this: *RequestContext, amount_: c_ulong, response: *App.Response) callconv(.C) bool {
+ const amount = @minimum(@truncate(u32, amount_), this.sendfile.remain);
+
+ if (amount == 0 or this.aborted) {
+ this.cleanupAfterSendfile();
+ return true;
+ }
+
+ const adjusted_count_temporary = @minimum(amount, @as(u63, std.math.maxInt(i32)));
+ // TODO we should not need this int cast; improve the return type of `@minimum`
+ const adjusted_count = @intCast(u63, adjusted_count_temporary);
+ var sbytes: std.os.off_t = adjusted_count;
+ const signed_offset = @bitCast(i64, this.sendfile.offset);
+
+ if (Environment.isLinux) {
+ const sent = @truncate(
+ u32,
+ std.os.linux.sendfile(response.getNativeHandle(), this.sendfile.fd, &this.sendfile.offset, amount),
+ );
+
+ this.sendfile.offset += sent;
+ this.sendfile.remain -= sent;
+
+ if (sent == 0 or this.aborted or this.sendfile.remain == 0) {
+ this.cleanupAfterSendfile();
+ return false;
+ }
+ } else {
+ const errcode = std.c.getErrno(std.c.sendfile(
+ this.sendfile.fd,
+ response.getNativeHandle(),
+
+ signed_offset,
+ &sbytes,
+ null,
+ 0,
+ ));
+ this.sendfile.offset += sbytes;
+ this.sendfile.remain -= if (errcode != .SUCCESS) @intCast(u32, sbytes) else 0;
+ if ((errcode != .AGAIN and errcode != .SUCCESS) or this.aborted or this.sendfile.remain == 0) {
+ if (errcode != .AGAIN and errcode != .SUCCESS) {
+ Output.prettyErrorln("Error: {s}", .{@tagName(errcode)});
+ Output.flush();
+ }
+ this.cleanupAfterSendfile();
+ return false;
+ }
+ }
+
+ this.resp.onWritable(*RequestContext, onSendfile, this);
+ return true;
+ }
+
+ pub fn onWritablePrepareSendfile(this: *RequestContext, _: c_ulong, _: *App.Response) callconv(.C) bool {
+ this.renderSendFile(this.blob);
+ return true;
+ }
+
+ pub fn onPrepareSendfileWrap(this: *anyopaque, fd: i32, size: anyerror!u32, _: *JSGlobalObject) void {
+ onPrepareSendfile(bun.cast(*RequestContext, this), fd, size);
+ }
+
+ fn onPrepareSendfile(this: *RequestContext, fd: i32, size: anyerror!u32) void {
+ this.setAbortHandler();
+ if (this.aborted) return;
+ const size_ = size catch {
+ this.req.setYield(true);
+ this.finalize();
+ return;
+ };
+ this.blob.size = size_;
+ const code = this.response_ptr.?.statusCode();
+ if (size_ == 0 and code >= 200 and code < 300) {
+ this.writeStatus(204);
+ } else {
+ this.writeStatus(code);
+ }
+
+ if (this.response_ptr.?.body.init.headers) |headers_| {
+ this.writeHeaders(headers_);
+ } else {
+ this.resp.writeHeaderInt("content-length", size_);
+ }
+
+ this.sendfile = .{
+ .fd = fd,
+ .remain = size_,
+ };
+
+ if (size_ == 0) {
+ this.cleanupAfterSendfile();
+ this.finalize();
+
+ return;
+ }
+ {
+ const wrote = std.os.write(
+ this.resp.getNativeHandle(),
+ "\r\n",
+ ) catch {
+ this.cleanupAfterSendfile();
+ return;
+ };
+ if (wrote == 0) {
+ this.cleanupAfterSendfile();
+ return;
+ }
+ }
+
+ // if we're not immediately writable, go ahead and try
+ if (this.sendfile.remain == size_) {
+ _ = this.onSendfile(size_, this.resp);
+ }
+ }
+
+ pub fn renderSendFile(this: *RequestContext, blob: JSC.WebCore.Blob) void {
+ if (this.has_sendfile_ctx) return;
+ this.has_sendfile_ctx = true;
+
+ JSC.WebCore.Blob.doOpenAndStatFile(
+ &this.blob,
+ *RequestContext,
+ this,
+ onPrepareSendfileWrap,
+ blob.globalThis,
+ );
+ }
+
pub fn doRender(this: *RequestContext) void {
if (this.aborted) {
return;
}
var response = this.response_ptr.?;
- this.blob = response.body.use();
- const status = response.statusCode();
+ var body = &response.body;
- if (response.body.init.headers) |headers_| {
- var headers: *JSC.WebCore.Headers = headers_.get();
- defer headers_.deref();
- var entries = headers.entries.slice();
- const names = entries.items(.name);
- const values = entries.items(.value);
-
- var status_text_buf: [48]u8 = undefined;
-
- if (status == 302) {
- this.resp.writeStatus("302 Found");
- } else {
- this.resp.writeStatus(std.fmt.bufPrint(&status_text_buf, "{d} HM", .{response.body.init.status_code}) catch unreachable);
+ if (body.value == .Error) {
+ this.resp.writeStatus("500 Internal Server Error");
+ this.resp.writeHeader("content-type", "text/plain");
+ this.resp.endWithoutBody();
+ JSC.VirtualMachine.vm.defaultErrorHandler(body.value.Error, null);
+ body.value = JSC.WebCore.Body.Value.empty;
+ this.finalize();
+ return;
+ }
+
+ if (body.value == .Blob) {
+ if (body.value.Blob.needsToReadFile()) {
+ this.blob = response.body.use();
+ this.req.setYield(false);
+ this.setAbortHandler();
+ this.resp.onWritable(*RequestContext, onWritablePrepareSendfile, this);
+ if (!this.has_sendfile_ctx) this.renderSendFile(this.blob);
+ return;
}
+ }
+
+ this.renderBytes(response);
+ }
- this.resp.writeHeaders(names, values, headers.buf.items);
+ pub fn renderBytes(this: *RequestContext, response: *JSC.WebCore.Response) void {
+ const status = response.statusCode();
+
+ this.writeStatus(status);
+
+ if (response.body.init.headers) |headers_| {
+ this.writeHeaders(headers_);
}
if (status == 302 or status == 202 or this.blob.size == 0) {
@@ -253,8 +443,8 @@ pub fn NewServer(comptime ssl_enabled: bool) type {
pub fn render(this: *RequestContext, response: *JSC.WebCore.Response) void {
this.response_ptr = response;
- this.resp.runCorked(*RequestContext, doRender, this);
- this.response_ptr = null;
+ // this.resp.runCorked(*RequestContext, doRender, this);
+ this.doRender();
}
};
@@ -292,7 +482,7 @@ pub fn NewServer(comptime ssl_enabled: bool) type {
}
if (ctx.response_jsvalue.jsTypeLoose() == .JSPromise) {
- resp.onAborted(*RequestContext, RequestContext.onAbort, ctx);
+ ctx.setAbortHandler();
JSC.VirtualMachine.vm.tick();
ctx.response_jsvalue.then(