diff options
-rw-r--r-- | src/bun.js/webcore/response.zig | 64 | ||||
-rw-r--r-- | src/http_client_async.zig | 194 | ||||
-rw-r--r-- | src/zlib.zig | 46 |
3 files changed, 177 insertions, 127 deletions
diff --git a/src/bun.js/webcore/response.zig b/src/bun.js/webcore/response.zig index 2164062a1..a1be53917 100644 --- a/src/bun.js/webcore/response.zig +++ b/src/bun.js/webcore/response.zig @@ -620,7 +620,7 @@ pub const Fetch = struct { http: ?*HTTPClient.AsyncHTTP = null, result: HTTPClient.HTTPClientResult = .{}, - metadata: ?HTTPClient.HTTPClientResult.ResultMetadata = .{}, + metadata: ?HTTPClient.HTTPResponseMetadata = null, javascript_vm: *VirtualMachine = undefined, global_this: *JSGlobalObject = undefined, request_body: HTTPRequestBody = undefined, @@ -695,6 +695,7 @@ pub const Fetch = struct { } fn clearData(this: *FetchTasklet) void { + log("clearData", .{}); const allocator = this.memory_reporter.allocator(); if (this.url_proxy_buffer.len > 0) { allocator.free(this.url_proxy_buffer); @@ -735,9 +736,12 @@ pub const Fetch = struct { } pub fn deinit(this: *FetchTasklet) void { + log("deinit", .{}); var reporter = this.memory_reporter; - if (this.http) |http| reporter.allocator().destroy(http); - reporter.allocator().destroy(this); + const allocator = reporter.allocator(); + + if (this.http) |http| allocator.destroy(http); + allocator.destroy(this); // reporter.assert(); bun.default_allocator.destroy(reporter); } @@ -746,11 +750,11 @@ pub const Fetch = struct { this.mutex.lock(); const success = this.result.isSuccess(); const globalThis = this.global_this; + const is_done = !success or !this.result.has_more; defer { this.has_schedule_callback.store(false, .Monotonic); this.mutex.unlock(); - - if (!success or !this.result.has_more) { + if (is_done) { var vm = globalThis.bunVM(); this.poll_ref.unref(vm); this.clearData(); @@ -836,7 +840,7 @@ pub const Fetch = struct { response.body.value = body_value; this.scheduled_response_buffer = .{ - .allocator = bun.default_allocator, + .allocator = this.memory_reporter.allocator(), .list = .{ .items = &.{}, .capacity = 0, @@ -854,6 +858,7 @@ pub const Fetch = struct { pub fn onProgressUpdate(this: *FetchTasklet) void { JSC.markBinding(@src()); + log("onProgressUpdate", .{}); if (this.is_waiting_body) { return this.onBodyReceived(); } @@ -867,6 +872,7 @@ pub const Fetch = struct { var vm = globalThis.bunVM(); if (promise_value.isEmptyOrUndefinedOrNull()) { + log("onProgressUpdate: promise_value is null", .{}); ref.strong.deinit(); this.has_schedule_callback.store(false, .Monotonic); this.mutex.unlock(); @@ -880,6 +886,7 @@ pub const Fetch = struct { const tracker = this.tracker; tracker.willDispatch(globalThis); defer { + log("onProgressUpdate: promise_value is not null", .{}); tracker.didDispatch(globalThis); ref.strong.deinit(); this.has_schedule_callback.store(false, .Monotonic); @@ -910,6 +917,8 @@ pub const Fetch = struct { } pub fn onReject(this: *FetchTasklet) JSValue { + log("onReject", .{}); + if (this.signal) |signal| { this.signal = null; signal.detach(this); @@ -931,8 +940,9 @@ pub const Fetch = struct { var path: bun.String = undefined; + // some times we don't have metadata so we also check http.url if (this.metadata) |metadata| { - path = bun.String.create(metadata.href); + path = bun.String.create(metadata.url); } else if (this.http) |http| { path = bun.String.create(http.url.href); } else { @@ -972,8 +982,9 @@ pub const Fetch = struct { var scheduled_response_buffer = this.scheduled_response_buffer.list; // This means we have received part of the body but not the whole thing if (scheduled_response_buffer.items.len > 0) { + this.memory_reporter.discard(scheduled_response_buffer.allocatedSlice()); this.scheduled_response_buffer = .{ - .allocator = default_allocator, + .allocator = this.memory_reporter.allocator(), .list = .{ .items = &.{}, .capacity = 0, @@ -1022,7 +1033,7 @@ pub const Fetch = struct { }, }; this.scheduled_response_buffer = .{ - .allocator = default_allocator, + .allocator = this.memory_reporter.allocator(), .list = .{ .items = &.{}, .capacity = 0, @@ -1033,13 +1044,15 @@ pub const Fetch = struct { } fn toResponse(this: *FetchTasklet, allocator: std.mem.Allocator) Response { + log("toResponse", .{}); + std.debug.assert(this.metadata != null); // at this point we always should have metadata var metadata = this.metadata.?; const http_response = metadata.response; this.is_waiting_body = this.result.has_more; return Response{ .allocator = allocator, - .url = bun.String.createAtomIfPossible(metadata.href), + .url = bun.String.createAtomIfPossible(metadata.url), .status_text = bun.String.createAtomIfPossible(http_response.status), .redirected = this.result.redirected, .body = .{ @@ -1053,6 +1066,7 @@ pub const Fetch = struct { } pub fn onResolve(this: *FetchTasklet) JSValue { + log("onResolve", .{}); const allocator = bun.default_allocator; var response = allocator.create(Response) catch unreachable; response.* = this.toResponse(allocator); @@ -1224,8 +1238,10 @@ pub const Fetch = struct { task.mutex.lock(); defer task.mutex.unlock(); task.result = result; + // metadata should be provided only once so we preserve it until we consume it if (result.metadata) |metadata| { + std.debug.assert(task.metadata == null); task.metadata = metadata; } task.body_size = result.body_size; @@ -1235,7 +1251,6 @@ pub const Fetch = struct { if (success) { _ = task.scheduled_response_buffer.write(task.response_buffer.list.items) catch @panic("OOM"); } - // reset for reuse task.response_buffer.reset(); @@ -1244,6 +1259,7 @@ pub const Fetch = struct { return; } } + task.javascript_vm.eventLoop().enqueueTaskConcurrent(task.concurrent_task.from(task, .manual_deinit)); } }; @@ -1357,6 +1373,7 @@ pub const Fetch = struct { // clean hostname if any if (hostname) |host| { allocator.free(host); + hostname = null; } free_memory_reporter = true; return JSPromise.rejectedPromiseValue(globalThis, err); @@ -1380,6 +1397,7 @@ pub const Fetch = struct { // clean hostname if any if (hostname) |host| { allocator.free(host); + hostname = null; } free_memory_reporter = true; return JSPromise.rejectedPromiseValue( @@ -1410,6 +1428,7 @@ pub const Fetch = struct { // clean hostname if any if (hostname) |host| { allocator.free(host); + hostname = null; } // an error was thrown return JSC.JSValue.jsUndefined(); @@ -1421,18 +1440,27 @@ pub const Fetch = struct { if (options.fastGet(ctx.ptr(), .headers)) |headers_| { if (headers_.as(FetchHeaders)) |headers__| { if (headers__.fastGet(JSC.FetchHeaders.HTTPHeaderName.Host)) |_hostname| { + if (hostname) |host| { + allocator.free(host); + } hostname = _hostname.toOwnedSliceZ(allocator) catch unreachable; } headers = Headers.from(headers__, allocator, .{ .body = &body }) catch unreachable; // TODO: make this one pass } else if (FetchHeaders.createFromJS(ctx.ptr(), headers_)) |headers__| { if (headers__.fastGet(JSC.FetchHeaders.HTTPHeaderName.Host)) |_hostname| { + if (hostname) |host| { + allocator.free(host); + } hostname = _hostname.toOwnedSliceZ(allocator) catch unreachable; } headers = Headers.from(headers__, allocator, .{ .body = &body }) catch unreachable; headers__.deref(); } else if (request.headers) |head| { if (head.fastGet(JSC.FetchHeaders.HTTPHeaderName.Host)) |_hostname| { + if (hostname) |host| { + allocator.free(host); + } hostname = _hostname.toOwnedSliceZ(allocator) catch unreachable; } headers = Headers.from(head, allocator, .{ .body = &body }) catch unreachable; @@ -1480,6 +1508,7 @@ pub const Fetch = struct { // clean hostname if any if (hostname) |host| { allocator.free(host); + hostname = null; } allocator.free(url_proxy_buffer); @@ -1504,6 +1533,9 @@ pub const Fetch = struct { body = request.body.value.useAsAnyBlob(); if (request.headers) |head| { if (head.fastGet(JSC.FetchHeaders.HTTPHeaderName.Host)) |_hostname| { + if (hostname) |host| { + allocator.free(host); + } hostname = _hostname.toOwnedSliceZ(allocator) catch unreachable; } headers = Headers.from(head, allocator, .{ .body = &body }) catch unreachable; @@ -1520,6 +1552,7 @@ pub const Fetch = struct { // clean hostname if any if (hostname) |host| { allocator.free(host); + hostname = null; } return JSPromise.rejectedPromiseValue(globalThis, err); } @@ -1541,6 +1574,7 @@ pub const Fetch = struct { // clean hostname if any if (hostname) |host| { allocator.free(host); + hostname = null; } const err = JSC.toTypeError(.ERR_INVALID_ARG_VALUE, "fetch() URL is invalid", .{}, ctx); return JSPromise.rejectedPromiseValue(globalThis, err); @@ -1567,6 +1601,7 @@ pub const Fetch = struct { // clean hostname if any if (hostname) |host| { allocator.free(host); + hostname = null; } // an error was thrown return JSC.JSValue.jsUndefined(); @@ -1576,6 +1611,9 @@ pub const Fetch = struct { if (options.fastGet(ctx.ptr(), .headers)) |headers_| { if (headers_.as(FetchHeaders)) |headers__| { if (headers__.fastGet(JSC.FetchHeaders.HTTPHeaderName.Host)) |_hostname| { + if (hostname) |host| { + allocator.free(host); + } hostname = _hostname.toOwnedSliceZ(allocator) catch unreachable; } headers = Headers.from(headers__, allocator, .{ .body = &body }) catch unreachable; @@ -1583,6 +1621,9 @@ pub const Fetch = struct { } else if (FetchHeaders.createFromJS(ctx.ptr(), headers_)) |headers__| { defer headers__.deref(); if (headers__.fastGet(JSC.FetchHeaders.HTTPHeaderName.Host)) |_hostname| { + if (hostname) |host| { + allocator.free(host); + } hostname = _hostname.toOwnedSliceZ(allocator) catch unreachable; } headers = Headers.from(headers__, allocator, .{ .body = &body }) catch unreachable; @@ -1633,6 +1674,7 @@ pub const Fetch = struct { // clean hostname if any if (hostname) |host| { allocator.free(host); + hostname = null; } allocator.free(url_proxy_buffer); free_memory_reporter = true; diff --git a/src/http_client_async.zig b/src/http_client_async.zig index 3b1982d9a..4f7647b4e 100644 --- a/src/http_client_async.zig +++ b/src/http_client_async.zig @@ -1018,7 +1018,17 @@ pub const HTTPStage = enum { pub const InternalState = struct { response_message_buffer: MutableString = undefined, - pending_response: picohttp.Response = undefined, + /// pending response is the temporary storage for the response headers, url and status code + /// this uses shared_response_headers_buf to store the headers + /// this will be turned null once the metadata is cloned + pending_response: ?picohttp.Response = null, + + /// This is the cloned metadata containing the response headers, url and status code after the .headers phase are received + /// will be turned null once returned to the user (the ownership is transferred to the user) + /// this can happen after await fetch(...) and the body can continue streaming when this is already null + /// the user will receive only chunks of the body stored in body_out_str + cloned_metadata: ?HTTPResponseMetadata = null, + allow_keepalive: bool = true, received_last_chunk: bool = false, transfer_encoding: Encoding = Encoding.identity, @@ -1027,6 +1037,7 @@ pub const InternalState = struct { chunked_decoder: picohttp.phr_chunked_decoder = .{}, zlib_reader: ?*Zlib.ZlibReaderArrayList = null, stage: Stage = Stage.pending, + /// This is owned by the user and should not be freed here body_out_str: ?*MutableString = null, compressed_body: MutableString = undefined, content_length: ?usize = null, @@ -1037,7 +1048,6 @@ pub const InternalState = struct { fail: anyerror = error.NoError, request_stage: HTTPStage = .pending, response_stage: HTTPStage = .pending, - metadata_sent: bool = false, pub fn init(body: HTTPRequestBody, body_out_str: *MutableString) InternalState { return .{ @@ -1047,7 +1057,7 @@ pub const InternalState = struct { .response_message_buffer = MutableString{ .allocator = default_allocator, .list = .{} }, .body_out_str = body_out_str, .stage = Stage.pending, - .pending_response = picohttp.Response{}, + .pending_response = null, }; } @@ -1055,7 +1065,7 @@ pub const InternalState = struct { return this.transfer_encoding == Encoding.chunked; } - pub fn reset(this: *InternalState) void { + pub fn reset(this: *InternalState, allocator: std.mem.Allocator) void { this.compressed_body.deinit(); this.response_message_buffer.deinit(); @@ -1066,6 +1076,15 @@ pub const InternalState = struct { reader.deinit(); } + // if we are holding a cloned_metadata we need to deinit it + // this should never happen because we should always return the metadata to the user + std.debug.assert(this.cloned_metadata == null); + // just in case we check and free to avoid leaks + if (this.cloned_metadata != null) { + this.cloned_metadata.?.deinit(allocator); + this.cloned_metadata = null; + } + this.* = .{ .body_out_str = body_msg, .compressed_body = MutableString{ .allocator = default_allocator, .list = .{} }, @@ -1175,23 +1194,6 @@ pub const InternalState = struct { }, } - return this.postProcessBody(); - } - - pub fn postProcessBody(this: *InternalState) usize { - - // we only touch it if we did not sent the headers yet - if (!this.metadata_sent) { - var response = &this.pending_response; - if (this.content_encoding_i < response.headers.len) { - // if it compressed with this header, it is no longer - var mutable_headers = std.ArrayListUnmanaged(picohttp.Header){ .items = response.headers, .capacity = response.headers.len }; - _ = mutable_headers.orderedRemove(this.content_encoding_i); - response.headers = mutable_headers.items; - this.content_encoding_i = std.math.maxInt(@TypeOf(this.content_encoding_i)); - } - } - return this.body_out_str.?.list.items.len; } }; @@ -1226,7 +1228,6 @@ force_last_modified: bool = false, if_modified_since: string = "", request_content_len_buf: ["-4294967295".len]u8 = undefined, -cloned_metadata: HTTPResponseMetadata = .{}, http_proxy: ?URL = null, proxy_authorization: ?[]u8 = null, proxy_tunneling: bool = false, @@ -1863,6 +1864,7 @@ pub fn buildRequest(this: *HTTPClient, body_len: usize) picohttp.Request { } pub fn doRedirect(this: *HTTPClient) void { + std.debug.assert(this.state.cloned_metadata == null); var body_out_str = this.state.body_out_str.?; this.remaining_redirect_count -|= 1; std.debug.assert(this.redirect_type == FetchRedirect.follow); @@ -1871,7 +1873,7 @@ pub fn doRedirect(this: *HTTPClient) void { this.fail(error.TooManyRedirects); return; } - this.state.reset(); + this.state.reset(this.allocator); // also reset proxy to redirect this.proxy_tunneling = false; if (this.proxy_tunnel != null) { @@ -1930,10 +1932,18 @@ fn start_(this: *HTTPClient, comptime is_ssl: bool) void { const Task = ThreadPool.Task; -const HTTPResponseMetadata = struct { +pub const HTTPResponseMetadata = struct { url: []const u8 = "", owned_buf: []u8 = "", response: picohttp.Response = .{}, + pub fn deinit(this: *HTTPResponseMetadata, allocator: std.mem.Allocator) void { + if (this.owned_buf.len > 0) allocator.free(this.owned_buf); + if (this.response.headers.len > 0) allocator.free(this.response.headers); + this.owned_buf = &.{}; + this.url = ""; + this.response.headers = &.{}; + this.response.status = ""; + } }; fn printRequest(request: picohttp.Request) void { @@ -2269,7 +2279,7 @@ fn retryProxyHandshake(this: *HTTPClient, comptime is_ssl: bool, socket: NewHTTP this.startProxySendHeaders(is_ssl, socket); } fn startProxyHandshake(this: *HTTPClient, comptime is_ssl: bool, socket: NewHTTPContext(is_ssl).HTTPSocket) void { - this.state.reset(); + this.state.reset(this.allocator); this.state.response_stage = .proxy_handshake; this.state.request_stage = .proxy_handshake; const proxy = ProxyTunnel.init(is_ssl, this, socket); @@ -2309,9 +2319,10 @@ pub fn onData(this: *HTTPClient, comptime is_ssl: bool, incoming_data: []const u needs_move = false; } + // we reset the pending_response each time wich means that on parse error this will be always be empty this.state.pending_response = picohttp.Response{}; - const response = picohttp.Response.parseParts( + var response = picohttp.Response.parseParts( to_read, &shared_response_headers_buf, &amount_read, @@ -2336,13 +2347,14 @@ pub fn onData(this: *HTTPClient, comptime is_ssl: bool, incoming_data: []const u return; }; + // we save the successful parsed response this.state.pending_response = response; var body_buf = to_read[@min(@as(usize, @intCast(response.bytes_read)), to_read.len)..]; var deferred_redirect: ?*URLBufferPool.Node = null; const can_continue = this.handleResponseMetadata( - response, + &response, // If there are multiple consecutive redirects // and the redirect differs in hostname // the new URL buffer may point to invalid memory after @@ -2378,9 +2390,20 @@ pub fn onData(this: *HTTPClient, comptime is_ssl: bool, incoming_data: []const u return; }; - this.cloneMetadata(); + if (this.state.content_encoding_i < response.headers.len) { + // if it compressed with this header, it is no longer because we will decompress it + var mutable_headers = std.ArrayListUnmanaged(picohttp.Header){ .items = response.headers, .capacity = response.headers.len }; + _ = mutable_headers.orderedRemove(this.state.content_encoding_i); + response.headers = mutable_headers.items; + this.state.content_encoding_i = std.math.maxInt(@TypeOf(this.state.content_encoding_i)); + // we need to reset the pending response because we removed a header + this.state.pending_response = response; + } if (!can_continue) { + // this means that the request ended + // clone metadata and return the progress at this point + this.cloneMetadata(); // if is chuncked but no body is expected we mark the last chunk this.state.received_last_chunk = true; // if is not we ignore the content_length @@ -2390,10 +2413,14 @@ pub fn onData(this: *HTTPClient, comptime is_ssl: bool, incoming_data: []const u } if (this.proxy_tunneling and this.proxy_tunnel == null) { + // we are proxing we dont need to cloneMetadata yet this.startProxyHandshake(is_ssl, socket); return; } + // we have body data incoming so we clone metadata and keep going + this.cloneMetadata(); + if (body_buf.len == 0) { // no body data yet, but we can report the headers if (this.signals.get(.header_progress)) { @@ -2537,7 +2564,7 @@ pub fn onData(this: *HTTPClient, comptime is_ssl: bool, incoming_data: []const u return; }, else => { - this.state.pending_response = .{}; + this.state.pending_response = null; this.closeAndFail(error.UnexpectedData, is_ssl, socket); return; }, @@ -2558,8 +2585,8 @@ fn fail(this: *HTTPClient, err: anyerror) void { this.state.stage = .fail; const callback = this.result_callback; - const result = this.toResult(this.cloned_metadata); - this.state.reset(); + const result = this.toResult(); + this.state.reset(this.allocator); this.proxy_tunneling = false; callback.run(result); @@ -2567,22 +2594,38 @@ fn fail(this: *HTTPClient, err: anyerror) void { // We have to clone metadata immediately after use fn cloneMetadata(this: *HTTPClient) void { - var builder_ = StringBuilder{}; - var builder = &builder_; - this.state.pending_response.count(builder); - builder.count(this.url.href); - builder.allocate(this.allocator) catch unreachable; - var headers_buf = this.allocator.alloc(picohttp.Header, this.state.pending_response.headers.len) catch unreachable; - const response = this.state.pending_response.clone(headers_buf, builder); - - this.state.pending_response = response; - - const href = builder.append(this.url.href); - this.cloned_metadata = .{ - .owned_buf = builder.ptr.?[0..builder.cap], - .response = response, - .url = href, - }; + std.debug.assert(this.state.pending_response != null); + if (this.state.pending_response) |response| { + // we should never clone metadata twice + std.debug.assert(this.state.cloned_metadata == null); + // just in case we check and free + if (this.state.cloned_metadata != null) { + this.state.cloned_metadata.?.deinit(this.allocator); + this.state.cloned_metadata = null; + } + var builder_ = StringBuilder{}; + var builder = &builder_; + response.count(builder); + builder.count(this.url.href); + builder.allocate(this.allocator) catch unreachable; + // headers_buf is owned by the cloned_response (aka cloned_response.headers) + var headers_buf = this.allocator.alloc(picohttp.Header, response.headers.len) catch unreachable; + const cloned_response = response.clone(headers_buf, builder); + + // we clean the temporary response since cloned_metadata is now the owner + this.state.pending_response = null; + + const href = builder.append(this.url.href); + this.state.cloned_metadata = .{ + .owned_buf = builder.ptr.?[0..builder.cap], + .response = cloned_response, + .url = href, + }; + } else { + // we should never clone metadata that dont exists + // we added a empty metadata just in case but will hit the std.debug.assert + this.state.cloned_metadata = .{}; + } } pub fn setTimeout(this: *HTTPClient, socket: anytype, amount: c_uint) void { @@ -2606,8 +2649,7 @@ pub fn progressUpdate(this: *HTTPClient, comptime is_ssl: bool, ctx: *NewHTTPCon var out_str = this.state.body_out_str.?; var body = out_str.*; - this.cloned_metadata.response = this.state.pending_response; - const result = this.toResult(this.cloned_metadata); + const result = this.toResult(); const callback = this.result_callback; if (is_done) { @@ -2623,7 +2665,7 @@ pub fn progressUpdate(this: *HTTPClient, comptime is_ssl: bool, ctx: *NewHTTPCon socket.close(0, null); } - this.state.reset(); + this.state.reset(this.allocator); this.state.response_stage = .done; this.state.request_stage = .done; this.state.stage = .done; @@ -2649,8 +2691,8 @@ pub const HTTPClientResult = struct { body: ?*MutableString = null, has_more: bool = false, fail: anyerror = error.NoError, - - metadata: ?ResultMetadata = null, + /// Owns the response metadata aka headers, url and status code + metadata: ?HTTPResponseMetadata = null, /// For Http Client requests /// when Content-Length is provided this represents the whole size of the request @@ -2659,23 +2701,6 @@ pub const HTTPClientResult = struct { body_size: BodySize = .unknown, redirected: bool = false, - pub const ResultMetadata = struct { - response: picohttp.Response = .{}, - metadata_buf: []u8 = &.{}, - href: []const u8 = "", - headers_buf: []picohttp.Header = &.{}, - - pub fn deinit(this: *ResultMetadata, allocator: std.mem.Allocator) void { - if (this.metadata_buf.len > 0) allocator.free(this.metadata_buf); - if (this.headers_buf.len > 0) allocator.free(this.headers_buf); - this.headers_buf = &.{}; - this.metadata_buf = &.{}; - this.href = ""; - this.response.headers = &.{}; - this.response.status = ""; - } - }; - pub const BodySize = union(enum) { total_received: usize, content_length: usize, @@ -2722,22 +2747,18 @@ pub const HTTPClientResult = struct { }; }; -pub fn toResult(this: *HTTPClient, metadata: HTTPResponseMetadata) HTTPClientResult { +pub fn toResult(this: *HTTPClient) HTTPClientResult { const body_size: HTTPClientResult.BodySize = if (this.state.isChunkedEncoding()) .{ .total_received = this.state.total_body_received } else if (this.state.content_length) |content_length| .{ .content_length = content_length } else .{ .unknown = {} }; - if (!this.state.metadata_sent) { - this.state.metadata_sent = true; + if (this.state.cloned_metadata) |metadata| { + // transfer owner ship of the metadata here + this.state.cloned_metadata = null; return HTTPClientResult{ - .metadata = .{ - .response = metadata.response, - .metadata_buf = metadata.owned_buf, - .href = metadata.url, - .headers_buf = metadata.response.headers, - }, + .metadata = metadata, .body = this.state.body_out_str, .redirected = this.remaining_redirect_count != default_redirect_count, .fail = this.state.fail, @@ -2804,8 +2825,6 @@ fn handleResponseBodyFromSinglePacket(this: *HTTPClient, incoming_data: []const progress.setCompletedItems(incoming_data.len); progress.context.maybeRefresh(); } - - _ = this.state.postProcessBody(); } fn handleResponseBodyFromMultiplePackets(this: *HTTPClient, incoming_data: []const u8) !bool { @@ -3002,7 +3021,7 @@ fn handleResponseBodyChunkedEncodingFromSinglePacket( pub fn handleResponseMetadata( this: *HTTPClient, - response: picohttp.Response, + response: *picohttp.Response, deferred_redirect: *?*URLBufferPool.Node, ) !bool { var location: string = ""; @@ -3015,7 +3034,7 @@ pub fn handleResponseMetadata( this.state.content_length = content_length; } else { // ignore body size for HEAD requests - this.state.content_length = content_length; + this.state.content_length = 0; } }, hashHeaderConst("Content-Encoding") => { @@ -3059,16 +3078,15 @@ pub fn handleResponseMetadata( } if (this.verbose) { - printResponse(response); + printResponse(response.*); } - this.state.pending_response = response; if (pretend_304) { - this.state.pending_response.status_code = 304; + response.status_code = 304; } if (this.proxy_tunneling and this.proxy_tunnel == null) { - if (this.state.pending_response.status_code == 200) { + if (response.status_code == 200) { //signal to continue the proxing return true; } @@ -3077,10 +3095,10 @@ pub fn handleResponseMetadata( this.proxy_tunneling = false; } - const is_redirect = this.state.pending_response.status_code >= 300 and this.state.pending_response.status_code <= 399; + const is_redirect = response.status_code >= 300 and response.status_code <= 399; if (is_redirect) { if (this.redirect_type == FetchRedirect.follow and location.len > 0 and this.remaining_redirect_count > 0) { - switch (this.state.pending_response.status_code) { + switch (response.status_code) { 302, 301, 307, 308, 303 => { if (strings.indexOf(location, "://")) |i| { var url_buf = URLBufferPool.get(default_allocator); diff --git a/src/zlib.zig b/src/zlib.zig index b578f0ede..6b2e9dc48 100644 --- a/src/zlib.zig +++ b/src/zlib.zig @@ -3,6 +3,8 @@ const std = @import("std"); const bun = @import("root").bun; +const mimalloc = @import("./allocators/mimalloc.zig"); + pub const MAX_WBITS = 15; test "Zlib Read" { @@ -240,22 +242,19 @@ pub fn NewZlibReader(comptime Writer: type, comptime buffer_size: usize) type { buf: [buffer_size]u8, zlib: zStream_struct, allocator: std.mem.Allocator, - arena: @import("root").bun.ArenaAllocator, state: State = State.Uninitialized, - pub fn alloc(ctx: *anyopaque, items: uInt, len: uInt) callconv(.C) *anyopaque { - var this = @as(*ZlibReader, @ptrCast(@alignCast(ctx))); - const buf = this.arena.allocator().alloc(u8, items * len) catch unreachable; - return buf.ptr; + pub fn alloc(_: *anyopaque, items: uInt, len: uInt) callconv(.C) *anyopaque { + return mimalloc.mi_malloc(items * len) orelse unreachable; } - // we free manually all at once - pub fn free(_: *anyopaque, _: *anyopaque) callconv(.C) void {} + pub fn free(_: *anyopaque, data: *anyopaque) callconv(.C) void { + mimalloc.mi_free(data); + } pub fn deinit(this: *ZlibReader) void { var allocator = this.allocator; this.end(); - this.arena.deinit(); allocator.destroy(this); } @@ -274,7 +273,6 @@ pub fn NewZlibReader(comptime Writer: type, comptime buffer_size: usize) type { .buf = std.mem.zeroes([buffer_size]u8), .allocator = allocator, .zlib = undefined, - .arena = @import("root").bun.ArenaAllocator.init(allocator), }; zlib_reader.zlib = zStream_struct{ @@ -424,22 +422,19 @@ pub const ZlibReaderArrayList = struct { list_ptr: *std.ArrayListUnmanaged(u8), zlib: zStream_struct, allocator: std.mem.Allocator, - arena: @import("root").bun.ArenaAllocator, state: State = State.Uninitialized, - pub fn alloc(ctx: *anyopaque, items: uInt, len: uInt) callconv(.C) *anyopaque { - var this = @as(*ZlibReader, @ptrCast(@alignCast(ctx))); - const buf = this.allocator.alloc(u8, items * len) catch unreachable; - return buf.ptr; + pub fn alloc(_: *anyopaque, items: uInt, len: uInt) callconv(.C) *anyopaque { + return mimalloc.mi_malloc(items * len) orelse unreachable; } - // we free manually all at once - pub fn free(_: *anyopaque, _: *anyopaque) callconv(.C) void {} + pub fn free(_: *anyopaque, data: *anyopaque) callconv(.C) void { + mimalloc.mi_free(data); + } pub fn deinit(this: *ZlibReader) void { var allocator = this.allocator; this.end(); - this.arena.deinit(); allocator.destroy(this); } @@ -475,7 +470,6 @@ pub const ZlibReaderArrayList = struct { .list_ptr = list, .allocator = allocator, .zlib = undefined, - .arena = @import("root").bun.ArenaAllocator.init(allocator), }; zlib_reader.zlib = zStream_struct{ @@ -835,22 +829,19 @@ pub const ZlibCompressorArrayList = struct { list_ptr: *std.ArrayListUnmanaged(u8), zlib: zStream_struct, allocator: std.mem.Allocator, - arena: @import("root").bun.ArenaAllocator, state: State = State.Uninitialized, - pub fn alloc(ctx: *anyopaque, items: uInt, len: uInt) callconv(.C) *anyopaque { - var this = @as(*ZlibCompressor, @ptrCast(@alignCast(ctx))); - const buf = this.allocator.alloc(u8, items * len) catch unreachable; - return buf.ptr; + pub fn alloc(_: *anyopaque, items: uInt, len: uInt) callconv(.C) *anyopaque { + return mimalloc.mi_malloc(items * len) orelse unreachable; } - // we free manually all at once - pub fn free(_: *anyopaque, _: *anyopaque) callconv(.C) void {} + pub fn free(_: *anyopaque, data: *anyopaque) callconv(.C) void { + mimalloc.mi_free(data); + } pub fn deinit(this: *ZlibCompressor) void { var allocator = this.allocator; this.end(); - this.arena.deinit(); allocator.destroy(this); } @@ -874,7 +865,6 @@ pub const ZlibCompressorArrayList = struct { .list_allocator = list_allocator, .allocator = allocator, .zlib = undefined, - .arena = @import("root").bun.ArenaAllocator.init(allocator), }; zlib_reader.zlib = zStream_struct{ @@ -909,7 +899,7 @@ pub const ZlibCompressorArrayList = struct { @sizeOf(zStream_struct), )) { ReturnCode.Ok => { - try zlib_reader.list.ensureTotalCapacityPrecise(allocator, deflateBound(&zlib_reader.zlib, input.len)); + try zlib_reader.list.ensureTotalCapacityPrecise(list_allocator, deflateBound(&zlib_reader.zlib, input.len)); zlib_reader.list_ptr.* = zlib_reader.list; zlib_reader.zlib.avail_out = @as(uInt, @truncate(zlib_reader.list.capacity)); zlib_reader.zlib.next_out = zlib_reader.list.items.ptr; |