197 Commits

Author SHA1 Message Date
Andrew Kelley
5b34a1b718 std.http: disable the test that was never passing on windows
We didn't know it wasn't passing before because it wasn't actually being
run.
2024-02-23 02:37:11 -07:00
Andrew Kelley
d7ac8c8e65 wasi: don't try to test http
wasi does not support networking
2024-02-23 02:37:11 -07:00
Andrew Kelley
483b63d301 std.http: migrate remaining test/standalone/http.zig to std lib
These tests were not being actually run. Now they are run along with
testing the standard library.
2024-02-23 02:37:11 -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
737e7be46c std.http: refactor unit tests
avoid a little bit of boilerplate
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
40ed3c4d24 std.http.Client: add keep_alive option to fetch 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
63acc856c1 std.http.Client: remove invalid use of refAllDecls 2024-02-23 02:37:11 -07:00
Andrew Kelley
e204b2ca92 std.http.Client.connectUnix: handle unsupported OS at compile time 2024-02-23 02:37:11 -07:00
Andrew Kelley
6de8748b05 std.http: skip tests on wasi and single-threaded
WASI does not support networking, and these tests require threads.
2024-02-23 02:37:11 -07:00
Andrew Kelley
651aa5e8e4 std.http.Client: eliminate arena allocator usage
Before, this code constructed an arena allocator and then used it when
handling redirects.

You know what's better than having threads fight over an allocator?
Avoiding dynamic memory allocation in the first place.

This commit reuses the http headers static buffer for handling
redirects. The new location is copied to the beginning of the static
header buffer and then the subsequent request uses a subslice of that
buffer.
2024-02-23 02:37:11 -07:00
Andrew Kelley
743a0c966d std.http.Client: remove bad decisions from fetch()
* "storage" is a better name than "strategy".
* The most flexible memory-based storage API is appending to an
  ArrayList.
* HTTP method should default to POST if there is a payload.
* Avoid storing unnecessary data in the FetchResult
* Avoid the need for a deinit() method in the FetchResult

The decisions that this logic made about how to handle files is beyond
repair:
- fail to use sendfile() on a plain connection
- redundant stat
- does not handle arbitrary streams
So, file-based response storage is no longer supported. Users should use
the lower-level open() API which allows avoiding these pitfalls.
2024-02-23 02:37:11 -07:00
Andrew Kelley
0ddcb83418 std.http.Client.fetch: remove inappropriate seek
no
2024-02-23 02:37:11 -07:00
Andrew Kelley
7036644ed2 std.http.Client: remove advisory file lock on fetch
This is not an appropriate place to put this code. It belongs in the
caller's code, if at all.
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
ae630b695e std.http.Client.connect: case insensitive host comparison
for checking if a proxy is connecting to itself
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
78192637fb std.http: parser fixes
* add API for iterating over custom HTTP headers
* remove `trailing` flag from std.http.Client.parse. Instead, simply
  don't call parse() for trailers.
* fix the logic inside that parse() function. it was using wrong std.mem
  functions, ignoring malformed data, and returned errors on dead
  branches.
* simplify logic inside wait()
* fix HeadersParser not dropping the 2 read bytes of \r\n after a
  chunked transfer
* move the trailers test to be a std lib unit test and make it pass
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
3d61890d24 std: convert http trailers test to unit test
making it no longer dead code. it is currently failing.
2024-02-23 02:37:11 -07:00
Andrew Kelley
cf4a2c4d18 std.http.Client.Response.ParseError: remove OutOfMemory
This can no longer fail due to OOM.
2024-02-23 02:37:11 -07:00
Andrew Kelley
99a5de9dbb git fetching: fix redirect handling
I mistakenly thought this was dead code in an earlier commit in this
branch. This commit restores the proper behavior.
2024-02-23 02:37:11 -07:00
Andrew Kelley
b6ca89fa7c std.http.Client: disable zstd for now
The Allocator requirement is problematic.
2024-02-23 02:37:11 -07:00
Andrew Kelley
4d401e6159 std.http: remove Headers API
I originally removed these in 402f967ed5339fa3d828b7fe1d57cdb5bf38dbf2.
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
f46447e6a1 std.http.Client.fetch: add redirect behavior to options 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 919a3bae1c5f2024b09e127a15c752d9dc0aa9a6
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
Jacob Young
919a3bae1c http: protect against zero-length chunks
A zero-length chunk marks the end of the body, so prevent any from
possibly occurring in the middle of the body.
2024-02-08 01:29:49 -08:00
Jacob Young
c1e7d0c08f http: optimize allocations for proxy basic authorization 2024-01-31 15:00:27 +01:00
Jacob Young
a111f805cd http: avoid allocator use when encoding basic authorization 2024-01-31 15:00:27 +01:00
Jacob Young
82b37ea024 http: support basic access authentication 2024-01-31 12:05:12 +01:00
Nameless
a7dbc57a35 std.http.Client: read response messages with no length until eof 2024-01-18 15:43:58 -08:00
Nameless
b723296e1f std.http: add missing documentation and a few examples 2024-01-13 18:51:38 -08:00