Commit 743a0c966d
Changed files (2)
lib
std
http
test
standalone
lib/std/http/Client.zig
@@ -700,7 +700,7 @@ pub const Request = struct {
pub const SendError = Connection.WriteError || error{ InvalidContentLength, UnsupportedTransferEncoding };
pub const SendOptions = struct {
- /// Specifies that the uri should be used as is. You guarantee that the uri is already escaped.
+ /// Specifies that the uri is already escaped.
raw_uri: bool = false,
};
@@ -1562,12 +1562,16 @@ pub fn open(
pub const FetchOptions = struct {
server_header_buffer: ?[]u8 = null,
- response_strategy: ResponseStrategy = .{ .storage = .{ .dynamic = 16 * 1024 * 1024 } },
redirect_behavior: ?Request.RedirectBehavior = null,
+ /// If the server sends a body, it will be appended to this ArrayList.
+ /// `max_append_size` provides an upper limit for how much they can grow.
+ response_storage: ResponseStorage = .ignore,
+ max_append_size: ?usize = null,
+
location: Location,
- method: http.Method = .GET,
- payload: Payload = .none,
+ method: ?http.Method = null,
+ payload: ?[]const u8 = null,
raw_uri: bool = false,
/// Standard headers that have default, but overridable, behavior.
@@ -1586,111 +1590,76 @@ pub const FetchOptions = struct {
uri: Uri,
};
- pub const Payload = union(enum) {
- string: []const u8,
- file: std.fs.File,
- none,
- };
-
- pub const ResponseStrategy = union(enum) {
- storage: StorageStrategy,
- file: std.fs.File,
- none,
- };
-
- pub const StorageStrategy = union(enum) {
- /// In this case, the client's Allocator will be used to store the
- /// entire HTTP header. This value is the maximum total size of
- /// HTTP headers allowed, otherwise
- /// error.HttpHeadersExceededSizeLimit is returned from read().
- dynamic: usize,
- /// This is used to store the entire HTTP header. If the HTTP
- /// header is too big to fit, `error.HttpHeadersExceededSizeLimit`
- /// is returned from read(). When this is used, `error.OutOfMemory`
- /// cannot be returned from `read()`.
- static: []u8,
+ pub const ResponseStorage = union(enum) {
+ ignore,
+ /// Only the existing capacity will be used.
+ static: *std.ArrayListUnmanaged(u8),
+ dynamic: *std.ArrayList(u8),
};
};
pub const FetchResult = struct {
status: http.Status,
- body: ?[]const u8 = null,
-
- allocator: Allocator,
- options: FetchOptions,
-
- pub fn deinit(res: *FetchResult) void {
- if (res.options.response_strategy == .storage and
- res.options.response_strategy.storage == .dynamic)
- {
- if (res.body) |body| res.allocator.free(body);
- }
- }
};
/// Perform a one-shot HTTP request with the provided options.
///
/// This function is threadsafe.
-pub fn fetch(client: *Client, allocator: Allocator, options: FetchOptions) !FetchResult {
+pub fn fetch(client: *Client, options: FetchOptions) !FetchResult {
const uri = switch (options.location) {
.url => |u| try Uri.parse(u),
.uri => |u| u,
};
var server_header_buffer: [16 * 1024]u8 = undefined;
- var req = try open(client, options.method, uri, .{
+ const method: http.Method = options.method orelse
+ if (options.payload != null) .POST else .GET;
+
+ var req = try open(client, method, uri, .{
.server_header_buffer = options.server_header_buffer orelse &server_header_buffer,
.redirect_behavior = options.redirect_behavior orelse
- if (options.payload == .none) @enumFromInt(3) else .unhandled,
+ if (options.payload == null) @enumFromInt(3) else .unhandled,
.headers = options.headers,
.extra_headers = options.extra_headers,
.privileged_headers = options.privileged_headers,
});
defer req.deinit();
- switch (options.payload) {
- .string => |str| req.transfer_encoding = .{ .content_length = str.len },
- .file => |file| req.transfer_encoding = .{ .content_length = (try file.stat()).size },
- .none => {},
- }
+ if (options.payload) |payload| req.transfer_encoding = .{ .content_length = payload.len };
try req.send(.{ .raw_uri = options.raw_uri });
- switch (options.payload) {
- .string => |str| try req.writeAll(str),
- .file => |file| {
- var fifo = std.fifo.LinearFifo(u8, .{ .Static = 8192 }).init();
- try fifo.pump(file.reader(), req.writer());
- },
- .none => {},
- }
+ if (options.payload) |payload| try req.writeAll(payload);
try req.finish();
try req.wait();
- var res: FetchResult = .{
- .status = req.response.status,
- .allocator = allocator,
- .options = options,
- };
-
- switch (options.response_strategy) {
- .storage => |storage| switch (storage) {
- .dynamic => |max| res.body = try req.reader().readAllAlloc(allocator, max),
- .static => |buf| res.body = buf[0..try req.reader().readAll(buf)],
+ switch (options.response_storage) {
+ .ignore => {
+ // Take advantage of request internals to discard the response body
+ // and make the connection available for another request.
+ req.response.skip = true;
+ assert(try req.transferRead(&.{}) == 0); // No buffer is necessary when skipping.
},
- .file => |file| {
- var fifo = std.fifo.LinearFifo(u8, .{ .Static = 8192 }).init();
- try fifo.pump(req.reader(), file.writer());
+ .dynamic => |list| {
+ const max_append_size = options.max_append_size orelse 2 * 1024 * 1024;
+ try req.reader().readAllArrayList(list, max_append_size);
},
- .none => { // Take advantage of request internals to discard the response body and make the connection available for another request.
- req.response.skip = true;
-
- assert(try req.transferRead(&.{}) == 0); // we're skipping, no buffer is necessary
+ .static => |list| {
+ const buf = b: {
+ const buf = list.unusedCapacitySlice();
+ if (options.max_append_size) |len| {
+ if (len < buf.len) break :b buf[0..len];
+ }
+ break :b buf;
+ };
+ list.items.len += try req.reader().readAll(buf);
},
}
- return res;
+ return .{
+ .status = req.response.status,
+ };
}
test {
test/standalone/http.zig
@@ -586,17 +586,20 @@ pub fn main() !void {
defer calloc.free(location);
log.info("{s}", .{location});
- var res = try client.fetch(calloc, .{
+ var body = std.ArrayList(u8).init(calloc);
+ defer body.deinit();
+
+ const res = try client.fetch(.{
.location = .{ .url = location },
.method = .POST,
- .payload = .{ .string = "Hello, World!\n" },
+ .payload = "Hello, World!\n",
.extra_headers = &.{
.{ .name = "content-type", .value = "text/plain" },
},
+ .response_storage = .{ .dynamic = &body },
});
- defer res.deinit();
-
- try testing.expectEqualStrings("Hello, World!\n", res.body.?);
+ try testing.expectEqual(.ok, res.status);
+ try testing.expectEqualStrings("Hello, World!\n", body.items);
}
{ // expect: 100-continue