struct Response [src]

A HTTP response originating from a server.

Fields

version: http.Version
status: http.Status
reason: []const u8
location: ?[]const u8 = nullPoints into the user-provided server_header_buffer.
content_type: ?[]const u8 = nullPoints into the user-provided server_header_buffer.
content_disposition: ?[]const u8 = nullPoints into the user-provided server_header_buffer.
keep_alive: bool
content_length: ?u64 = nullIf present, the number of bytes in the response body.
transfer_encoding: http.TransferEncoding = .noneIf present, the transfer encoding of the response body, otherwise none.
transfer_compression: http.ContentEncoding = .identityIf present, the compression of the response body, otherwise identity (no compression).
parser: proto.HeadersParser
compression: Compression = .none
skip: bool = falseWhether the response body should be skipped. Any data read from the response body will be discarded.

Members

Source

pub const Response = struct { version: http.Version, status: http.Status, reason: []const u8, /// Points into the user-provided `server_header_buffer`. location: ?[]const u8 = null, /// Points into the user-provided `server_header_buffer`. content_type: ?[]const u8 = null, /// Points into the user-provided `server_header_buffer`. content_disposition: ?[]const u8 = null, keep_alive: bool, /// If present, the number of bytes in the response body. content_length: ?u64 = null, /// If present, the transfer encoding of the response body, otherwise none. transfer_encoding: http.TransferEncoding = .none, /// If present, the compression of the response body, otherwise identity (no compression). transfer_compression: http.ContentEncoding = .identity, parser: proto.HeadersParser, compression: Compression = .none, /// Whether the response body should be skipped. Any data read from the /// response body will be discarded. skip: bool = false, pub const ParseError = error{ HttpHeadersInvalid, HttpHeaderContinuationsUnsupported, HttpTransferEncodingUnsupported, HttpConnectionHeaderUnsupported, InvalidContentLength, CompressionUnsupported, }; pub fn parse(res: *Response, bytes: []const u8) ParseError!void { var it = mem.splitSequence(u8, bytes, "\r\n"); const first_line = it.next().?; if (first_line.len < 12) { return error.HttpHeadersInvalid; } const version: http.Version = switch (int64(first_line[0..8])) { int64("HTTP/1.0") => .@"HTTP/1.0", int64("HTTP/1.1") => .@"HTTP/1.1", else => return error.HttpHeadersInvalid, }; if (first_line[8] != ' ') return error.HttpHeadersInvalid; const status: http.Status = @enumFromInt(parseInt3(first_line[9..12])); const reason = mem.trimLeft(u8, first_line[12..], " "); res.version = version; res.status = status; res.reason = reason; res.keep_alive = switch (version) { .@"HTTP/1.0" => false, .@"HTTP/1.1" => true, }; while (it.next()) |line| { if (line.len == 0) return; switch (line[0]) { ' ', '\t' => return error.HttpHeaderContinuationsUnsupported, else => {}, } var line_it = mem.splitScalar(u8, line, ':'); const header_name = line_it.next().?; const header_value = mem.trim(u8, line_it.rest(), " \t"); if (header_name.len == 0) return error.HttpHeadersInvalid; if (std.ascii.eqlIgnoreCase(header_name, "connection")) { res.keep_alive = !std.ascii.eqlIgnoreCase(header_value, "close"); } else if (std.ascii.eqlIgnoreCase(header_name, "content-type")) { res.content_type = header_value; } else if (std.ascii.eqlIgnoreCase(header_name, "location")) { res.location = header_value; } else if (std.ascii.eqlIgnoreCase(header_name, "content-disposition")) { res.content_disposition = header_value; } else if (std.ascii.eqlIgnoreCase(header_name, "transfer-encoding")) { // Transfer-Encoding: second, first // Transfer-Encoding: deflate, chunked var iter = mem.splitBackwardsScalar(u8, header_value, ','); const first = iter.first(); const trimmed_first = mem.trim(u8, first, " "); var next: ?[]const u8 = first; if (std.meta.stringToEnum(http.TransferEncoding, trimmed_first)) |transfer| { if (res.transfer_encoding != .none) return error.HttpHeadersInvalid; // we already have a transfer encoding res.transfer_encoding = transfer; next = iter.next(); } if (next) |second| { const trimmed_second = mem.trim(u8, second, " "); if (std.meta.stringToEnum(http.ContentEncoding, trimmed_second)) |transfer| { if (res.transfer_compression != .identity) return error.HttpHeadersInvalid; // double compression is not supported res.transfer_compression = transfer; } else { return error.HttpTransferEncodingUnsupported; } } if (iter.next()) |_| return error.HttpTransferEncodingUnsupported; } else if (std.ascii.eqlIgnoreCase(header_name, "content-length")) { const content_length = std.fmt.parseInt(u64, header_value, 10) catch return error.InvalidContentLength; if (res.content_length != null and res.content_length != content_length) return error.HttpHeadersInvalid; res.content_length = content_length; } else if (std.ascii.eqlIgnoreCase(header_name, "content-encoding")) { if (res.transfer_compression != .identity) return error.HttpHeadersInvalid; const trimmed = mem.trim(u8, header_value, " "); if (std.meta.stringToEnum(http.ContentEncoding, trimmed)) |ce| { res.transfer_compression = ce; } else { return error.HttpTransferEncodingUnsupported; } } } return error.HttpHeadersInvalid; // missing empty line } test parse { const response_bytes = "HTTP/1.1 200 OK\r\n" ++ "LOcation:url\r\n" ++ "content-tYpe: text/plain\r\n" ++ "content-disposition:attachment; filename=example.txt \r\n" ++ "content-Length:10\r\n" ++ "TRansfer-encoding:\tdeflate, chunked \r\n" ++ "connectioN:\t keep-alive \r\n\r\n"; var header_buffer: [1024]u8 = undefined; var res = Response{ .status = undefined, .reason = undefined, .version = undefined, .keep_alive = false, .parser = .init(&header_buffer), }; @memcpy(header_buffer[0..response_bytes.len], response_bytes); res.parser.header_bytes_len = response_bytes.len; try res.parse(response_bytes); try testing.expectEqual(.@"HTTP/1.1", res.version); try testing.expectEqualStrings("OK", res.reason); try testing.expectEqual(.ok, res.status); try testing.expectEqualStrings("url", res.location.?); try testing.expectEqualStrings("text/plain", res.content_type.?); try testing.expectEqualStrings("attachment; filename=example.txt", res.content_disposition.?); try testing.expectEqual(true, res.keep_alive); try testing.expectEqual(10, res.content_length.?); try testing.expectEqual(.chunked, res.transfer_encoding); try testing.expectEqual(.deflate, res.transfer_compression); } inline fn int64(array: *const [8]u8) u64 { return @bitCast(array.*); } fn parseInt3(text: *const [3]u8) u10 { if (use_vectors) { const nnn: @Vector(3, u8) = text.*; const zero: @Vector(3, u8) = .{ '0', '0', '0' }; const mmm: @Vector(3, u10) = .{ 100, 10, 1 }; return @reduce(.Add, @as(@Vector(3, u10), nnn -% zero) *% mmm); } return std.fmt.parseInt(u10, text, 10) catch unreachable; } test parseInt3 { const expectEqual = testing.expectEqual; try expectEqual(@as(u10, 0), parseInt3("000")); try expectEqual(@as(u10, 418), parseInt3("418")); try expectEqual(@as(u10, 999), parseInt3("999")); } pub fn iterateHeaders(r: Response) http.HeaderIterator { return .init(r.parser.get()); } test iterateHeaders { const response_bytes = "HTTP/1.1 200 OK\r\n" ++ "LOcation:url\r\n" ++ "content-tYpe: text/plain\r\n" ++ "content-disposition:attachment; filename=example.txt \r\n" ++ "content-Length:10\r\n" ++ "TRansfer-encoding:\tdeflate, chunked \r\n" ++ "connectioN:\t keep-alive \r\n\r\n"; var header_buffer: [1024]u8 = undefined; var res = Response{ .status = undefined, .reason = undefined, .version = undefined, .keep_alive = false, .parser = .init(&header_buffer), }; @memcpy(header_buffer[0..response_bytes.len], response_bytes); res.parser.header_bytes_len = response_bytes.len; var it = res.iterateHeaders(); { const header = it.next().?; try testing.expectEqualStrings("LOcation", header.name); try testing.expectEqualStrings("url", header.value); try testing.expect(!it.is_trailer); } { const header = it.next().?; try testing.expectEqualStrings("content-tYpe", header.name); try testing.expectEqualStrings("text/plain", header.value); try testing.expect(!it.is_trailer); } { const header = it.next().?; try testing.expectEqualStrings("content-disposition", header.name); try testing.expectEqualStrings("attachment; filename=example.txt", header.value); try testing.expect(!it.is_trailer); } { const header = it.next().?; try testing.expectEqualStrings("content-Length", header.name); try testing.expectEqualStrings("10", header.value); try testing.expect(!it.is_trailer); } { const header = it.next().?; try testing.expectEqualStrings("TRansfer-encoding", header.name); try testing.expectEqualStrings("deflate, chunked", header.value); try testing.expect(!it.is_trailer); } { const header = it.next().?; try testing.expectEqualStrings("connectioN", header.name); try testing.expectEqualStrings("keep-alive", header.value); try testing.expect(!it.is_trailer); } try testing.expectEqual(null, it.next()); } }