-
Notifications
You must be signed in to change notification settings - Fork 70
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
Fix request-compression stream handling #822
Conversation
* ensure compressing stream wrapper is closed, hence compressors are flushed * handle streaming output (also move header manipulation up to be visible in streaming mode) * ensure compression stream is wrapped only once (vs. each time a writer to the output message calls `getBody`) Also... * extracted the plugin to it's own artifact to avoid module cycles * extended the tests to actually verify (de-)compression for a set of request factories
riptide-compression/src/main/java/org/zalando/riptide/compression/RequestCompressionPlugin.java
Show resolved
Hide resolved
|
||
@AllArgsConstructor | ||
@Getter | ||
private static final class DelegatingHttpOutputMessage implements HttpOutputMessage { |
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.
At least in the LogbookPlugin
this class already exists
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.
It's just a couple of lines. No need to couple them together by trying to reuse something.
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.
Why not to move them to core?
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.
Not worth having this kind of coupling just to save 3 lines of code.
} | ||
|
||
private void writeToCompressing(HttpOutputMessage message) throws IOException { | ||
try (final WrappingHttpOutputMessage compressingMessage = |
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 am not sure whether this is the right time to close
the output stream, the interfaces of Spring are very confusing to me. It seems to work though looking at the integration tests.
import java.io.IOException; | ||
import java.io.OutputStream; | ||
|
||
final class WrappingHttpOutputMessage implements HttpOutputMessage, AutoCloseable { |
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'd not be surprised if such class already exists somewhere in this project
riptide-compression/src/main/java/org/zalando/riptide/compression/Compression.java
Outdated
Show resolved
Hide resolved
...-compression/src/test/java/org/zalando/riptide/compression/RequestCompressionPluginTest.java
Show resolved
Hide resolved
update(message.getHeaders()); | ||
|
||
if (message instanceof StreamingHttpOutputMessage) { | ||
final StreamingHttpOutputMessage streaming = (StreamingHttpOutputMessage) message; |
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'm thinking whether this could be simplified and done only once for all entities, since it's always the same pattern.
...ide-compression/src/main/java/org/zalando/riptide/compression/WrappingHttpOutputMessage.java
Outdated
Show resolved
Hide resolved
riptide-compression/src/main/java/org/zalando/riptide/compression/Compression.java
Outdated
Show resolved
Hide resolved
...-compression/src/test/java/org/zalando/riptide/compression/RequestCompressionPluginTest.java
Outdated
Show resolved
Hide resolved
riptide-compression/src/main/java/org/zalando/riptide/compression/RequestCompressionPlugin.java
Show resolved
Hide resolved
0f8d89d
to
f2dde3c
Compare
f2dde3c
to
3d25bc4
Compare
|
||
@API(status = EXPERIMENTAL) | ||
@AllArgsConstructor(staticName = "of") | ||
@Getter |
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.
Instead of Getter
it could expose a decorate method directly
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.
@AllArgsConstructor(staticName = "of")
public final class Compression {
@Getter
private final String contentEncoding;
private final ThrowingUnaryOperator<OutputStream, IOException> outputStreamDecorator;
public OutputStream compress(final OutputStream stream) throws IOException {
return outputStreamDecorator.tryApply(stream);
}
}
Like this?
Working with contentEncoding
may still be done by RequestCompressionPlugin
(via the getter on Compression
), or also the check for arguments.getHeaders().containsKey(CONTENT_ENCODING)
should move IMO,
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.
Maybe something like this?
public interface Compression {
public static final Compression GZIP = new DefaultCompression("gzip", GzipOutputStream::new);
void update(HttpHeaders headers);
OutputStream decorate(OutputStream stream) throws IOException;
}
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.
What do you think of:
Working with contentEncoding may still be done by RequestCompressionPlugin (via the getter on Compression), or also the check for arguments.getHeaders().containsKey(CONTENT_ENCODING) should move IMO,
Is related to your comment in #822 (comment)
private final ThrowingUnaryOperator<OutputStream, IOException> wrapper; | ||
private OutputStream stream; | ||
|
||
WrappingHttpOutputMessage(HttpOutputMessage message, ThrowingUnaryOperator<OutputStream, IOException> wrapper) { |
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.
@AllArgsConstructor
?
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.
OutputStream stream
is not injected, but managed internally.
|
||
### Limitations | ||
|
||
* You must only configure a single `RequestCompressionPlugin` as only a single encoding is applied currently. |
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.
Can we reworded since you can adding multiple, but only the first one takes effect.
@Override | ||
public RequestExecution aroundNetwork(final RequestExecution execution) { | ||
return arguments -> { | ||
final Entity entity = arguments.getEntity(); |
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.
final boolean alreadyCompressed = arguments.getHeaders().containsKey(CONTENT_ENCODING);
if (alreadyCompressed) {
return execution.execute(arguments);
}
final Entity entity = arguments.getEntity();
if (entity.isEmpty()) {
return execution.execute(arguments);
}
return ...;
@Override | ||
public OutputStream getBody() throws IOException { | ||
if (stream == null) { | ||
stream = wrapper.apply(message.getBody()); |
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.
Actually, this whole class is only used within writeToCompressing
, i.e. it's safe to decorate the stream early on since we can be sure it will be called.
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.
Ok, it'll be on your head! 😉
void shouldCompressRequestBody(final ClientHttpRequestFactory factory) { | ||
driver.addExpectation(onRequestTo("/") | ||
.withMethod(POST) | ||
.withHeader("X-Content-Encoding", "gzip") // written by Jetty's GzipHandler |
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.
Is Jetty removing the original header and adds this one to indicate it was compressed?
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.
Yes, I did not want to put some listener/callback mechanism in my (un-)gzipping rest client driver Jetty hack for now...
👍 |
This is an attempt to fix the broken
RequestCompressionPlugin
.Description
getBody
)Also...
request factories
Types of changes
Checklist: