Commit graph

93 commits

Author SHA1 Message Date
Andrew Kelley
031044b399 std: fix macos compilation errors 2025-10-29 06:20:49 -07:00
Andrew Kelley
623290ea9b
Merge pull request #24864 from ifreund/fix-std-cmd
http.BodyWriter: handle EOF in chunkedSendFile, simplify
2025-08-16 16:24:11 -07:00
Isaac Freund
0cfd07bc86
http.BodyWriter: handle EOF in chunkedSendFile, simplify
With these changes, the `zig std` command now works again and doesn't
trigger assertion failures or mess up the chunked transfer encoding.
2025-08-16 22:07:16 +02:00
Andrew Kelley
a0f9a5e78d std: more reliable HTTP and TLS networking
* std.Io.Reader: fix confused semantics of rebase. Before it was
  ambiguous whether it was supposed to be based on end or seek. Now it
  is clearly based on seek, with an added assertion for clarity.

* std.crypto.tls.Client: fix panic due to not enough buffer size
  available. Also, avoid unnecessary rebasing.

* std.http.Reader: introduce max_head_len to limit HTTP header length.
  This prevents crash in underlying reader which may require a minimum
  buffer length.

* std.http.Client: choose better buffer sizes for streams and TLS
  client. Crucially, the buffer shared by HTTP reader and TLS client
  needs to be big enough for all http headers *and* the max TLS record
  size. Bump HTTP header size default from 4K to 8K.

fixes #24872

I have noticed however that there are still fetch problems
2025-08-16 00:16:15 -07:00
kcbanner
125c4a265a Fix respondWebSocket, enable --webui on Windows
This commit re-enables the --webui functionality on windows, with the caveat that rebuild functionality is still disabled (due to deadlocks caused by reading to / writing from the same non-overlapped socket on multiple threads). I updated the UI to be aware of this, and hide the `Rebuild` button.

http.Server: Remove incorrect advance() call. This was causing browsers to disconnect the websocket, as we were sending undefined bytes.
build.WebServer: Re-enable on windows, but disable functionality that requires receiving messages from the client
build-web: Show total times in tables
2025-08-09 16:06:33 -04:00
Andrew Kelley
163a827826 std.http: remove custom method support
let's see if anybody notices it missing
2025-08-07 10:04:52 -07:00
Andrew Kelley
618a435ad4 std.http.Server: add safety for invalidated Head strings
and fix bad unit test API usage that it finds
2025-08-07 10:04:52 -07:00
Andrew Kelley
fef41c66db update build system to new http.Server API 2025-08-07 10:04:29 -07:00
Andrew Kelley
e2d81bf6c0 http fixes 2025-08-07 10:04:29 -07:00
Andrew Kelley
02908a2d8c std.http: rework for new std.Io API 2025-08-07 10:04:28 -07:00
Justus Klausecker
7c35070b90 zig fmt: apply new cast builtin order 2025-08-03 14:59:56 +02:00
Andrew Kelley
83513ade35 std.compress: rework flate to new I/O API 2025-07-31 22:10:11 -07:00
Andrew Kelley
3687eada6a std: rework zstd for new I/O API
This passes tests but it doesn't provide as big a window size as is
required to decompress larger streams.

The next commit in this branch will work towards that, without
introducing an additional buffer.
2025-07-25 14:15:33 -07:00
Andrew Kelley
df1fa36feb Revert "std.http.Server: add Request.getHeader() function (#21625)"
This reverts commit 21823d1b5d.

It's a bad API that could easily lead to O(N^2) logic.
2025-01-27 11:13:56 -08:00
dbubel
21823d1b5d
std.http.Server: add Request.getHeader() function (#21625) 2025-01-27 17:58:05 +00:00
Nameless
aecd9cc6d1 std.posix.iovec: use .base and .len instead of .iov_base and .iov_len 2024-04-28 00:20:30 -07:00
Nameless
e62b0773cc
std.http: add tests against regressions for conforming fields 2024-02-28 15:12:44 -06:00
Nameless
81713f20a6
std.http: header whitespace is optional, and not part of value 2024-02-28 15:12:43 -06:00
Jacob Young
4e2570baaf http: fix fetching a github release
* Support different keep alive defaults with different http versions.
 * Fix incorrect usage of `copyBackwards`, which copies in a backwards
   direction allowing data to be moved forward in a buffer, not
   backwards in a buffer.
2024-02-26 20:11:43 -08:00
Andrew Kelley
1b79a42da0 std.http.Server: fix use case of streaming both reading and writing 2024-02-26 13:42:01 -08:00
Techatrix
a07218cc43 http: handle header fields with empty value 2024-02-25 12:07:13 +01:00
Andrew Kelley
653d4158cd std.http.Server: expose arbitrary HTTP headers
Ultimate flexibility, just be sure to destroy the correct amount of
information when looking at them.
2024-02-23 02:58:02 -07:00
Andrew Kelley
10beb19ce7 std.http: assert against \r\n in headers
The HTTP specification does not provide a way to escape \r\n in headers,
so it's the API user's responsibility to ensure the header names and
values do not contain \r\n. Also header names must not contain ':'.

It's an assertion, not an error, because the calling code very likely is
using hard-coded values or server-provided values that do not need to be
checked, and the error would be unreachable anyway.

Untrusted user input must not be put directly into into HTTP headers.
2024-02-23 02:37:11 -07:00
Andrew Kelley
d051b13963 std.http.Server: implement respondStreaming with unknown len
no content-length header
no transfer-encoding header
2024-02-23 02:37:11 -07:00
Andrew Kelley
abde76a808 std.http.Server: handle expect: 100-continue requests
The API automatically handles these requests as expected. After
receiveHead(), the server has a chance to notice the expectation and do
something about it. If it does not, then the Server implementation will
handle it by sending the continuation header when the read stream is
created.

Both respond() and respondStreaming() send the continuation header as
part of discarding the request body, only if the read stream has not
already been created.
2024-02-23 02:37:11 -07:00
Andrew Kelley
380916c0f8 std.http.Server.Request.Respond: support all transfer encodings
Before I mistakenly thought that missing content-length meant zero when
it actually means to stream until the connection is closed.

Now the respond() function accepts transfer_encoding which can be left
as default (use content.len for content-length), set to none which makes
it omit the content-length, or chunked, which makes it format the
response as a chunked transfer even though the server has the entire
contents already buffered.

The echo-content tests are moved from test/standalone/http.zig to the
standard library where they are actually run.
2024-02-23 02:37:11 -07:00
Andrew Kelley
c0d8ac83eb std.http.Server: fix handling of HEAD + chunked 2024-02-23 02:37:11 -07:00
Andrew Kelley
2e7d8062ca std.http.Server: fix seeing phantom request 2024-02-23 02:37:11 -07:00
Andrew Kelley
b4b9f6aa4a std.http.Server: reimplement chunked uploading
* Uncouple std.http.ChunkParser from protocol.zig
* Fix receiveHead not passing leftover buffer through the header parser.
* Fix content-length read streaming

This implementation handles the final chunk length correctly rather than
"hoping" that the buffer already contains \r\n.
2024-02-23 02:37:11 -07:00
Andrew Kelley
6395ba852a std.http.Server: rework the API entirely
Mainly, this removes the poorly named `wait`, `send`, `finish`
functions, which all operated on the same "Response" object, which was
actually being used as the request.

Now, it looks like this:
1. std.net.Server.accept() gives you a std.net.Server.Connection
2. std.http.Server.init() with the connection
3. Server.receiveHead() gives you a Request
4. Request.reader() gives you a body reader
5. Request.respond() is a one-shot, or Request.respondStreaming() creates
   a Response
6. Response.writer() gives you a body writer
7. Response.end() finishes the response; Response.endChunked() allows
   passing response trailers.

In other words, the type system now guides the API user down the correct
path.

receiveHead allows extra bytes to be read into the read buffer, and then
will reuse those bytes for the body or the next request upon connection
reuse.

respond(), the one-shot function, will send the entire response in one
syscall.

Streaming response bodies no longer wastefully wraps every call to write
with a chunk header and trailer; instead it only sends the HTTP chunk
wrapper when flushing. This means the user can still control when it
happens but it also does not add unnecessary chunks.

Empirically, in my example project that uses this API, the usage code is
significantly less noisy, it has less error handling while handling
errors more correctly, it's more obvious what is happening, and it is
syscall-optimal.

Additionally:
* Uncouple std.http.HeadParser from protocol.zig
* Delete std.Server.Connection; use std.net.Server.Connection instead.
  - The API user supplies the read buffer when initializing the
    http.Server, and it is used for the HTTP head as well as a buffer
    for reading the body into.
* Replace and document the State enum. No longer is there both "start"
  and "first".
2024-02-23 02:37:11 -07:00
Andrew Kelley
2df3de1e20 std.http.Server: no more dynamic allocation
In particular remove OutOfMemory from the error set for parsing client
http headers.
2024-02-23 02:37:11 -07:00
Andrew Kelley
68d3e103b7 full send 2024-02-23 02:37:11 -07:00
Andrew Kelley
968d08af6d std.http.Server.Connection: remove dead code 2024-02-23 02:37:11 -07:00
Andrew Kelley
c7fc2d76ce std.http.Server: move closing bool
It does not belong in the Connection struct
2024-02-23 02:37:11 -07:00
Andrew Kelley
6129ecd4fe std.net, std.http: simplify 2024-02-23 02:37:11 -07:00
Andrew Kelley
f1565e3d09 std.http.Server.accept can no longer fail from OOM 2024-02-23 02:37:11 -07:00
Andrew Kelley
ddb754ff2f std.http: fix parsing incorrect tokenization 2024-02-23 02:37:11 -07:00
Andrew Kelley
f9dff2fcf1 std.http: fields at the top of the struct
Perhaps the language should enforce this.
2024-02-23 02:37:11 -07:00
Andrew Kelley
d574875f00 Revert "std.http: remove 'done' flag"
This reverts commit 42be972a72c86b32ad8403d082ab42763c6facec.

Using a bit to distinguish between headers and trailers is fine. It was
just named and documented poorly.
2024-02-23 02:37:11 -07:00
Andrew Kelley
4d401e6159 std.http: remove Headers API
I originally removed these in 402f967ed5.
I allowed them to be added back in #15299 because they were smuggled in
alongside a bug fix, however, I wasn't kidding when I said that I wanted
to take the design of std.http in a different direction than using this
data structure.

Instead, some headers are provided via explicit field names populated
while parsing the HTTP request/response, and some are provided via
new fields that support passing extra, arbitrary headers.

This resulted in simplification of logic in many places, as well as
elimination of the possibility of failure in many places. There is
less deinitialization code happening now.

Furthermore, it made it no longer necessary to clone the headers data
structure in order to handle redirects.

http_proxy and https_proxy fields are now pointers since it is common
for them to be unpopulated.

loadDefaultProxies is changed into initDefaultProxies to communicate
that it does not actually load anything from disk or from the network.
The function now is leaky; the API user must pass an already
instantiated arena allocator. Removes the need to deinitialize proxies.

Before, proxies stored arbitrary sets of headers. Now they only store
the authorization value.

Removed the duplicated code between https_proxy and http_proxy. Finally,
parsing failures of the environment variables result in errors being
emitted rather than silently ignoring the proxy.

error.CompressionNotSupported is renamed to
error.CompressionUnsupported, matching the naming convention from all
the other errors in the same set.

Removed documentation comments that were redundant with field and type
names.

Disabling zstd decompression in the server for now; see #18937.

I found some apparently dead code in src/Package/Fetch/git.zig. I want
to check with Ian about this.

I discovered that test/standalone/http.zig is dead code, it is only
being compiled but not being run. Furthermore it hangs at the end if you
run it manually. The previous commits in this branch were written under
the assumption that this test was being run with
`zig build test-standalone`.
2024-02-23 02:37:11 -07:00
Andrew Kelley
00acf8a66d std.http.Server: remove source code from doc comments
Documentation comments are not an appropriate place to put code samples.
2024-02-23 02:37:11 -07:00
Andrew Kelley
50e2a5f673 std.http: remove 'done' flag
This is a state machine that already has a `state` field. No need to
additionally store "done" - it just makes things unnecessarily
complicated and buggy.
2024-02-23 02:37:11 -07:00
Andrew Kelley
b47bd031ca std.http.Server: protect against zero-length chunks
companion commit to 919a3bae1c
2024-02-23 02:37:10 -07:00
Andrew Kelley
90bd4f226e std.http: remove the ability to heap-allocate headers
The buffer for HTTP headers is now always provided via a static buffer.
As a consequence, OutOfMemory is no longer a member of the read() error
set, and the API and implementation of Client and Server are simplified.

error.HttpHeadersExceededSizeLimit is renamed to
error.HttpHeadersOversize.
2024-02-23 02:37:10 -07:00
Andrew Kelley
f1cf300c8f std.http.Server: fix error set
It incorrectly had NotWriteable and MessageTooLong in it.
2024-02-23 02:37:10 -07:00
Andrew Kelley
f58c59f89f std.http.Server: don't emit Server HTTP header
Let the user add that if they wish to. It's not strictly necessary, and
arguably a harmful default.
2024-02-23 02:37:10 -07:00
Igor Anić
d645114f7e add deflate implemented from first principles
Zig deflate compression/decompression implementation. It supports compression and decompression of gzip, zlib and raw deflate format.

Fixes #18062.

This PR replaces current compress/gzip and compress/zlib packages. Deflate package is renamed to flate. Flate is common name for deflate/inflate where deflate is compression and inflate decompression.

There are breaking change. Methods signatures are changed because of removal of the allocator, and I also unified API for all three namespaces (flate, gzip, zlib).

Currently I put old packages under v1 namespace they are still available as compress/v1/gzip, compress/v1/zlib, compress/v1/deflate. Idea is to give users of the current API little time to postpone analyzing what they had to change. Although that rises question when it is safe to remove that v1 namespace.

Here is current API in the compress package:

```Zig
// deflate
    fn compressor(allocator, writer, options) !Compressor(@TypeOf(writer))
    fn Compressor(comptime WriterType) type

    fn decompressor(allocator, reader, null) !Decompressor(@TypeOf(reader))
    fn Decompressor(comptime ReaderType: type) type

// gzip
    fn compress(allocator, writer, options) !Compress(@TypeOf(writer))
    fn Compress(comptime WriterType: type) type

    fn decompress(allocator, reader) !Decompress(@TypeOf(reader))
    fn Decompress(comptime ReaderType: type) type

// zlib
    fn compressStream(allocator, writer, options) !CompressStream(@TypeOf(writer))
    fn CompressStream(comptime WriterType: type) type

    fn decompressStream(allocator, reader) !DecompressStream(@TypeOf(reader))
    fn DecompressStream(comptime ReaderType: type) type

// xz
   fn decompress(allocator: Allocator, reader: anytype) !Decompress(@TypeOf(reader))
   fn Decompress(comptime ReaderType: type) type

// lzma
    fn decompress(allocator, reader) !Decompress(@TypeOf(reader))
    fn Decompress(comptime ReaderType: type) type

// lzma2
    fn decompress(allocator, reader, writer !void

// zstandard:
    fn DecompressStream(ReaderType, options) type
    fn decompressStream(allocator, reader) DecompressStream(@TypeOf(reader), .{})
    struct decompress
```

The proposed naming convention:
 - Compressor/Decompressor for functions which return type, like Reader/Writer/GeneralPurposeAllocator
 - compressor/compressor for functions which are initializers for that type, like reader/writer/allocator
 - compress/decompress for one shot operations, accepts reader/writer pair, like read/write/alloc

```Zig
/// Compress from reader and write compressed data to the writer.
fn compress(reader: anytype, writer: anytype, options: Options) !void

/// Create Compressor which outputs the writer.
fn compressor(writer: anytype, options: Options) !Compressor(@TypeOf(writer))

/// Compressor type
fn Compressor(comptime WriterType: type) type

/// Decompress from reader and write plain data to the writer.
fn decompress(reader: anytype, writer: anytype) !void

/// Create Decompressor which reads from reader.
fn decompressor(reader: anytype) Decompressor(@TypeOf(reader)

/// Decompressor type
fn Decompressor(comptime ReaderType: type) type

```

Comparing this implementation with the one we currently have in Zig's standard library (std).
Std is roughly 1.2-1.4 times slower in decompression, and 1.1-1.2 times slower in compression. Compressed sizes are pretty much same in both cases.
More resutls in [this](https://github.com/ianic/flate) repo.

This library uses static allocations for all structures, doesn't require allocator. That makes sense especially for deflate where all structures, internal buffers are allocated to the full size. Little less for inflate where we std version uses less memory by not preallocating to theoretical max size array which are usually not fully used.

For deflate this library allocates 395K while std 779K.
For inflate this library allocates 74.5K while std around 36K.

Inflate difference is because we here use 64K history instead of 32K in std.

If merged existing usage of compress gzip/zlib/deflate need some changes. Here is example with necessary changes in comments:

```Zig

const std = @import("std");

// To get this file:
// wget -nc -O war_and_peace.txt https://www.gutenberg.org/ebooks/2600.txt.utf-8
const data = @embedFile("war_and_peace.txt");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer std.debug.assert(gpa.deinit() == .ok);
    const allocator = gpa.allocator();

    try oldDeflate(allocator);
    try new(std.compress.flate, allocator);

    try oldZlib(allocator);
    try new(std.compress.zlib, allocator);

    try oldGzip(allocator);
    try new(std.compress.gzip, allocator);
}

pub fn new(comptime pkg: type, allocator: std.mem.Allocator) !void {
    var buf = std.ArrayList(u8).init(allocator);
    defer buf.deinit();

    // Compressor
    var cmp = try pkg.compressor(buf.writer(), .{});
    _ = try cmp.write(data);
    try cmp.finish();

    var fbs = std.io.fixedBufferStream(buf.items);
    // Decompressor
    var dcp = pkg.decompressor(fbs.reader());

    const plain = try dcp.reader().readAllAlloc(allocator, std.math.maxInt(usize));
    defer allocator.free(plain);
    try std.testing.expectEqualSlices(u8, data, plain);
}

pub fn oldDeflate(allocator: std.mem.Allocator) !void {
    const deflate = std.compress.v1.deflate;

    // Compressor
    var buf = std.ArrayList(u8).init(allocator);
    defer buf.deinit();
    // Remove allocator
    // Rename deflate -> flate
    var cmp = try deflate.compressor(allocator, buf.writer(), .{});
    _ = try cmp.write(data);
    try cmp.close(); // Rename to finish
    cmp.deinit(); // Remove

    // Decompressor
    var fbs = std.io.fixedBufferStream(buf.items);
    // Remove allocator and last param
    // Rename deflate -> flate
    // Remove try
    var dcp = try deflate.decompressor(allocator, fbs.reader(), null);
    defer dcp.deinit(); // Remove

    const plain = try dcp.reader().readAllAlloc(allocator, std.math.maxInt(usize));
    defer allocator.free(plain);
    try std.testing.expectEqualSlices(u8, data, plain);
}

pub fn oldZlib(allocator: std.mem.Allocator) !void {
    const zlib = std.compress.v1.zlib;

    var buf = std.ArrayList(u8).init(allocator);
    defer buf.deinit();

    // Compressor
    // Rename compressStream => compressor
    // Remove allocator
    var cmp = try zlib.compressStream(allocator, buf.writer(), .{});
    _ = try cmp.write(data);
    try cmp.finish();
    cmp.deinit(); // Remove

    var fbs = std.io.fixedBufferStream(buf.items);
    // Decompressor
    // decompressStream => decompressor
    // Remove allocator
    // Remove try
    var dcp = try zlib.decompressStream(allocator, fbs.reader());
    defer dcp.deinit(); // Remove

    const plain = try dcp.reader().readAllAlloc(allocator, std.math.maxInt(usize));
    defer allocator.free(plain);
    try std.testing.expectEqualSlices(u8, data, plain);
}

pub fn oldGzip(allocator: std.mem.Allocator) !void {
    const gzip = std.compress.v1.gzip;

    var buf = std.ArrayList(u8).init(allocator);
    defer buf.deinit();

    // Compressor
    // Rename compress => compressor
    // Remove allocator
    var cmp = try gzip.compress(allocator, buf.writer(), .{});
    _ = try cmp.write(data);
    try cmp.close(); // Rename to finisho
    cmp.deinit(); // Remove

    var fbs = std.io.fixedBufferStream(buf.items);
    // Decompressor
    // Rename decompress => decompressor
    // Remove allocator
    // Remove try
    var dcp = try gzip.decompress(allocator, fbs.reader());
    defer dcp.deinit(); // Remove

    const plain = try dcp.reader().readAllAlloc(allocator, std.math.maxInt(usize));
    defer allocator.free(plain);
    try std.testing.expectEqualSlices(u8, data, plain);
}

```
2024-02-14 18:28:20 +01:00
Nameless
b723296e1f std.http: add missing documentation and a few examples 2024-01-13 18:51:38 -08:00
Jacob Young
509be7cf1f x86_64: fix std test failures 2023-11-03 23:18:21 -04:00
Jordyfel
61861ef395 std.http: account for renames in docs 2023-11-01 14:46:00 +02:00