Skip to content

Instantly share code, notes, and snippets.

@codingtony
Last active December 21, 2017 04:37
Show Gist options
  • Save codingtony/6564901 to your computer and use it in GitHub Desktop.
Save codingtony/6564901 to your computer and use it in GitHub Desktop.
An handy, yet simple ChunkedOutputSteam for HTTP chunked response with Netty 4.0 Useful when you have an application that writes to an OutputStream and you don't want (or cannot) rewrite it. Released under APL 2.0 Any bugs? let me know!
/************************************************************************
Copyright 2013 Tony Bussieres
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*************************************************************************/
package com.codingtony.gist;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.DefaultHttpContent;
import java.io.IOException;
import java.io.OutputStream;
/**
* Class to help application that are built to write to an
* OutputStream to chunk the content
*
* <pre>
* {@code
DefaultHttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK);
HttpHeaders.setTransferEncodingChunked(response);
response.headers().set(CONTENT_TYPE, "application/octet-stream");
//other headers
ctx.write(response);
// code of the application that use the ChunkOutputStream
// Don't forget to close the ChunkOutputStream after use!
ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT).addListener(ChannelFutureListener.CLOSE);
}
</pre>
* @author tbussier
*
*/
public class ChunkOutputStream extends OutputStream {
final ByteBuf buffer;
final ChannelHandlerContext ctx;
ChunkOutputStream(ChannelHandlerContext ctx, int chunksize) {
if (chunksize < 1) {
throw new IllegalArgumentException("Chunk size must be at least 1");
}
this.buffer = Unpooled.buffer(0, chunksize);
this.ctx = ctx;
}
@Override
public void write(int b) throws IOException {
if (buffer.maxWritableBytes() < 1) {
flush();
}
buffer.writeByte(b);
}
@Override
public void close() throws IOException {
flush();
super.close();
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
int dataLengthLeftToWrite = len;
int dataToWriteOffset = off;
int spaceLeftInCurrentChunk;
while ((spaceLeftInCurrentChunk = buffer.maxWritableBytes()) < dataLengthLeftToWrite) {
buffer.writeBytes(b, dataToWriteOffset, spaceLeftInCurrentChunk);
dataToWriteOffset = dataToWriteOffset + spaceLeftInCurrentChunk;
dataLengthLeftToWrite = dataLengthLeftToWrite - spaceLeftInCurrentChunk;
flush();
}
if (dataLengthLeftToWrite > 0) {
buffer.writeBytes(b, dataToWriteOffset, dataLengthLeftToWrite);
}
}
@Override
public void flush() throws IOException {
ctx.writeAndFlush(new DefaultHttpContent(buffer.copy()));
buffer.clear();
super.flush();
}
}
@danielflower
Copy link

Thanks for this.

One question: if you set the buffer to, say, 4 bytes, and then write exactly 4 bytes, it doesn't flush (until the next write). I think it probably should flush, so at the end of the write method I added:

    if (buffer.maxWritableBytes() < 1) {
        flush();
    }

Do you see any issues with that or have a better suggestion?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment