| ... | ... | @@ -700,7 +700,7 @@ pub const Request = struct { |
| 700 | 700 | pub const SendError = Connection.WriteError || error{ InvalidContentLength, UnsupportedTransferEncoding }; |
| 701 | 701 | |
| 702 | 702 | pub const SendOptions = struct { |
| 703 | | /// Specifies that the uri should be used as is. You guarantee that the uri is already escaped. |
| 703 | /// Specifies that the uri is already escaped. |
| 704 | 704 | raw_uri: bool = false, |
| 705 | 705 | }; |
| 706 | 706 | |
| ... | ... | @@ -1562,12 +1562,16 @@ pub fn open( |
| 1562 | 1562 | |
| 1563 | 1563 | pub const FetchOptions = struct { |
| 1564 | 1564 | server_header_buffer: ?[]u8 = null, |
| 1565 | | response_strategy: ResponseStrategy = .{ .storage = .{ .dynamic = 16 * 1024 * 1024 } }, |
| 1566 | 1565 | redirect_behavior: ?Request.RedirectBehavior = null, |
| 1567 | 1566 | |
| 1567 | /// If the server sends a body, it will be appended to this ArrayList. |
| 1568 | /// `max_append_size` provides an upper limit for how much they can grow. |
| 1569 | response_storage: ResponseStorage = .ignore, |
| 1570 | max_append_size: ?usize = null, |
| 1571 | |
| 1568 | 1572 | location: Location, |
| 1569 | | method: http.Method = .GET, |
| 1570 | | payload: Payload = .none, |
| 1573 | method: ?http.Method = null, |
| 1574 | payload: ?[]const u8 = null, |
| 1571 | 1575 | raw_uri: bool = false, |
| 1572 | 1576 | |
| 1573 | 1577 | /// Standard headers that have default, but overridable, behavior. |
| ... | ... | @@ -1586,111 +1590,76 @@ pub const FetchOptions = struct { |
| 1586 | 1590 | uri: Uri, |
| 1587 | 1591 | }; |
| 1588 | 1592 | |
| 1589 | | pub const Payload = union(enum) { |
| 1590 | | string: []const u8, |
| 1591 | | file: std.fs.File, |
| 1592 | | none, |
| 1593 | | }; |
| 1594 | | |
| 1595 | | pub const ResponseStrategy = union(enum) { |
| 1596 | | storage: StorageStrategy, |
| 1597 | | file: std.fs.File, |
| 1598 | | none, |
| 1599 | | }; |
| 1600 | | |
| 1601 | | pub const StorageStrategy = union(enum) { |
| 1602 | | /// In this case, the client's Allocator will be used to store the |
| 1603 | | /// entire HTTP header. This value is the maximum total size of |
| 1604 | | /// HTTP headers allowed, otherwise |
| 1605 | | /// error.HttpHeadersExceededSizeLimit is returned from read(). |
| 1606 | | dynamic: usize, |
| 1607 | | /// This is used to store the entire HTTP header. If the HTTP |
| 1608 | | /// header is too big to fit, `error.HttpHeadersExceededSizeLimit` |
| 1609 | | /// is returned from read(). When this is used, `error.OutOfMemory` |
| 1610 | | /// cannot be returned from `read()`. |
| 1611 | | static: []u8, |
| 1593 | pub const ResponseStorage = union(enum) { |
| 1594 | ignore, |
| 1595 | /// Only the existing capacity will be used. |
| 1596 | static: *std.ArrayListUnmanaged(u8), |
| 1597 | dynamic: *std.ArrayList(u8), |
| 1612 | 1598 | }; |
| 1613 | 1599 | }; |
| 1614 | 1600 | |
| 1615 | 1601 | pub const FetchResult = struct { |
| 1616 | 1602 | status: http.Status, |
| 1617 | | body: ?[]const u8 = null, |
| 1618 | | |
| 1619 | | allocator: Allocator, |
| 1620 | | options: FetchOptions, |
| 1621 | | |
| 1622 | | pub fn deinit(res: *FetchResult) void { |
| 1623 | | if (res.options.response_strategy == .storage and |
| 1624 | | res.options.response_strategy.storage == .dynamic) |
| 1625 | | { |
| 1626 | | if (res.body) |body| res.allocator.free(body); |
| 1627 | | } |
| 1628 | | } |
| 1629 | 1603 | }; |
| 1630 | 1604 | |
| 1631 | 1605 | /// Perform a one-shot HTTP request with the provided options. |
| 1632 | 1606 | /// |
| 1633 | 1607 | /// This function is threadsafe. |
| 1634 | | pub fn fetch(client: *Client, allocator: Allocator, options: FetchOptions) !FetchResult { |
| 1608 | pub fn fetch(client: *Client, options: FetchOptions) !FetchResult { |
| 1635 | 1609 | const uri = switch (options.location) { |
| 1636 | 1610 | .url => |u| try Uri.parse(u), |
| 1637 | 1611 | .uri => |u| u, |
| 1638 | 1612 | }; |
| 1639 | 1613 | var server_header_buffer: [16 * 1024]u8 = undefined; |
| 1640 | 1614 | |
| 1641 | | var req = try open(client, options.method, uri, .{ |
| 1615 | const method: http.Method = options.method orelse |
| 1616 | if (options.payload != null) .POST else .GET; |
| 1617 | |
| 1618 | var req = try open(client, method, uri, .{ |
| 1642 | 1619 | .server_header_buffer = options.server_header_buffer orelse &server_header_buffer, |
| 1643 | 1620 | .redirect_behavior = options.redirect_behavior orelse |
| 1644 | | if (options.payload == .none) @enumFromInt(3) else .unhandled, |
| 1621 | if (options.payload == null) @enumFromInt(3) else .unhandled, |
| 1645 | 1622 | .headers = options.headers, |
| 1646 | 1623 | .extra_headers = options.extra_headers, |
| 1647 | 1624 | .privileged_headers = options.privileged_headers, |
| 1648 | 1625 | }); |
| 1649 | 1626 | defer req.deinit(); |
| 1650 | 1627 | |
| 1651 | | switch (options.payload) { |
| 1652 | | .string => |str| req.transfer_encoding = .{ .content_length = str.len }, |
| 1653 | | .file => |file| req.transfer_encoding = .{ .content_length = (try file.stat()).size }, |
| 1654 | | .none => {}, |
| 1655 | | } |
| 1628 | if (options.payload) |payload| req.transfer_encoding = .{ .content_length = payload.len }; |
| 1656 | 1629 | |
| 1657 | 1630 | try req.send(.{ .raw_uri = options.raw_uri }); |
| 1658 | 1631 | |
| 1659 | | switch (options.payload) { |
| 1660 | | .string => |str| try req.writeAll(str), |
| 1661 | | .file => |file| { |
| 1662 | | var fifo = std.fifo.LinearFifo(u8, .{ .Static = 8192 }).init(); |
| 1663 | | try fifo.pump(file.reader(), req.writer()); |
| 1664 | | }, |
| 1665 | | .none => {}, |
| 1666 | | } |
| 1632 | if (options.payload) |payload| try req.writeAll(payload); |
| 1667 | 1633 | |
| 1668 | 1634 | try req.finish(); |
| 1669 | 1635 | try req.wait(); |
| 1670 | 1636 | |
| 1671 | | var res: FetchResult = .{ |
| 1672 | | .status = req.response.status, |
| 1673 | | .allocator = allocator, |
| 1674 | | .options = options, |
| 1675 | | }; |
| 1676 | | |
| 1677 | | switch (options.response_strategy) { |
| 1678 | | .storage => |storage| switch (storage) { |
| 1679 | | .dynamic => |max| res.body = try req.reader().readAllAlloc(allocator, max), |
| 1680 | | .static => |buf| res.body = buf[0..try req.reader().readAll(buf)], |
| 1637 | switch (options.response_storage) { |
| 1638 | .ignore => { |
| 1639 | // Take advantage of request internals to discard the response body |
| 1640 | // and make the connection available for another request. |
| 1641 | req.response.skip = true; |
| 1642 | assert(try req.transferRead(&.{}) == 0); // No buffer is necessary when skipping. |
| 1681 | 1643 | }, |
| 1682 | | .file => |file| { |
| 1683 | | var fifo = std.fifo.LinearFifo(u8, .{ .Static = 8192 }).init(); |
| 1684 | | try fifo.pump(req.reader(), file.writer()); |
| 1644 | .dynamic => |list| { |
| 1645 | const max_append_size = options.max_append_size orelse 2 * 1024 * 1024; |
| 1646 | try req.reader().readAllArrayList(list, max_append_size); |
| 1685 | 1647 | }, |
| 1686 | | .none => { // Take advantage of request internals to discard the response body and make the connection available for another request. |
| 1687 | | req.response.skip = true; |
| 1688 | | |
| 1689 | | assert(try req.transferRead(&.{}) == 0); // we're skipping, no buffer is necessary |
| 1648 | .static => |list| { |
| 1649 | const buf = b: { |
| 1650 | const buf = list.unusedCapacitySlice(); |
| 1651 | if (options.max_append_size) |len| { |
| 1652 | if (len < buf.len) break :b buf[0..len]; |
| 1653 | } |
| 1654 | break :b buf; |
| 1655 | }; |
| 1656 | list.items.len += try req.reader().readAll(buf); |
| 1690 | 1657 | }, |
| 1691 | 1658 | } |
| 1692 | 1659 | |
| 1693 | | return res; |
| 1660 | return .{ |
| 1661 | .status = req.response.status, |
| 1662 | }; |
| 1694 | 1663 | } |
| 1695 | 1664 | |
| 1696 | 1665 | test { |