/*
* 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.protocol.tcp.server;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.reactivex.netty.channel.AbstractConnectionToChannelBridge;
import io.reactivex.netty.channel.ChannelSubscriberEvent;
import io.reactivex.netty.channel.Connection;
import io.reactivex.netty.channel.ConnectionImpl;
import io.reactivex.netty.channel.EmitConnectionEvent;
import io.reactivex.netty.events.Clock;
import io.reactivex.netty.events.EventAttributeKeys;
import io.reactivex.netty.protocol.tcp.server.events.TcpServerEventPublisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rx.Observable;
import rx.Subscriber;
import rx.functions.Action0;
import rx.functions.Func1;
import java.nio.channels.ClosedChannelException;
import static java.util.concurrent.TimeUnit.*;
/**
* An implementation of {@link AbstractConnectionToChannelBridge} for servers.
*
* @param <R> The type of objects read from the server using this bridge.
* @param <W> The type of objects written to this server using this bridge.
*/
public class TcpServerConnectionToChannelBridge<R, W> extends AbstractConnectionToChannelBridge<R, W> {
private static final Logger logger = LoggerFactory.getLogger(TcpServerConnectionToChannelBridge.class);
private static final String HANDLER_NAME = "server-conn-channel-bridge";
private final ConnectionHandler<R, W> connectionHandler;
private final TcpServerEventPublisher eventPublisher;
private final boolean isSecure;
private final ChannelSubscriberEvent<R, W> channelSubscriberEvent;
private TcpServerConnectionToChannelBridge(ConnectionHandler<R, W> connectionHandler,
TcpServerEventPublisher eventPublisher, boolean isSecure) {
super(HANDLER_NAME, eventPublisher, eventPublisher);
this.connectionHandler = connectionHandler;
this.eventPublisher = eventPublisher;
this.isSecure = isSecure;
channelSubscriberEvent = new ChannelSubscriberEvent<>(new NewChannelSubscriber());
}
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
userEventTriggered(ctx, channelSubscriberEvent);
if (!isSecure) {/*When secure, the event is triggered post SSL handshake via the SslCodec*/
userEventTriggered(ctx, EmitConnectionEvent.INSTANCE);
}
super.channelRegistered(ctx);
}
public static <R, W> TcpServerConnectionToChannelBridge<R, W> addToPipeline(ChannelPipeline pipeline,
ConnectionHandler<R, W> connectionHandler,
TcpServerEventPublisher eventPublisher,
boolean isSecure) {
TcpServerConnectionToChannelBridge<R, W> toAdd = new TcpServerConnectionToChannelBridge<>(connectionHandler,
eventPublisher, isSecure);
pipeline.addLast(HANDLER_NAME, toAdd);
return toAdd;
}
private final class NewChannelSubscriber extends Subscriber<Channel> {
@Override
public void onCompleted() {
// No Op.
}
@Override
public void onError(Throwable e) {
logger.error("Error while listening for new client connections.", e);
}
@Override
public void onNext(final Channel channel) {
channel.attr(EventAttributeKeys.EVENT_PUBLISHER).set(eventPublisher);
channel.attr(EventAttributeKeys.CONNECTION_EVENT_LISTENER).set(eventPublisher);
final Connection<R, W> connection = ConnectionImpl.fromChannel(channel);
final long startTimeNanos = eventPublisher.publishingEnabled() ? Clock.newStartTimeNanos() : -1;
if (eventPublisher.publishingEnabled()) {
eventPublisher.onNewClientConnected();
}
Observable<Void> handledObservable;
try {
if (eventPublisher.publishingEnabled()) {
eventPublisher.onConnectionHandlingStart(Clock.onEndNanos(startTimeNanos), NANOSECONDS);
}
handledObservable = connectionHandler.handle(connection);
} catch (Throwable throwable) {
handledObservable = Observable.error(throwable);
}
if (null == handledObservable) {
logger.error("Connection handler returned null.");
handledObservable = Observable.empty();
}
handledObservable
.onErrorResumeNext(
new Func1<Throwable, Observable<? extends Void>>() {
@Override
public Observable<? extends Void> call(Throwable throwable) {
if (throwable instanceof ClosedChannelException) {
return Observable.empty();
} else {
/*Since, this is always reading input for new requests, it will always get a
closed channel exception on connection close from client. No point in logging
that error.*/
if (eventPublisher.publishingEnabled()) {
eventPublisher.onConnectionHandlingFailed(Clock.onEndNanos(startTimeNanos),
NANOSECONDS,
throwable);
}
logger.error("Error processing connection.", throwable);
return connection.close();
}
}
})
.ambWith(connection.closeListener())
.concatWith(connection.close())
.doOnCompleted(new Action0() {
@Override
public void call() {
if (eventPublisher.publishingEnabled()) {
eventPublisher.onConnectionHandlingSuccess(Clock.onEndNanos(startTimeNanos),
NANOSECONDS);
}
}
})
.subscribe();
}
}
}