Skip to content

Commit 2cdc0a8

Browse files
committed
std.http.Client: do not heap allocate for requests
1 parent ed23615 commit 2cdc0a8

1 file changed

Lines changed: 38 additions & 67 deletions

File tree

lib/std/http/Client.zig

Lines changed: 38 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,8 @@ const net = std.net;
99
const Client = @This();
1010
const Url = std.Url;
1111

12+
/// TODO: remove this field (currently required due to tcpConnectToHost)
1213
allocator: std.mem.Allocator,
13-
headers: std.ArrayListUnmanaged(u8) = .{},
14-
active_requests: usize = 0,
1514
ca_bundle: std.crypto.Certificate.Bundle = .{},
1615

1716
/// TODO: emit error.UnexpectedEndOfStream or something like that when the read
@@ -20,44 +19,23 @@ ca_bundle: std.crypto.Certificate.Bundle = .{},
2019
pub const Request = struct {
2120
client: *Client,
2221
stream: net.Stream,
23-
headers: std.ArrayListUnmanaged(u8) = .{},
2422
tls_client: std.crypto.tls.Client,
2523
protocol: Protocol,
2624
response_headers: http.Headers = .{},
2725

28-
pub const Protocol = enum { http, https };
29-
30-
pub const Options = struct {
26+
pub const Headers = struct {
3127
method: http.Method = .GET,
32-
};
28+
connection: Connection,
3329

34-
pub fn deinit(req: *Request) void {
35-
req.client.active_requests -= 1;
36-
req.headers.deinit(req.client.allocator);
37-
req.* = undefined;
38-
}
30+
pub const Connection = enum {
31+
close,
32+
@"keep-alive",
33+
};
34+
};
3935

40-
pub fn addHeader(req: *Request, name: []const u8, value: []const u8) !void {
41-
const gpa = req.client.allocator;
42-
// Ensure an extra +2 for the \r\n in end()
43-
try req.headers.ensureUnusedCapacity(gpa, name.len + value.len + 6);
44-
req.headers.appendSliceAssumeCapacity(name);
45-
req.headers.appendSliceAssumeCapacity(": ");
46-
req.headers.appendSliceAssumeCapacity(value);
47-
req.headers.appendSliceAssumeCapacity("\r\n");
48-
}
36+
pub const Protocol = enum { http, https };
4937

50-
pub fn end(req: *Request) !void {
51-
req.headers.appendSliceAssumeCapacity("\r\n");
52-
switch (req.protocol) {
53-
.http => {
54-
try req.stream.writeAll(req.headers.items);
55-
},
56-
.https => {
57-
try req.tls_client.writeAll(req.stream, req.headers.items);
58-
},
59-
}
60-
}
38+
pub const Options = struct {};
6139

6240
pub fn readAll(req: *Request, buffer: []u8) !usize {
6341
return readAtLeast(req, buffer, buffer.len);
@@ -113,13 +91,14 @@ pub const Request = struct {
11391
}
11492
};
11593

116-
pub fn deinit(client: *Client) void {
117-
assert(client.active_requests == 0);
118-
client.headers.deinit(client.allocator);
94+
pub fn deinit(client: *Client, gpa: std.mem.Allocator) void {
95+
client.ca_bundle.deinit(gpa);
11996
client.* = undefined;
12097
}
12198

122-
pub fn request(client: *Client, url: Url, options: Request.Options) !Request {
99+
pub fn request(client: *Client, url: Url, headers: Request.Headers, options: Request.Options) !Request {
100+
_ = options; // we have no options yet
101+
123102
const protocol = std.meta.stringToEnum(Request.Protocol, url.scheme) orelse
124103
return error.UnsupportedUrlScheme;
125104
const port: u16 = url.port orelse switch (protocol) {
@@ -133,8 +112,6 @@ pub fn request(client: *Client, url: Url, options: Request.Options) !Request {
133112
.protocol = protocol,
134113
.tls_client = undefined,
135114
};
136-
client.active_requests += 1;
137-
errdefer req.deinit();
138115

139116
switch (protocol) {
140117
.http => {},
@@ -146,36 +123,30 @@ pub fn request(client: *Client, url: Url, options: Request.Options) !Request {
146123
},
147124
}
148125

149-
try req.headers.ensureUnusedCapacity(
150-
client.allocator,
151-
@tagName(options.method).len +
152-
1 +
153-
url.path.len +
154-
" HTTP/1.1\r\nHost: ".len +
155-
url.host.len +
156-
"\r\nUpgrade-Insecure-Requests: 1\r\n".len +
157-
client.headers.items.len +
158-
2, // for the \r\n at the end of headers
159-
);
160-
req.headers.appendSliceAssumeCapacity(@tagName(options.method));
161-
req.headers.appendSliceAssumeCapacity(" ");
162-
req.headers.appendSliceAssumeCapacity(url.path);
163-
req.headers.appendSliceAssumeCapacity(" HTTP/1.1\r\nHost: ");
164-
req.headers.appendSliceAssumeCapacity(url.host);
165-
switch (protocol) {
166-
.https => req.headers.appendSliceAssumeCapacity("\r\nUpgrade-Insecure-Requests: 1\r\n"),
167-
.http => req.headers.appendSliceAssumeCapacity("\r\n"),
126+
{
127+
var h = try std.BoundedArray(u8, 1000).init(0);
128+
try h.appendSlice(@tagName(headers.method));
129+
try h.appendSlice(" ");
130+
try h.appendSlice(url.path);
131+
try h.appendSlice(" HTTP/1.1\r\nHost: ");
132+
try h.appendSlice(url.host);
133+
switch (protocol) {
134+
.https => try h.appendSlice("\r\nUpgrade-Insecure-Requests: 1\r\n"),
135+
.http => try h.appendSlice("\r\n"),
136+
}
137+
try h.writer().print("Connection: {s}\r\n", .{@tagName(headers.connection)});
138+
try h.appendSlice("\r\n");
139+
140+
const header_bytes = h.slice();
141+
switch (req.protocol) {
142+
.http => {
143+
try req.stream.writeAll(header_bytes);
144+
},
145+
.https => {
146+
try req.tls_client.writeAll(req.stream, header_bytes);
147+
},
148+
}
168149
}
169-
req.headers.appendSliceAssumeCapacity(client.headers.items);
170150

171151
return req;
172152
}
173-
174-
pub fn addHeader(client: *Client, name: []const u8, value: []const u8) !void {
175-
const gpa = client.allocator;
176-
try client.headers.ensureUnusedCapacity(gpa, name.len + value.len + 4);
177-
client.headers.appendSliceAssumeCapacity(name);
178-
client.headers.appendSliceAssumeCapacity(": ");
179-
client.headers.appendSliceAssumeCapacity(value);
180-
client.headers.appendSliceAssumeCapacity("\r\n");
181-
}

0 commit comments

Comments
 (0)