-
-
Save Zorgatone/968ce86711aecea984a2c4a9771eed5f to your computer and use it in GitHub Desktop.
| const builtin = @import("builtin"); | |
| const std = @import("std"); | |
| // This version won't read/print headers, just the response | |
| pub fn main() !void { | |
| var writer_buffer: [8 * 1024]u8 = undefined; | |
| var redirect_buffer: [8 * 1024]u8 = undefined; | |
| var writer = std.fs.File.stdout().writer(&writer_buffer); | |
| var debug_allocator: std.heap.DebugAllocator(.{}) = .init; | |
| defer switch (builtin.mode) { | |
| .Debug => std.debug.assert(debug_allocator.deinit() == .ok), | |
| .ReleaseFast, .ReleaseSmall, .ReleaseSafe => { | |
| // Nothing | |
| }, | |
| }; | |
| const allocator = switch (builtin.mode) { | |
| .Debug => debug_allocator.allocator(), | |
| .ReleaseFast, .ReleaseSmall, .ReleaseSafe => std.heap.smp_allocator, | |
| }; | |
| const uri = try std.Uri.parse("https://postman-echo.com/get"); | |
| var client: std.http.Client = .{ .allocator = allocator }; | |
| defer client.deinit(); | |
| const result = try client.fetch(.{ | |
| .location = .{ .uri = uri }, | |
| .method = .GET, | |
| .redirect_buffer = &redirect_buffer, | |
| .response_writer = &writer.interface, | |
| }); | |
| if (builtin.mode == .Debug) { | |
| std.debug.assert(result.status == .ok); | |
| } | |
| try writer.interface.flush(); | |
| } |
| const builtin = @import("builtin"); | |
| const std = @import("std"); | |
| // Manual http request printing also the response headers (won't work if chunked or compressed) | |
| pub fn main() !void { | |
| var writer_buffer: [8 * 1024]u8 = undefined; | |
| var redirect_buffer: [8 * 1024]u8 = undefined; | |
| var transfer_buffer: [8 * 1024]u8 = undefined; | |
| var reader_buffer: [8 * 1024]u8 = undefined; | |
| var writer = std.fs.File.stdout().writer(&writer_buffer); | |
| var debug_allocator: std.heap.DebugAllocator(.{}) = .init; | |
| defer switch (builtin.mode) { | |
| .Debug => std.debug.assert(debug_allocator.deinit() == .ok), | |
| .ReleaseFast, .ReleaseSmall, .ReleaseSafe => { | |
| // Nothing | |
| }, | |
| }; | |
| const allocator = switch (builtin.mode) { | |
| .Debug => debug_allocator.allocator(), | |
| .ReleaseFast, .ReleaseSmall, .ReleaseSafe => std.heap.smp_allocator, | |
| }; | |
| const uri = try std.Uri.parse("https://postman-echo.com/get"); | |
| var client: std.http.Client = .{ .allocator = allocator }; | |
| defer client.deinit(); | |
| var request = try client.request(.GET, uri, .{}); | |
| defer request.deinit(); | |
| try request.sendBodiless(); | |
| const response = try request.receiveHead(&redirect_buffer); | |
| _ = try writer.interface.write(response.head.bytes); | |
| const content_length = response.head.content_length; | |
| const reader = request.reader.bodyReader(&transfer_buffer, .none, content_length); | |
| var done = false; | |
| var bytes_read: usize = 0; | |
| while (!done) { | |
| const size = try reader.readSliceShort(&reader_buffer); | |
| if (size > 0) { | |
| bytes_read += size; | |
| _ = try writer.interface.write(reader_buffer[0..size]); | |
| } | |
| if (content_length) |c_len| { | |
| if (bytes_read >= c_len) { | |
| done = true; | |
| } | |
| } | |
| if (size < reader_buffer.len) { | |
| done = true; | |
| } | |
| } | |
| try writer.interface.flush(); | |
| } |
@Numeez done, updated the example with proper usage of the headers, response.head.content_length is optional and request.reader.bodyReader accepts an optional so this will work also when the Content-Length header is missing.
I'm not manually parsing the headers to look for the content-length, and also now I'm reading the response in a loop until the connection is closed or content-length bytes have been read (if present)
I believe there should be an even better way using just http_client.fetch() and passing all that's needed as options in a struct, and takes care of most of the code I've manually written in this example
@Numeez added an example with client.fetch() in file http-fetch.zig while keeping the full manual example in file http-get.zig
Thank you for the update
The new snippet is a better way of dealing with response irrespective of underlying handling of headers
@Zorgatone I have a couple questions about your approach out of genuine curiosity, trying to learn more about zig.
1>>
http-fetch.zig:
25: const uri = try std.Uri.parse("https://postman-echo.com/get");
Is there a reason to parse the url here as a Uri instead of letting the call to fetch() do it?
In Client.zig it appears the call to fetch() already parses the Uri and does the work:
pub fn fetch(client: *Client, options: FetchOptions) FetchError!FetchResult {
const uri = switch (options.location) {
.url => |u| try Uri.parse(u),
.uri => |u| u,
};
...2>>
http-fetch.zig:
11: var writer = std.fs.File.stdout().writer(&writer_buffer);
Is there a reason why you chose a file descriptor here? With 0.15.1 we get std.Io which gives a lot of functions for "free" that are quite useful and you can always use a std.heap.FixedBufferAllocator with std.Io.Writer.Allocating.init() to keep it on the stack if you don't want to allocate to the heap.
-
well, this was an easy example so you could go with both approaches. Since it's going to use Uri.parse anyway, I did it earlier, in case I want to reuse the same uri for multiple fetch/http requests.
-
Not sure I understand your question, what do you mean why am I using a file descriptor here? I am using the new
std.Ioin this example (all readers, writers also implement the std.Io interface now, ie.writer.interfaceandreader.interface). Turns out standard output (stdout) is also implemented as a file descriptor (macOs, linux). Your example writer.Allocating + heap.FixedBufferAllocator would make sense if you wanted to write to some array (buffer) in memory you could then read, or chunked.buffered stream/pipe to an actual file on the file system (instead of stdout). Here I'm only printing (streaming/piping) to the console (stdout) to debug the result of the HTTP request (body).
Did I answer your question, or did you want to know something else?
Thanks for this. #1 makes sense. #2 your perspective is helping me understand zig better. Your explanation makes sense to me.
@Numeez AFAIK HTTP/1.0 requires the content-length header as mandatory: https://www.w3.org/Protocols/HTTP/1.0/draft-ietf-http-spec.html#Content-Length
I'll improve this snippet, I found out I can access headers of the response in a better way without parsing it RAW