Skip to content

Commit a89b0eb

Browse files
committed
http: move header done callback in its own func
And call it only after the headers are parsed, either from data callback or end of the request.
1 parent f753253 commit a89b0eb

File tree

1 file changed

+74
-68
lines changed

1 file changed

+74
-68
lines changed

src/http/Client.zig

Lines changed: 74 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,15 @@ fn perform(self: *Client, timeout_ms: c_int) !void {
379379
defer transfer.deinit();
380380

381381
if (errorCheck(msg.data.result)) {
382+
// In case of request w/o data, we need to call the header done
383+
// callback now.
384+
if (!transfer._header_done_called) {
385+
transfer.headerDoneCallback(easy) catch |err| {
386+
log.err(.http, "header_done_callback", .{ .err = err });
387+
self.requestFailed(transfer, err);
388+
continue;
389+
};
390+
}
382391
transfer.req.done_callback(transfer.ctx) catch |err| {
383392
// transfer isn't valid at this point, don't use it.
384393
log.err(.http, "done_callback", .{ .err = err });
@@ -572,6 +581,9 @@ pub const Transfer = struct {
572581
proxy_response_header: ?ResponseHeader = null,
573582
response_header: ?ResponseHeader = null,
574583

584+
// track if the header callbacks done have been called.
585+
_header_done_called: bool = false,
586+
575587
_notified_fail: bool = false,
576588

577589
_handle: ?*Handle = null,
@@ -678,6 +690,48 @@ pub const Transfer = struct {
678690
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_COOKIE, @as([*c]const u8, @ptrCast(cookies.items.ptr))));
679691
}
680692

693+
// headerDoneCallback is called once the headers have been read.
694+
// It can be called either on dataCallback or once the request for those
695+
// w/o body.
696+
fn headerDoneCallback(transfer: *Transfer, easy: *c.CURL) !void {
697+
std.debug.assert(transfer._header_done_called == false);
698+
std.debug.assert(transfer.response_header != null);
699+
700+
defer transfer._header_done_called = true;
701+
702+
if (getResponseHeader(easy, "content-type", 0)) |ct| {
703+
var hdr = &transfer.response_header.?;
704+
const value = ct.value;
705+
const len = @min(value.len, ResponseHeader.MAX_CONTENT_TYPE_LEN);
706+
hdr._content_type_len = len;
707+
@memcpy(hdr._content_type[0..len], value[0..len]);
708+
}
709+
710+
var i: usize = 0;
711+
while (true) {
712+
const ct = getResponseHeader(easy, "set-cookie", i);
713+
if (ct == null) break;
714+
transfer.req.cookie_jar.populateFromResponse(&transfer.uri, ct.?.value) catch |err| {
715+
log.err(.http, "set cookie", .{ .err = err, .req = transfer });
716+
return err;
717+
};
718+
i += 1;
719+
if (i >= ct.?.amount) break;
720+
}
721+
722+
transfer.req.header_callback(transfer) catch |err| {
723+
log.err(.http, "header_callback", .{ .err = err, .req = transfer });
724+
return err;
725+
};
726+
727+
if (transfer.client.notification) |notification| {
728+
notification.dispatch(.http_response_header_done, &.{
729+
.transfer = transfer,
730+
});
731+
}
732+
}
733+
734+
// headerCallback is called by curl on each request's header line read.
681735
fn headerCallback(buffer: [*]const u8, header_count: usize, buf_len: usize, data: *anyopaque) callconv(.c) usize {
682736
// libcurl should only ever emit 1 header at a time
683737
std.debug.assert(header_count == 1);
@@ -692,20 +746,9 @@ pub const Transfer = struct {
692746

693747
const header = buffer[0 .. buf_len - 2];
694748

695-
if (transfer.response_header == null) {
696-
if (transfer._redirecting and buf_len == 2) {
697-
// parse and set cookies for the redirection.
698-
redirectionCookies(transfer, easy) catch |err| {
699-
log.debug(.http, "redirection cookies", .{ .err = err });
700-
return 0;
701-
};
702-
return buf_len;
703-
}
704-
705-
if (buf_len < 13 or std.mem.startsWith(u8, header, "HTTP/") == false) {
706-
if (transfer._redirecting) {
707-
return buf_len;
708-
}
749+
// Is it the first header line?
750+
if (std.mem.startsWith(u8, header, "HTTP/")) {
751+
if (buf_len < 13) {
709752
log.debug(.http, "invalid response line", .{ .line = header });
710753
return 0;
711754
}
@@ -741,75 +784,31 @@ pub const Transfer = struct {
741784
return buf_len;
742785
}
743786

744-
transfer.bytes_received += buf_len;
787+
if (transfer._redirecting == false) {
788+
transfer.bytes_received += buf_len;
789+
}
745790

746791
if (buf_len != 2) {
747792
return buf_len;
748793
}
749794

750795
// Starting here, we get the last header line.
751796

752-
// We're connecting to a proxy. Consider the first request to the
797+
// We're connecting to a proxy. Consider the first request to be the
753798
// proxy's result.
754799
if (transfer._use_proxy and transfer.proxy_response_header == null) {
755-
// We have to cases:
756-
// 1. for http://, we have one request. So both
757-
// proxy_response_header and response_header will have the same
758-
// value.
759-
//
760-
// 2. for https://, we two successive requests, a CONNECT to the
761-
// proxy and a final request. So proxy_response_header and
762-
// response_header may have different values.
763800
transfer.proxy_response_header = transfer.response_header;
764-
765-
// Detect if the request is a CONNECT to the proxy. There might be
766-
// a better way to detect this, but I didn't find a better one.
767-
// When we don't force curl to always use tunneling, it uses
768-
// CONNECT tunnel only for https requests.
769-
const is_connect = std.mem.startsWith(u8, std.mem.span(transfer.proxy_response_header.?.url), "https");
770-
771-
// If the CONNECT is successful, curl will create a following
772-
// request to the final target, so we reset
773-
// transfer.response_header to get the "real" data.
774-
if (is_connect and transfer.proxy_response_header.?.status == 200) {
775-
transfer.response_header = null;
776-
return buf_len;
777-
}
778-
779-
// If the CONNECT fails, use the request result as it would be our
780-
// final request.
781801
}
782802

783-
if (getResponseHeader(easy, "content-type", 0)) |ct| {
784-
var hdr = &transfer.response_header.?;
785-
const value = ct.value;
786-
const len = @min(value.len, ResponseHeader.MAX_CONTENT_TYPE_LEN);
787-
hdr._content_type_len = len;
788-
@memcpy(hdr._content_type[0..len], value[0..len]);
789-
}
790-
791-
var i: usize = 0;
792-
while (true) {
793-
const ct = getResponseHeader(easy, "set-cookie", i);
794-
if (ct == null) break;
795-
transfer.req.cookie_jar.populateFromResponse(&transfer.uri, ct.?.value) catch |err| {
796-
log.err(.http, "set cookie", .{ .err = err, .req = transfer });
803+
if (transfer._redirecting) {
804+
// parse and set cookies for the redirection.
805+
redirectionCookies(transfer, easy) catch |err| {
806+
log.debug(.http, "redirection cookies", .{ .err = err });
807+
return 0;
797808
};
798-
i += 1;
799-
if (i >= ct.?.amount) break;
809+
return buf_len;
800810
}
801811

802-
transfer.req.header_callback(transfer) catch |err| {
803-
log.err(.http, "header_callback", .{ .err = err, .req = transfer });
804-
// returning < buf_len terminates the request
805-
return 0;
806-
};
807-
808-
if (transfer.client.notification) |notification| {
809-
notification.dispatch(.http_response_header_done, &.{
810-
.transfer = transfer,
811-
});
812-
}
813812
return buf_len;
814813
}
815814

@@ -827,6 +826,13 @@ pub const Transfer = struct {
827826
return chunk_len;
828827
}
829828

829+
if (!transfer._header_done_called) {
830+
transfer.headerDoneCallback(easy) catch |err| {
831+
log.err(.http, "header_done_callback", .{ .err = err, .req = transfer });
832+
return c.CURL_WRITEFUNC_ERROR;
833+
};
834+
}
835+
830836
transfer.bytes_received += chunk_len;
831837
const chunk = buffer[0..chunk_len];
832838
transfer.req.data_callback(transfer, chunk) catch |err| {

0 commit comments

Comments
 (0)