/* * Copyright 2016 Netflix, 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, * 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 io.reactivex.netty.client; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPipeline; import io.netty.util.AttributeKey; import io.reactivex.netty.channel.AbstractConnectionToChannelBridge; import io.reactivex.netty.channel.ChannelSubscriberEvent; import io.reactivex.netty.channel.Connection; import io.reactivex.netty.channel.ConnectionInputSubscriberResetEvent; import io.reactivex.netty.channel.EmitConnectionEvent; import io.reactivex.netty.client.events.ClientEventListener; import io.reactivex.netty.client.pool.PooledConnection; import io.reactivex.netty.events.EventAttributeKeys; import io.reactivex.netty.events.EventPublisher; import io.reactivex.netty.internal.ExecuteInEventloopAction; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import rx.Subscriber; import rx.observers.SafeSubscriber; import rx.subscriptions.Subscriptions; /** * An implementation of {@link AbstractConnectionToChannelBridge} for clients. * * <h2>Reuse</h2> * * A channel can be reused for multiple operations, provided the reuses is signalled by {@link ConnectionReuseEvent}. * Failure to do so, will result in errors on the {@link Subscriber} trying to reuse the channel. * A typical reuse should have the following events: * <PRE> ChannelSubscriberEvent => ConnectionInputSubscriberEvent => ConnectionReuseEvent => ConnectionInputSubscriberEvent => ConnectionReuseEvent => ConnectionInputSubscriberEvent </PRE> * * @param <R> Type read from the connection held by this handler. * @param <W> Type written to the connection held by this handler. */ public class ClientConnectionToChannelBridge<R, W> extends AbstractConnectionToChannelBridge<R, W> { public static final AttributeKey<Boolean> DISCARD_CONNECTION = AttributeKey.valueOf("rxnetty_discard_connection"); private static final Logger logger = LoggerFactory.getLogger(ClientConnectionToChannelBridge.class); private static final String HANDLER_NAME = "client-conn-channel-bridge"; private EventPublisher eventPublisher; private ClientEventListener eventListener; private final boolean isSecure; private Channel channel; private ClientConnectionToChannelBridge(boolean isSecure) { super(HANDLER_NAME, EventAttributeKeys.CONNECTION_EVENT_LISTENER, EventAttributeKeys.EVENT_PUBLISHER); this.isSecure = isSecure; } @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { channel = ctx.channel(); eventPublisher = channel.attr(EventAttributeKeys.EVENT_PUBLISHER).get(); eventListener = ctx.channel().attr(EventAttributeKeys.CLIENT_EVENT_LISTENER).get(); if (null == eventPublisher) { logger.error("No Event publisher bound to the channel, closing channel."); ctx.channel().close(); return; } if (eventPublisher.publishingEnabled() && null == eventListener) { logger.error("No Event listener bound to the channel and event publishing is enabled., closing channel."); ctx.channel().close(); return; } super.handlerAdded(ctx); } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { if (!isSecure) {/*When secure, the event is triggered post SSL handshake via the SslCodec*/ userEventTriggered(ctx, EmitConnectionEvent.INSTANCE); } super.channelActive(ctx); } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { super.userEventTriggered(ctx, evt); // Super handles ConnectionInputSubscriberResetEvent to reset the subscriber. if (evt instanceof ConnectionReuseEvent) { @SuppressWarnings("unchecked") ConnectionReuseEvent<R, W> event = (ConnectionReuseEvent<R, W>) evt; newConnectionReuseEvent(ctx.channel(), event); } } @Override protected void onNewReadSubscriber(Subscriber<? super R> subscriber) { // Unsubscribe from the input closes the connection as there can only be one subscriber to the // input and, if nothing is read, it means, nobody is using the connection. // For fire-and-forget usecases, one should explicitly ignore content on the connection which // adds a discard all subscriber that never unsubscribes. For this case, then, the close becomes // explicit. subscriber.add(Subscriptions.create(new ExecuteInEventloopAction(channel) { @Override public void run() { if (!connectionInputSubscriberExists(channel)) { Connection<?, ?> connection = channel.attr(Connection.CONNECTION_ATTRIBUTE_KEY).get(); if (null != connection) { connection.closeNow(); } } } })); } private void newConnectionReuseEvent(Channel channel, final ConnectionReuseEvent<R, W> event) { Subscriber<? super PooledConnection<R, W>> subscriber = event.getSubscriber(); if (isValidToEmit(subscriber)) { subscriber.onNext(event.getPooledConnection()); checkEagerSubscriptionIfConfigured(channel); } else { event.getPooledConnection().close(false); // If pooled connection not sent to the subscriber, release to the pool. } } public static <R, W> ClientConnectionToChannelBridge<R, W> addToPipeline(ChannelPipeline pipeline, boolean isSecure) { ClientConnectionToChannelBridge<R, W> toAdd = new ClientConnectionToChannelBridge<>(isSecure); pipeline.addLast(HANDLER_NAME, toAdd); return toAdd; } /** * An event to indicate channel/{@link Connection} reuse. This event should be used for clients that pool * connections. For every reuse of a connection (connection creation still uses {@link ChannelSubscriberEvent}) * the corresponding subscriber must be sent via this event. * * Every instance of this event resets the older subscriber attached to the connection and connection input. This * means sending an {@link Subscriber#onCompleted()} to both of those subscribers. It is assumed that the actual * {@link Subscriber} is similar to {@link SafeSubscriber} which can handle duplicate terminal events. * * @param <I> Type read from the connection held by the event. * @param <O> Type written to the connection held by the event. */ public static final class ConnectionReuseEvent<I, O> implements ConnectionInputSubscriberResetEvent { private final Subscriber<? super PooledConnection<I, O>> subscriber; private final PooledConnection<I, O> pooledConnection; public ConnectionReuseEvent(Subscriber<? super PooledConnection<I, O>> subscriber, PooledConnection<I, O> pooledConnection) { this.subscriber = subscriber; this.pooledConnection = pooledConnection; } public Subscriber<? super PooledConnection<I, O>> getSubscriber() { return subscriber; } public PooledConnection<I, O> getPooledConnection() { return pooledConnection; } } /** * An event to indicate release of a {@link PooledConnection}. */ public static final class PooledConnectionReleaseEvent { public static final PooledConnectionReleaseEvent INSTANCE = new PooledConnectionReleaseEvent(); private PooledConnectionReleaseEvent() { } } }