authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-01-03 16:03:28-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-01-04 18:37:53-07:00
log2cdc0a8b504a501d3eb5184f096720e6a5dc351a
tree62c768bb13c8987da673cf9c55d9407689854909
parented2361563860031559addbe276eb64114b93cec5

std.http.Client: do not heap allocate for requests


1 files changed, 38 insertions(+), 67 deletions(-)

lib/std/http/Client.zig+38-67
......@@ -9,9 +9,8 @@ const net = std.net;
99const Client = @This();
1010const Url = std.Url;
1111
12/// TODO: remove this field (currently required due to tcpConnectToHost)
1213allocator: std.mem.Allocator,
13headers: std.ArrayListUnmanaged(u8) = .{},
14active_requests: usize = 0,
1514ca_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 = .{},
2019pub 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
116pub fn deinit(client: *Client) void {
117 assert(client.active_requests == 0);
118 client.headers.deinit(client.allocator);
94pub fn deinit(client: *Client, gpa: std.mem.Allocator) void {
95 client.ca_bundle.deinit(gpa);
11996 client.* = undefined;
12097}
12198
122pub fn request(client: *Client, url: Url, options: Request.Options) !Request {
99pub 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
174pub 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}