reactor.ipc.netty.channel.ContextHandler.java Source code

Java tutorial

Introduction

Here is the source code for reactor.ipc.netty.channel.ContextHandler.java

Source

/*
 * Copyright (c) 2011-2016 Pivotal Software Inc, All Rights Reserved.
 *
 * 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 reactor.ipc.netty.channel;

import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.pool.ChannelPool;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslHandler;
import io.netty.util.AttributeKey;
import io.netty.util.concurrent.Future;
import org.reactivestreams.Publisher;
import reactor.core.Cancellation;
import reactor.core.publisher.Mono;
import reactor.core.publisher.MonoSink;
import reactor.ipc.netty.NettyContext;
import reactor.ipc.netty.NettyPipeline;
import reactor.ipc.netty.options.ClientOptions;
import reactor.ipc.netty.options.NettyOptions;
import reactor.ipc.netty.options.ServerOptions;
import reactor.util.Logger;
import reactor.util.Loggers;

/**
 * A one time-set channel pipeline callback to emit {@link NettyContext} state for clean
 * disposing. A {@link ContextHandler} is bound to a user-facing {@link MonoSink}
 *
 * @param <CHANNEL> the channel type
 *
 * @author Stephane Maldini
 */
public abstract class ContextHandler<CHANNEL extends Channel> extends ChannelInitializer<CHANNEL>
        implements Cancellation {

    /**
     * Create a new client context
     *
     * @param sink
     * @param options
     * @param loggingHandler
     * @param secure
     * @param channelOpFactory
     * @param <CHANNEL>
     *
     * @return a new {@link ContextHandler} for clients
     */
    public static <CHANNEL extends Channel> ContextHandler<CHANNEL> newClientContext(MonoSink<NettyContext> sink,
            ClientOptions options, LoggingHandler loggingHandler, boolean secure,
            ChannelOperations.OnNew<CHANNEL> channelOpFactory) {
        return newClientContext(sink, options, loggingHandler, secure, null, channelOpFactory);
    }

    /**
     * Create a new client context with optional pool support
     *
     * @param sink
     * @param options
     * @param loggingHandler
     * @param secure
     * @param channelOpFactory
     * @param pool
     * @param <CHANNEL>
     *
     * @return a new {@link ContextHandler} for clients
     */
    public static <CHANNEL extends Channel> ContextHandler<CHANNEL> newClientContext(MonoSink<NettyContext> sink,
            ClientOptions options, LoggingHandler loggingHandler, boolean secure, ChannelPool pool,
            ChannelOperations.OnNew<CHANNEL> channelOpFactory) {
        if (pool != null) {
            return new PooledClientContextHandler<>(channelOpFactory, options, sink, loggingHandler, secure, pool);
        }
        return new ClientContextHandler<>(channelOpFactory, options, sink, loggingHandler, secure);
    }

    /**
     * Create a new server context
     *
     * @param sink
     * @param options
     * @param loggingHandler
     * @param channelOpFactory
     *
     * @return a new {@link ContextHandler} for servers
     */
    public static ContextHandler<Channel> newServerContext(MonoSink<NettyContext> sink, ServerOptions options,
            LoggingHandler loggingHandler, ChannelOperations.OnNew<Channel> channelOpFactory) {
        return new ServerContextHandler(channelOpFactory, options, sink, loggingHandler);
    }

    final MonoSink<NettyContext> sink;
    final NettyOptions<?, ?> options;
    final LoggingHandler loggingHandler;
    final ChannelOperations.OnNew<CHANNEL> channelOpFactory;

    BiConsumer<ChannelPipeline, ContextHandler<Channel>> pipelineConfigurator;
    boolean fired;
    boolean autoCreateOperations;

    /**
     * @param channelOpFactory
     * @param options
     * @param sink
     * @param loggingHandler
     */
    @SuppressWarnings("unchecked")
    protected ContextHandler(ChannelOperations.OnNew<CHANNEL> channelOpFactory, NettyOptions<?, ?> options,
            MonoSink<NettyContext> sink, LoggingHandler loggingHandler) {
        this.options = options;
        this.channelOpFactory = Objects.requireNonNull(channelOpFactory, "channelOpFactory");
        this.sink = sink;
        this.loggingHandler = loggingHandler;
        this.autoCreateOperations = true;
    }

    /**
     * Setup protocol specific handlers such as HTTP codec. Should be called before
     * binding the context to any channel or bootstrap.
     *
     * @param pipelineConfigurator a configurator for extra codecs in the {@link
     * ChannelPipeline}
     *
     * @return this context
     */
    public final ContextHandler<CHANNEL> onPipeline(
            BiConsumer<ChannelPipeline, ContextHandler<Channel>> pipelineConfigurator) {
        this.pipelineConfigurator = Objects.requireNonNull(pipelineConfigurator, "pipelineConfigurator");
        return this;
    }

    /**
     * Allow the {@link ChannelOperations} to be created automatically on pipeline setup
     *
     * @param autoCreateOperations should auto create {@link ChannelOperations}
     *
     * @return this context
     */
    public final ContextHandler<CHANNEL> autoCreateOperations(boolean autoCreateOperations) {
        this.autoCreateOperations = autoCreateOperations;
        return this;
    }

    /**
     * Return a new {@link ChannelOperations} or null if one of the two
     * following conditions are not met:
     * <ul>
     * <li>{@link #autoCreateOperations(boolean)} is true
     * </li>
     * <li>The passed message is not null</li>
     * </ul>
     *
     * @param channel the current {@link Channel}
     * @param msg an optional message inbound, meaning the channel has already been
     * started before
     *
     * @return a new {@link ChannelOperations}
     */
    @SuppressWarnings("unchecked")
    public final ChannelOperations<?, ?> createOperations(Channel channel, Object msg) {

        if (autoCreateOperations || msg != null) {
            ChannelOperations<?, ?> op = channelOpFactory.create((CHANNEL) channel, this, msg);
            channel.attr(ChannelOperations.OPERATIONS_KEY).set(op);

            channel.eventLoop().execute(op::onHandlerStart);
            return op;
        }

        return null;
    }

    /**
     * Trigger {@link MonoSink#success(Object)} that will signal
     * {@link reactor.ipc.netty.NettyConnector#newHandler(BiFunction)} returned
     * {@link Mono} subscriber.
     *
     * @param context optional context to succeed the associated {@link MonoSink}
     */
    public abstract void fireContextActive(NettyContext context);

    /**
     * Trigger {@link MonoSink#error(Throwable)} that will signal
     * {@link reactor.ipc.netty.NettyConnector#newHandler(BiFunction)} returned
     * {@link Mono} subscriber.
     *
     * @param t error to fail the associated {@link MonoSink}
     */
    public void fireContextError(Throwable t) {
        if (!fired) {
            fired = true;
            sink.error(t);
        }
    }

    /**
     * One-time only future setter
     *
     * @param future the connect/bind future to associate with and cancel on dispose
     */
    public abstract void setFuture(Future<?> future);

    /**
     * @param channel
     */
    protected void doStarted(Channel channel) {
        //ignore
    }

    @Override
    protected void initChannel(CHANNEL ch) throws Exception {
        doPipeline(ch.pipeline());
        ch.pipeline().addLast(NettyPipeline.BridgeSetup, new BridgeSetupHandler(this));
        if (log.isDebugEnabled()) {
            log.debug("After pipeline {}", ch.pipeline().toString());
        }
    }

    /**
     * @param channel
     */
    protected void doDropped(Channel channel) {
        //ignore
    }

    /**
     * @param pipeline
     */
    protected abstract void doPipeline(ChannelPipeline pipeline);

    /**
     * Cleanly terminate a channel according to the current context handler type.
     * Server might keep alive and recycle connections, pooled client will release and
     * classic client will close.
     *
     * @param channel the channel to unregister
     */
    protected void terminateChannel(Channel channel) {
        dispose();
    }

    /**
     * Return a Publisher to signal onComplete on {@link Channel} close or release.
     *
     * @param channel the channel to monitor
     *
     * @return a Publisher to signal onComplete on {@link Channel} close or release.
     */
    protected abstract Publisher<Void> onCloseOrRelease(Channel channel);

    static final AbortedException ABORTED = new AbortedException() {
        @Override
        public synchronized Throwable fillInStackTrace() {
            return this;
        }

    };

    static final Logger log = Loggers.getLogger(ContextHandler.class);

    static void addSslAndLogHandlers(NettyOptions<?, ?> options, MonoSink<NettyContext> sink,
            LoggingHandler loggingHandler, boolean secure, ChannelPipeline pipeline) {
        SslHandler sslHandler = secure ? options.getSslHandler(pipeline.channel().alloc()) : null;
        if (sslHandler != null) {
            if (log.isDebugEnabled()) {
                log.debug("SSL enabled using engine {}", sslHandler.engine().getClass().getSimpleName());
            }
            if (log.isTraceEnabled()) {
                pipeline.addFirst(NettyPipeline.SslLoggingHandler, new LoggingHandler(SslReadHandler.class));
                pipeline.addAfter(NettyPipeline.SslLoggingHandler, NettyPipeline.SslHandler, sslHandler);
            } else {
                pipeline.addFirst(NettyPipeline.SslHandler, sslHandler);
            }
            if (log.isDebugEnabled()) {
                pipeline.addAfter(NettyPipeline.SslHandler, NettyPipeline.LoggingHandler, loggingHandler);
                pipeline.addAfter(NettyPipeline.LoggingHandler, NettyPipeline.SslReader, new SslReadHandler(sink));
            } else {
                pipeline.addAfter(NettyPipeline.SslHandler, NettyPipeline.SslReader, new SslReadHandler(sink));
            }
        } else if (log.isDebugEnabled()) {
            pipeline.addFirst(NettyPipeline.LoggingHandler, loggingHandler);
        }
    }

    static final class BridgeSetupHandler extends ChannelInboundHandlerAdapter {

        final ContextHandler<Channel> parent;

        boolean active;

        @SuppressWarnings("unchecked")
        BridgeSetupHandler(ContextHandler<?> parent) {
            this.parent = (ContextHandler<Channel>) parent;
        }

        @Override
        @SuppressWarnings("unchecked")
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            if (!active) {
                active = true;

                if (parent.options.onChannelInit() != null) {
                    if (parent.options.onChannelInit().test(ctx.channel())) {
                        if (log.isDebugEnabled()) {
                            log.debug("DROPPED by onChannelInit predicate {}", ctx.channel());
                        }
                        parent.doDropped(ctx.channel());
                        return;
                    }
                }

                try {
                    if (parent.pipelineConfigurator != null) {
                        parent.pipelineConfigurator.accept(ctx.pipeline(), parent);
                    }
                    ctx.pipeline().addLast(NettyPipeline.ReactiveBridge, new ChannelOperationsHandler(parent));
                } catch (Exception t) {
                    if (log.isErrorEnabled()) {
                        log.error("Error while binding a channelOperation with: " + ctx.channel().toString()
                                + " on " + ctx.pipeline(), t);
                    }
                } finally {
                    if (null != parent.options.afterChannelInit()) {
                        parent.options.afterChannelInit().accept(ctx.channel());
                    }
                }
                ctx.pipeline().remove(this);
            }
            ctx.fireChannelActive();
        }

        @Override
        public void channelInactive(ChannelHandlerContext ctx) throws Exception {
            if (!active) {
                ctx.pipeline().remove(this);
                parent.terminateChannel(ctx.channel());
                parent.fireContextError(ABORTED);
            }
            ctx.fireChannelInactive();
        }

        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            if (!active) {
                if (log.isErrorEnabled()) {
                    log.error("Error while binding a channelOperation with: " + ctx.channel().toString(), cause);
                }
                ctx.pipeline().remove(this);
                parent.terminateChannel(ctx.channel());
                parent.fireContextError(cause);
            }
            ctx.fireExceptionCaught(cause);
        }
    }

    static final AttributeKey<Boolean> CLOSE_CHANNEL = AttributeKey.newInstance("CLOSE_CHANNEL");
}