Skip to content

Commit a0d2e34

Browse files
committed
netty: Gzip encoding seems to break AssetHandler fix jooby-project#1656
1 parent b128f6e commit a0d2e34

5 files changed

Lines changed: 84 additions & 10 deletions

File tree

jooby/src/main/java/io/jooby/ServerOptions.java

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,12 +24,6 @@
2424
*/
2525
public class ServerOptions {
2626

27-
/** Default port <code>80</code>. */
28-
public static final int PORT = 80;
29-
30-
/** Default port <code>443</code>. */
31-
public static final int SECURE_PORT = 443;
32-
3327
/** Default application port <code>8080</code> or the value of system property <code>server.port</code>. */
3428
public static final int SERVER_PORT = Integer
3529
.parseInt(System.getProperty("server.port", "8080"));
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package io.jooby.internal.netty;
2+
3+
import io.netty.buffer.ByteBuf;
4+
import io.netty.channel.ChannelHandlerContext;
5+
import io.netty.channel.ChannelPromise;
6+
import io.netty.handler.codec.http.DefaultHttpContent;
7+
import io.netty.handler.codec.http.HttpContentCompressor;
8+
9+
class HttpChunkContentCompressor extends HttpContentCompressor {
10+
@Override public void write(ChannelHandlerContext ctx, Object msg,
11+
ChannelPromise promise) throws Exception {
12+
if (msg instanceof ByteBuf) {
13+
// convert ByteBuf to HttpContent to make it work with compression. This is needed as we use the
14+
// ChunkedWriteHandler to send files when compression is enabled.
15+
ByteBuf buff = (ByteBuf) msg;
16+
if (buff.isReadable()) {
17+
// We only encode non empty buffers, as empty buffers can be used for determining when
18+
// the content has been flushed and it confuses the HttpContentCompressor
19+
// if we let it go
20+
msg = new DefaultHttpContent(buff);
21+
}
22+
}
23+
super.write(ctx, msg, promise);
24+
}
25+
}

modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyContext.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@
8787
import java.util.Set;
8888
import java.util.concurrent.Executor;
8989
import java.util.concurrent.TimeUnit;
90+
import java.util.stream.Stream;
9091

9192
import static io.netty.buffer.Unpooled.copiedBuffer;
9293
import static io.netty.buffer.Unpooled.wrappedBuffer;
@@ -584,7 +585,7 @@ public void flush() {
584585
DefaultHttpResponse rsp = new DefaultHttpResponse(HTTP_1_1, status, setHeaders);
585586
responseStarted = true;
586587

587-
if (isSecure()) {
588+
if (isSecure() || isGzip()) {
588589
prepareChunked();
589590

590591
HttpChunkedInput chunkedInput = new HttpChunkedInput(
@@ -786,7 +787,10 @@ private void prepareChunked() {
786787
// remove flusher, doesn't play well with streaming/chunked responses
787788
ChannelPipeline pipeline = ctx.pipeline();
788789
if (pipeline.get("chunker") == null) {
789-
pipeline.addAfter("encoder", "chunker", new ChunkedWriteHandler());
790+
Stream.of("compressor", "encoder")
791+
.filter(name -> pipeline.get(name) != null)
792+
.findFirst()
793+
.ifPresent(name -> pipeline.addAfter(name, "chunker", new ChunkedWriteHandler()));
790794
}
791795
if (!setHeaders.contains(CONTENT_LENGTH)) {
792796
setHeaders.set(TRANSFER_ENCODING, CHUNKED);
@@ -796,4 +800,8 @@ private void prepareChunked() {
796800
@Override public String toString() {
797801
return getMethod() + " " + getRequestPath();
798802
}
803+
804+
private boolean isGzip() {
805+
return getRouter().getServerOptions().getGzip();
806+
}
799807
}

modules/jooby-netty/src/main/java/io/jooby/internal/netty/NettyPipeline.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,10 @@ public void initChannel(SocketChannel ch) {
5757
if (sslContext != null) {
5858
p.addLast("ssl", sslContext.newHandler(ch.alloc()));
5959
}
60-
p.addLast("encoder", new HttpResponseEncoder());
6160
p.addLast("decoder", new HttpRequestDecoder(_4KB, _8KB, bufferSize, false));
61+
p.addLast("encoder", new HttpResponseEncoder());
6262
if (gzip) {
63-
p.addLast("gzip", new HttpContentCompressor());
63+
p.addLast("compressor", new HttpChunkContentCompressor());
6464
}
6565
p.addLast("handler", new NettyHandler(service, router, maxRequestSize, bufferSize, factory,
6666
defaultHeaders));
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package io.jooby;
2+
3+
import io.jooby.junit.ServerTest;
4+
import io.jooby.junit.ServerTestRunner;
5+
import okhttp3.ResponseBody;
6+
7+
import java.io.ByteArrayInputStream;
8+
import java.io.IOException;
9+
import java.util.Scanner;
10+
import java.util.zip.GZIPInputStream;
11+
12+
import static org.junit.jupiter.api.Assertions.assertEquals;
13+
14+
public class Issue1656 {
15+
16+
@ServerTest
17+
public void gzip(ServerTestRunner runner) {
18+
runner.define(app -> {
19+
app.setServerOptions(new ServerOptions().setGzip(true));
20+
app.assets("/static/*", "/files");
21+
}).ready(client -> {
22+
client.get("/static/fileupload.js").prepare(req -> {
23+
req.addHeader("Accept-Encoding", "gzip");
24+
}).execute(rsp -> {
25+
ResponseBody body = rsp.body();
26+
long len = body.contentLength();
27+
if (len == -1) {
28+
assertEquals("chunked", rsp.header("Transfer-Encoding"));
29+
} else {
30+
assertEquals(63, rsp.body().contentLength());
31+
}
32+
assertEquals("gzip", rsp.header("content-encoding"));
33+
assertEquals("(function () { console.log('ready');})();", ungzip(body.bytes()).trim());
34+
});
35+
});
36+
}
37+
38+
private String ungzip(byte[] buff) throws IOException {
39+
GZIPInputStream gzip = new GZIPInputStream(new ByteArrayInputStream(buff));
40+
Scanner scanner = new Scanner(gzip);
41+
StringBuilder str = new StringBuilder();
42+
while (scanner.hasNext()) {
43+
str.append(scanner.nextLine());
44+
}
45+
return str.toString();
46+
}
47+
}

0 commit comments

Comments
 (0)