struct Response [src]
Fields
stream: net.Stream
send_buffer: []u8
send_buffer_start: usizeIndex of the first byte in send_buffer.
This is 0 unless a short write happens in write.
send_buffer_end: usizeIndex of the last byte + 1 in send_buffer.
transfer_encoding: TransferEncodingnull means transfer-encoding: chunked.
As a debugging utility, counts down to zero as bytes are written.
elide_body: bool
chunk_len: usizeIndicates 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.
Members
- end (Function)
- endChunked (Function)
- EndChunkedOptions (struct)
- flush (Function)
- TransferEncoding (union)
- write (Function)
- writeAll (Function)
- WriteError (Error Set)
- writer (Function)
Source
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 = .{
.{
.base = r.send_buffer.ptr + r.send_buffer_start,
.len = send_buffer_len,
},
.{
.base = bytes.ptr,
.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 = .{
.{
.base = r.send_buffer.ptr + r.send_buffer_start,
.len = send_buffer_len - r.chunk_len,
},
.{
.base = chunk_header.ptr,
.len = chunk_header.len,
},
.{
.base = r.send_buffer.ptr + r.send_buffer_end - r.chunk_len,
.len = r.chunk_len,
},
.{
.base = bytes.ptr,
.len = bytes.len,
},
.{
.base = "\r\n",
.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] = .{
.base = http_headers.ptr,
.len = http_headers.len,
};
iovecs_len += 1;
if (r.chunk_len > 0) {
iovecs[iovecs_len] = .{
.base = chunk_header.ptr,
.len = chunk_header.len,
};
iovecs_len += 1;
iovecs[iovecs_len] = .{
.base = r.send_buffer.ptr + r.send_buffer_end - r.chunk_len,
.len = r.chunk_len,
};
iovecs_len += 1;
iovecs[iovecs_len] = .{
.base = "\r\n",
.len = 2,
};
iovecs_len += 1;
}
if (end_trailers) |trailers| {
iovecs[iovecs_len] = .{
.base = "0\r\n",
.len = 3,
};
iovecs_len += 1;
for (trailers) |trailer| {
iovecs[iovecs_len] = .{
.base = trailer.name.ptr,
.len = trailer.name.len,
};
iovecs_len += 1;
iovecs[iovecs_len] = .{
.base = ": ",
.len = 2,
};
iovecs_len += 1;
if (trailer.value.len != 0) {
iovecs[iovecs_len] = .{
.base = trailer.value.ptr,
.len = trailer.value.len,
};
iovecs_len += 1;
}
iovecs[iovecs_len] = .{
.base = "\r\n",
.len = 2,
};
iovecs_len += 1;
}
iovecs[iovecs_len] = .{
.base = "\r\n",
.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,
};
}
}