structstd.http.Client.Response[src]

A HTTP response originating from a server.

Fields

version: http.Version
status: http.Status
reason: []const u8
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

Points into the user-provided server_header_buffer.

keep_alive: bool
content_length: ?u64 = null

If present, the number of bytes in the response body.

transfer_encoding: http.TransferEncoding = .none

If present, the transfer encoding of the response body, otherwise none.

transfer_compression: http.ContentEncoding = .identity

If present, the compression of the response body, otherwise identity (no compression).

parser: proto.HeadersParser
compression: Compression = .none
skip: bool = false

Whether the response body should be skipped. Any data read from the response body will be discarded.

Error Sets

Error SetParseError[src]

Errors

anyerror means the error set is known only at runtime.

CompressionUnsupported
HttpConnectionHeaderUnsupported
HttpHeaderContinuationsUnsupported
HttpHeadersInvalid
HttpTransferEncodingUnsupported
InvalidContentLength

Source Code

Source code
pub const ParseError = error{
    HttpHeadersInvalid,
    HttpHeaderContinuationsUnsupported,
    HttpTransferEncodingUnsupported,
    HttpConnectionHeaderUnsupported,
    InvalidContentLength,
    CompressionUnsupported,
}

Functions

Functionparse[src]

pub fn parse(res: *Response, bytes: []const u8) ParseError!void

Parameters

res: *Response
bytes: []const u8

Example Usage

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);
}

Source Code

Source code
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
}

FunctioniterateHeaders[src]

pub fn iterateHeaders(r: Response) http.HeaderIterator

Parameters

Example Usage

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());
}

Source Code

Source code
pub fn iterateHeaders(r: Response) http.HeaderIterator {
    return .init(r.parser.get());
}

Source Code

Source code
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());
    }
}