aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/bun.js/webcore/response.zig64
-rw-r--r--src/http_client_async.zig194
-rw-r--r--src/zlib.zig46
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;