diff options
| author | 2022-12-04 00:55:05 -0800 | |
|---|---|---|
| committer | 2022-12-04 00:55:05 -0800 | |
| commit | 0617896d7045e129abac8d7fd22df0e6626d92c8 (patch) | |
| tree | 144c597fbff7c071713acea7a1d2e7e02faffbb4 /src | |
| parent | 1c3cb22d1f99d112200ae689896d46543631fad2 (diff) | |
| download | bun-0617896d7045e129abac8d7fd22df0e6626d92c8.tar.gz bun-0617896d7045e129abac8d7fd22df0e6626d92c8.tar.zst bun-0617896d7045e129abac8d7fd22df0e6626d92c8.zip | |
[Bun.serve] Implement `Content-Range` support with `Bun.file()`
Diffstat (limited to 'src')
| -rw-r--r-- | src/bun.js/api/server.zig | 120 | ||||
| -rw-r--r-- | src/bun.js/webcore/response.zig | 5 | ||||
| -rw-r--r-- | src/bun.js/webcore/streams.zig | 1 |
3 files changed, 111 insertions, 15 deletions
diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig index e2892f88f..9f3256c97 100644 --- a/src/bun.js/api/server.zig +++ b/src/bun.js/api/server.zig @@ -646,6 +646,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp has_sendfile_ctx: bool = false, has_called_error_handler: bool = false, needs_content_length: bool = false, + needs_content_range: bool = false, sendfile: SendfileContext = undefined, request_js_object: JSC.C.JSObjectRef = null, request_body_buf: std.ArrayListUnmanaged(u8) = .{}, @@ -883,6 +884,28 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp return false; } + // TODO: should we cork? + pub fn onWritableCompleteResponseBufferAndMetadata(this: *RequestContext, write_offset: c_ulong, resp: *App.Response) callconv(.C) bool { + std.debug.assert(this.resp == resp); + + if (this.aborted) { + this.finalizeForAbort(); + return false; + } + + if (!this.has_written_status) { + this.renderMetadata(); + } + + if (this.method == .HEAD) { + resp.end("", this.shouldCloseConnection()); + this.finalize(); + return false; + } + + return this.sendWritableBytesForCompleteResponseBuffer(this.response_buf_owned.items, write_offset, resp); + } + pub fn onWritableCompleteResponseBuffer(this: *RequestContext, write_offset: c_ulong, resp: *App.Response) callconv(.C) bool { std.debug.assert(this.resp == resp); if (this.aborted) { @@ -1119,7 +1142,7 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp const errcode = linux.getErrno(val); - this.sendfile.remain -= @intCast(Blob.SizeType, this.sendfile.offset - start); + this.sendfile.remain -|= @intCast(Blob.SizeType, this.sendfile.offset -| start); if (errcode != .SUCCESS or this.aborted or this.sendfile.remain == 0 or val == 0) { if (errcode != .AGAIN and errcode != .SUCCESS and errcode != .PIPE) { @@ -1132,7 +1155,6 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp } else { var sbytes: std.os.off_t = adjusted_count; const signed_offset = @bitCast(i64, @as(u64, this.sendfile.offset)); - const errcode = std.c.getErrno(std.c.sendfile( this.sendfile.fd, this.sendfile.socket_fd, @@ -1143,8 +1165,8 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp 0, )); const wrote = @intCast(Blob.SizeType, sbytes); - this.sendfile.offset += wrote; - this.sendfile.remain -= wrote; + this.sendfile.offset +|= wrote; + this.sendfile.remain -|= wrote; if (errcode != .AGAIN or this.aborted or this.sendfile.remain == 0 or sbytes == 0) { if (errcode != .AGAIN and errcode != .SUCCESS and errcode != .PIPE) { Output.prettyErrorln("Error: {s}", .{@tagName(errcode)}); @@ -1280,19 +1302,39 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp } } - this.blob.Blob.size = @intCast(Blob.SizeType, stat.size); + const original_size = this.blob.Blob.size; + const stat_size = @intCast(Blob.SizeType, stat.size); + this.blob.Blob.size = if (std.os.S.ISREG(stat.mode)) + stat_size + else + @minimum(original_size, stat_size); + this.needs_content_length = true; this.sendfile = .{ .fd = fd, - .remain = this.blob.Blob.size, + .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, }; + // if we are sending only part of a file, include the content-range header + // only include content-range automatically when using a file path instead of an fd + // this is to better support manually controlling the behavior + if (std.os.S.ISREG(stat.mode) and auto_close) { + this.needs_content_range = (this.sendfile.remain -| this.sendfile.offset) != stat_size; + } + + // we know the bounds when we are sending a regular file + if (std.os.S.ISREG(stat.mode)) { + this.sendfile.offset = @minimum(this.sendfile.offset, stat_size); + this.sendfile.remain = @minimum(@maximum(this.sendfile.remain, this.sendfile.offset), stat_size) -| this.sendfile.offset; + } + this.resp.runCorkedWithType(*RequestContext, renderMetadataAndNewline, this); - if (this.blob.Blob.size == 0) { + if (this.sendfile.remain == 0 or !this.method.hasBody()) { this.cleanupAndFinalizeAfterSendfile(); return; } @@ -1339,9 +1381,28 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp this.blob.Blob.resolveSize(); this.doRenderBlob(); } else { - this.blob.Blob.size = @truncate(Blob.SizeType, result.result.buf.len); + const stat_size = @intCast(Blob.SizeType, result.result.total_size); + const original_size = this.blob.Blob.size; + + this.blob.Blob.size = if (original_size == 0 or original_size == Blob.max_size) + stat_size + else + @minimum(original_size, stat_size); + + if (!this.has_written_status) + this.needs_content_range = true; + + // this is used by content-range + this.sendfile = .{ + .fd = @truncate(i32, bun.invalid_fd), + .remain = @truncate(Blob.SizeType, result.result.buf.len), + .offset = this.blob.Blob.offset, + .auto_close = false, + .socket_fd = -999, + }; + this.response_buf_owned = .{ .items = result.result.buf, .capacity = result.result.buf.len }; - this.resp.onWritable(*RequestContext, onWritableCompleteResponseBuffer, this); + this.resp.onWritable(*RequestContext, onWritableCompleteResponseBufferAndMetadata, this); } } @@ -2078,13 +2139,18 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp pub fn renderMetadata(this: *RequestContext) void { var response: *JSC.WebCore.Response = this.response_ptr.?; var status = response.statusCode(); - const size = this.blob.size(); + var needs_content_range = this.needs_content_range; + + const size = if (needs_content_range) + this.sendfile.remain + else + this.blob.size(); + status = if (status == 200 and size == 0 and !this.blob.isDetached()) 204 else status; - this.writeStatus(status); var needs_content_type = true; const content_type: MimeType = brk: { if (response.body.init.headers) |headers_| { @@ -2105,12 +2171,23 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp }; var has_content_disposition = false; - if (response.body.init.headers) |headers_| { - this.writeHeaders(headers_); has_content_disposition = headers_.fastHas(.ContentDisposition); + needs_content_range = needs_content_range and headers_.fastHas(.ContentRange); + if (needs_content_range) { + status = 206; + } + + this.writeStatus(status); + this.writeHeaders(headers_); + response.body.init.headers = null; headers_.deref(); + } else if (needs_content_range) { + status = 206; + this.writeStatus(status); + } else { + this.writeStatus(status); } if (needs_content_type and @@ -2146,6 +2223,23 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp this.resp.writeHeaderInt("content-length", size); this.needs_content_length = false; } + + if (needs_content_range) { + var content_range_buf: [1024]u8 = undefined; + + this.resp.writeHeader( + "content-range", + std.fmt.bufPrint( + &content_range_buf, + // we omit the full size of the Blob because it could + // change between requests and this potentially leaks + // PII undesirably + "bytes {d}-{d}/*", + .{ this.sendfile.offset, this.sendfile.offset + this.sendfile.remain }, + ) catch "bytes */*", + ); + this.needs_content_range = false; + } } pub fn renderBytes(this: *RequestContext) void { diff --git a/src/bun.js/webcore/response.zig b/src/bun.js/webcore/response.zig index d6332b993..b31bd3075 100644 --- a/src/bun.js/webcore/response.zig +++ b/src/bun.js/webcore/response.zig @@ -2010,6 +2010,7 @@ pub const Blob = struct { pub const Read = struct { buf: []u8, is_temporary: bool = false, + total_size: SizeType = 0, }; pub const ResultType = SystemError.Maybe(Read); @@ -2105,7 +2106,7 @@ pub const Blob = struct { return; } - cb(cb_ctx, .{ .result = .{ .buf = buf, .is_temporary = true } }); + cb(cb_ctx, .{ .result = .{ .buf = buf, .total_size = this.size, .is_temporary = true } }); } pub fn run(this: *ReadFile, task: *ReadFileTask) void { this.runAsync(task); @@ -2154,6 +2155,8 @@ pub const Blob = struct { const file = &this.file_store; const needs_close = fd != null_fd and file.pathlike == .path and fd > 2; + this.size = @maximum(this.read_len, this.size); + if (needs_close) { this.doClose(); } diff --git a/src/bun.js/webcore/streams.zig b/src/bun.js/webcore/streams.zig index 801fb4741..e9c075074 100644 --- a/src/bun.js/webcore/streams.zig +++ b/src/bun.js/webcore/streams.zig @@ -4445,7 +4445,6 @@ pub const FileReader = struct { return .{ .owned_and_done = this.drainInternalBuffer() }; } - return this.readable().read(buffer, view, this.globalThis()); } |
