-
Notifications
You must be signed in to change notification settings - Fork 91
Streaming gzip response compression #1448
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
| /// response.extensions_mut().insert(NoCompression); | ||
| /// ``` | ||
| #[derive(Debug, Clone, Copy)] | ||
| pub struct NoCompression; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems like a way for a dropshot handler to tell dropshot not to compress the response even though it might otherwise compress it. That's neat, but happy to get rid of it if we don't need it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
meh up to you
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see a downside to keeping it so I'll keep it
| [dependencies.tokio-util] | ||
| version = "0.7" | ||
| features = [ "io", "compat" ] | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Used for converting between AsyncRead/AsyncWrite from async-compression and the streams used by Body from hyper.
| } | ||
|
|
||
| #[tokio::test] | ||
| async fn test_gzip_compression_with_accept_encoding() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the primary happy path test for compression.
ahl
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I gave this a look over and it looks good; let me know if you'd like me to look in more detail. One thing I'm unsure of is how the content-length is set given the streaming compression. It should reflect the compressed size rather than the original size--is that right? Thanks for doing this.
|
I was confused about what is supposed to happen if the client can't handle a streaming response, but it seems all HTTP/1.1 clients must be able to handle chunked transfer encoding:
https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.1 Full answer from GPT-5 through the Codex CLI below. Pretty helpful. The line "HTTP/1.1 clients that advertise Dropshot only decides to gzip when Why this doesn't break non-streaming clients: HTTP/1.1 clients that advertise Matrix (server view vs client capability):
If you have a population of |
|
Thanks for the clarification. I see in the tests how you're validating the content-length header, etc. Should compression be configurable in the server settings? i.e. would it make sense to e.g. have compression=on be the default but allow one to make a server that never compresses responses? |
|
Honestly not sure. Kind of hard to imagine why you would want to not compress if a client asked for it. But on the other hand it seems heavy-handed to just do it. |
|
I added the config option (default true) and I used some helpers to shorten the tests a bit, which to me makes them a little more scannable, though you might prefer things inlined. I think this is ready for a real review. |
Gemini's theory: When a test case requests the /large-response endpoint but compression is disabled (either by config, lack of header, or rejection), the server attempts to write the full ~5KB JSON body to the socket. On illumos, the default TCP socket buffer size is likely smaller than on macOS/Linux, causing the server's write operation to block because the test client never reads the data to drain the buffer. When teardown() is called, the server hangs trying to finish the write during graceful shutdown, eventually timing out. The fix is to consume the response body in these tests.
|
From the commit message on 9be5395:
The fix did work, so it seems directionally right even if the TCP socket buffer size isn't the precise reason. Not sure whether it's worth chasing down the reason further. |
|
It does not fail on macOS with a smaller TCP socket buffer (thanks @rmustacc for the suggestion): $ sysctl net.inet.tcp.sendspace net.inet.tcp.recvspace
net.inet.tcp.sendspace: 131072
net.inet.tcp.recvspace: 131072
$ sudo sysctl net.inet.tcp.sendspace=4096
$ sudo sysctl net.inet.tcp.recvspace=4096
|
ahl
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking good; can you please update the changelog to document this?
dropshot/src/config.rs
Outdated
| /// Whether to enable gzip compression for responses when response contents | ||
| /// allow it and clients ask for it through the Accept-Encoding header. | ||
| /// Defaults to true. | ||
| pub compression: bool, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are there more than 2 options when it comes to compression? I.e. might there be additional configuration in the future; rather than a bool might we want an enum? i.e. avoid further breaking changes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very good point. I checked what tower-http do and they have a lot more than a bool going on:
pub enum CompressionLevel {
Fastest,
Best,
#[default]
Default,
Precise(i32),
}
pub(crate) struct AcceptEncoding {
pub(crate) gzip: bool,
pub(crate) deflate: bool,
pub(crate) br: bool,
pub(crate) zstd: bool,
}
pub struct CompressionLayer<P = DefaultPredicate> {
accept: AcceptEncoding,
predicate: P,
quality: CompressionLevel,
}That is overkill for us but it gives a sense of the way we might complicate this. The most likely one to me would be that we add brotli. I'll figure it out.
| /// response.extensions_mut().insert(NoCompression); | ||
| /// ``` | ||
| #[derive(Debug, Clone, Copy)] | ||
| pub struct NoCompression; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
meh up to you
|
Tested in Nexus locally (pushed changes as
|

Closes #221
compressionconfig option defaults to false to avoid existing consumers accidentally changing behavior on upgradeAccept-Encodingrequest header as well as the MIME type matching a hard-coded list of compressible types (JSON, text, XML)Vary: Accept-Encodingheader is added to all compressible responses, which makes sure caches don't, e.g., cache a compressed version and serve it to someone who can't accept itNoCompressionextension lets an endpoint opt out of compression