
com.stony.reactor.jersey.HttpContentInputStream Maven / Gradle / Ivy
package com.stony.reactor.jersey;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* reactor-netty-ext
*
com.stony.reactor.jersey
*
* @author stony
* @version 上午11:30
* @since 2018/1/19
* @see netflix.karyon.transport.util.HttpContentInputStream
*/
public class HttpContentInputStream extends InputStream {
private static final Logger logger = LoggerFactory.getLogger(HttpContentInputStream.class);
private final Lock lock = new ReentrantLock();
private volatile boolean isClosed = false;
private volatile boolean isCompleted = false;
private volatile Throwable completedWithError = null;
private final Condition contentAvailabilityMonitor = lock.newCondition();
private final ByteBuf contentBuffer;
public HttpContentInputStream(final ByteBufAllocator allocator, final Flux content) {
contentBuffer = allocator.buffer();
content.subscribe(byteBuf -> {
lock.lock();
try {
//This is not only to stop writing 0 bytes as it might seems
//In case of no payload request, like GET
//We are getting onNext( 0 bytes ), onComplete during the stress conditions
//AFTER we say subscriber.onCompleted() and tiered down and close
//request stream, this doesn't contradict logic, in fact 0 bytes is just the same as nothing to write
//but that save us annoying log record every time this happens
if (byteBuf.readableBytes() > 0) {
contentBuffer.writeBytes(byteBuf);
}
contentAvailabilityMonitor.signalAll();
} catch (Exception e) {
logger.error("Error on server", e);
} finally {
lock.unlock();
}
},
throwable -> {
lock.lock();
try {
completedWithError = throwable;
isCompleted = true;
logger.error("Flux Observer, got error: " + throwable.getMessage());
contentAvailabilityMonitor.signalAll();
} finally {
lock.unlock();
}
},
() -> {
lock.lock();
try {
isCompleted = true;
logger.debug("Processing complete");
contentAvailabilityMonitor.signalAll();
} finally {
lock.unlock();
}
});
}
@Override
public int available() throws IOException {
return isCompleted ? contentBuffer.readableBytes() : 0;
}
@Override
public void mark(int readlimit) {
contentBuffer.markReaderIndex();
}
@Override
public boolean markSupported() {
return true;
}
@Override
public int read() throws IOException {
lock.lock();
try {
if (!await()) {
return -1;
}
return contentBuffer.readByte() & 0xff;
} finally {
lock.unlock();
}
}
@Override
public int read(final byte[] b, final int off, final int len) throws IOException {
if (b == null) {
throw new NullPointerException("Null buffer");
}
if (len < 0 || off < 0 || len > b.length - off) {
throw new IndexOutOfBoundsException("Invalid index");
}
if (len == 0) {
return 0;
}
lock.lock();
try {
if (!await()) {
return -1;
}
int size = Math.min(len, contentBuffer.readableBytes());
contentBuffer.readBytes(b, off, size);
return size;
} finally {
lock.unlock();
}
}
@Override
public void close() throws IOException {
//we need a sync block here, as the double close sometimes is reality and we want to decrement ref. counter only once
synchronized (this) {
if (isClosed) {
return;
}
isClosed = true;
}
contentBuffer.release();
}
@Override
public void reset() throws IOException {
contentBuffer.resetReaderIndex();
}
@Override
public long skip(long n) throws IOException {
//per InputStream contract, if n is negative, 0 bytes are skipped
if (n <= 0) {
return 0;
}
if (n > Integer.MAX_VALUE) {
return skipBytes(Integer.MAX_VALUE);
} else {
return skipBytes((int) n);
}
}
private int skipBytes(int n) throws IOException {
int nBytes = Math.min(available(), n);
contentBuffer.skipBytes(nBytes);
return nBytes;
}
private boolean await() throws IOException {
//await in here
while (!isCompleted && !contentBuffer.isReadable()) {
try {
contentAvailabilityMonitor.await();
} catch (InterruptedException e) {
// Restore interrupt status and bailout
Thread.currentThread().interrupt();
logger.error("Interrupted: " + e.getMessage());
throw new IOException(e);
}
}
if (completedWithError != null) {
throw new IOException(completedWithError);
}
if (isCompleted && !contentBuffer.isReadable()) {
return false;
}
return true;
}
}