Skip to content

Commit 3c985ab

Browse files
committed
Fix the incorrect content-encoding header when sendFile is used in non-SSL
1 parent 259f4d9 commit 3c985ab

File tree

2 files changed

+53
-6
lines changed

2 files changed

+53
-6
lines changed

src/main/java/io/cdap/http/internal/BasicHttpResponder.java

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@
2626
import io.netty.channel.Channel;
2727
import io.netty.channel.ChannelFuture;
2828
import io.netty.channel.ChannelFutureListener;
29+
import io.netty.channel.ChannelHandler;
2930
import io.netty.channel.ChannelHandlerContext;
31+
import io.netty.channel.ChannelPipeline;
3032
import io.netty.channel.DefaultFileRegion;
3133
import io.netty.channel.FileRegion;
3234
import io.netty.handler.codec.http.DefaultFullHttpResponse;
@@ -51,6 +53,7 @@
5153
import java.io.IOException;
5254
import java.io.RandomAccessFile;
5355
import java.nio.charset.StandardCharsets;
56+
import java.util.NoSuchElementException;
5457
import java.util.concurrent.atomic.AtomicBoolean;
5558
import javax.annotation.Nullable;
5659

@@ -127,10 +130,21 @@ public void sendFile(File file, HttpHeaders headers) throws IOException {
127130
// The HttpChunkedInput will write out the last content
128131
channel.writeAndFlush(new HttpChunkedInput(new ChunkedFile(raf, 8192)));
129132
} else {
130-
// The FileRegion will close the file channel when it is done sending.
131-
FileRegion region = new DefaultFileRegion(raf.getChannel(), 0, file.length());
132-
channel.write(region);
133-
channel.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
133+
final Runnable completion = prepareSendFile(channel);
134+
try {
135+
// The FileRegion will close the file channel when it is done sending.
136+
FileRegion region = new DefaultFileRegion(raf.getChannel(), 0, file.length());
137+
channel.write(region);
138+
channel.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT).addListener(new ChannelFutureListener() {
139+
@Override
140+
public void operationComplete(ChannelFuture future) {
141+
completion.run();
142+
}
143+
});
144+
} catch (Throwable t) {
145+
completion.run();
146+
throw t;
147+
}
134148
}
135149
} catch (Throwable t) {
136150
try {
@@ -228,6 +242,35 @@ private void checkNotResponded() {
228242
}
229243
}
230244

245+
/**
246+
* Prepares the given {@link Channel} for the sending file.
247+
*
248+
* @param channel the channel to prepare
249+
* @return a {@link Runnable} that should be called when the send file is completed to revert the action
250+
*/
251+
private Runnable prepareSendFile(Channel channel) {
252+
// Remove the "compressor" from the pipeline to skip content encoding since FileRegion do zero-copy write,
253+
// hence bypassing any user space data operation.
254+
try {
255+
final ChannelPipeline pipeline = channel.pipeline();
256+
final ChannelHandler compressor = pipeline.remove("compressor");
257+
return new Runnable() {
258+
@Override
259+
public void run() {
260+
pipeline.addAfter("codec", "compressor", compressor);
261+
}
262+
};
263+
} catch (NoSuchElementException e) {
264+
// Ignore if there is no compressor
265+
return new Runnable() {
266+
@Override
267+
public void run() {
268+
// no-op
269+
}
270+
};
271+
}
272+
}
273+
231274
/**
232275
* A {@link ChunkedInput} implementation that produce chunks using {@link BodyProducer}.
233276
*/

src/test/java/io/cdap/http/HttpServerTest.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,10 @@ public void testUploadDisconnect() throws Exception {
280280
public void testSendFile() throws IOException {
281281
File filePath = new File(tmpFolder.newFolder(), "test.txt");
282282
HttpURLConnection urlConn = request("/test/v1/stream/file", HttpMethod.POST);
283+
// Enable accepting compression. For HTTP case, the response won't be compressed since FileRegion is being used.
284+
// For HTTPs, the response will be compressed.
285+
// See https://github.com/cdapio/netty-http/issues/68
286+
urlConn.setRequestProperty(HttpHeaderNames.ACCEPT_ENCODING.toString(), HttpHeaderValues.GZIP_DEFLATE.toString());
283287
urlConn.setRequestProperty("File-Path", filePath.getAbsolutePath());
284288
urlConn.getOutputStream().write("content".getBytes(StandardCharsets.UTF_8));
285289
Assert.assertEquals(200, urlConn.getResponseCode());
@@ -845,7 +849,7 @@ public void testCompressResponse() throws Exception {
845849
urlConn.setRequestProperty(HttpHeaderNames.ACCEPT_ENCODING.toString(), HttpHeaderValues.GZIP_DEFLATE.toString());
846850

847851
Assert.assertEquals(HttpResponseStatus.OK.code(), urlConn.getResponseCode());
848-
Assert.assertTrue(urlConn.getHeaderField(HttpHeaderNames.CONTENT_ENCODING.toString()) != null);
852+
Assert.assertNotNull(urlConn.getHeaderField(HttpHeaderNames.CONTENT_ENCODING.toString()));
849853

850854
Assert.assertEquals("Testing message", getContent(urlConn));
851855

@@ -854,7 +858,7 @@ public void testCompressResponse() throws Exception {
854858
urlConn.setRequestProperty(HttpHeaderNames.ACCEPT_ENCODING.toString(), HttpHeaderValues.GZIP_DEFLATE.toString());
855859

856860
Assert.assertEquals(HttpResponseStatus.OK.code(), urlConn.getResponseCode());
857-
Assert.assertTrue(urlConn.getHeaderField(HttpHeaderNames.CONTENT_ENCODING.toString()) != null);
861+
Assert.assertNotNull(urlConn.getHeaderField(HttpHeaderNames.CONTENT_ENCODING.toString()));
858862

859863
Assert.assertEquals("Testing message chunk", getContent(urlConn));
860864
}

0 commit comments

Comments
 (0)