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;...@@ -9,9 +9,8 @@ const net = std.net;
9const Client = @This();9const Client = @This();
10const Url = std.Url;10const Url = std.Url;
1111
12/// TODO: remove this field (currently required due to tcpConnectToHost)
12allocator: std.mem.Allocator,13allocator: std.mem.Allocator,
13headers: std.ArrayListUnmanaged(u8) = .{},
14active_requests: usize = 0,
15ca_bundle: std.crypto.Certificate.Bundle = .{},14ca_bundle: std.crypto.Certificate.Bundle = .{},
1615
17/// TODO: emit error.UnexpectedEndOfStream or something like that when the read16/// TODO: emit error.UnexpectedEndOfStream or something like that when the read
...@@ -20,44 +19,23 @@ ca_bundle: std.crypto.Certificate.Bundle = .{},...@@ -20,44 +19,23 @@ ca_bundle: std.crypto.Certificate.Bundle = .{},
20pub const Request = struct {19pub const Request = struct {
21 client: *Client,20 client: *Client,
22 stream: net.Stream,21 stream: net.Stream,
23 headers: std.ArrayListUnmanaged(u8) = .{},
24 tls_client: std.crypto.tls.Client,22 tls_client: std.crypto.tls.Client,
25 protocol: Protocol,23 protocol: Protocol,
26 response_headers: http.Headers = .{},24 response_headers: http.Headers = .{},
2725
28 pub const Protocol = enum { http, https };26 pub const Headers = struct {
29
30 pub const Options = struct {
31 method: http.Method = .GET,27 method: http.Method = .GET,
32 };28 connection: Connection,
3329
34 pub fn deinit(req: *Request) void {30 pub const Connection = enum {
35 req.client.active_requests -= 1;31 close,
36 req.headers.deinit(req.client.allocator);32 @"keep-alive",
37 req.* = undefined;33 };
38 }34 };
3935
40 pub fn addHeader(req: *Request, name: []const u8, value: []const u8) !void {36 pub const Protocol = enum { http, https };
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 }
4937
50 pub fn end(req: *Request) !void {38 pub const Options = struct {};
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 }
6139
62 pub fn readAll(req: *Request, buffer: []u8) !usize {40 pub fn readAll(req: *Request, buffer: []u8) !usize {
63 return readAtLeast(req, buffer, buffer.len);41 return readAtLeast(req, buffer, buffer.len);
...@@ -113,13 +91,14 @@ pub const Request = struct {...@@ -113,13 +91,14 @@ pub const Request = struct {
113 }91 }
114};92};
11593
116pub fn deinit(client: *Client) void {94pub fn deinit(client: *Client, gpa: std.mem.Allocator) void {
117 assert(client.active_requests == 0);95 client.ca_bundle.deinit(gpa);
118 client.headers.deinit(client.allocator);
119 client.* = undefined;96 client.* = undefined;
120}97}
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
123 const protocol = std.meta.stringToEnum(Request.Protocol, url.scheme) orelse102 const protocol = std.meta.stringToEnum(Request.Protocol, url.scheme) orelse
124 return error.UnsupportedUrlScheme;103 return error.UnsupportedUrlScheme;
125 const port: u16 = url.port orelse switch (protocol) {104 const port: u16 = url.port orelse switch (protocol) {
...@@ -133,8 +112,6 @@ pub fn request(client: *Client, url: Url, options: Request.Options) !Request {...@@ -133,8 +112,6 @@ pub fn request(client: *Client, url: Url, options: Request.Options) !Request {
133 .protocol = protocol,112 .protocol = protocol,
134 .tls_client = undefined,113 .tls_client = undefined,
135 };114 };
136 client.active_requests += 1;
137 errdefer req.deinit();
138115
139 switch (protocol) {116 switch (protocol) {
140 .http => {},117 .http => {},
...@@ -146,36 +123,30 @@ pub fn request(client: *Client, url: Url, options: Request.Options) !Request {...@@ -146,36 +123,30 @@ pub fn request(client: *Client, url: Url, options: Request.Options) !Request {
146 },123 },
147 }124 }
148125
149 try req.headers.ensureUnusedCapacity(126 {
150 client.allocator,127 var h = try std.BoundedArray(u8, 1000).init(0);
151 @tagName(options.method).len +128 try h.appendSlice(@tagName(headers.method));
152 1 +129 try h.appendSlice(" ");
153 url.path.len +130 try h.appendSlice(url.path);
154 " HTTP/1.1\r\nHost: ".len +131 try h.appendSlice(" HTTP/1.1\r\nHost: ");
155 url.host.len +132 try h.appendSlice(url.host);
156 "\r\nUpgrade-Insecure-Requests: 1\r\n".len +133 switch (protocol) {
157 client.headers.items.len +134 .https => try h.appendSlice("\r\nUpgrade-Insecure-Requests: 1\r\n"),
158 2, // for the \r\n at the end of headers135 .http => try h.appendSlice("\r\n"),
159 );136 }
160 req.headers.appendSliceAssumeCapacity(@tagName(options.method));137 try h.writer().print("Connection: {s}\r\n", .{@tagName(headers.connection)});
161 req.headers.appendSliceAssumeCapacity(" ");138 try h.appendSlice("\r\n");
162 req.headers.appendSliceAssumeCapacity(url.path);139
163 req.headers.appendSliceAssumeCapacity(" HTTP/1.1\r\nHost: ");140 const header_bytes = h.slice();
164 req.headers.appendSliceAssumeCapacity(url.host);141 switch (req.protocol) {
165 switch (protocol) {142 .http => {
166 .https => req.headers.appendSliceAssumeCapacity("\r\nUpgrade-Insecure-Requests: 1\r\n"),143 try req.stream.writeAll(header_bytes);
167 .http => req.headers.appendSliceAssumeCapacity("\r\n"),144 },
145 .https => {
146 try req.tls_client.writeAll(req.stream, header_bytes);
147 },
148 }
168 }149 }
169 req.headers.appendSliceAssumeCapacity(client.headers.items);
170150
171 return req;151 return req;
172}152}
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}