
reactor.io.net.impl.netty.NettyChannelHandlerBridge Maven / Gradle / Ivy
/*
* Copyright (c) 2011-2014 Pivotal Software, Inc.
*
* 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,
* WITHIN 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 reactor.io.net.impl.netty;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.util.ReferenceCountUtil;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.Environment;
import reactor.core.processor.CancelException;
import reactor.core.support.Exceptions;
import reactor.core.support.NonBlocking;
import reactor.fn.Consumer;
import reactor.io.buffer.Buffer;
import reactor.io.net.ChannelStream;
import reactor.io.net.ReactorChannelHandler;
import reactor.io.net.Spec;
import reactor.rx.action.support.DefaultSubscriber;
import reactor.rx.subscription.PushSubscription;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
/**
* Netty {@link io.netty.channel.ChannelInboundHandler} implementation that passes data to a Reactor {@link
* reactor.io.net.ChannelStream}.
*
* @author Jon Brisbin
* @author Stephane Maldini
*/
public class NettyChannelHandlerBridge extends ChannelDuplexHandler {
protected static final Logger log = LoggerFactory.getLogger(NettyChannelHandlerBridge.class);
protected final ReactorChannelHandler> handler;
protected final NettyChannelStream channelStream;
protected PushSubscription channelSubscription;
private ByteBuf remainder;
private volatile int channelRef = 0;
protected static final AtomicIntegerFieldUpdater CHANNEL_REF =
AtomicIntegerFieldUpdater.newUpdater(NettyChannelHandlerBridge.class, "channelRef");
public NettyChannelHandlerBridge(
ReactorChannelHandler> handler, NettyChannelStream channelStream
) {
this.handler = handler;
this.channelStream = channelStream;
}
public PushSubscription subscription() {
return channelSubscription;
}
@Override
public void userEventTriggered(final ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt != null && evt.getClass().equals(ChannelInputSubscriberEvent.class)) {
if (null == channelSubscription) {
@SuppressWarnings("unchecked")
ChannelInputSubscriberEvent subscriberEvent = (ChannelInputSubscriberEvent) evt;
CHANNEL_REF.incrementAndGet(NettyChannelHandlerBridge.this);
this.channelSubscription = new PushSubscription(null, subscriberEvent.inputSubscriber) {
@Override
protected void onRequest(long n) {
if (n == Long.MAX_VALUE) {
ctx.channel().config().setAutoRead(true);
}
ctx.read();
}
@Override
public void cancel() {
super.cancel();
channelSubscription = null;
//log.debug("Cancel read");
ctx.channel().config().setAutoRead(false);
CHANNEL_REF.decrementAndGet(NettyChannelHandlerBridge.this);
}
};
subscriberEvent.inputSubscriber.onSubscribe(channelSubscription);
} else {
channelSubscription.onError(new IllegalStateException("Only one connection input subscriber allowed."));
}
}
super.userEventTriggered(ctx, evt);
}
@Override
public void channelActive(final ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
handler.apply(channelStream)
.subscribe(new DefaultSubscriber() {
@Override
public void onSubscribe(Subscription s) {
s.request(Long.MAX_VALUE);
}
@Override
public void onError(Throwable t) {
log.error("Error processing connection. Closing the channel.", t);
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
}
@Override
public void onComplete() {
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
}
});
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
if (channelSubscription == null) {
return;
}
try {
super.channelReadComplete(ctx);
if (channelSubscription.pendingRequestSignals() != Long.MAX_VALUE){
channelSubscription.updatePendingRequests(-1);
if(channelSubscription.pendingRequestSignals() > 0l) {
ctx.read();
}
}
} catch (Throwable throwable) {
if (channelSubscription != null) {
channelSubscription.onError(throwable);
} else if (Environment.alive()) {
Environment.get().routeError(throwable);
}
}
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
try {
if (this.channelSubscription != null) {
channelSubscription.onComplete();
channelSubscription = null;
}
super.channelInactive(ctx);
} catch (Throwable err) {
if (channelSubscription != null) {
channelSubscription.onError(err);
} else if (Environment.alive()) {
Environment.get().routeError(err);
}
}
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
doRead(ctx, msg);
}
@SuppressWarnings("unchecked")
protected final void doRead(ChannelHandlerContext ctx, Object msg) {
try {
if (null == channelSubscription || msg == Unpooled.EMPTY_BUFFER) {
ReferenceCountUtil.release(msg);
return;
}
if (channelStream.getDecoder() == Spec.NOOP_DECODER || !ByteBuf.class.isAssignableFrom(msg.getClass())) {
channelSubscription.onNext((IN) msg);
return;
} else if (channelStream.getDecoder() == null) {
try {
channelSubscription.onNext((IN) new Buffer(((ByteBuf) msg).nioBuffer()));
} finally {
ReferenceCountUtil.release(msg);
}
return;
}
ByteBuf data = (ByteBuf) msg;
if (remainder == null) {
try {
passToConnection(data);
} finally {
if (data.isReadable()) {
remainder = data;
} else {
data.release();
}
}
return;
}
if (!bufferHasSufficientCapacity(remainder, data)) {
ByteBuf combined = createCombinedBuffer(remainder, data, ctx);
remainder.release();
remainder = combined;
} else {
remainder.writeBytes(data);
}
data.release();
try {
passToConnection(remainder);
} finally {
if (remainder.isReadable()) {
remainder.discardSomeReadBytes();
} else {
remainder.release();
remainder = null;
}
}
} catch (Throwable t) {
if (channelSubscription != null) {
channelSubscription.onError(t);
} else if (Environment.alive()) {
Environment.get().routeError(t);
}
}
}
@Override
public void write(final ChannelHandlerContext ctx, Object msg, final ChannelPromise promise) throws Exception {
if (msg instanceof Publisher) {
CHANNEL_REF.incrementAndGet(this);
@SuppressWarnings("unchecked")
Publisher> data = (Publisher>) msg;
final long capacity = msg instanceof NonBlocking ? ((NonBlocking) data).getCapacity() : Long.MAX_VALUE;
if (capacity == Long.MAX_VALUE) {
data.subscribe(new FlushOnTerminateSubscriber(ctx, promise));
} else {
data.subscribe(new FlushOnCapacitySubscriber(ctx, promise, capacity));
}
} else {
super.write(ctx, msg, promise);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (channelSubscription != null) {
channelSubscription.onError(cause);
} else if (Environment.alive()) {
Environment.get().routeError(cause);
} else {
log.error("Unexpected issue", cause);
}
}
protected ChannelFuture doOnWrite(Object data, ChannelHandlerContext ctx) {
if (data.getClass().equals(Buffer.class)) {
return ctx.channel().write(convertBufferToByteBuff(ctx, (Buffer) data));
} else if (Unpooled.EMPTY_BUFFER != data) {
return ctx.channel().write(data);
}
return null;
}
protected static ByteBuf convertBufferToByteBuff(ChannelHandlerContext ctx, Buffer data) {
ByteBuf buff = ctx.alloc().buffer(data.remaining());
return buff.writeBytes(data.byteBuffer());
}
protected void doOnTerminate(ChannelHandlerContext ctx, ChannelFuture last, final ChannelPromise promise) {
CHANNEL_REF.decrementAndGet(this);
if (ctx.channel().isOpen()) {
ChannelFutureListener listener = new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
promise.trySuccess();
} else {
promise.tryFailure(future.cause());
}
}
};
if (last != null) {
ctx.flush();
last.addListener(listener);
} else {
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(listener);
}
} else {
promise.trySuccess();
}
}
private static boolean bufferHasSufficientCapacity(ByteBuf receiver, ByteBuf provider) {
return receiver.writerIndex() <= receiver.maxCapacity() - provider.readableBytes();
}
private static ByteBuf createCombinedBuffer(ByteBuf partOne, ByteBuf partTwo, ChannelHandlerContext ctx) {
ByteBuf combined = ctx.alloc().buffer(partOne.readableBytes() + partTwo.readableBytes());
combined.writeBytes(partOne);
combined.writeBytes(partTwo);
return combined;
}
private void passToConnection(ByteBuf data) {
Buffer b = new Buffer(data.nioBuffer());
int start = b.position();
if (null != channelStream.getDecoder() && null != b.byteBuffer()) {
IN read = channelStream.getDecoder().apply(b);
if (read != null) {
channelSubscription.onNext(read);
}
}
//data.remaining() > 0;
data.skipBytes(b.position() - start);
}
protected void doOnSubscribe(ChannelHandlerContext ctx, final Subscription s) {
}
/**
* An event to attach a {@link Subscriber} to the {@link NettyChannelStream}
* created by {@link NettyChannelHandlerBridge}
*
* @param
*/
public static final class ChannelInputSubscriberEvent {
private final Subscriber inputSubscriber;
public ChannelInputSubscriberEvent(Subscriber inputSubscriber) {
if (null == inputSubscriber) {
throw new IllegalArgumentException("Connection input subscriber must not be null.");
}
this.inputSubscriber = inputSubscriber;
}
}
private class FlushOnTerminateSubscriber extends DefaultSubscriber
© 2015 - 2025 Weber Informatics LLC | Privacy Policy