package com.github.dockerjava.netty.handler; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import java.io.IOException; import java.io.InputStream; import com.github.dockerjava.api.async.ResultCallback; /** * Handler that converts an incoming byte stream to an {@link InputStream}. * * @author marcus */ public class HttpResponseStreamHandler extends SimpleChannelInboundHandler<ByteBuf> { private ResultCallback<InputStream> resultCallback; private final HttpResponseInputStream stream = new HttpResponseInputStream(); public HttpResponseStreamHandler(ResultCallback<InputStream> resultCallback) { this.resultCallback = resultCallback; } @Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { invokeCallbackOnFirstRead(); stream.write(msg.copy()); } private void invokeCallbackOnFirstRead() { if (resultCallback != null) { resultCallback.onNext(stream); resultCallback = null; } } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { stream.writeComplete(); super.channelInactive(ctx); } public static class HttpResponseInputStream extends InputStream { private boolean writeCompleted = false; private boolean closed = false; private ByteBuf current = null; private final Object lock = new Object(); public void write(ByteBuf byteBuf) throws InterruptedException { synchronized (lock) { if (closed) { return; } while (current != null) { lock.wait(); if (closed) { return; } } current = byteBuf; lock.notifyAll(); } } public void writeComplete() { synchronized (lock) { writeCompleted = true; lock.notifyAll(); } } @Override public void close() throws IOException { synchronized (lock) { closed = true; releaseCurrent(); lock.notifyAll(); } } @Override public int available() throws IOException { synchronized (lock) { poll(0); return readableBytes(); } } private int readableBytes() { if (current != null) { return current.readableBytes(); } else { return 0; } } @Override public int read() throws IOException { byte[] b = new byte[1]; int n = read(b, 0, 1); return n != -1 ? b[0] : -1; } @Override public int read(byte[] b, int off, int len) throws IOException { synchronized (lock) { off = poll(off); if (current == null) { return -1; } else { int availableBytes = Math.min(len, current.readableBytes() - off); current.readBytes(b, off, availableBytes); return availableBytes; } } } private int poll(int off) throws IOException { synchronized (lock) { while (readableBytes() <= off) { try { if (closed) { throw new IOException("Stream closed"); } off -= releaseCurrent(); if (writeCompleted) { return off; } while (current == null) { lock.wait(); if (closed) { throw new IOException("Stream closed"); } if (writeCompleted && current == null) { return off; } } } catch (InterruptedException e) { throw new RuntimeException(e); } } return off; } } private int releaseCurrent() { synchronized (lock) { if (current != null) { int n = current.readableBytes(); current.release(); current = null; lock.notifyAll(); return n; } return 0; } } } }