    //! Blocking HTTP server implementation.
    //! Handles a single connection's lifecycle.

    connection: net.Server.Connection,
    /// Keeps track of whether the Server is ready to accept a new request on the
    /// same connection, and makes invalid API usage cause assertion failures
    /// rather than HTTP protocol violations.
    state: State,
    /// User-provided buffer that must outlive this Server.
    /// Used to store the client's entire HTTP header.
    read_buffer: []u8,
    /// Amount of available data inside read_buffer.
    read_buffer_len: usize,
    /// Index into `read_buffer` of the first byte of the next HTTP request.
    next_request_start: usize,

    pub const State = enum {
        /// The connection is available to be used for the first time, or reused.
        ready,
        /// An error occurred in `receiveHead`.
        receiving_head,
        /// A Request object has been obtained and from there a Response can be
        /// opened.
        received_head,
        /// The client is uploading something to this Server.
        receiving_body,
        /// The connection is eligible for another HTTP request, however the client
        /// and server did not negotiate a persistent connection.
        closing,
    };

    /// Initialize an HTTP server that can respond to multiple requests on the same
    /// connection.
    /// The returned `Server` is ready for `receiveHead` to be called.
    pub fn init(connection: net.Server.Connection, read_buffer: []u8) Server {
        return .{
            .connection = connection,
            .state = .ready,
            .read_buffer = read_buffer,
            .read_buffer_len = 0,
            .next_request_start = 0,
        };
    }

    pub const ReceiveHeadError = error{
        /// Client sent too many bytes of HTTP headers.
        /// The HTTP specification suggests to respond with a 431 status code
        /// before closing the connection.
        HttpHeadersOversize,
        /// Client sent headers that did not conform to the HTTP protocol.
        HttpHeadersInvalid,
        /// A low level I/O error occurred trying to read the headers.
        HttpHeadersUnreadable,
        /// Partial HTTP request was received but the connection was closed before
        /// fully receiving the headers.
        HttpRequestTruncated,
        /// The client sent 0 bytes of headers before closing the stream.
        /// In other words, a keep-alive connection was finally closed.
        HttpConnectionClosing,
    };

    /// The header bytes reference the read buffer that Server was initialized with
    /// and remain alive until the next call to receiveHead.
    pub fn receiveHead(s: *Server) ReceiveHeadError!Request {
        assert(s.state == .ready);
        s.state = .received_head;
        errdefer s.state = .receiving_head;

        // In case of a reused connection, move the next request's bytes to the

        // beginning of the buffer.

        if (s.next_request_start > 0) {
            if (s.read_buffer_len > s.next_request_start) {
                rebase(s, 0);
            } else {
                s.read_buffer_len = 0;
            }
        }

        var hp: http.HeadParser = .{};

        if (s.read_buffer_len > 0) {
            const bytes = s.read_buffer[0..s.read_buffer_len];
            const end = hp.feed(bytes);
            if (hp.state == .finished)
                return finishReceivingHead(s, end);
        }

        while (true) {
            const buf = s.read_buffer[s.read_buffer_len..];
            if (buf.len == 0)
                return error.HttpHeadersOversize;
            const read_n = s.connection.stream.read(buf) catch
                return error.HttpHeadersUnreadable;
            if (read_n == 0) {
                if (s.read_buffer_len > 0) {
                    return error.HttpRequestTruncated;
                } else {
                    return error.HttpConnectionClosing;
                }
            }
            s.read_buffer_len += read_n;
            const bytes = buf[0..read_n];
            const end = hp.feed(bytes);
            if (hp.state == .finished)
                return finishReceivingHead(s, s.read_buffer_len - bytes.len + end);
        }
    }

    fn finishReceivingHead(s: *Server, head_end: usize) ReceiveHeadError!Request {
        return .{
            .server = s,
            .head_end = head_end,
            .head = Request.Head.parse(s.read_buffer[0..head_end]) catch
                return error.HttpHeadersInvalid,
            .reader_state = undefined,
        };
    }

    pub const Request = struct {
        server: *Server,
        /// Index into Server's read_buffer.
        head_end: usize,
        head: Head,
        reader_state: union {
            remaining_content_length: u64,
            chunk_parser: http.ChunkParser,
        },

        pub const Compression = union(enum) {
            pub const DeflateDecompressor = std.compress.zlib.Decompressor(std.io.AnyReader);
            pub const GzipDecompressor = std.compress.gzip.Decompressor(std.io.AnyReader);
            pub const ZstdDecompressor = std.compress.zstd.Decompressor(std.io.AnyReader);

            deflate: DeflateDecompressor,
            gzip: GzipDecompressor,
            zstd: ZstdDecompressor,
            none: void,
        };

        pub const Head = struct {
            method: http.Method,
            target: []const u8,
            version: http.Version,
            expect: ?[]const u8,
            content_type: ?[]const u8,
            content_length: ?u64,
            transfer_encoding: http.TransferEncoding,
            transfer_compression: http.ContentEncoding,
            keep_alive: bool,
            compression: Compression,

            pub const ParseError = error{
                UnknownHttpMethod,
                HttpHeadersInvalid,
                HttpHeaderContinuationsUnsupported,
                HttpTransferEncodingUnsupported,
                HttpConnectionHeaderUnsupported,
                InvalidContentLength,
                CompressionUnsupported,
                MissingFinalNewline,
            };

            pub fn parse(bytes: []const u8) ParseError!Head {
                var it = mem.splitSequence(u8, bytes, "\r\n");

                const first_line = it.next().?;
                if (first_line.len < 10)
                    return error.HttpHeadersInvalid;

                const method_end = mem.indexOfScalar(u8, first_line, ' ') orelse
                    return error.HttpHeadersInvalid;
                if (method_end > 24) return error.HttpHeadersInvalid;

                const method_str = first_line[0..method_end];
                const method: http.Method = @enumFromInt(http.Method.parse(method_str));

                const version_start = mem.lastIndexOfScalar(u8, first_line, ' ') orelse
                    return error.HttpHeadersInvalid;
                if (version_start == method_end) return error.HttpHeadersInvalid;

                const version_str = first_line[version_start + 1 ..];
                if (version_str.len != 8) return error.HttpHeadersInvalid;
                const version: http.Version = switch (int64(version_str[0..8])) {
                    int64("HTTP/1.0") => .@"HTTP/1.0",
                    int64("HTTP/1.1") => .@"HTTP/1.1",
                    else => return error.HttpHeadersInvalid,
                };

                const target = first_line[method_end + 1 .. version_start];

                var head: Head = .{
                    .method = method,
                    .target = target,
                    .version = version,
                    .expect = null,
                    .content_type = null,
                    .content_length = null,
                    .transfer_encoding = .none,
                    .transfer_compression = .identity,
                    .keep_alive = switch (version) {
                        .@"HTTP/1.0" => false,
                        .@"HTTP/1.1" => true,
                    },
                    .compression = .none,
                };

                while (it.next()) |line| {
                    if (line.len == 0) return head;
                    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")) {
                        head.keep_alive = !std.ascii.eqlIgnoreCase(header_value, "close");
                    } else if (std.ascii.eqlIgnoreCase(header_name, "expect")) {
                        head.expect = header_value;
                    } else if (std.ascii.eqlIgnoreCase(header_name, "content-type")) {
                        head.content_type = header_value;
                    } else if (std.ascii.eqlIgnoreCase(header_name, "content-length")) {
                        if (head.content_length != null) return error.HttpHeadersInvalid;
                        head.content_length = std.fmt.parseInt(u64, header_value, 10) catch
                            return error.InvalidContentLength;
                    } else if (std.ascii.eqlIgnoreCase(header_name, "content-encoding")) {
                        if (head.transfer_compression != .identity) return error.HttpHeadersInvalid;

                        const trimmed = mem.trim(u8, header_value, " ");

                        if (std.meta.stringToEnum(http.ContentEncoding, trimmed)) |ce| {
                            head.transfer_compression = ce;
                        } else {
                            return error.HttpTransferEncodingUnsupported;
                        }
                    } 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 (head.transfer_encoding != .none)
                                return error.HttpHeadersInvalid; // we already have a transfer encoding

                            head.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 (head.transfer_compression != .identity)
                                    return error.HttpHeadersInvalid; // double compression is not supported

                                head.transfer_compression = transfer;
                            } else {
                                return error.HttpTransferEncodingUnsupported;
                            }
                        }

                        if (iter.next()) |_| return error.HttpTransferEncodingUnsupported;
                    }
                }
                return error.MissingFinalNewline;
            }

            test parse {
                const request_bytes = "GET /hi HTTP/1.0\r\n" ++
                    "content-tYpe: text/plain\r\n" ++
                    "content-Length:10\r\n" ++
                    "expeCt:   100-continue \r\n" ++
                    "TRansfer-encoding:\tdeflate, chunked \r\n" ++
                    "connectioN:\t keep-alive \r\n\r\n";

                const req = try parse(request_bytes);

                try testing.expectEqual(.GET, req.method);
                try testing.expectEqual(.@"HTTP/1.0", req.version);
                try testing.expectEqualStrings("/hi", req.target);

                try testing.expectEqualStrings("text/plain", req.content_type.?);
                try testing.expectEqualStrings("100-continue", req.expect.?);

                try testing.expectEqual(true, req.keep_alive);
                try testing.expectEqual(10, req.content_length.?);
                try testing.expectEqual(.chunked, req.transfer_encoding);
                try testing.expectEqual(.deflate, req.transfer_compression);
            }

            inline fn int64(array: *const [8]u8) u64 {
                return @bitCast(array.*);
            }
        };

        pub fn iterateHeaders(r: *Request) http.HeaderIterator {
            return http.HeaderIterator.init(r.server.read_buffer[0..r.head_end]);
        }

        test iterateHeaders {
            const request_bytes = "GET /hi HTTP/1.0\r\n" ++
                "content-tYpe: text/plain\r\n" ++
                "content-Length:10\r\n" ++
                "expeCt:   100-continue \r\n" ++
                "TRansfer-encoding:\tdeflate, chunked \r\n" ++
                "connectioN:\t keep-alive \r\n\r\n";

            var read_buffer: [500]u8 = undefined;
            @memcpy(read_buffer[0..request_bytes.len], request_bytes);

            var server: Server = .{
                .connection = undefined,
                .state = .ready,
                .read_buffer = &read_buffer,
                .read_buffer_len = request_bytes.len,
                .next_request_start = 0,
            };

            var request: Request = .{
                .server = &server,
                .head_end = request_bytes.len,
                .head = undefined,
                .reader_state = undefined,
            };

            var it = request.iterateHeaders();
            {
                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-Length", header.name);
                try testing.expectEqualStrings("10", header.value);
                try testing.expect(!it.is_trailer);
            }
            {
                const header = it.next().?;
                try testing.expectEqualStrings("expeCt", header.name);
                try testing.expectEqualStrings("100-continue", 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());
        }

        pub const RespondOptions = struct {
            version: http.Version = .@"HTTP/1.1",
            status: http.Status = .ok,
            reason: ?[]const u8 = null,
            keep_alive: bool = true,
            extra_headers: []const http.Header = &.{},
            transfer_encoding: ?http.TransferEncoding = null,
        };

        /// Send an entire HTTP response to the client, including headers and body.
        ///
        /// Automatically handles HEAD requests by omitting the body.
        ///
        /// Unless `transfer_encoding` is specified, uses the "content-length"
        /// header.
        ///
        /// If the request contains a body and the connection is to be reused,
        /// discards the request body, leaving the Server in the `ready` state. If
        /// this discarding fails, the connection is marked as not to be reused and
        /// no error is surfaced.
        ///
        /// Asserts status is not `continue`.
        /// Asserts there are at most 25 extra_headers.
        /// Asserts that "\r\n" does not occur in any header name or value.
        pub fn respond(
            request: *Request,
            content: []const u8,
            options: RespondOptions,
        ) Response.WriteError!void {
            const max_extra_headers = 25;
            assert(options.status != .@"continue");
            assert(options.extra_headers.len <= max_extra_headers);
            if (std.debug.runtime_safety) {
                for (options.extra_headers) |header| {
                    assert(header.name.len != 0);
                    assert(std.mem.indexOfScalar(u8, header.name, ':') == null);
                    assert(std.mem.indexOfPosLinear(u8, header.name, 0, "\r\n") == null);
                    assert(std.mem.indexOfPosLinear(u8, header.value, 0, "\r\n") == null);
                }
            }

            const transfer_encoding_none = (options.transfer_encoding orelse .chunked) == .none;
            const server_keep_alive = !transfer_encoding_none and options.keep_alive;
            const keep_alive = request.discardBody(server_keep_alive);

            const phrase = options.reason orelse options.status.phrase() orelse "";

            var first_buffer: [500]u8 = undefined;
            var h = std.ArrayListUnmanaged(u8).initBuffer(&first_buffer);
            if (request.head.expect != null) {
                // reader() and hence discardBody() above sets expect to null if it

                // is handled. So the fact that it is not null here means unhandled.

                h.appendSliceAssumeCapacity("HTTP/1.1 417 Expectation Failed\r\n");
                if (!keep_alive) h.appendSliceAssumeCapacity("connection: close\r\n");
                h.appendSliceAssumeCapacity("content-length: 0\r\n\r\n");
                try request.server.connection.stream.writeAll(h.items);
                return;
            }
            h.fixedWriter().print("{s} {d} {s}\r\n", .{
                @tagName(options.version), @intFromEnum(options.status), phrase,
            }) catch unreachable;

            switch (options.version) {
                .@"HTTP/1.0" => if (keep_alive) h.appendSliceAssumeCapacity("connection: keep-alive\r\n"),
                .@"HTTP/1.1" => if (!keep_alive) h.appendSliceAssumeCapacity("connection: close\r\n"),
            }

            if (options.transfer_encoding) |transfer_encoding| switch (transfer_encoding) {
                .none => {},
                .chunked => h.appendSliceAssumeCapacity("transfer-encoding: chunked\r\n"),
            } else {
                h.fixedWriter().print("content-length: {d}\r\n", .{content.len}) catch unreachable;
            }

            var chunk_header_buffer: [18]u8 = undefined;
            var iovecs: [max_extra_headers * 4 + 3]std.posix.iovec_const = undefined;
            var iovecs_len: usize = 0;

            iovecs[iovecs_len] = .{
                .iov_base = h.items.ptr,
                .iov_len = h.items.len,
            };
            iovecs_len += 1;

            for (options.extra_headers) |header| {
                iovecs[iovecs_len] = .{
                    .iov_base = header.name.ptr,
                    .iov_len = header.name.len,
                };
                iovecs_len += 1;

                iovecs[iovecs_len] = .{
                    .iov_base = ": ",
                    .iov_len = 2,
                };
                iovecs_len += 1;

                if (header.value.len != 0) {
                    iovecs[iovecs_len] = .{
                        .iov_base = header.value.ptr,
                        .iov_len = header.value.len,
                    };
                    iovecs_len += 1;
                }

                iovecs[iovecs_len] = .{
                    .iov_base = "\r\n",
                    .iov_len = 2,
                };
                iovecs_len += 1;
            }

            iovecs[iovecs_len] = .{
                .iov_base = "\r\n",
                .iov_len = 2,
            };
            iovecs_len += 1;

            if (request.head.method != .HEAD) {
                const is_chunked = (options.transfer_encoding orelse .none) == .chunked;
                if (is_chunked) {
                    if (content.len > 0) {
                        const chunk_header = std.fmt.bufPrint(
                            &chunk_header_buffer,
                            "{x}\r\n",
                            .{content.len},
                        ) catch unreachable;

                        iovecs[iovecs_len] = .{
                            .iov_base = chunk_header.ptr,
                            .iov_len = chunk_header.len,
                        };
                        iovecs_len += 1;

                        iovecs[iovecs_len] = .{
                            .iov_base = content.ptr,
                            .iov_len = content.len,
                        };
                        iovecs_len += 1;

                        iovecs[iovecs_len] = .{
                            .iov_base = "\r\n",
                            .iov_len = 2,
                        };
                        iovecs_len += 1;
                    }

                    iovecs[iovecs_len] = .{
                        .iov_base = "0\r\n\r\n",
                        .iov_len = 5,
                    };
                    iovecs_len += 1;
                } else if (content.len > 0) {
                    iovecs[iovecs_len] = .{
                        .iov_base = content.ptr,
                        .iov_len = content.len,
                    };
                    iovecs_len += 1;
                }
            }

            try request.server.connection.stream.writevAll(iovecs[0..iovecs_len]);
        }

        pub const RespondStreamingOptions = struct {
            /// An externally managed slice of memory used to batch bytes before
            /// sending. `respondStreaming` asserts this is large enough to store
            /// the full HTTP response head.
            ///
            /// Must outlive the returned Response.
            send_buffer: []u8,
            /// If provided, the response will use the content-length header;
            /// otherwise it will use transfer-encoding: chunked.
            content_length: ?u64 = null,
            /// Options that are shared with the `respond` method.
            respond_options: RespondOptions = .{},
        };

        /// The header is buffered but not sent until Response.flush is called.
        ///
        /// If the request contains a body and the connection is to be reused,
        /// discards the request body, leaving the Server in the `ready` state. If
        /// this discarding fails, the connection is marked as not to be reused and
        /// no error is surfaced.
        ///
        /// HEAD requests are handled transparently by setting a flag on the
        /// returned Response to omit the body. However it may be worth noticing
        /// that flag and skipping any expensive work that would otherwise need to
        /// be done to satisfy the request.
        ///
        /// Asserts `send_buffer` is large enough to store the entire response header.
        /// Asserts status is not `continue`.
        pub fn respondStreaming(request: *Request, options: RespondStreamingOptions) Response {
            const o = options.respond_options;
            assert(o.status != .@"continue");
            const transfer_encoding_none = (o.transfer_encoding orelse .chunked) == .none;
            const server_keep_alive = !transfer_encoding_none and o.keep_alive;
            const keep_alive = request.discardBody(server_keep_alive);
            const phrase = o.reason orelse o.status.phrase() orelse "";

            var h = std.ArrayListUnmanaged(u8).initBuffer(options.send_buffer);

            const elide_body = if (request.head.expect != null) eb: {
                // reader() and hence discardBody() above sets expect to null if it

                // is handled. So the fact that it is not null here means unhandled.

                h.appendSliceAssumeCapacity("HTTP/1.1 417 Expectation Failed\r\n");
                if (!keep_alive) h.appendSliceAssumeCapacity("connection: close\r\n");
                h.appendSliceAssumeCapacity("content-length: 0\r\n\r\n");
                break :eb true;
            } else eb: {
                h.fixedWriter().print("{s} {d} {s}\r\n", .{
                    @tagName(o.version), @intFromEnum(o.status), phrase,
                }) catch unreachable;

                switch (o.version) {
                    .@"HTTP/1.0" => if (keep_alive) h.appendSliceAssumeCapacity("connection: keep-alive\r\n"),
                    .@"HTTP/1.1" => if (!keep_alive) h.appendSliceAssumeCapacity("connection: close\r\n"),
                }

                if (o.transfer_encoding) |transfer_encoding| switch (transfer_encoding) {
                    .chunked => h.appendSliceAssumeCapacity("transfer-encoding: chunked\r\n"),
                    .none => {},
                } else if (options.content_length) |len| {
                    h.fixedWriter().print("content-length: {d}\r\n", .{len}) catch unreachable;
                } else {
                    h.appendSliceAssumeCapacity("transfer-encoding: chunked\r\n");
                }

                for (o.extra_headers) |header| {
                    assert(header.name.len != 0);
                    h.appendSliceAssumeCapacity(header.name);
                    h.appendSliceAssumeCapacity(": ");
                    h.appendSliceAssumeCapacity(header.value);
                    h.appendSliceAssumeCapacity("\r\n");
                }

                h.appendSliceAssumeCapacity("\r\n");
                break :eb request.head.method == .HEAD;
            };

            return .{
                .stream = request.server.connection.stream,
                .send_buffer = options.send_buffer,
                .send_buffer_start = 0,
                .send_buffer_end = h.items.len,
                .transfer_encoding = if (o.transfer_encoding) |te| switch (te) {
                    .chunked => .chunked,
                    .none => .none,
                } else if (options.content_length) |len| .{
                    .content_length = len,
                } else .chunked,
                .elide_body = elide_body,
                .chunk_len = 0,
            };
        }

        pub const ReadError = net.Stream.ReadError || error{
            HttpChunkInvalid,
            HttpHeadersOversize,
        };

        fn read_cl(context: *const anyopaque, buffer: []u8) ReadError!usize {
            const request: *Request = @constCast(@alignCast(@ptrCast(context)));
            const s = request.server;

            const remaining_content_length = &request.reader_state.remaining_content_length;
            if (remaining_content_length.* == 0) {
                s.state = .ready;
                return 0;
            }
            assert(s.state == .receiving_body);
            const available = try fill(s, request.head_end);
            const len = @min(remaining_content_length.*, available.len, buffer.len);
            @memcpy(buffer[0..len], available[0..len]);
            remaining_content_length.* -= len;
            s.next_request_start += len;
            if (remaining_content_length.* == 0)
                s.state = .ready;
            return len;
        }

        fn fill(s: *Server, head_end: usize) ReadError![]u8 {
            const available = s.read_buffer[s.next_request_start..s.read_buffer_len];
            if (available.len > 0) return available;
            s.next_request_start = head_end;
            s.read_buffer_len = head_end + try s.connection.stream.read(s.read_buffer[head_end..]);
            return s.read_buffer[head_end..s.read_buffer_len];
        }

        fn read_chunked(context: *const anyopaque, buffer: []u8) ReadError!usize {
            const request: *Request = @constCast(@alignCast(@ptrCast(context)));
            const s = request.server;

            const cp = &request.reader_state.chunk_parser;
            const head_end = request.head_end;

            // Protect against returning 0 before the end of stream.

            var out_end: usize = 0;
            while (out_end == 0) {
                switch (cp.state) {
                    .invalid => return 0,
                    .data => {
                        assert(s.state == .receiving_body);
                        const available = try fill(s, head_end);
                        const len = @min(cp.chunk_len, available.len, buffer.len);
                        @memcpy(buffer[0..len], available[0..len]);
                        cp.chunk_len -= len;
                        if (cp.chunk_len == 0)
                            cp.state = .data_suffix;
                        out_end += len;
                        s.next_request_start += len;
                        continue;
                    },
                    else => {
                        assert(s.state == .receiving_body);
                        const available = try fill(s, head_end);
                        const n = cp.feed(available);
                        switch (cp.state) {
                            .invalid => return error.HttpChunkInvalid,
                            .data => {
                                if (cp.chunk_len == 0) {
                                    // The next bytes in the stream are trailers,

                                    // or \r\n to indicate end of chunked body.

                                    //

                                    // This function must append the trailers at

                                    // head_end so that headers and trailers are

                                    // together.

                                    //

                                    // Since returning 0 would indicate end of

                                    // stream, this function must read all the

                                    // trailers before returning.

                                    if (s.next_request_start > head_end) rebase(s, head_end);
                                    var hp: http.HeadParser = .{};
                                    {
                                        const bytes = s.read_buffer[head_end..s.read_buffer_len];
                                        const end = hp.feed(bytes);
                                        if (hp.state == .finished) {
                                            cp.state = .invalid;
                                            s.state = .ready;
                                            s.next_request_start = s.read_buffer_len - bytes.len + end;
                                            return out_end;
                                        }
                                    }
                                    while (true) {
                                        const buf = s.read_buffer[s.read_buffer_len..];
                                        if (buf.len == 0)
                                            return error.HttpHeadersOversize;
                                        const read_n = try s.connection.stream.read(buf);
                                        s.read_buffer_len += read_n;
                                        const bytes = buf[0..read_n];
                                        const end = hp.feed(bytes);
                                        if (hp.state == .finished) {
                                            cp.state = .invalid;
                                            s.state = .ready;
                                            s.next_request_start = s.read_buffer_len - bytes.len + end;
                                            return out_end;
                                        }
                                    }
                                }
                                const data = available[n..];
                                const len = @min(cp.chunk_len, data.len, buffer.len);
                                @memcpy(buffer[0..len], data[0..len]);
                                cp.chunk_len -= len;
                                if (cp.chunk_len == 0)
                                    cp.state = .data_suffix;
                                out_end += len;
                                s.next_request_start += n + len;
                                continue;
                            },
                            else => continue,
                        }
                    },
                }
            }
            return out_end;
        }

        pub const ReaderError = Response.WriteError || error{
            /// The client sent an expect HTTP header value other than
            /// "100-continue".
            HttpExpectationFailed,
        };

        /// In the case that the request contains "expect: 100-continue", this
        /// function writes the continuation header, which means it can fail with a
        /// write error. After sending the continuation header, it sets the
        /// request's expect field to `null`.
        ///
        /// Asserts that this function is only called once.
        pub fn reader(request: *Request) ReaderError!std.io.AnyReader {
            const s = request.server;
            assert(s.state == .received_head);
            s.state = .receiving_body;
            s.next_request_start = request.head_end;

            if (request.head.expect) |expect| {
                if (mem.eql(u8, expect, "100-continue")) {
                    try request.server.connection.stream.writeAll("HTTP/1.1 100 Continue\r\n\r\n");
                    request.head.expect = null;
                } else {
                    return error.HttpExpectationFailed;
                }
            }

            switch (request.head.transfer_encoding) {
                .chunked => {
                    request.reader_state = .{ .chunk_parser = http.ChunkParser.init };
                    return .{
                        .readFn = read_chunked,
                        .context = request,
                    };
                },
                .none => {
                    request.reader_state = .{
                        .remaining_content_length = request.head.content_length orelse 0,
                    };
                    return .{
                        .readFn = read_cl,
                        .context = request,
                    };
                },
            }
        }

        /// Returns whether the connection should remain persistent.
        /// If it would fail, it instead sets the Server state to `receiving_body`
        /// and returns false.
        fn discardBody(request: *Request, keep_alive: bool) bool {
            // Prepare to receive another request on the same connection.

            // There are two factors to consider:

            // * Any body the client sent must be discarded.

            // * The Server's read_buffer may already have some bytes in it from

            //   whatever came after the head, which may be the next HTTP request

            //   or the request body.

            // If the connection won't be kept alive, then none of this matters

            // because the connection will be severed after the response is sent.

            const s = request.server;
            if (keep_alive and request.head.keep_alive) switch (s.state) {
                .received_head => {
                    const r = request.reader() catch return false;
                    _ = r.discard() catch return false;
                    assert(s.state == .ready);
                    return true;
                },
                .receiving_body, .ready => return true,
                else => unreachable,
            };

            // Avoid clobbering the state in case a reading stream already exists.

            switch (s.state) {
                .received_head => s.state = .closing,
                else => {},
            }
            return false;
        }
    };

    pub const Response = struct {
        stream: net.Stream,
        send_buffer: []u8,
        /// Index of the first byte in `send_buffer`.
        /// This is 0 unless a short write happens in `write`.
        send_buffer_start: usize,
        /// Index of the last byte + 1 in `send_buffer`.
        send_buffer_end: usize,
        /// `null` means transfer-encoding: chunked.
        /// As a debugging utility, counts down to zero as bytes are written.
        transfer_encoding: TransferEncoding,
        elide_body: bool,
        /// Indicates how much of the end of the `send_buffer` corresponds to a
        /// chunk. This amount of data will be wrapped by an HTTP chunk header.
        chunk_len: usize,

        pub const TransferEncoding = union(enum) {
            /// End of connection signals the end of the stream.
            none,
            /// As a debugging utility, counts down to zero as bytes are written.
            content_length: u64,
            /// Each chunk is wrapped in a header and trailer.
            chunked,
        };

        pub const WriteError = net.Stream.WriteError;

        /// When using content-length, asserts that the amount of data sent matches
        /// the value sent in the header, then calls `flush`.
        /// Otherwise, transfer-encoding: chunked is being used, and it writes the
        /// end-of-stream message, then flushes the stream to the system.
        /// Respects the value of `elide_body` to omit all data after the headers.
        pub fn end(r: *Response) WriteError!void {
            switch (r.transfer_encoding) {
                .content_length => |len| {
                    assert(len == 0); // Trips when end() called before all bytes written.

                    try flush_cl(r);
                },
                .none => {
                    try flush_cl(r);
                },
                .chunked => {
                    try flush_chunked(r, &.{});
                },
            }
            r.* = undefined;
        }

        pub const EndChunkedOptions = struct {
            trailers: []const http.Header = &.{},
        };

        /// Asserts that the Response is using transfer-encoding: chunked.
        /// Writes the end-of-stream message and any optional trailers, then
        /// flushes the stream to the system.
        /// Respects the value of `elide_body` to omit all data after the headers.
        /// Asserts there are at most 25 trailers.
        pub fn endChunked(r: *Response, options: EndChunkedOptions) WriteError!void {
            assert(r.transfer_encoding == .chunked);
            try flush_chunked(r, options.trailers);
            r.* = undefined;
        }

        /// If using content-length, asserts that writing these bytes to the client
        /// would not exceed the content-length value sent in the HTTP header.
        /// May return 0, which does not indicate end of stream. The caller decides
        /// when the end of stream occurs by calling `end`.
        pub fn write(r: *Response, bytes: []const u8) WriteError!usize {
            switch (r.transfer_encoding) {
                .content_length, .none => return write_cl(r, bytes),
                .chunked => return write_chunked(r, bytes),
            }
        }

        fn write_cl(context: *const anyopaque, bytes: []const u8) WriteError!usize {
            const r: *Response = @constCast(@alignCast(@ptrCast(context)));

            var trash: u64 = std.math.maxInt(u64);
            const len = switch (r.transfer_encoding) {
                .content_length => |*len| len,
                else => &trash,
            };

            if (r.elide_body) {
                len.* -= bytes.len;
                return bytes.len;
            }

            if (bytes.len + r.send_buffer_end > r.send_buffer.len) {
                const send_buffer_len = r.send_buffer_end - r.send_buffer_start;
                var iovecs: [2]std.posix.iovec_const = .{
                    .{
                        .iov_base = r.send_buffer.ptr + r.send_buffer_start,
                        .iov_len = send_buffer_len,
                    },
                    .{
                        .iov_base = bytes.ptr,
                        .iov_len = bytes.len,
                    },
                };
                const n = try r.stream.writev(&iovecs);

                if (n >= send_buffer_len) {
                    // It was enough to reset the buffer.

                    r.send_buffer_start = 0;
                    r.send_buffer_end = 0;
                    const bytes_n = n - send_buffer_len;
                    len.* -= bytes_n;
                    return bytes_n;
                }

                // It didn't even make it through the existing buffer, let

                // alone the new bytes provided.

                r.send_buffer_start += n;
                return 0;
            }

            // All bytes can be stored in the remaining space of the buffer.

            @memcpy(r.send_buffer[r.send_buffer_end..][0..bytes.len], bytes);
            r.send_buffer_end += bytes.len;
            len.* -= bytes.len;
            return bytes.len;
        }

        fn write_chunked(context: *const anyopaque, bytes: []const u8) WriteError!usize {
            const r: *Response = @constCast(@alignCast(@ptrCast(context)));
            assert(r.transfer_encoding == .chunked);

            if (r.elide_body)
                return bytes.len;

            if (bytes.len + r.send_buffer_end > r.send_buffer.len) {
                const send_buffer_len = r.send_buffer_end - r.send_buffer_start;
                const chunk_len = r.chunk_len + bytes.len;
                var header_buf: [18]u8 = undefined;
                const chunk_header = std.fmt.bufPrint(&header_buf, "{x}\r\n", .{chunk_len}) catch unreachable;

                var iovecs: [5]std.posix.iovec_const = .{
                    .{
                        .iov_base = r.send_buffer.ptr + r.send_buffer_start,
                        .iov_len = send_buffer_len - r.chunk_len,
                    },
                    .{
                        .iov_base = chunk_header.ptr,
                        .iov_len = chunk_header.len,
                    },
                    .{
                        .iov_base = r.send_buffer.ptr + r.send_buffer_end - r.chunk_len,
                        .iov_len = r.chunk_len,
                    },
                    .{
                        .iov_base = bytes.ptr,
                        .iov_len = bytes.len,
                    },
                    .{
                        .iov_base = "\r\n",
                        .iov_len = 2,
                    },
                };
                // TODO make this writev instead of writevAll, which involves

                // complicating the logic of this function.

                try r.stream.writevAll(&iovecs);
                r.send_buffer_start = 0;
                r.send_buffer_end = 0;
                r.chunk_len = 0;
                return bytes.len;
            }

            // All bytes can be stored in the remaining space of the buffer.

            @memcpy(r.send_buffer[r.send_buffer_end..][0..bytes.len], bytes);
            r.send_buffer_end += bytes.len;
            r.chunk_len += bytes.len;
            return bytes.len;
        }

        /// If using content-length, asserts that writing these bytes to the client
        /// would not exceed the content-length value sent in the HTTP header.
        pub fn writeAll(r: *Response, bytes: []const u8) WriteError!void {
            var index: usize = 0;
            while (index < bytes.len) {
                index += try write(r, bytes[index..]);
            }
        }

        /// Sends all buffered data to the client.
        /// This is redundant after calling `end`.
        /// Respects the value of `elide_body` to omit all data after the headers.
        pub fn flush(r: *Response) WriteError!void {
            switch (r.transfer_encoding) {
                .none, .content_length => return flush_cl(r),
                .chunked => return flush_chunked(r, null),
            }
        }

        fn flush_cl(r: *Response) WriteError!void {
            try r.stream.writeAll(r.send_buffer[r.send_buffer_start..r.send_buffer_end]);
            r.send_buffer_start = 0;
            r.send_buffer_end = 0;
        }

        fn flush_chunked(r: *Response, end_trailers: ?[]const http.Header) WriteError!void {
            const max_trailers = 25;
            if (end_trailers) |trailers| assert(trailers.len <= max_trailers);
            assert(r.transfer_encoding == .chunked);

            const http_headers = r.send_buffer[r.send_buffer_start .. r.send_buffer_end - r.chunk_len];

            if (r.elide_body) {
                try r.stream.writeAll(http_headers);
                r.send_buffer_start = 0;
                r.send_buffer_end = 0;
                r.chunk_len = 0;
                return;
            }

            var header_buf: [18]u8 = undefined;
            const chunk_header = std.fmt.bufPrint(&header_buf, "{x}\r\n", .{r.chunk_len}) catch unreachable;

            var iovecs: [max_trailers * 4 + 5]std.posix.iovec_const = undefined;
            var iovecs_len: usize = 0;

            iovecs[iovecs_len] = .{
                .iov_base = http_headers.ptr,
                .iov_len = http_headers.len,
            };
            iovecs_len += 1;

            if (r.chunk_len > 0) {
                iovecs[iovecs_len] = .{
                    .iov_base = chunk_header.ptr,
                    .iov_len = chunk_header.len,
                };
                iovecs_len += 1;

                iovecs[iovecs_len] = .{
                    .iov_base = r.send_buffer.ptr + r.send_buffer_end - r.chunk_len,
                    .iov_len = r.chunk_len,
                };
                iovecs_len += 1;

                iovecs[iovecs_len] = .{
                    .iov_base = "\r\n",
                    .iov_len = 2,
                };
                iovecs_len += 1;
            }

            if (end_trailers) |trailers| {
                iovecs[iovecs_len] = .{
                    .iov_base = "0\r\n",
                    .iov_len = 3,
                };
                iovecs_len += 1;

                for (trailers) |trailer| {
                    iovecs[iovecs_len] = .{
                        .iov_base = trailer.name.ptr,
                        .iov_len = trailer.name.len,
                    };
                    iovecs_len += 1;

                    iovecs[iovecs_len] = .{
                        .iov_base = ": ",
                        .iov_len = 2,
                    };
                    iovecs_len += 1;

                    if (trailer.value.len != 0) {
                        iovecs[iovecs_len] = .{
                            .iov_base = trailer.value.ptr,
                            .iov_len = trailer.value.len,
                        };
                        iovecs_len += 1;
                    }

                    iovecs[iovecs_len] = .{
                        .iov_base = "\r\n",
                        .iov_len = 2,
                    };
                    iovecs_len += 1;
                }

                iovecs[iovecs_len] = .{
                    .iov_base = "\r\n",
                    .iov_len = 2,
                };
                iovecs_len += 1;
            }

            try r.stream.writevAll(iovecs[0..iovecs_len]);
            r.send_buffer_start = 0;
            r.send_buffer_end = 0;
            r.chunk_len = 0;
        }

        pub fn writer(r: *Response) std.io.AnyWriter {
            return .{
                .writeFn = switch (r.transfer_encoding) {
                    .none, .content_length => write_cl,
                    .chunked => write_chunked,
                },
                .context = r,
            };
        }
    };

    fn rebase(s: *Server, index: usize) void {
        const leftover = s.read_buffer[s.next_request_start..s.read_buffer_len];
        const dest = s.read_buffer[index..][0..leftover.len];
        if (leftover.len <= s.next_request_start - index) {
            @memcpy(dest, leftover);
        } else {
            mem.copyBackwards(u8, dest, leftover);
        }
        s.read_buffer_len = index + leftover.len;
    }

    const std = @import("../std.zig");
    const http = std.http;
    const mem = std.mem;
    const net = std.net;
    const Uri = std.Uri;
    const assert = std.debug.assert;
    const testing = std.testing;

    const Server = @This();