update to 4.1.69, remove rx/reactive subprojects
This commit is contained in:
parent
7f69090bcb
commit
b2135d4c80
282 changed files with 3 additions and 44789 deletions
|
@ -1,14 +1,12 @@
|
||||||
group = org.xbib
|
group = org.xbib
|
||||||
name = netty-http
|
name = netty-http
|
||||||
version = 4.1.68.0
|
version = 4.1.69.0
|
||||||
|
|
||||||
gradle.wrapper.version = 6.6.1
|
gradle.wrapper.version = 6.6.1
|
||||||
|
|
||||||
netty.version = 4.1.68.Final
|
netty.version = 4.1.69.Final
|
||||||
tcnative.version = 2.0.43.Final
|
tcnative.version = 2.0.44.Final
|
||||||
bouncycastle.version = 1.69
|
bouncycastle.version = 1.69
|
||||||
reactivestreams.version = 1.0.3
|
|
||||||
reactivex.version = 1.3.8
|
|
||||||
conscrypt.version = 2.5.2
|
conscrypt.version = 2.5.2
|
||||||
javassist.version = 3.28.0-GA
|
javassist.version = 3.28.0-GA
|
||||||
jackson.version = 2.11.4
|
jackson.version = 2.11.4
|
||||||
|
@ -16,5 +14,3 @@ mockito.version = 3.10.0
|
||||||
xbib.net.version = 2.1.1
|
xbib.net.version = 2.1.1
|
||||||
xbib-guice.version = 4.4.2
|
xbib-guice.version = 4.4.2
|
||||||
junit.version = 5.7.1
|
junit.version = 5.7.1
|
||||||
# uuhh, too many tests to update to jupiter in rx...
|
|
||||||
junit4.version = 4.13.1
|
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
This work is based on
|
|
||||||
|
|
||||||
https://github.com/ReactiveX/RxNetty
|
|
||||||
|
|
||||||
(branch 0.5.x as of 22-Sep-2019)
|
|
|
@ -1,8 +0,0 @@
|
||||||
dependencies {
|
|
||||||
api "io.reactivex:rxjava:${project.property('reactivex.version')}"
|
|
||||||
implementation "io.netty:netty-codec-http:${project.property('netty.version')}"
|
|
||||||
implementation "io.netty:netty-transport-native-epoll:${project.property('netty.version')}"
|
|
||||||
testImplementation "org.mockito:mockito-core:${project.property('mockito.version')}"
|
|
||||||
testRuntimeOnly "org.junit.vintage:junit-vintage-engine:${project.property('junit.version')}"
|
|
||||||
testImplementation "junit:junit:${project.property('junit4.version')}"
|
|
||||||
}
|
|
|
@ -1,46 +0,0 @@
|
||||||
/*
|
|
||||||
* 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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A list of all handler names added by the framework. This is just to ensure consistency in naming.
|
|
||||||
*/
|
|
||||||
public enum HandlerNames {
|
|
||||||
|
|
||||||
SslHandler("ssl-handler"),
|
|
||||||
SslConnectionEmissionHandler("ssl-connection-emitter"),
|
|
||||||
WireLogging("wire-logging-handler"),
|
|
||||||
WriteTransformer("write-transformer"),
|
|
||||||
ClientReadTimeoutHandler("client-read-timeout-handler"),
|
|
||||||
ClientChannelActiveBufferingHandler("client-channel-active-buffer-handler"),
|
|
||||||
;
|
|
||||||
|
|
||||||
private final String name;
|
|
||||||
|
|
||||||
HandlerNames(String name) {
|
|
||||||
this.name = qualify(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String qualify(String name) {
|
|
||||||
return "_rx_netty_" + name;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,97 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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;
|
|
||||||
|
|
||||||
import io.reactivex.netty.threads.RxEventLoopProvider;
|
|
||||||
import io.reactivex.netty.threads.SingleNioLoopProvider;
|
|
||||||
|
|
||||||
public final class RxNetty {
|
|
||||||
|
|
||||||
private static volatile RxEventLoopProvider rxEventLoopProvider = new SingleNioLoopProvider(Runtime.getRuntime().availableProcessors());
|
|
||||||
|
|
||||||
private static volatile boolean usingNativeTransport;
|
|
||||||
private static volatile boolean disableEventPublishing;
|
|
||||||
|
|
||||||
private RxNetty() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An implementation of {@link RxEventLoopProvider} to be used by all clients and servers created after this call.
|
|
||||||
*
|
|
||||||
* @param provider New provider to use.
|
|
||||||
*
|
|
||||||
* @return Existing provider.
|
|
||||||
*/
|
|
||||||
public static RxEventLoopProvider useEventLoopProvider(RxEventLoopProvider provider) {
|
|
||||||
RxEventLoopProvider oldProvider = rxEventLoopProvider;
|
|
||||||
rxEventLoopProvider = provider;
|
|
||||||
return oldProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RxEventLoopProvider getRxEventLoopProvider() {
|
|
||||||
return rxEventLoopProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A global flag to start using netty's <a href="https://github.com/netty/netty/wiki/Native-transports">native protocol</a>
|
|
||||||
* if applicable for a client or server.
|
|
||||||
*
|
|
||||||
* <b>This does not evaluate whether the native transport is available for the OS or not.</b>
|
|
||||||
*
|
|
||||||
* So, this method should be called conditionally when the caller is sure that the OS supports the native protocol.
|
|
||||||
*
|
|
||||||
* Alternatively, this can be done selectively per client and server instance.
|
|
||||||
*/
|
|
||||||
public static void useNativeTransportIfApplicable() {
|
|
||||||
usingNativeTransport = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A global flag to disable the effects of calling {@link #useNativeTransportIfApplicable()}
|
|
||||||
*/
|
|
||||||
public static void disableNativeTransport() {
|
|
||||||
usingNativeTransport = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enables publishing of events for RxNetty.
|
|
||||||
*/
|
|
||||||
public static void enableEventPublishing() {
|
|
||||||
disableEventPublishing = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disables publishing of events for RxNetty.
|
|
||||||
*/
|
|
||||||
public static void disableEventPublishing() {
|
|
||||||
disableEventPublishing = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} if event publishing is disabled.
|
|
||||||
*
|
|
||||||
* @return {@code true} if event publishing is disabled.
|
|
||||||
*/
|
|
||||||
public static boolean isEventPublishingDisabled() {
|
|
||||||
return disableEventPublishing;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean isUsingNativeTransport() {
|
|
||||||
return usingNativeTransport;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,392 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.channel;
|
|
||||||
|
|
||||||
import io.netty.channel.Channel;
|
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
|
||||||
import io.netty.channel.ChannelOption;
|
|
||||||
import io.netty.util.AttributeKey;
|
|
||||||
import io.netty.util.ReferenceCountUtil;
|
|
||||||
import io.netty.util.internal.EmptyArrays;
|
|
||||||
import io.reactivex.netty.channel.events.ConnectionEventListener;
|
|
||||||
import io.reactivex.netty.events.EventPublisher;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
import rx.Producer;
|
|
||||||
import rx.Subscriber;
|
|
||||||
import rx.exceptions.MissingBackpressureException;
|
|
||||||
|
|
||||||
import java.nio.channels.ClosedChannelException;
|
|
||||||
import java.util.concurrent.atomic.AtomicLongFieldUpdater;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A bridge between a {@link Connection} instance and the associated {@link Channel}.
|
|
||||||
*
|
|
||||||
* All operations on {@link Connection} will pass through this bridge to an appropriate action on the {@link Channel}
|
|
||||||
*
|
|
||||||
* <h2>Lazy {@link Connection#getInput()} subscription</h2>
|
|
||||||
*
|
|
||||||
* Lazy subscriptions are allowed on {@link Connection#getInput()} if and only if the channel is configured to
|
|
||||||
* not read data automatically (i.e. {@link ChannelOption#AUTO_READ} is set to {@code false}). Otherwise,
|
|
||||||
* if {@link Connection#getInput()} is subscribed lazily, the subscriber always receives an error. The content
|
|
||||||
* in this case is disposed upon reading.
|
|
||||||
*
|
|
||||||
* @param <R> Type read from the connection held by this handler.
|
|
||||||
* @param <W> Type written to the connection held by this handler.
|
|
||||||
*/
|
|
||||||
public abstract class AbstractConnectionToChannelBridge<R, W> extends BackpressureManagingHandler {
|
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(AbstractConnectionToChannelBridge.class.getName());
|
|
||||||
|
|
||||||
@SuppressWarnings("ThrowableInstanceNeverThrown")
|
|
||||||
private static final IllegalStateException ONLY_ONE_CONN_SUB_ALLOWED =
|
|
||||||
new IllegalStateException("Only one subscriber allowed for connection observable.");
|
|
||||||
@SuppressWarnings("ThrowableInstanceNeverThrown")
|
|
||||||
private static final IllegalStateException ONLY_ONE_CONN_INPUT_SUB_ALLOWED =
|
|
||||||
new IllegalStateException("Only one subscriber allowed for connection input.");
|
|
||||||
@SuppressWarnings("ThrowableInstanceNeverThrown")
|
|
||||||
private static final IllegalStateException LAZY_CONN_INPUT_SUB =
|
|
||||||
new IllegalStateException("Channel is set to auto-read but the subscription was lazy.");
|
|
||||||
|
|
||||||
@SuppressWarnings("ThrowableInstanceNeverThrown")
|
|
||||||
private static final ClosedChannelException CLOSED_CHANNEL_EXCEPTION = new ClosedChannelException();
|
|
||||||
|
|
||||||
static {
|
|
||||||
ONLY_ONE_CONN_INPUT_SUB_ALLOWED.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE);
|
|
||||||
ONLY_ONE_CONN_SUB_ALLOWED.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE);
|
|
||||||
LAZY_CONN_INPUT_SUB.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE);
|
|
||||||
CLOSED_CHANNEL_EXCEPTION.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final AttributeKey<ConnectionEventListener> eventListenerAttributeKey;
|
|
||||||
private final AttributeKey<EventPublisher> eventPublisherAttributeKey;
|
|
||||||
|
|
||||||
protected ConnectionEventListener eventListener;
|
|
||||||
protected EventPublisher eventPublisher;
|
|
||||||
private Subscriber<? super Channel> newChannelSub;
|
|
||||||
private ReadProducer<R> readProducer;
|
|
||||||
private boolean raiseErrorOnInputSubscription;
|
|
||||||
private boolean connectionEmitted;
|
|
||||||
|
|
||||||
protected AbstractConnectionToChannelBridge(String thisHandlerName, ConnectionEventListener eventListener,
|
|
||||||
EventPublisher eventPublisher) {
|
|
||||||
super(thisHandlerName);
|
|
||||||
if (null == eventListener) {
|
|
||||||
throw new IllegalArgumentException("Event listener can not be null.");
|
|
||||||
}
|
|
||||||
if (null == eventPublisher) {
|
|
||||||
throw new IllegalArgumentException("Event publisher can not be null.");
|
|
||||||
}
|
|
||||||
this.eventListener = eventListener;
|
|
||||||
this.eventPublisher = eventPublisher;
|
|
||||||
eventListenerAttributeKey = null;
|
|
||||||
eventPublisherAttributeKey = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected AbstractConnectionToChannelBridge(String thisHandlerName,
|
|
||||||
AttributeKey<ConnectionEventListener> eventListenerAttributeKey,
|
|
||||||
AttributeKey<EventPublisher> eventPublisherAttributeKey) {
|
|
||||||
super(thisHandlerName);
|
|
||||||
this.eventListenerAttributeKey = eventListenerAttributeKey;
|
|
||||||
this.eventPublisherAttributeKey = eventPublisherAttributeKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
|
|
||||||
if (null == eventListener && null == eventPublisher) {
|
|
||||||
eventListener = ctx.channel().attr(eventListenerAttributeKey).get();
|
|
||||||
eventPublisher = ctx.channel().attr(eventPublisherAttributeKey).get();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (null == eventPublisher) {
|
|
||||||
logger.log(Level.SEVERE, "No Event publisher bound to the channel, closing channel.");
|
|
||||||
ctx.channel().close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (eventPublisher.publishingEnabled() && null == eventListener) {
|
|
||||||
logger.log(Level.SEVERE, "No Event listener bound to the channel and publising is enabled, closing channel.");
|
|
||||||
ctx.channel().close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.pipeline().addFirst(new BytesInspector(eventPublisher, eventListener));
|
|
||||||
|
|
||||||
super.handlerAdded(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
|
||||||
if (!connectionEmitted && isValidToEmit(newChannelSub)) {
|
|
||||||
emitNewConnection(ctx.channel());
|
|
||||||
connectionEmitted = true;
|
|
||||||
}
|
|
||||||
super.channelInactive(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
|
|
||||||
|
|
||||||
if (isValidToEmitToReadSubscriber(readProducer)) {
|
|
||||||
/*If the subscriber is still active, then it expects data but the channel is closed.*/
|
|
||||||
readProducer.sendOnError(CLOSED_CHANNEL_EXCEPTION);
|
|
||||||
}
|
|
||||||
|
|
||||||
super.channelUnregistered(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
|
||||||
if (evt instanceof EmitConnectionEvent) {
|
|
||||||
if (!connectionEmitted) {
|
|
||||||
emitNewConnection(ctx.channel());
|
|
||||||
connectionEmitted = true;
|
|
||||||
}
|
|
||||||
} else if (evt instanceof ConnectionCreationFailedEvent) {
|
|
||||||
if (isValidToEmit(newChannelSub)) {
|
|
||||||
newChannelSub.onError(((ConnectionCreationFailedEvent)evt).getThrowable());
|
|
||||||
}
|
|
||||||
} else if (evt instanceof ChannelSubscriberEvent) {
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
final ChannelSubscriberEvent<R, W> channelSubscriberEvent = (ChannelSubscriberEvent<R, W>) evt;
|
|
||||||
|
|
||||||
newConnectionSubscriber(channelSubscriberEvent);
|
|
||||||
} else if (evt instanceof ConnectionInputSubscriberEvent) {
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
ConnectionInputSubscriberEvent<R, W> event = (ConnectionInputSubscriberEvent<R, W>) evt;
|
|
||||||
|
|
||||||
newConnectionInputSubscriber(ctx.channel(), event.getSubscriber(), false);
|
|
||||||
} else if (evt instanceof ConnectionInputSubscriberResetEvent) {
|
|
||||||
resetConnectionInputSubscriber();
|
|
||||||
} else if (evt instanceof ConnectionInputSubscriberReplaceEvent) {
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
ConnectionInputSubscriberReplaceEvent<R, W> event = (ConnectionInputSubscriberReplaceEvent<R, W>) evt;
|
|
||||||
replaceConnectionInputSubscriber(ctx.channel(), event);
|
|
||||||
}
|
|
||||||
|
|
||||||
super.userEventTriggered(ctx, evt);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
|
||||||
public void newMessage(ChannelHandlerContext ctx, Object msg) {
|
|
||||||
if (isValidToEmitToReadSubscriber(readProducer)) {
|
|
||||||
try {
|
|
||||||
readProducer.sendOnNext((R) msg);
|
|
||||||
} catch (ClassCastException e) {
|
|
||||||
ReferenceCountUtil.release(msg); // Since, this was not sent to the subscriber, release the msg.
|
|
||||||
readProducer.sendOnError(e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.log(Level.WARNING, "Data received on channel, but no subscriber registered. Discarding data. Message class: "
|
|
||||||
+ msg.getClass().getName() + ", channel: " + ctx.channel());
|
|
||||||
ReferenceCountUtil.release(msg); // No consumer of the message, so discard.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean shouldReadMore(ChannelHandlerContext ctx) {
|
|
||||||
return null != readProducer && readProducer.shouldReadMore(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
|
|
||||||
if (!connectionEmitted && isValidToEmit(newChannelSub)) {
|
|
||||||
newChannelSub.onError(cause);
|
|
||||||
} else if (isValidToEmitToReadSubscriber(readProducer)) {
|
|
||||||
readProducer.sendOnError(cause);
|
|
||||||
} else {
|
|
||||||
logger.log(Level.INFO, "Exception in the pipeline and none of the subscribers are active.", cause);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static boolean isValidToEmit(Subscriber<?> subscriber) {
|
|
||||||
return null != subscriber && !subscriber.isUnsubscribed();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isValidToEmitToReadSubscriber(ReadProducer<?> readProducer) {
|
|
||||||
return null != readProducer && !readProducer.subscriber.isUnsubscribed();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean connectionInputSubscriberExists(Channel channel) {
|
|
||||||
assert channel.eventLoop().inEventLoop();
|
|
||||||
|
|
||||||
return null != readProducer && null != readProducer.subscriber && !readProducer.subscriber.isUnsubscribed();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void onNewReadSubscriber(Subscriber<? super R> subscriber) {
|
|
||||||
// NOOP
|
|
||||||
}
|
|
||||||
|
|
||||||
protected final void checkEagerSubscriptionIfConfigured(Channel channel) {
|
|
||||||
if (channel.config().isAutoRead() && null == readProducer) {
|
|
||||||
// If the channel is set to auto-read and there is no eager subscription then, we should raise errors
|
|
||||||
// when a subscriber arrives.
|
|
||||||
raiseErrorOnInputSubscription = true;
|
|
||||||
final Subscriber<? super R> discardAll = ConnectionInputSubscriberEvent.discardAllInput()
|
|
||||||
.getSubscriber();
|
|
||||||
final ReadProducer<R> producer = new ReadProducer<>(discardAll, channel);
|
|
||||||
discardAll.setProducer(producer);
|
|
||||||
readProducer = producer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected final Subscriber<? super Channel> getNewChannelSub() {
|
|
||||||
return newChannelSub;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void emitNewConnection(Channel channel) {
|
|
||||||
if (isValidToEmit(newChannelSub)) {
|
|
||||||
try {
|
|
||||||
newChannelSub.onNext(channel);
|
|
||||||
connectionEmitted = true;
|
|
||||||
checkEagerSubscriptionIfConfigured(channel);
|
|
||||||
newChannelSub.onCompleted();
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.log(Level.SEVERE, "Error emitting a new connection. Closing this channel.", e);
|
|
||||||
channel.close();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
channel.close(); // Closing the connection if not sent to a subscriber.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void resetConnectionInputSubscriber() {
|
|
||||||
final Subscriber<? super R> connInputSub = null == readProducer? null : readProducer.subscriber;
|
|
||||||
if (isValidToEmit(connInputSub)) {
|
|
||||||
connInputSub.onCompleted();
|
|
||||||
}
|
|
||||||
raiseErrorOnInputSubscription = false;
|
|
||||||
readProducer = null; // A subsequent event should set it to the desired subscriber.
|
|
||||||
}
|
|
||||||
|
|
||||||
private void newConnectionInputSubscriber(final Channel channel, final Subscriber<? super R> subscriber,
|
|
||||||
boolean replace) {
|
|
||||||
final Subscriber<? super R> connInputSub = null == readProducer ? null : readProducer.subscriber;
|
|
||||||
if (isValidToEmit(connInputSub)) {
|
|
||||||
if (!replace) {
|
|
||||||
/*Allow only once concurrent input subscriber but allow concatenated subscribers*/
|
|
||||||
subscriber.onError(ONLY_ONE_CONN_INPUT_SUB_ALLOWED);
|
|
||||||
} else {
|
|
||||||
setNewReadProducer(channel, subscriber);
|
|
||||||
connInputSub.onCompleted();
|
|
||||||
}
|
|
||||||
} else if (raiseErrorOnInputSubscription) {
|
|
||||||
subscriber.onError(LAZY_CONN_INPUT_SUB);
|
|
||||||
} else {
|
|
||||||
setNewReadProducer(channel, subscriber);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setNewReadProducer(Channel channel, Subscriber<? super R> subscriber) {
|
|
||||||
final ReadProducer<R> producer = new ReadProducer<>(subscriber, channel);
|
|
||||||
subscriber.setProducer(producer);
|
|
||||||
onNewReadSubscriber(subscriber);
|
|
||||||
readProducer = producer;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void replaceConnectionInputSubscriber(Channel channel, ConnectionInputSubscriberReplaceEvent<R, W> event) {
|
|
||||||
ConnectionInputSubscriberEvent<R, W> newSubEvent = event.getNewSubEvent();
|
|
||||||
newConnectionInputSubscriber(channel, newSubEvent.getSubscriber(),
|
|
||||||
true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void newConnectionSubscriber(ChannelSubscriberEvent<R, W> event) {
|
|
||||||
if (null == newChannelSub) {
|
|
||||||
newChannelSub = event.getSubscriber();
|
|
||||||
} else {
|
|
||||||
event.getSubscriber().onError(ONLY_ONE_CONN_SUB_ALLOWED);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*Visible for testing*/ static final class ReadProducer<T> extends RequestReadIfRequiredEvent implements Producer {
|
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
private static final AtomicLongFieldUpdater<ReadProducer> REQUEST_UPDATER =
|
|
||||||
AtomicLongFieldUpdater.newUpdater(ReadProducer.class, "requested");/*Updater for requested*/
|
|
||||||
private volatile long requested; // Updated by REQUEST_UPDATER, required to be volatile.
|
|
||||||
|
|
||||||
private final Subscriber<? super T> subscriber;
|
|
||||||
private final Channel channel;
|
|
||||||
|
|
||||||
/*Visible for testing*/ ReadProducer(Subscriber<? super T> subscriber, Channel channel) {
|
|
||||||
this.subscriber = subscriber;
|
|
||||||
this.channel = channel;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void request(long n) {
|
|
||||||
if (Long.MAX_VALUE != requested) {
|
|
||||||
if (Long.MAX_VALUE == n) {
|
|
||||||
// Now turning off backpressure
|
|
||||||
REQUEST_UPDATER.set(this, Long.MAX_VALUE);
|
|
||||||
} else {
|
|
||||||
// add n to field but check for overflow
|
|
||||||
while (true) {
|
|
||||||
final long current = requested;
|
|
||||||
long next = current + n;
|
|
||||||
// check for overflow
|
|
||||||
if (next < 0) {
|
|
||||||
next = Long.MAX_VALUE;
|
|
||||||
}
|
|
||||||
if (REQUEST_UPDATER.compareAndSet(this, current, next)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!channel.config().isAutoRead()) {
|
|
||||||
channel.pipeline().fireUserEventTriggered(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendOnError(Throwable throwable) {
|
|
||||||
subscriber.onError(throwable);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendOnComplete() {
|
|
||||||
subscriber.onCompleted();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendOnNext(T nextItem) {
|
|
||||||
if (requested > 0) {
|
|
||||||
if (REQUEST_UPDATER.get(this) != Long.MAX_VALUE) {
|
|
||||||
REQUEST_UPDATER.decrementAndGet(this);
|
|
||||||
}
|
|
||||||
subscriber.onNext(nextItem);
|
|
||||||
} else {
|
|
||||||
subscriber.onError(new MissingBackpressureException(
|
|
||||||
"Received more data on the channel than demanded by the subscriber."));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean shouldReadMore(ChannelHandlerContext ctx) {
|
|
||||||
return !subscriber.isUnsubscribed() && REQUEST_UPDATER.get(this) > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*Visible for testing*/long getRequested() {
|
|
||||||
return requested;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "ReadProducer{" + "requested=" + requested + '}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,72 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.channel;
|
|
||||||
|
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
|
||||||
import io.netty.util.internal.TypeParameterMatcher;
|
|
||||||
import rx.annotations.Beta;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A transformer to be used for modifying the type of objects written on a {@link Connection}.
|
|
||||||
*
|
|
||||||
* <h2>Why is this required?</h2>
|
|
||||||
*
|
|
||||||
* The type of an object can usually be transformed using {@code Observable.map()}, however, while writing on a
|
|
||||||
* {@link Connection}, typically one requires to allocate buffers. Although a {@code Connection} provides a way to
|
|
||||||
* retrieve the {@link ByteBufAllocator} via the {@code Channel}, allocating buffers from outside the eventloop will
|
|
||||||
* lead to buffer bloats as the allocators will typically use thread-local buffer pools. <p>
|
|
||||||
*
|
|
||||||
* This transformer is always invoked from within the eventloop and hence does not have buffer bloating issues, even
|
|
||||||
* when transformations happen outside the eventloop.
|
|
||||||
*
|
|
||||||
* @param <T> Source type.
|
|
||||||
* @param <TT> Target type.
|
|
||||||
*/
|
|
||||||
@Beta
|
|
||||||
public abstract class AllocatingTransformer<T, TT> {
|
|
||||||
|
|
||||||
private final TypeParameterMatcher matcher;
|
|
||||||
|
|
||||||
protected AllocatingTransformer() {
|
|
||||||
matcher = TypeParameterMatcher.find(this, AllocatingTransformer.class, "T");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Asserts whether the passed message can be transformed using this transformer.
|
|
||||||
*
|
|
||||||
* @param msg Message to transform.
|
|
||||||
*
|
|
||||||
* @return {@code true} if the message can be transformed.
|
|
||||||
*/
|
|
||||||
protected boolean acceptMessage(Object msg) {
|
|
||||||
return matcher.match(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transforms the passed message and adds the output to the returned list.
|
|
||||||
*
|
|
||||||
* @param toTransform Message to transform.
|
|
||||||
* @param allocator Allocating for allocating buffers, if required.
|
|
||||||
*
|
|
||||||
* @return Output of the transformation.
|
|
||||||
*/
|
|
||||||
public abstract List<TT> transform(T toTransform, ByteBufAllocator allocator);
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.channel;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An event to register a custom transformer of data written on a channel.
|
|
||||||
*
|
|
||||||
* @param <T> Source type for the transformer.
|
|
||||||
* @param <TT> Target type for the transformer.
|
|
||||||
*/
|
|
||||||
public final class AppendTransformerEvent<T, TT> {
|
|
||||||
|
|
||||||
private final AllocatingTransformer<T, TT> transformer;
|
|
||||||
|
|
||||||
public AppendTransformerEvent(AllocatingTransformer<T, TT> transformer) {
|
|
||||||
if (null == transformer) {
|
|
||||||
throw new NullPointerException("Transformer can not be null.");
|
|
||||||
}
|
|
||||||
this.transformer = transformer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AllocatingTransformer<T, TT> getTransformer() {
|
|
||||||
return transformer;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.channel;
|
|
||||||
|
|
||||||
import io.netty.util.ReferenceCountUtil;
|
|
||||||
import rx.Observable.Operator;
|
|
||||||
import rx.Subscriber;
|
|
||||||
|
|
||||||
class AutoReleaseOperator<T> implements Operator<T, T> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Subscriber<? super T> call(final Subscriber<? super T> subscriber) {
|
|
||||||
return new Subscriber<T>(subscriber) {
|
|
||||||
@Override
|
|
||||||
public void onCompleted() {
|
|
||||||
subscriber.onCompleted();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(Throwable e) {
|
|
||||||
subscriber.onError(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNext(T t) {
|
|
||||||
try {
|
|
||||||
subscriber.onNext(t);
|
|
||||||
} finally {
|
|
||||||
ReferenceCountUtil.release(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,710 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.channel;
|
|
||||||
|
|
||||||
import io.netty.channel.Channel;
|
|
||||||
import io.netty.channel.ChannelDuplexHandler;
|
|
||||||
import io.netty.channel.ChannelFuture;
|
|
||||||
import io.netty.channel.ChannelFutureListener;
|
|
||||||
import io.netty.channel.ChannelHandler;
|
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
|
||||||
import io.netty.channel.ChannelPromise;
|
|
||||||
import io.netty.util.ReferenceCountUtil;
|
|
||||||
import io.netty.util.internal.RecyclableArrayList;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
import rx.Observable;
|
|
||||||
import rx.Scheduler;
|
|
||||||
import rx.Subscriber;
|
|
||||||
import rx.functions.Action0;
|
|
||||||
import rx.schedulers.Schedulers;
|
|
||||||
import rx.subscriptions.Subscriptions;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
|
||||||
|
|
||||||
public abstract class BackpressureManagingHandler extends ChannelDuplexHandler {
|
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(BackpressureManagingHandler.class.getName());
|
|
||||||
|
|
||||||
/*Visible for testing*/ enum State {
|
|
||||||
ReadRequested,
|
|
||||||
Reading,
|
|
||||||
Buffering,
|
|
||||||
DrainingBuffer,
|
|
||||||
Stopped,
|
|
||||||
}
|
|
||||||
|
|
||||||
private RecyclableArrayList buffer;
|
|
||||||
private int currentBufferIndex;
|
|
||||||
private State currentState = State.Buffering; /*Buffer unless explicitly asked to read*/
|
|
||||||
private boolean continueDraining;
|
|
||||||
private final BytesWriteInterceptor bytesWriteInterceptor;
|
|
||||||
|
|
||||||
protected BackpressureManagingHandler(String thisHandlerName) {
|
|
||||||
bytesWriteInterceptor = new BytesWriteInterceptor(thisHandlerName);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("fallthrough")
|
|
||||||
@Override
|
|
||||||
public final void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
|
||||||
|
|
||||||
if (State.Stopped != currentState && !shouldReadMore(ctx)) {
|
|
||||||
currentState = State.Buffering;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (currentState) {
|
|
||||||
case ReadRequested:
|
|
||||||
currentState = State.Reading;
|
|
||||||
case Reading:
|
|
||||||
newMessage(ctx, msg);
|
|
||||||
break;
|
|
||||||
case Buffering:
|
|
||||||
case DrainingBuffer:
|
|
||||||
if (null == buffer) {
|
|
||||||
buffer = RecyclableArrayList.newInstance();
|
|
||||||
}
|
|
||||||
buffer.add(msg);
|
|
||||||
break;
|
|
||||||
case Stopped:
|
|
||||||
logger.log(Level.WARNING, "Message read after handler removed, discarding the same. Message class: "
|
|
||||||
+ msg.getClass().getName());
|
|
||||||
ReferenceCountUtil.release(msg);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
|
|
||||||
ctx.pipeline().addFirst(bytesWriteInterceptor);
|
|
||||||
currentState = State.Buffering;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
|
|
||||||
/*On shut down, all the handlers are removed from the pipeline, so we don't need to explicitly remove the
|
|
||||||
additional handlers added in handlerAdded()*/
|
|
||||||
currentState = State.Stopped;
|
|
||||||
if (null != buffer) {
|
|
||||||
if (!buffer.isEmpty()) {
|
|
||||||
for (Object item : buffer) {
|
|
||||||
ReferenceCountUtil.release(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buffer.recycle();
|
|
||||||
buffer = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
|
|
||||||
|
|
||||||
switch (currentState) {
|
|
||||||
case ReadRequested:
|
|
||||||
/*Nothing read from the last request, forward to read() and let it take the decision on what to do.*/
|
|
||||||
break;
|
|
||||||
case Reading:
|
|
||||||
/*
|
|
||||||
* After read completion, move to Buffering, unless an explicit read is issued, which moves to an
|
|
||||||
* appropriate state.
|
|
||||||
*/
|
|
||||||
currentState = State.Buffering;
|
|
||||||
break;
|
|
||||||
case Buffering:
|
|
||||||
/*Keep buffering, unless the buffer drains and more items are requested*/
|
|
||||||
break;
|
|
||||||
case DrainingBuffer:
|
|
||||||
/*Keep draining, unless the buffer drains and more items are requested*/
|
|
||||||
break;
|
|
||||||
case Stopped:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.fireChannelReadComplete();
|
|
||||||
|
|
||||||
if (!ctx.channel().config().isAutoRead() && shouldReadMore(ctx)) {
|
|
||||||
read(ctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final void read(ChannelHandlerContext ctx) throws Exception {
|
|
||||||
switch (currentState) {
|
|
||||||
case ReadRequested:
|
|
||||||
/*Nothing read since last request, but requested more, so push the demand upstream.*/
|
|
||||||
ctx.read();
|
|
||||||
break;
|
|
||||||
case Reading:
|
|
||||||
/*
|
|
||||||
* We are already reading data and the read has not completed as that would move the state to buffering.
|
|
||||||
* So, ignore this read, or otherwise, read is requested on the channel, unnecessarily.
|
|
||||||
*/
|
|
||||||
break;
|
|
||||||
case Buffering:
|
|
||||||
/*
|
|
||||||
* We were buffering and now a read was requested, so start draining the buffer.
|
|
||||||
*/
|
|
||||||
currentState = State.DrainingBuffer;
|
|
||||||
continueDraining = true;
|
|
||||||
/*
|
|
||||||
* Looping here to drain, instead of having it done via readComplete -> read -> readComplete loop to reduce
|
|
||||||
* call stack depth. Otherwise, the stackdepth is proportional to number of items in the buffer and hence
|
|
||||||
* for large buffers will overflow stack.
|
|
||||||
*/
|
|
||||||
while (continueDraining && null != buffer && currentBufferIndex < buffer.size()) {
|
|
||||||
Object nextItem = buffer.get(currentBufferIndex++);
|
|
||||||
newMessage(ctx, nextItem); /*Send the next message.*/
|
|
||||||
/*
|
|
||||||
* If there is more read demand then that should come as part of read complete or later as another
|
|
||||||
* read (this method) invocation. */
|
|
||||||
continueDraining = false;
|
|
||||||
channelReadComplete(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (continueDraining) {
|
|
||||||
if (null != buffer) {
|
|
||||||
/*Outstanding read demand and buffer is empty, so recycle the buffer and pass the read upstream.*/
|
|
||||||
recycleBuffer();
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
* Since, continueDraining is true and we have broken out of the drain loop, it means that there are no
|
|
||||||
* items in the buffer and there is more read demand. Switch to read requested and send the read demand
|
|
||||||
* downstream.
|
|
||||||
*/
|
|
||||||
currentState = State.ReadRequested;
|
|
||||||
ctx.read();
|
|
||||||
} else {
|
|
||||||
/*
|
|
||||||
* There is no more demand, so set the state to buffering and so another read invocation can start
|
|
||||||
* draining.
|
|
||||||
*/
|
|
||||||
currentState = State.Buffering;
|
|
||||||
/*If buffer is empty, then recycle.*/
|
|
||||||
if (null != buffer && currentBufferIndex >= buffer.size()) {
|
|
||||||
recycleBuffer();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case DrainingBuffer:
|
|
||||||
/*Already draining buffer, so break the call stack and let the caller keep draining.*/
|
|
||||||
continueDraining = true;
|
|
||||||
break;
|
|
||||||
case Stopped:
|
|
||||||
/*Invalid, pass it downstream.*/
|
|
||||||
ctx.read();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Intercepts a write on the channel. The following message types are handled:
|
|
||||||
*
|
|
||||||
* <ul>
|
|
||||||
<li>String: If the pipeline is not configured to write a String, this converts the string to a {@link io.netty.buffer.ByteBuf} and
|
|
||||||
then writes it on the channel.</li>
|
|
||||||
<li>byte[]: If the pipeline is not configured to write a byte[], this converts the byte[] to a {@link io.netty.buffer.ByteBuf} and
|
|
||||||
then writes it on the channel.</li>
|
|
||||||
<li>Observable: Subscribes to the {@link Observable} and writes all items, requesting the next item if and only if
|
|
||||||
the channel is writable as indicated by {@link Channel#isWritable()}</li>
|
|
||||||
</ul>
|
|
||||||
*
|
|
||||||
* @param ctx Channel handler context.
|
|
||||||
* @param msg Message to write.
|
|
||||||
* @param promise Promise for the completion of write.
|
|
||||||
*
|
|
||||||
* @throws Exception If there is an error handling this write.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
|
||||||
if (msg instanceof Observable) {
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
Observable observable = (Observable) msg; /*One can write heterogeneous objects on a channel.*/
|
|
||||||
final WriteStreamSubscriber subscriber = bytesWriteInterceptor.newSubscriber(ctx, promise);
|
|
||||||
subscriber.subscribeTo(observable);
|
|
||||||
} else {
|
|
||||||
ctx.write(msg, promise);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
|
||||||
if (evt instanceof RequestReadIfRequiredEvent) {
|
|
||||||
RequestReadIfRequiredEvent requestReadIfRequiredEvent = (RequestReadIfRequiredEvent) evt;
|
|
||||||
if (requestReadIfRequiredEvent.shouldReadMore(ctx)) {
|
|
||||||
read(ctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
super.userEventTriggered(ctx, evt);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract void newMessage(ChannelHandlerContext ctx, Object msg);
|
|
||||||
|
|
||||||
protected abstract boolean shouldReadMore(ChannelHandlerContext ctx);
|
|
||||||
|
|
||||||
/*Visible for testing*/ RecyclableArrayList getBuffer() {
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*Visible for testing*/ int getCurrentBufferIndex() {
|
|
||||||
return currentBufferIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*Visible for testing*/ State getCurrentState() {
|
|
||||||
return currentState;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void recycleBuffer() {
|
|
||||||
buffer.recycle();
|
|
||||||
currentBufferIndex = 0;
|
|
||||||
buffer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static abstract class RequestReadIfRequiredEvent {
|
|
||||||
|
|
||||||
protected abstract boolean shouldReadMore(ChannelHandlerContext ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This handler inspects write to see if a write made it to {@link BytesWriteInterceptor} inline with a write call.
|
|
||||||
* The reasons why a write would not make it to the channel, would be:
|
|
||||||
* <ul>
|
|
||||||
<li>If there is a handler in the pipeline that runs in a different group.</li>
|
|
||||||
<li>If there is a handler that collects many items to produce a single item.</li>
|
|
||||||
</ul>
|
|
||||||
*
|
|
||||||
* When a write did not reach the {@link BytesWriteInterceptor}, no request for more items will be generated and
|
|
||||||
* we could get into a deadlock where a handler is waiting for more items (collect case) but no more items arrive as
|
|
||||||
* no more request is generated. In order to avoid this deadlock, this handler will detect the situation and
|
|
||||||
* trigger more request in this case.
|
|
||||||
*
|
|
||||||
* Why a separate handler?
|
|
||||||
*
|
|
||||||
* This needs to be different than {@link BytesWriteInterceptor} as we need it immediately after
|
|
||||||
* {@link BackpressureManagingHandler} so that no other handler eats a write and {@link BytesWriteInterceptor} is
|
|
||||||
* always the first handler in the pipeline to be right before the channel and hence maintain proper demand.
|
|
||||||
*/
|
|
||||||
static final class WriteInspector extends ChannelDuplexHandler {
|
|
||||||
|
|
||||||
private final BytesWriteInterceptor bytesWriteInterceptor;
|
|
||||||
|
|
||||||
WriteInspector(BytesWriteInterceptor bytesWriteInterceptor) {
|
|
||||||
this.bytesWriteInterceptor = bytesWriteInterceptor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
|
||||||
/*Both these handlers always run in the same executor, so it's safe to access this variable.*/
|
|
||||||
bytesWriteInterceptor.messageReceived = false; /*reset flag for this write*/
|
|
||||||
ctx.write(msg, promise);
|
|
||||||
if (!bytesWriteInterceptor.messageReceived) {
|
|
||||||
bytesWriteInterceptor.requestMoreIfWritable(ctx.channel());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Regulates write->request more->write process on the channel.
|
|
||||||
*
|
|
||||||
* Why is this a separate handler?
|
|
||||||
* The sole purpose of this handler is to request more items from each of the Observable streams producing items to
|
|
||||||
* write. It is important to request more items only when the current item is written on the channel i.e. added to
|
|
||||||
* the ChannelOutboundBuffer. If we request more from outside the pipeline (from WriteStreamSubscriber.onNext())
|
|
||||||
* then it may so happen that the onNext is not from within this eventloop and hence instead of being written to
|
|
||||||
* the channel, is added to the task queue of the EventLoop. Requesting more items in such a case, would mean we
|
|
||||||
* keep adding the writes to the eventloop queue and not on the channel buffer. This would mean that the channel
|
|
||||||
* writability would not truly indicate the buffer.
|
|
||||||
*/
|
|
||||||
/*Visible for testing*/ static final class BytesWriteInterceptor extends ChannelDuplexHandler implements Runnable {
|
|
||||||
|
|
||||||
/*Visible for testing*/ static final String WRITE_INSPECTOR_HANDLER_NAME = "write-inspector";
|
|
||||||
/*Visible for testing*/ static final int MAX_PER_SUBSCRIBER_REQUEST = 64;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Since, unsubscribes can happen on a different thread, this has to be thread-safe.
|
|
||||||
*/
|
|
||||||
private final ConcurrentLinkedQueue<WriteStreamSubscriber> subscribers = new ConcurrentLinkedQueue<>();
|
|
||||||
private final String parentHandlerName;
|
|
||||||
|
|
||||||
/* This should always be access from the eventloop and can be used to manage state before and after a write to
|
|
||||||
* see if a write started from {@link WriteInspector} made it to this handler.
|
|
||||||
*/
|
|
||||||
private boolean messageReceived;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The intent here is to equally divide the request to all subscribers but do not put a hard-bound on whether
|
|
||||||
* the subscribers are actually adhering to the limit (by not throwing MissingBackpressureException). This keeps
|
|
||||||
* the request distribution simple and still give opprotunities for subscribers to optimize (increase the limit)
|
|
||||||
* if there is a signal that the consumption is slower than the producer.
|
|
||||||
*
|
|
||||||
* Worst case of this scheme is request-1 per subscriber which happens when there are as many subscribers as
|
|
||||||
* the max limit.
|
|
||||||
*/
|
|
||||||
private int perSubscriberMaxRequest = MAX_PER_SUBSCRIBER_REQUEST;
|
|
||||||
private Channel channel;
|
|
||||||
private boolean removeTaskScheduled; // Guarded by this
|
|
||||||
|
|
||||||
BytesWriteInterceptor(String parentHandlerName) {
|
|
||||||
this.parentHandlerName = parentHandlerName;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(ChannelHandlerContext ctx, final Object msg, ChannelPromise promise) throws Exception {
|
|
||||||
ctx.write(msg, promise);
|
|
||||||
messageReceived = true;
|
|
||||||
requestMoreIfWritable(ctx.channel());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
|
|
||||||
channel = ctx.channel();
|
|
||||||
WriteInspector writeInspector = new WriteInspector(this);
|
|
||||||
ChannelHandler parent = ctx.pipeline().get(parentHandlerName);
|
|
||||||
if (null != parent) {
|
|
||||||
ctx.pipeline().addBefore(parentHandlerName, WRITE_INSPECTOR_HANDLER_NAME, writeInspector);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
|
|
||||||
if (ctx.channel().isWritable()) {
|
|
||||||
requestMoreIfWritable(ctx.channel());
|
|
||||||
}
|
|
||||||
super.channelWritabilityChanged(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
public WriteStreamSubscriber newSubscriber(final ChannelHandlerContext ctx, ChannelPromise promise) {
|
|
||||||
int currentSubCount = subscribers.size();
|
|
||||||
recalculateMaxPerSubscriber(currentSubCount, currentSubCount + 1);
|
|
||||||
|
|
||||||
final WriteStreamSubscriber sub = new WriteStreamSubscriber(ctx, promise, perSubscriberMaxRequest);
|
|
||||||
sub.add(Subscriptions.create(new Action0() {
|
|
||||||
@Override
|
|
||||||
public void call() {
|
|
||||||
boolean _schedule;
|
|
||||||
/*Schedule the task once as the task runs through and removes all unsubscribed subscribers*/
|
|
||||||
synchronized (BytesWriteInterceptor.this) {
|
|
||||||
_schedule = !removeTaskScheduled;
|
|
||||||
removeTaskScheduled = true;
|
|
||||||
}
|
|
||||||
if (_schedule) {
|
|
||||||
ctx.channel().eventLoop().execute(BytesWriteInterceptor.this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
subscribers.add(sub);
|
|
||||||
return sub;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*Visible for testing*/List<WriteStreamSubscriber> getSubscribers() {
|
|
||||||
return Collections.unmodifiableList(new ArrayList<>(subscribers));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void requestMoreIfWritable(Channel channel) {
|
|
||||||
assert channel.eventLoop().inEventLoop();
|
|
||||||
|
|
||||||
for (WriteStreamSubscriber subscriber: subscribers) {
|
|
||||||
if (!subscriber.isUnsubscribed() && channel.isWritable()) {
|
|
||||||
subscriber.requestMoreIfNeeded(perSubscriberMaxRequest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
synchronized (this) {
|
|
||||||
removeTaskScheduled = false;
|
|
||||||
}
|
|
||||||
int oldSubCount = subscribers.size();
|
|
||||||
for (Iterator<WriteStreamSubscriber> iterator = subscribers.iterator(); iterator.hasNext(); ) {
|
|
||||||
WriteStreamSubscriber subscriber = iterator.next();
|
|
||||||
if (subscriber.isUnsubscribed()) {
|
|
||||||
iterator.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int newSubCount = subscribers.size();
|
|
||||||
recalculateMaxPerSubscriber(oldSubCount, newSubCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called from within the eventloop, whenever the subscriber queue is modified. This modifies the per subscriber
|
|
||||||
* request limit by equally distributing the demand. Minimum demand to any subscriber is 1.
|
|
||||||
*/
|
|
||||||
private void recalculateMaxPerSubscriber(int oldSubCount, int newSubCount) {
|
|
||||||
assert channel.eventLoop().inEventLoop();
|
|
||||||
perSubscriberMaxRequest = newSubCount == 0 || oldSubCount == 0
|
|
||||||
? MAX_PER_SUBSCRIBER_REQUEST
|
|
||||||
: perSubscriberMaxRequest * oldSubCount / newSubCount;
|
|
||||||
|
|
||||||
perSubscriberMaxRequest = Math.max(1, perSubscriberMaxRequest);
|
|
||||||
|
|
||||||
if (logger.isLoggable(Level.FINE)) {
|
|
||||||
logger.log(Level.FINE, "Channel " + channel +
|
|
||||||
" modifying per subscriber max request. Old subscribers count " + oldSubCount +
|
|
||||||
" new subscribers count " + newSubCount +
|
|
||||||
" new Value {} " + perSubscriberMaxRequest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Backpressure enabled subscriber to an Observable written on this channel. This connects the promise for writing
|
|
||||||
* the Observable to all the promises created per write (per onNext).
|
|
||||||
*/
|
|
||||||
/*Visible for testing*/static class WriteStreamSubscriber extends Subscriber<Object> {
|
|
||||||
|
|
||||||
private final ChannelHandlerContext ctx;
|
|
||||||
private final ChannelPromise overarchingWritePromise;
|
|
||||||
|
|
||||||
private final int initialRequest;
|
|
||||||
private long maxBufferSize;
|
|
||||||
private long pending; /*Guarded by guard*/
|
|
||||||
private long lowWaterMark;
|
|
||||||
|
|
||||||
private final Object guard = new Object();
|
|
||||||
private boolean isDone; /*Guarded by guard*/
|
|
||||||
private Scheduler.Worker writeWorker; /*Guarded by guard*/
|
|
||||||
private boolean atleastOneWriteEnqueued; /*Guarded by guard*/
|
|
||||||
private int enqueued; /*Guarded by guard*/
|
|
||||||
|
|
||||||
private boolean isPromiseCompletedOnWriteComplete; /*Guarded by guard. Only transition should be false->true*/
|
|
||||||
|
|
||||||
private int listeningTo;
|
|
||||||
|
|
||||||
/*Visible for testing*/ WriteStreamSubscriber(ChannelHandlerContext ctx, ChannelPromise promise,
|
|
||||||
int initialRequest) {
|
|
||||||
this.ctx = ctx;
|
|
||||||
overarchingWritePromise = promise;
|
|
||||||
this.initialRequest = initialRequest;
|
|
||||||
promise.addListener(new ChannelFutureListener() {
|
|
||||||
@Override
|
|
||||||
public void operationComplete(ChannelFuture future) throws Exception {
|
|
||||||
if (future.isCancelled()) {
|
|
||||||
unsubscribe(); /*Unsubscribe from source if the promise is cancelled.*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onStart() {
|
|
||||||
requestMoreIfNeeded(initialRequest);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCompleted() {
|
|
||||||
onTermination(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(Throwable e) {
|
|
||||||
onTermination(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNext(Object nextItem) {
|
|
||||||
final boolean enqueue;
|
|
||||||
boolean inEL = ctx.channel().eventLoop().inEventLoop();
|
|
||||||
|
|
||||||
synchronized (guard) {
|
|
||||||
pending--;
|
|
||||||
if (null == writeWorker) {
|
|
||||||
if (!inEL) {
|
|
||||||
atleastOneWriteEnqueued = true;
|
|
||||||
}
|
|
||||||
if (atleastOneWriteEnqueued) {
|
|
||||||
writeWorker = Schedulers.computation().createWorker();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enqueue = null != writeWorker && (inEL || enqueued > 0);
|
|
||||||
|
|
||||||
if (enqueue) {
|
|
||||||
enqueued++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final ChannelFuture channelFuture = enqueue ? enqueueWrite(nextItem) : ctx.write(nextItem);
|
|
||||||
|
|
||||||
synchronized (guard) {
|
|
||||||
listeningTo++;
|
|
||||||
}
|
|
||||||
|
|
||||||
channelFuture.addListener(new ChannelFutureListener() {
|
|
||||||
@Override
|
|
||||||
public void operationComplete(ChannelFuture future) throws Exception {
|
|
||||||
|
|
||||||
if (overarchingWritePromise.isDone()) {
|
|
||||||
/*
|
|
||||||
* Overarching promise will be done if and only if there was an error or all futures have
|
|
||||||
* completed. In both cases, this callback is useless, hence return from here.
|
|
||||||
* IOW, if we are here, it can be two cases:
|
|
||||||
*
|
|
||||||
* - There has already been a write that has failed. So, the promise is done with failure.
|
|
||||||
* - There was a write that arrived after termination of the Observable.
|
|
||||||
*
|
|
||||||
* Two above isn't possible as per Rx contract.
|
|
||||||
* One above is possible but is not of any consequence w.r.t this listener as this listener does
|
|
||||||
* not give callbacks to specific writes
|
|
||||||
*/
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean _isPromiseCompletedOnWriteComplete;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The intent here is to NOT give listener callbacks via promise completion within the sync block.
|
|
||||||
* So, a co-ordination b/w the thread sending Observable terminal event and thread sending write
|
|
||||||
* completion event is required.
|
|
||||||
* The only work to be done in the Observable terminal event thread is to whether the
|
|
||||||
* overarchingWritePromise is to be completed or not.
|
|
||||||
* The errors are not buffered, so the overarchingWritePromise is completed in this callback w/o
|
|
||||||
* knowing whether any more writes will arive or not.
|
|
||||||
* This co-oridantion is done via the flag isPromiseCompletedOnWriteComplete
|
|
||||||
*/
|
|
||||||
synchronized (guard) {
|
|
||||||
listeningTo--;
|
|
||||||
if (0 == listeningTo && isDone) {
|
|
||||||
/*
|
|
||||||
* If the listening count is 0 and no more items will arrive, this thread wins the race of
|
|
||||||
* completing the overarchingWritePromise
|
|
||||||
*/
|
|
||||||
isPromiseCompletedOnWriteComplete = true;
|
|
||||||
}
|
|
||||||
_isPromiseCompletedOnWriteComplete = isPromiseCompletedOnWriteComplete;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Exceptions are not buffered but completion is only sent when there are no more items to be
|
|
||||||
* received for write.
|
|
||||||
*/
|
|
||||||
if (!future.isSuccess()) {
|
|
||||||
overarchingWritePromise.tryFailure(future.cause());
|
|
||||||
/*
|
|
||||||
* Unsubscribe this subscriber when write fails as we are completing the promise which is
|
|
||||||
* attached to the listener of the write results.
|
|
||||||
*/
|
|
||||||
unsubscribe();
|
|
||||||
} else if (_isPromiseCompletedOnWriteComplete) { /*Once set to true, never goes back to false.*/
|
|
||||||
/*Complete only when no more items will arrive and all writes are completed*/
|
|
||||||
overarchingWritePromise.trySuccess();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private ChannelFuture enqueueWrite(final Object nextItem) {
|
|
||||||
final ChannelPromise toReturn = ctx.channel().newPromise();
|
|
||||||
writeWorker.schedule(new Action0() {
|
|
||||||
@Override
|
|
||||||
public void call() {
|
|
||||||
ctx.write(nextItem, toReturn);
|
|
||||||
synchronized (guard) {
|
|
||||||
enqueued--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return toReturn;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onTermination(Throwable throwableIfAny) {
|
|
||||||
int _listeningTo;
|
|
||||||
boolean _shouldCompletePromise;
|
|
||||||
final boolean enqueueFlush;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The intent here is to NOT give listener callbacks via promise completion within the sync block.
|
|
||||||
* So, a co-ordination b/w the thread sending Observable terminal event and thread sending write
|
|
||||||
* completion event is required.
|
|
||||||
* The only work to be done in the Observable terminal event thread is to whether the
|
|
||||||
* overarchingWritePromise is to be completed or not.
|
|
||||||
* The errors are not buffered, so the overarchingWritePromise is completed in this callback w/o
|
|
||||||
* knowing whether any more writes will arive or not.
|
|
||||||
* This co-oridantion is done via the flag isPromiseCompletedOnWriteComplete
|
|
||||||
*/
|
|
||||||
synchronized (guard) {
|
|
||||||
enqueueFlush = atleastOneWriteEnqueued;
|
|
||||||
isDone = true;
|
|
||||||
_listeningTo = listeningTo;
|
|
||||||
/*
|
|
||||||
* Flag to indicate whether the write complete thread won the race and will complete the
|
|
||||||
* overarchingWritePromise
|
|
||||||
*/
|
|
||||||
_shouldCompletePromise = 0 == _listeningTo && !isPromiseCompletedOnWriteComplete;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (enqueueFlush) {
|
|
||||||
writeWorker.schedule(new Action0() {
|
|
||||||
@Override
|
|
||||||
public void call() {
|
|
||||||
ctx.flush();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (null != throwableIfAny) {
|
|
||||||
overarchingWritePromise.tryFailure(throwableIfAny);
|
|
||||||
} else {
|
|
||||||
if (_shouldCompletePromise) {
|
|
||||||
overarchingWritePromise.trySuccess();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Signals this subscriber to request more data from upstream, optionally modifying the max buffer size or max
|
|
||||||
* requests upstream. This will request more either if the new buffer size is greater than existing or pending
|
|
||||||
* items from upstream are less than the low water mark (which is half the max size).
|
|
||||||
*
|
|
||||||
* @param newMaxBufferSize New max buffer size, ignored if it is the same as existing.
|
|
||||||
*/
|
|
||||||
/*Visible for testing*/void requestMoreIfNeeded(long newMaxBufferSize) {
|
|
||||||
long toRequest = 0;
|
|
||||||
|
|
||||||
synchronized (guard) {
|
|
||||||
if (newMaxBufferSize > maxBufferSize) {
|
|
||||||
// Applicable only when request up is not triggered by pending < lowWaterMark.
|
|
||||||
toRequest = newMaxBufferSize - maxBufferSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
maxBufferSize = newMaxBufferSize;
|
|
||||||
lowWaterMark = maxBufferSize / 2;
|
|
||||||
|
|
||||||
if (pending < lowWaterMark) {
|
|
||||||
// Intentionally overwrites the existing toRequest as this includes all required changes.
|
|
||||||
toRequest = maxBufferSize - pending;
|
|
||||||
}
|
|
||||||
|
|
||||||
pending += toRequest;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (toRequest > 0) {
|
|
||||||
request(toRequest);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
|
||||||
public void subscribeTo(Observable observable) {
|
|
||||||
observable.subscribe(this); /*Need safe subscription as this is the subscriber and not a sub passed in*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,99 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.channel;
|
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
|
||||||
import io.netty.buffer.ByteBufHolder;
|
|
||||||
import io.netty.channel.ChannelDuplexHandler;
|
|
||||||
import io.netty.channel.ChannelFuture;
|
|
||||||
import io.netty.channel.ChannelFutureListener;
|
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
|
||||||
import io.netty.channel.ChannelPromise;
|
|
||||||
import io.netty.channel.FileRegion;
|
|
||||||
import io.reactivex.netty.channel.events.ConnectionEventListener;
|
|
||||||
import io.reactivex.netty.events.EventPublisher;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
public class BytesInspector extends ChannelDuplexHandler {
|
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(BytesInspector.class.getName());
|
|
||||||
|
|
||||||
private final ConnectionEventListener eventListener;
|
|
||||||
private final EventPublisher eventPublisher;
|
|
||||||
|
|
||||||
public BytesInspector(EventPublisher eventPublisher, ConnectionEventListener eventListener) {
|
|
||||||
this.eventPublisher = eventPublisher;
|
|
||||||
this.eventListener = eventListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
|
||||||
try {
|
|
||||||
if (ByteBuf.class.isAssignableFrom(msg.getClass())) {
|
|
||||||
publishBytesRead((ByteBuf) msg);
|
|
||||||
} else if (ByteBufHolder.class.isAssignableFrom(msg.getClass())) {
|
|
||||||
ByteBufHolder holder = (ByteBufHolder) msg;
|
|
||||||
publishBytesRead(holder.content());
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.log(Level.WARNING, "Failed to publish bytes read metrics event. This does *not* stop the pipeline processing.", e);
|
|
||||||
} finally {
|
|
||||||
super.channelRead(ctx, msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
|
||||||
try {
|
|
||||||
if (ByteBuf.class.isAssignableFrom(msg.getClass())) {
|
|
||||||
publishBytesWritten(((ByteBuf) msg).readableBytes(), promise);
|
|
||||||
} else if (ByteBufHolder.class.isAssignableFrom(msg.getClass())) {
|
|
||||||
publishBytesWritten(((ByteBufHolder)msg).content().readableBytes(), promise);
|
|
||||||
} else if (FileRegion.class.isAssignableFrom(msg.getClass())) {
|
|
||||||
publishBytesWritten(((FileRegion) msg).count(), promise);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.log(Level.WARNING, "Failed to publish bytes write metrics event. This does *not* stop the pipeline processing.", e);
|
|
||||||
} finally {
|
|
||||||
super.write(ctx, msg, promise);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
protected void publishBytesWritten(final long bytesToWrite, ChannelPromise promise) {
|
|
||||||
if (bytesToWrite <= 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (eventPublisher.publishingEnabled()) {
|
|
||||||
promise.addListener(new ChannelFutureListener() {
|
|
||||||
@Override
|
|
||||||
public void operationComplete(ChannelFuture future) throws Exception {
|
|
||||||
eventListener.onByteWritten(bytesToWrite);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
protected void publishBytesRead(ByteBuf byteBuf) {
|
|
||||||
if (null != byteBuf) {
|
|
||||||
eventListener.onByteRead(byteBuf.readableBytes());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,277 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.channel;
|
|
||||||
|
|
||||||
import io.netty.channel.Channel;
|
|
||||||
import io.netty.channel.FileRegion;
|
|
||||||
import io.netty.util.AttributeKey;
|
|
||||||
import rx.Observable;
|
|
||||||
import rx.functions.Func1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A list of user initiated operations that can be done on a channel.
|
|
||||||
*
|
|
||||||
* @param <W> Type of data that can be written on the associated channel.
|
|
||||||
*/
|
|
||||||
public interface ChannelOperations<W> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flush selector that always returns true.
|
|
||||||
*/
|
|
||||||
Func1<String, Boolean> FLUSH_ON_EACH_STRING = new Func1<String, Boolean>() {
|
|
||||||
@Override
|
|
||||||
public Boolean call(String next) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flush selector that always returns true.
|
|
||||||
*/
|
|
||||||
Func1<byte[], Boolean> FLUSH_ON_EACH_BYTES = new Func1<byte[], Boolean>() {
|
|
||||||
@Override
|
|
||||||
public Boolean call(byte[] next) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flush selector that always returns true.
|
|
||||||
*/
|
|
||||||
Func1<FileRegion, Boolean> FLUSH_ON_EACH_FILE_REGION = new Func1<FileRegion, Boolean>() {
|
|
||||||
@Override
|
|
||||||
public Boolean call(FileRegion next) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
AttributeKey<Boolean> FLUSH_ONLY_ON_READ_COMPLETE =
|
|
||||||
AttributeKey.valueOf("_rxnetyy-flush-only-on-read-complete");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On subscription of the returned {@link Observable}, writes the passed message stream on the underneath channel.
|
|
||||||
*
|
|
||||||
* All writes will be flushed on completion of the passed {@code Observable}
|
|
||||||
*
|
|
||||||
* @param msgs Stream of messages to write.
|
|
||||||
*
|
|
||||||
* @return {@link Observable} representing the result of this write. Every subscription to this {@link Observable}
|
|
||||||
* will replay the write on the channel.
|
|
||||||
*/
|
|
||||||
Observable<Void> write(Observable<W> msgs);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On subscription of the returned {@link Observable}, writes the passed message stream on the underneath channel
|
|
||||||
* and flushes the channel, everytime, {@code flushSelector} returns {@code true} . Any writes issued before
|
|
||||||
* subscribing, will also be flushed. However, the returned {@link Observable} will not capture the result of those
|
|
||||||
* writes, i.e. if the other writes, fail and this write does not, the returned {@link Observable} will not fail.
|
|
||||||
*
|
|
||||||
* @param msgs Message stream to write.
|
|
||||||
* @param flushSelector A {@link Func1} which is invoked for every item emitted from {@code msgs}. Channel is
|
|
||||||
* flushed, iff this function returns, {@code true}.
|
|
||||||
*
|
|
||||||
* @return An {@link Observable} representing the result of this write. Every
|
|
||||||
* subscription to this {@link Observable} will write the passed messages and flush all pending writes, when the
|
|
||||||
* {@code flushSelector} returns {@code true}
|
|
||||||
*/
|
|
||||||
Observable<Void> write(Observable<W> msgs, Func1<W, Boolean> flushSelector);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On subscription of the returned {@link Observable}, writes the passed message stream on the underneath channel
|
|
||||||
* and flushes the channel, on every write. Any writes issued before subscribing, will also be flushed. However, the
|
|
||||||
* returned {@link Observable} will not capture the result of those writes, i.e. if the other writes, fail and this
|
|
||||||
* write does not, the returned {@link Observable} will not fail.
|
|
||||||
*
|
|
||||||
* @param msgs Message stream to write.
|
|
||||||
*
|
|
||||||
* @return An {@link Observable} representing the result of this write. Every
|
|
||||||
* subscription to this {@link Observable} will write the passed messages and flush all pending writes, on every
|
|
||||||
* write.
|
|
||||||
*/
|
|
||||||
Observable<Void> writeAndFlushOnEach(Observable<W> msgs);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On subscription of the returned {@link Observable}, writes the passed message stream on the underneath channel.
|
|
||||||
*
|
|
||||||
* All writes will be flushed on completion of the passed {@code Observable}
|
|
||||||
*
|
|
||||||
* @param msgs Stream of messages to write.
|
|
||||||
*
|
|
||||||
* @return {@link Observable} representing the result of this write. Every subscription to this {@link Observable}
|
|
||||||
* will replay the write on the channel.
|
|
||||||
*/
|
|
||||||
Observable<Void> writeString(Observable<String> msgs);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On subscription of the returned {@link Observable}, writes the passed message stream on the underneath channel
|
|
||||||
* and flushes the channel, everytime, {@code flushSelector} returns {@code true} . Any writes issued before
|
|
||||||
* subscribing, will also be flushed. However, the returned {@link Observable} will not capture the result of those
|
|
||||||
* writes, i.e. if the other writes, fail and this write does not, the returned {@link Observable} will not fail.
|
|
||||||
*
|
|
||||||
* @param msgs Message stream to write.
|
|
||||||
* @param flushSelector A {@link Func1} which is invoked for every item emitted from {@code msgs}. Channel is
|
|
||||||
* flushed, iff this function returns, {@code true}.
|
|
||||||
*
|
|
||||||
* @return An {@link Observable} representing the result of this write. Every
|
|
||||||
* subscription to this {@link Observable} will write the passed messages and flush all pending writes, when the
|
|
||||||
* {@code flushSelector} returns {@code true}
|
|
||||||
*/
|
|
||||||
Observable<Void> writeString(Observable<String> msgs, Func1<String, Boolean> flushSelector);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On subscription of the returned {@link Observable}, writes the passed message stream on the underneath channel
|
|
||||||
* and flushes the channel, on every write. Any writes issued before subscribing, will also be flushed. However, the
|
|
||||||
* returned {@link Observable} will not capture the result of those writes, i.e. if the other writes, fail and this
|
|
||||||
* write does not, the returned {@link Observable} will not fail.
|
|
||||||
*
|
|
||||||
* @param msgs Message stream to write.
|
|
||||||
*
|
|
||||||
* @return An {@link Observable} representing the result of this write. Every
|
|
||||||
* subscription to this {@link Observable} will write the passed messages and flush all pending writes, on every
|
|
||||||
* write.
|
|
||||||
*/
|
|
||||||
Observable<Void> writeStringAndFlushOnEach(Observable<String> msgs);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On subscription of the returned {@link Observable}, writes the passed message stream on the underneath channel.
|
|
||||||
*
|
|
||||||
* All writes will be flushed on completion of the passed {@code Observable}
|
|
||||||
*
|
|
||||||
* @param msgs Stream of messages to write.
|
|
||||||
*
|
|
||||||
* @return {@link Observable} representing the result of this write. Every subscription to this {@link Observable}
|
|
||||||
* will replay the write on the channel.
|
|
||||||
*/
|
|
||||||
Observable<Void> writeBytes(Observable<byte[]> msgs);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On subscription of the returned {@link Observable}, writes the passed message stream on the underneath channel
|
|
||||||
* and flushes the channel, everytime, {@code flushSelector} returns {@code true} . Any writes issued before
|
|
||||||
* subscribing, will also be flushed. However, the returned {@link Observable} will not capture the result of those
|
|
||||||
* writes, i.e. if the other writes, fail and this write does not, the returned {@link Observable} will not fail.
|
|
||||||
*
|
|
||||||
* @param msgs Message stream to write.
|
|
||||||
* @param flushSelector A {@link Func1} which is invoked for every item emitted from {@code msgs}. Channel is
|
|
||||||
* flushed, iff this function returns, {@code true}.
|
|
||||||
*
|
|
||||||
* @return An {@link Observable} representing the result of this write. Every
|
|
||||||
* subscription to this {@link Observable} will write the passed messages and flush all pending writes, when the
|
|
||||||
* {@code flushSelector} returns {@code true}
|
|
||||||
*/
|
|
||||||
Observable<Void> writeBytes(Observable<byte[]> msgs, Func1<byte[], Boolean> flushSelector);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On subscription of the returned {@link Observable}, writes the passed message stream on the underneath channel
|
|
||||||
* and flushes the channel, on every write. Any writes issued before subscribing, will also be flushed. However, the
|
|
||||||
* returned {@link Observable} will not capture the result of those writes, i.e. if the other writes, fail and this
|
|
||||||
* write does not, the returned {@link Observable} will not fail.
|
|
||||||
*
|
|
||||||
* @param msgs Message stream to write.
|
|
||||||
*
|
|
||||||
* @return An {@link Observable} representing the result of this write. Every
|
|
||||||
* subscription to this {@link Observable} will write the passed messages and flush all pending writes, on every
|
|
||||||
* write.
|
|
||||||
*/
|
|
||||||
Observable<Void> writeBytesAndFlushOnEach(Observable<byte[]> msgs);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On subscription of the returned {@link Observable}, writes the passed message stream on the underneath channel.
|
|
||||||
*
|
|
||||||
* All writes will be flushed on completion of the passed {@code Observable}
|
|
||||||
*
|
|
||||||
* @param msgs Stream of messages to write.
|
|
||||||
*
|
|
||||||
* @return {@link Observable} representing the result of this write. Every subscription to this {@link Observable}
|
|
||||||
* will replay the write on the channel.
|
|
||||||
*/
|
|
||||||
Observable<Void> writeFileRegion(Observable<FileRegion> msgs);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On subscription of the returned {@link Observable}, writes the passed message stream on the underneath channel
|
|
||||||
* and flushes the channel, everytime, {@code flushSelector} returns {@code true} . Any writes issued before
|
|
||||||
* subscribing, will also be flushed. However, the returned {@link Observable} will not capture the result of those
|
|
||||||
* writes, i.e. if the other writes, fail and this write does not, the returned {@link Observable} will not fail.
|
|
||||||
*
|
|
||||||
* @param msgs Message stream to write.
|
|
||||||
* @param flushSelector A {@link Func1} which is invoked for every item emitted from {@code msgs}. Channel is
|
|
||||||
* flushed, iff this function returns, {@code true}.
|
|
||||||
*
|
|
||||||
* @return An {@link Observable} representing the result of this write. Every
|
|
||||||
* subscription to this {@link Observable} will write the passed messages and flush all pending writes, when the
|
|
||||||
* {@code flushSelector} returns {@code true}
|
|
||||||
*/
|
|
||||||
Observable<Void> writeFileRegion(Observable<FileRegion> msgs, Func1<FileRegion, Boolean> flushSelector);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* On subscription of the returned {@link Observable}, writes the passed message stream on the underneath channel
|
|
||||||
* and flushes the channel, on every write. Any writes issued before subscribing, will also be flushed. However, the
|
|
||||||
* returned {@link Observable} will not capture the result of those writes, i.e. if the other writes, fail and this
|
|
||||||
* write does not, the returned {@link Observable} will not fail.
|
|
||||||
*
|
|
||||||
* @param msgs Message stream to write.
|
|
||||||
*
|
|
||||||
* @return An {@link Observable} representing the result of this write. Every
|
|
||||||
* subscription to this {@link Observable} will write the passed messages and flush all pending writes, on every
|
|
||||||
* write.
|
|
||||||
*/
|
|
||||||
Observable<Void> writeFileRegionAndFlushOnEach(Observable<FileRegion> msgs);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Modifies the underneath channel to enable writing new type of objects that will be transformed using the passed
|
|
||||||
* {@code transformer}
|
|
||||||
*
|
|
||||||
* @param transformer Transformer to transform objects written to the channel.
|
|
||||||
*
|
|
||||||
* @param <WW> The target type of the transformer.
|
|
||||||
*
|
|
||||||
* @return A new instance of {@code ChannelOperations} that accepts the transformed type to write.
|
|
||||||
*/
|
|
||||||
<WW> ChannelOperations<WW> transformWrite(AllocatingTransformer<WW, W> transformer);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flushes any pending writes on this connection by calling {@link Channel#flush()}. This can be used for
|
|
||||||
* implementing any custom flusing strategies that otherwise can not be implemented by methods like
|
|
||||||
* {@link #write(Observable, Func1)}.
|
|
||||||
*/
|
|
||||||
void flush();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flushes any pending writes and closes the connection. Same as calling {@code close(true)}
|
|
||||||
*
|
|
||||||
* @return {@link Observable} representing the result of close.
|
|
||||||
*/
|
|
||||||
Observable<Void> close();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes this channel after flushing all pending writes.
|
|
||||||
*
|
|
||||||
* @return {@link Observable} representing the result of close and flush.
|
|
||||||
*/
|
|
||||||
Observable<Void> close(boolean flush);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the connection immediately. Same as calling {@link #close()} and subscribing to the returned
|
|
||||||
* {@code Observable}
|
|
||||||
*/
|
|
||||||
void closeNow();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an {@link Observable} that completes when this connection is closed.
|
|
||||||
*
|
|
||||||
* @return An {@link Observable} that completes when this connection is closed.
|
|
||||||
*/
|
|
||||||
Observable<Void> closeListener();
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.channel;
|
|
||||||
|
|
||||||
import io.netty.channel.Channel;
|
|
||||||
import rx.Subscriber;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An event to communicate the subscriber of a new channel created by {@link AbstractConnectionToChannelBridge}.
|
|
||||||
*
|
|
||||||
* <h2>Connection reuse</h2>
|
|
||||||
*
|
|
||||||
* For cases, where the {@link Connection} is pooled, reuse should be indicated explicitly via
|
|
||||||
* {@link ConnectionInputSubscriberResetEvent}. There can be multiple {@link ConnectionInputSubscriberResetEvent}s
|
|
||||||
* sent to the same channel and hence the same instance of {@link AbstractConnectionToChannelBridge}.
|
|
||||||
*
|
|
||||||
* @param <R> Type read from the connection held by the event.
|
|
||||||
* @param <W> Type written to the connection held by the event.
|
|
||||||
*/
|
|
||||||
public class ChannelSubscriberEvent<R, W> {
|
|
||||||
|
|
||||||
private final Subscriber<? super Channel> subscriber;
|
|
||||||
|
|
||||||
public ChannelSubscriberEvent(Subscriber<? super Channel> subscriber) {
|
|
||||||
this.subscriber = subscriber;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Subscriber<? super Channel> getSubscriber() {
|
|
||||||
return subscriber;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,314 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.channel;
|
|
||||||
|
|
||||||
import io.netty.channel.Channel;
|
|
||||||
import io.netty.channel.ChannelFuture;
|
|
||||||
import io.netty.channel.ChannelFutureListener;
|
|
||||||
import io.netty.channel.ChannelHandler;
|
|
||||||
import io.netty.channel.ChannelOption;
|
|
||||||
import io.netty.channel.ChannelPipeline;
|
|
||||||
import io.netty.util.AttributeKey;
|
|
||||||
import io.netty.util.ReferenceCountUtil;
|
|
||||||
import io.netty.util.concurrent.EventExecutorGroup;
|
|
||||||
import rx.Observable;
|
|
||||||
import rx.Observable.Transformer;
|
|
||||||
import rx.functions.Action1;
|
|
||||||
import rx.functions.Func1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An abstraction over netty's channel providing Rx APIs.
|
|
||||||
*
|
|
||||||
* <h2>Reading data</h2>
|
|
||||||
*
|
|
||||||
* Unless, {@link ChannelOption#AUTO_READ} is set to {@code true} on the underneath channel, data will be read from the
|
|
||||||
* connection if and only if there is a subscription to the input stream returned by {@link #getInput()}.
|
|
||||||
* In case, the input data is not required to be consumed, one should call {@link #ignoreInput()}, otherwise, data will
|
|
||||||
* never be read from the channel.
|
|
||||||
*
|
|
||||||
* @param <R> Type of object that is read from this connection.
|
|
||||||
* @param <W> Type of object that is written to this connection.
|
|
||||||
*/
|
|
||||||
public abstract class Connection<R, W> implements ChannelOperations<W> {
|
|
||||||
|
|
||||||
public static final AttributeKey<Connection> CONNECTION_ATTRIBUTE_KEY = AttributeKey.valueOf("rx-netty-conn-attr");
|
|
||||||
|
|
||||||
private final Channel nettyChannel;
|
|
||||||
private final ContentSource<R> contentSource;
|
|
||||||
protected final MarkAwarePipeline markAwarePipeline;
|
|
||||||
|
|
||||||
protected Connection(final Channel nettyChannel) {
|
|
||||||
if (null == nettyChannel) {
|
|
||||||
throw new IllegalArgumentException("Channel can not be null");
|
|
||||||
}
|
|
||||||
this.nettyChannel = nettyChannel;
|
|
||||||
markAwarePipeline = new MarkAwarePipeline(nettyChannel.pipeline());
|
|
||||||
contentSource = new ContentSource<>(nettyChannel, ConnectionInputSubscriberEvent::new);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Connection(Connection<R, W> toCopy) {
|
|
||||||
nettyChannel = toCopy.nettyChannel;
|
|
||||||
markAwarePipeline = toCopy.markAwarePipeline;
|
|
||||||
contentSource = toCopy.contentSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Connection(Connection<?, ?> toCopy, ContentSource<R> contentSource) {
|
|
||||||
nettyChannel = toCopy.nettyChannel;
|
|
||||||
markAwarePipeline = toCopy.markAwarePipeline;
|
|
||||||
this.contentSource = contentSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a stream of data that is read from the connection.
|
|
||||||
*
|
|
||||||
* Unless, {@link ChannelOption#AUTO_READ} is set to {@code true}, the content will only be read from the
|
|
||||||
* underneath channel, if there is a subscriber to the input.
|
|
||||||
* In case, input is not required to be read, call {@link #ignoreInput()}
|
|
||||||
*
|
|
||||||
* @return The stream of data that is read from the connection.
|
|
||||||
*/
|
|
||||||
public ContentSource<R> getInput() {
|
|
||||||
return contentSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ignores all input on this connection.
|
|
||||||
*
|
|
||||||
* Unless, {@link ChannelOption#AUTO_READ} is set to {@code true}, the content will only be read from the
|
|
||||||
* underneath channel, if there is a subscriber to the input. So, upon recieving this connection, either one should
|
|
||||||
* call this method or eventually subscribe to the stream returned by {@link #getInput()}
|
|
||||||
*
|
|
||||||
* @return An {@link Observable}, subscription to which will discard the input. This {@code Observable} will
|
|
||||||
* error/complete when the input errors/completes and unsubscription from here will unsubscribe from the content.
|
|
||||||
*/
|
|
||||||
public Observable<Void> ignoreInput() {
|
|
||||||
return getInput().map(new Func1<R, Void>() {
|
|
||||||
@Override
|
|
||||||
public Void call(R r) {
|
|
||||||
ReferenceCountUtil.release(r);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}).ignoreElements();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a {@link ChannelHandler} to {@link ChannelPipeline} for this connection. The specified handler is added at
|
|
||||||
* the first position of the pipeline as specified by {@link ChannelPipeline#addFirst(String, ChannelHandler)}
|
|
||||||
*
|
|
||||||
* <em>For better flexibility of pipeline modification, the method {@link #pipelineConfigurator(Action1)} will be
|
|
||||||
* more convenient.</em>
|
|
||||||
*
|
|
||||||
* @param name Name of the handler.
|
|
||||||
* @param handler Handler instance to add.
|
|
||||||
*
|
|
||||||
* @return {@code this}.
|
|
||||||
*/
|
|
||||||
public abstract <RR, WW> Connection<RR, WW> addChannelHandlerFirst(String name, ChannelHandler handler);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a {@link ChannelHandler} to {@link ChannelPipeline} for this connection. The specified handler is added at
|
|
||||||
* the first position of the pipeline as specified by
|
|
||||||
* {@link ChannelPipeline#addFirst(EventExecutorGroup, String, ChannelHandler)}
|
|
||||||
*
|
|
||||||
* <em>For better flexibility of pipeline modification, the method {@link #pipelineConfigurator(Action1)} will be
|
|
||||||
* more convenient.</em>
|
|
||||||
*
|
|
||||||
* @param group the {@link EventExecutorGroup} which will be used to execute the {@link ChannelHandler} methods
|
|
||||||
* @param name the name of the handler to append
|
|
||||||
* @param handler Handler instance to add.
|
|
||||||
*
|
|
||||||
* @return {@code this}.
|
|
||||||
*/
|
|
||||||
public abstract <RR, WW> Connection<RR, WW> addChannelHandlerFirst(EventExecutorGroup group, String name,
|
|
||||||
ChannelHandler handler);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a {@link ChannelHandler} to {@link ChannelPipeline} for this connection. The specified handler is added at
|
|
||||||
* the last position of the pipeline as specified by {@link ChannelPipeline#addLast(String, ChannelHandler)}
|
|
||||||
*
|
|
||||||
* <em>For better flexibility of pipeline modification, the method {@link #pipelineConfigurator(Action1)} will be
|
|
||||||
* more convenient.</em>
|
|
||||||
*
|
|
||||||
* @param name Name of the handler.
|
|
||||||
* @param handler Handler instance to add.
|
|
||||||
*
|
|
||||||
* @return {@code this}.
|
|
||||||
*/
|
|
||||||
public abstract <RR, WW> Connection<RR, WW> addChannelHandlerLast(String name, ChannelHandler handler);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a {@link ChannelHandler} to {@link ChannelPipeline} for this connection. The specified handler is added at
|
|
||||||
* the last position of the pipeline as specified by
|
|
||||||
* {@link ChannelPipeline#addLast(EventExecutorGroup, String, ChannelHandler)}
|
|
||||||
*
|
|
||||||
* <em>For better flexibility of pipeline modification, the method {@link #pipelineConfigurator(Action1)} will be
|
|
||||||
* more convenient.</em>
|
|
||||||
*
|
|
||||||
* @param group the {@link EventExecutorGroup} which will be used to execute the {@link ChannelHandler} methods
|
|
||||||
* @param name the name of the handler to append
|
|
||||||
* @param handler Handler instance to add.
|
|
||||||
*
|
|
||||||
* @return {@code this}.
|
|
||||||
*/
|
|
||||||
public abstract <RR, WW> Connection<RR, WW> addChannelHandlerLast(EventExecutorGroup group, String name,
|
|
||||||
ChannelHandler handler);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a {@link ChannelHandler} to {@link ChannelPipeline} for this connection. The specified
|
|
||||||
* handler is added before an existing handler with the passed {@code baseName} in the pipeline as specified by
|
|
||||||
* {@link ChannelPipeline#addBefore(String, String, ChannelHandler)}
|
|
||||||
*
|
|
||||||
* <em>For better flexibility of pipeline modification, the method {@link #pipelineConfigurator(Action1)} will be
|
|
||||||
* more convenient.</em>
|
|
||||||
*
|
|
||||||
* @param baseName the name of the existing handler
|
|
||||||
* @param name Name of the handler.
|
|
||||||
* @param handler Handler instance to add.
|
|
||||||
*
|
|
||||||
* @return {@code this}.
|
|
||||||
*/
|
|
||||||
public abstract <RR, WW> Connection<RR, WW> addChannelHandlerBefore(String baseName, String name,
|
|
||||||
ChannelHandler handler);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a {@link ChannelHandler} to {@link ChannelPipeline} for this connection. The specified
|
|
||||||
* handler is added before an existing handler with the passed {@code baseName} in the pipeline as specified by
|
|
||||||
* {@link ChannelPipeline#addBefore(EventExecutorGroup, String, String, ChannelHandler)}
|
|
||||||
*
|
|
||||||
* <em>For better flexibility of pipeline modification, the method {@link #pipelineConfigurator(Action1)} will be
|
|
||||||
* more convenient.</em>
|
|
||||||
*
|
|
||||||
* @param group the {@link EventExecutorGroup} which will be used to execute the {@link ChannelHandler}
|
|
||||||
* methods
|
|
||||||
* @param baseName the name of the existing handler
|
|
||||||
* @param name the name of the handler to append
|
|
||||||
* @param handler Handler instance to add.
|
|
||||||
*
|
|
||||||
* @return {@code this}.
|
|
||||||
*/
|
|
||||||
public abstract <RR, WW> Connection<RR, WW> addChannelHandlerBefore(EventExecutorGroup group, String baseName,
|
|
||||||
String name, ChannelHandler handler);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a {@link ChannelHandler} to {@link ChannelPipeline} for this connection. The specified
|
|
||||||
* handler is added after an existing handler with the passed {@code baseName} in the pipeline as specified by
|
|
||||||
* {@link ChannelPipeline#addAfter(String, String, ChannelHandler)}
|
|
||||||
*
|
|
||||||
* <em>For better flexibility of pipeline modification, the method {@link #pipelineConfigurator(Action1)} will be
|
|
||||||
* more convenient.</em>
|
|
||||||
*
|
|
||||||
* @param baseName the name of the existing handler
|
|
||||||
* @param name Name of the handler.
|
|
||||||
* @param handler Handler instance to add.
|
|
||||||
*
|
|
||||||
* @return {@code this}.
|
|
||||||
*/
|
|
||||||
public abstract <RR, WW> Connection<RR, WW> addChannelHandlerAfter(String baseName, String name,
|
|
||||||
ChannelHandler handler);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a {@link ChannelHandler} to {@link ChannelPipeline} for this connection. The specified
|
|
||||||
* handler is added after an existing handler with the passed {@code baseName} in the pipeline as specified by
|
|
||||||
* {@link ChannelPipeline#addAfter(EventExecutorGroup, String, String, ChannelHandler)}
|
|
||||||
*
|
|
||||||
* <em>For better flexibility of pipeline modification, the method {@link #pipelineConfigurator(Action1)} will be
|
|
||||||
* more convenient.</em>
|
|
||||||
*
|
|
||||||
* @param group the {@link EventExecutorGroup} which will be used to execute the {@link ChannelHandler} methods
|
|
||||||
* @param baseName the name of the existing handler
|
|
||||||
* @param name the name of the handler to append
|
|
||||||
* @param handler Handler instance to add.
|
|
||||||
*
|
|
||||||
* @return {@code this}.
|
|
||||||
*/
|
|
||||||
public abstract <RR, WW> Connection<RR, WW> addChannelHandlerAfter(EventExecutorGroup group, String baseName,
|
|
||||||
String name, ChannelHandler handler);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configures the {@link ChannelPipeline} for this channel, using the passed {@code pipelineConfigurator}.
|
|
||||||
*
|
|
||||||
* @param pipelineConfigurator Action to configure {@link ChannelPipeline}.
|
|
||||||
*
|
|
||||||
* @return {@code this}.
|
|
||||||
*/
|
|
||||||
public abstract <RR, WW> Connection<RR, WW> pipelineConfigurator(Action1<ChannelPipeline> pipelineConfigurator);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transforms this connection's input stream using the passed {@code transformer} to create a new
|
|
||||||
* {@code Connection} instance.
|
|
||||||
*
|
|
||||||
* @param transformer Transformer to transform the input stream.
|
|
||||||
*
|
|
||||||
* @param <RR> New type of the input stream.
|
|
||||||
*
|
|
||||||
* @return A new connection instance with the transformed read stream.
|
|
||||||
*/
|
|
||||||
public abstract <RR> Connection<RR, W> transformRead(Transformer<R, RR> transformer);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transforms this connection to enable writing a different object type.
|
|
||||||
*
|
|
||||||
* @param transformer Transformer to transform objects written to the channel.
|
|
||||||
*
|
|
||||||
* @param <WW> New object types to be written to the connection.
|
|
||||||
*
|
|
||||||
* @return A new connection instance with the new write type.
|
|
||||||
*/
|
|
||||||
public abstract <WW> Connection<R, WW> transformWrite(AllocatingTransformer<WW, W> transformer);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the {@link MarkAwarePipeline} for this connection, changes to which can be reverted at any point in time.
|
|
||||||
*/
|
|
||||||
public MarkAwarePipeline getResettableChannelPipeline() {
|
|
||||||
return markAwarePipeline;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the {@link ChannelPipeline} for this connection.
|
|
||||||
*
|
|
||||||
* @return {@link ChannelPipeline} for this connection.
|
|
||||||
*/
|
|
||||||
public ChannelPipeline getChannelPipeline() {
|
|
||||||
return nettyChannel.pipeline();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the underlying netty {@link Channel} for this connection.
|
|
||||||
*
|
|
||||||
* It is advisable to use this connection abstraction for all interactions with the channel, however, advanced users
|
|
||||||
* may find directly using the netty channel useful in some cases.
|
|
||||||
*
|
|
||||||
* @return The underlying netty {@link Channel} for this connection.
|
|
||||||
*/
|
|
||||||
public Channel unsafeNettyChannel() {
|
|
||||||
return nettyChannel;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* In order to make sure that the connection is correctly initialized, the listener needs to be added post
|
|
||||||
* constructor. Otherwise, there is a race-condition of the channel closed before the connection is completely
|
|
||||||
* created and the Connection.close() call on channel close can access the Connection object which isn't
|
|
||||||
* constructed completely. IOW, "this" escapes from the constructor if the listener is added in the constructor.
|
|
||||||
*/
|
|
||||||
protected void connectCloseToChannelClose() {
|
|
||||||
nettyChannel.closeFuture()
|
|
||||||
.addListener((ChannelFutureListener) future -> {
|
|
||||||
closeNow(); // Close this connection when the channel is closed.
|
|
||||||
});
|
|
||||||
nettyChannel.attr(CONNECTION_ATTRIBUTE_KEY).set(this);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.channel;
|
|
||||||
|
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
|
||||||
import io.netty.channel.ChannelInboundHandler;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An event to indicate to {@link AbstractConnectionToChannelBridge} that the subscriber as published by
|
|
||||||
* {@link ChannelSubscriberEvent} should be informed of a connection creation failure, instead of a new connection.
|
|
||||||
*
|
|
||||||
* <h2>Why do we need this?</h2>
|
|
||||||
*
|
|
||||||
* Since, emitting a connection can include a handshake for protocols such as TLS/SSL, it is not so that a new
|
|
||||||
* {@link io.reactivex.netty.channel.Connection} should be emitted as soon as the channel is active (i.e. inside
|
|
||||||
* {@link ChannelInboundHandler#channelActive(ChannelHandlerContext)}).
|
|
||||||
* For this reason, this event leaves it to the pipeline or any other entity outside to decide, when is the rite time to
|
|
||||||
* determine that a connection for a channel has failed creation.
|
|
||||||
*/
|
|
||||||
public final class ConnectionCreationFailedEvent {
|
|
||||||
|
|
||||||
private final Throwable throwable;
|
|
||||||
|
|
||||||
public ConnectionCreationFailedEvent(Throwable throwable) {
|
|
||||||
this.throwable = throwable;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Throwable getThrowable() {
|
|
||||||
return throwable;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,240 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.channel;
|
|
||||||
|
|
||||||
import io.netty.channel.Channel;
|
|
||||||
import io.netty.channel.ChannelHandler;
|
|
||||||
import io.netty.channel.ChannelPipeline;
|
|
||||||
import io.netty.channel.FileRegion;
|
|
||||||
import io.netty.util.concurrent.EventExecutorGroup;
|
|
||||||
import io.reactivex.netty.channel.events.ConnectionEventListener;
|
|
||||||
import io.reactivex.netty.events.EventAttributeKeys;
|
|
||||||
import io.reactivex.netty.events.EventPublisher;
|
|
||||||
import rx.Observable;
|
|
||||||
import rx.Observable.Transformer;
|
|
||||||
import rx.functions.Action1;
|
|
||||||
import rx.functions.Func1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An implementation of {@link Connection} delegating all {@link ChannelOperations} methods to
|
|
||||||
* {@link DefaultChannelOperations}.
|
|
||||||
*/
|
|
||||||
public final class ConnectionImpl<R, W> extends Connection<R, W> {
|
|
||||||
|
|
||||||
private final ChannelOperations<W> delegate;
|
|
||||||
|
|
||||||
private ConnectionImpl(Channel nettyChannel, ConnectionEventListener eventListener, EventPublisher eventPublisher) {
|
|
||||||
super(nettyChannel);
|
|
||||||
delegate = new DefaultChannelOperations<>(nettyChannel, eventListener, eventPublisher);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ConnectionImpl(Channel nettyChannel, ChannelOperations<W> delegate) {
|
|
||||||
super(nettyChannel);
|
|
||||||
this.delegate = delegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ConnectionImpl(ConnectionImpl<?, ?> toCopy, ContentSource<R> contentSource, ChannelOperations<W> delegate) {
|
|
||||||
super(toCopy, contentSource);
|
|
||||||
this.delegate = delegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> write(Observable<W> msgs) {
|
|
||||||
return delegate.write(msgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> write(Observable<W> msgs, Func1<W, Boolean> flushSelector) {
|
|
||||||
return delegate.write(msgs, flushSelector);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> writeAndFlushOnEach(Observable<W> msgs) {
|
|
||||||
return delegate.writeAndFlushOnEach(msgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> writeString(Observable<String> msgs) {
|
|
||||||
return delegate.writeString(msgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> writeString(Observable<String> msgs, Func1<String, Boolean> flushSelector) {
|
|
||||||
return delegate.writeString(msgs, flushSelector);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> writeStringAndFlushOnEach(Observable<String> msgs) {
|
|
||||||
return delegate.writeStringAndFlushOnEach(msgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> writeBytes(Observable<byte[]> msgs) {
|
|
||||||
return delegate.writeBytes(msgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> writeBytes(Observable<byte[]> msgs,
|
|
||||||
Func1<byte[], Boolean> flushSelector) {
|
|
||||||
return delegate.writeBytes(msgs, flushSelector);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> writeBytesAndFlushOnEach(Observable<byte[]> msgs) {
|
|
||||||
return delegate.writeBytesAndFlushOnEach(msgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> writeFileRegion(Observable<FileRegion> msgs) {
|
|
||||||
return delegate.writeFileRegion(msgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> writeFileRegion(Observable<FileRegion> msgs,
|
|
||||||
Func1<FileRegion, Boolean> flushSelector) {
|
|
||||||
return delegate.writeFileRegion(msgs, flushSelector);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> writeFileRegionAndFlushOnEach(Observable<FileRegion> msgs) {
|
|
||||||
return delegate.writeFileRegionAndFlushOnEach(msgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void flush() {
|
|
||||||
delegate.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> close() {
|
|
||||||
return delegate.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> close(boolean flush) {
|
|
||||||
return delegate.close(flush);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void closeNow() {
|
|
||||||
delegate.closeNow();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> closeListener() {
|
|
||||||
return delegate.closeListener();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <R, W> ConnectionImpl<R, W> fromChannel(Channel nettyChannel) {
|
|
||||||
EventPublisher ep = nettyChannel.attr(EventAttributeKeys.EVENT_PUBLISHER).get();
|
|
||||||
if (null == ep) {
|
|
||||||
throw new IllegalArgumentException("No event publisher set in the channel.");
|
|
||||||
}
|
|
||||||
|
|
||||||
ConnectionEventListener l = null;
|
|
||||||
if (ep.publishingEnabled()) {
|
|
||||||
l = nettyChannel.attr(EventAttributeKeys.CONNECTION_EVENT_LISTENER).get();
|
|
||||||
if (null == l) {
|
|
||||||
throw new IllegalArgumentException("No event listener set in the channel.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final ConnectionImpl<R, W> toReturn = new ConnectionImpl<>(nettyChannel, l, ep);
|
|
||||||
toReturn.connectCloseToChannelClose();
|
|
||||||
return toReturn;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*Visible for testing*/static <R, W> ConnectionImpl<R, W> create(Channel nettyChannel,
|
|
||||||
ChannelOperations<W> delegate) {
|
|
||||||
final ConnectionImpl<R, W> toReturn = new ConnectionImpl<>(nettyChannel, delegate);
|
|
||||||
toReturn.connectCloseToChannelClose();
|
|
||||||
return toReturn;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <RR, WW> Connection<RR, WW> addChannelHandlerFirst(String name, ChannelHandler handler) {
|
|
||||||
getResettableChannelPipeline().markIfNotYetMarked().addFirst(name, handler);
|
|
||||||
return cast();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <RR, WW> Connection<RR, WW> addChannelHandlerFirst(EventExecutorGroup group, String name,
|
|
||||||
ChannelHandler handler) {
|
|
||||||
getResettableChannelPipeline().markIfNotYetMarked().addFirst(group, name, handler);
|
|
||||||
return cast();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <RR, WW> Connection<RR, WW> addChannelHandlerLast(String name, ChannelHandler handler) {
|
|
||||||
getResettableChannelPipeline().markIfNotYetMarked().addLast(name, handler);
|
|
||||||
return cast();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <RR, WW> Connection<RR, WW> addChannelHandlerLast(EventExecutorGroup group, String name,
|
|
||||||
ChannelHandler handler) {
|
|
||||||
getResettableChannelPipeline().markIfNotYetMarked().addLast(group, name, handler);
|
|
||||||
return cast();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <RR, WW> Connection<RR, WW> addChannelHandlerBefore(String baseName, String name, ChannelHandler handler) {
|
|
||||||
getResettableChannelPipeline().markIfNotYetMarked().addBefore(baseName, name, handler);
|
|
||||||
return cast();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <RR, WW> Connection<RR, WW> addChannelHandlerBefore(EventExecutorGroup group, String baseName, String name,
|
|
||||||
ChannelHandler handler) {
|
|
||||||
getResettableChannelPipeline().markIfNotYetMarked().addBefore(group, baseName, name, handler);
|
|
||||||
return cast();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <RR, WW> Connection<RR, WW> addChannelHandlerAfter(String baseName, String name, ChannelHandler handler) {
|
|
||||||
getResettableChannelPipeline().markIfNotYetMarked().addAfter(baseName, name, handler);
|
|
||||||
return cast();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <RR, WW> Connection<RR, WW> addChannelHandlerAfter(EventExecutorGroup group, String baseName, String name,
|
|
||||||
ChannelHandler handler) {
|
|
||||||
getResettableChannelPipeline().markIfNotYetMarked().addAfter(group, baseName, name, handler);
|
|
||||||
return cast();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <RR, WW> Connection<RR, WW> pipelineConfigurator(Action1<ChannelPipeline> pipelineConfigurator) {
|
|
||||||
pipelineConfigurator.call(getResettableChannelPipeline().markIfNotYetMarked());
|
|
||||||
return cast();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <RR> Connection<RR, W> transformRead(Transformer<R, RR> transformer) {
|
|
||||||
return new ConnectionImpl<>(this, getInput().transform(transformer), delegate);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <WW> Connection<R, WW> transformWrite(AllocatingTransformer<WW, W> transformer) {
|
|
||||||
return new ConnectionImpl<>(this, getInput(), delegate.transformWrite(transformer));
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
protected <RR, WW> Connection<RR, WW> cast() {
|
|
||||||
return (Connection<RR, WW>) this;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,64 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.channel;
|
|
||||||
|
|
||||||
import io.netty.util.ReferenceCountUtil;
|
|
||||||
import rx.Subscriber;
|
|
||||||
import rx.functions.Action1;
|
|
||||||
import rx.observers.Subscribers;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An event to communicate the subscriber of the associated connection input stream created by
|
|
||||||
* {@link AbstractConnectionToChannelBridge}.
|
|
||||||
*
|
|
||||||
* <h2>Multiple events on the same channel</h2>
|
|
||||||
*
|
|
||||||
* Multiple instance of this event can be sent on the same channel, provided that there is a
|
|
||||||
* {@link ConnectionInputSubscriberResetEvent} between two consecutive {@link ConnectionInputSubscriberEvent}s
|
|
||||||
*
|
|
||||||
* @param <R> Type read from the connection held by the event.
|
|
||||||
* @param <W> Type written to the connection held by the event.
|
|
||||||
*/
|
|
||||||
public final class ConnectionInputSubscriberEvent<R, W> {
|
|
||||||
|
|
||||||
private final Subscriber<? super R> subscriber;
|
|
||||||
|
|
||||||
public ConnectionInputSubscriberEvent(Subscriber<? super R> subscriber) {
|
|
||||||
if (null == subscriber) {
|
|
||||||
throw new NullPointerException("Subscriber can not be null");
|
|
||||||
}
|
|
||||||
this.subscriber = subscriber;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Subscriber<? super R> getSubscriber() {
|
|
||||||
return subscriber;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <II, OO> ConnectionInputSubscriberEvent<II, OO> discardAllInput() {
|
|
||||||
return new ConnectionInputSubscriberEvent<>(Subscribers.create(new Action1<II>() {
|
|
||||||
@Override
|
|
||||||
public void call(II msg) {
|
|
||||||
ReferenceCountUtil.release(msg);
|
|
||||||
}
|
|
||||||
}, new Action1<Throwable>() {
|
|
||||||
@Override
|
|
||||||
public void call(Throwable throwable) {
|
|
||||||
// Empty as we are discarding input anyways.
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.channel;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This event is an indication to atomically replace existing connection input subscriber, if any, with another.
|
|
||||||
*/
|
|
||||||
public class ConnectionInputSubscriberReplaceEvent<R, W> {
|
|
||||||
|
|
||||||
private final ConnectionInputSubscriberEvent<R, W> newSubEvent;
|
|
||||||
|
|
||||||
public ConnectionInputSubscriberReplaceEvent(ConnectionInputSubscriberEvent<R, W> newSubEvent) {
|
|
||||||
this.newSubEvent = newSubEvent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConnectionInputSubscriberEvent<R, W> getNewSubEvent() {
|
|
||||||
return newSubEvent;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.channel;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This event is an indication that there will be multiple subscribers to the connection input stream. This event
|
|
||||||
* must be sent as many times as the subscribers to the input. This typically will be the case for client-side
|
|
||||||
* connections when a channel is pooled and reused.
|
|
||||||
*/
|
|
||||||
public interface ConnectionInputSubscriberResetEvent {
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,44 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.channel;
|
|
||||||
|
|
||||||
import rx.Subscriber;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An event to communicate the subscriber of a new connection created by {@link AbstractConnectionToChannelBridge}.
|
|
||||||
*
|
|
||||||
* <h2>Connection reuse</h2>
|
|
||||||
*
|
|
||||||
* For cases, where the {@link Connection} is pooled, reuse should be indicated explicitly via
|
|
||||||
* {@link ConnectionInputSubscriberResetEvent}. There can be multiple {@link ConnectionInputSubscriberResetEvent}s
|
|
||||||
* sent to the same channel and hence the same instance of {@link AbstractConnectionToChannelBridge}.
|
|
||||||
*
|
|
||||||
* @param <R> Type read from the connection held by the event.
|
|
||||||
* @param <W> Type written to the connection held by the event.
|
|
||||||
*/
|
|
||||||
public class ConnectionSubscriberEvent<R, W> {
|
|
||||||
|
|
||||||
private final Subscriber<? super Connection<R, W>> subscriber;
|
|
||||||
|
|
||||||
public ConnectionSubscriberEvent(Subscriber<? super Connection<R, W>> subscriber) {
|
|
||||||
this.subscriber = subscriber;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Subscriber<? super Connection<R, W>> getSubscriber() {
|
|
||||||
return subscriber;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,96 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.channel;
|
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
|
||||||
import io.netty.buffer.ByteBufHolder;
|
|
||||||
import io.netty.channel.Channel;
|
|
||||||
import rx.Observable;
|
|
||||||
import rx.Subscriber;
|
|
||||||
import rx.functions.Func1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A source for any content/data read from a channel.
|
|
||||||
*
|
|
||||||
* <h2>Managing {@link ByteBuf} lifecycle.</h2>
|
|
||||||
*
|
|
||||||
* If this source emits {@link ByteBuf} or a {@link ByteBufHolder}, using {@link #autoRelease()} will release the buffer
|
|
||||||
* after emitting it from this source.
|
|
||||||
*
|
|
||||||
* <h2>Replaying content</h2>
|
|
||||||
*
|
|
||||||
* Since, the content read from a channel is not re-readable, this also provides a {@link #replayable()} function that
|
|
||||||
* produces a source which can be subscribed multiple times to replay the same data. This is specially useful if the
|
|
||||||
* content read from one channel is written on to another with an option to retry.
|
|
||||||
*
|
|
||||||
* @param <T>
|
|
||||||
*/
|
|
||||||
public final class ContentSource<T> extends Observable<T> {
|
|
||||||
|
|
||||||
private ContentSource(final Observable<T> source) {
|
|
||||||
super(new OnSubscribe<T>() {
|
|
||||||
@Override
|
|
||||||
public void call(Subscriber<? super T> subscriber) {
|
|
||||||
source.unsafeSubscribe(subscriber);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public ContentSource(final Channel channel, final Func1<Subscriber<? super T>, Object> subscriptionEventFactory) {
|
|
||||||
super(new OnSubscribe<T>() {
|
|
||||||
@Override
|
|
||||||
public void call(Subscriber<? super T> subscriber) {
|
|
||||||
channel.pipeline()
|
|
||||||
.fireUserEventTriggered(subscriptionEventFactory.call(subscriber));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public ContentSource(final Throwable error) {
|
|
||||||
super(new OnSubscribe<T>() {
|
|
||||||
@Override
|
|
||||||
public void call(Subscriber<? super T> subscriber) {
|
|
||||||
subscriber.onError(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If this source emits {@link ByteBuf} or {@link ByteBufHolder} then using this operator will release the buffer
|
|
||||||
* after it is emitted from this source.
|
|
||||||
*
|
|
||||||
* @return A new instance of the stream with auto-release enabled.
|
|
||||||
*/
|
|
||||||
public Observable<T> autoRelease() {
|
|
||||||
return this.lift(new AutoReleaseOperator<T>());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This provides a replayable content source that only subscribes once to the actual content and then caches it,
|
|
||||||
* till {@link DisposableContentSource#dispose()} is called.
|
|
||||||
*
|
|
||||||
* @return A new replayable content source.
|
|
||||||
*/
|
|
||||||
public DisposableContentSource<T> replayable() {
|
|
||||||
return DisposableContentSource.createNew(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <R> ContentSource<R> transform(Transformer<T, R> transformer) {
|
|
||||||
return new ContentSource<>(transformer.call(this));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,368 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.channel;
|
|
||||||
|
|
||||||
import io.netty.channel.Channel;
|
|
||||||
import io.netty.channel.ChannelFuture;
|
|
||||||
import io.netty.channel.ChannelFutureListener;
|
|
||||||
import io.netty.channel.FileRegion;
|
|
||||||
import io.reactivex.netty.channel.events.ConnectionEventListener;
|
|
||||||
import io.reactivex.netty.events.Clock;
|
|
||||||
import io.reactivex.netty.events.EventPublisher;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
import rx.Observable;
|
|
||||||
import rx.Observable.OnSubscribe;
|
|
||||||
import rx.Subscriber;
|
|
||||||
import rx.functions.Action0;
|
|
||||||
import rx.functions.Action1;
|
|
||||||
import rx.functions.Actions;
|
|
||||||
import rx.functions.Func1;
|
|
||||||
import rx.subscriptions.Subscriptions;
|
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
|
|
||||||
|
|
||||||
import static java.util.concurrent.TimeUnit.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default implementation for {@link ChannelOperations}.
|
|
||||||
*
|
|
||||||
* @param <W> Type of data that can be written on the associated channel.
|
|
||||||
*/
|
|
||||||
public class DefaultChannelOperations<W> implements ChannelOperations<W> {
|
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(DefaultChannelOperations.class.getName());
|
|
||||||
|
|
||||||
/** Field updater for closeIssued. */
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
private static final AtomicIntegerFieldUpdater<DefaultChannelOperations> CLOSE_ISSUED_UPDATER
|
|
||||||
= AtomicIntegerFieldUpdater.newUpdater(DefaultChannelOperations.class, "closeIssued");
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
private volatile int closeIssued; // updated by the atomic updater, so required to be volatile.
|
|
||||||
|
|
||||||
private final Channel nettyChannel;
|
|
||||||
private final ConnectionEventListener eventListener;
|
|
||||||
private final EventPublisher eventPublisher;
|
|
||||||
|
|
||||||
private final Observable<Void> closeObservable;
|
|
||||||
private final Observable<Void> flushAndCloseObservable;
|
|
||||||
|
|
||||||
private final Func1<W, Boolean> flushOnEachSelector = new Func1<W, Boolean>() {
|
|
||||||
@Override
|
|
||||||
public Boolean call(W w) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public DefaultChannelOperations(final Channel nettyChannel, ConnectionEventListener eventListener,
|
|
||||||
EventPublisher eventPublisher) {
|
|
||||||
this.nettyChannel = nettyChannel;
|
|
||||||
this.eventListener = eventListener;
|
|
||||||
this.eventPublisher = eventPublisher;
|
|
||||||
closeObservable = Observable.create(new OnSubscribeForClose(nettyChannel));
|
|
||||||
flushAndCloseObservable = closeObservable.doOnSubscribe(new Action0() {
|
|
||||||
@Override
|
|
||||||
public void call() {
|
|
||||||
flush();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> write(final Observable<W> msgs) {
|
|
||||||
return _write(msgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> write(Observable<W> msgs, final Func1<W, Boolean> flushSelector) {
|
|
||||||
return _write(msgs, flushSelector);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> writeAndFlushOnEach(Observable<W> msgs) {
|
|
||||||
return _write(msgs, flushOnEachSelector);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> writeString(Observable<String> msgs) {
|
|
||||||
return _write(msgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> writeString(Observable<String> msgs, Func1<String, Boolean> flushSelector) {
|
|
||||||
return _write(msgs, flushSelector);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> writeStringAndFlushOnEach(Observable<String> msgs) {
|
|
||||||
return writeString(msgs, FLUSH_ON_EACH_STRING);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> writeBytes(Observable<byte[]> msgs) {
|
|
||||||
return _write(msgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> writeBytes(Observable<byte[]> msgs, Func1<byte[], Boolean> flushSelector) {
|
|
||||||
return _write(msgs, flushSelector);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> writeBytesAndFlushOnEach(Observable<byte[]> msgs) {
|
|
||||||
return _write(msgs, FLUSH_ON_EACH_BYTES);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> writeFileRegion(Observable<FileRegion> msgs) {
|
|
||||||
return _write(msgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> writeFileRegion(Observable<FileRegion> msgs, Func1<FileRegion, Boolean> flushSelector) {
|
|
||||||
return _write(msgs, flushSelector);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> writeFileRegionAndFlushOnEach(Observable<FileRegion> msgs) {
|
|
||||||
return writeFileRegion(msgs, FLUSH_ON_EACH_FILE_REGION);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <WW> ChannelOperations<WW> transformWrite(AllocatingTransformer<WW, W> transformer) {
|
|
||||||
nettyChannel.pipeline().fireUserEventTriggered(new AppendTransformerEvent<>(transformer));
|
|
||||||
return new DefaultChannelOperations<>(nettyChannel, eventListener, eventPublisher);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void flush() {
|
|
||||||
if (eventPublisher.publishingEnabled()) {
|
|
||||||
final long startTimeNanos = Clock.newStartTimeNanos();
|
|
||||||
eventListener.onFlushStart();
|
|
||||||
if (nettyChannel.eventLoop().inEventLoop()) {
|
|
||||||
_flushInEventloop(startTimeNanos);
|
|
||||||
} else {
|
|
||||||
nettyChannel.eventLoop()
|
|
||||||
.execute(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
_flushInEventloop(startTimeNanos);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
nettyChannel.flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> close() {
|
|
||||||
return close(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> close(boolean flush) {
|
|
||||||
return flush ? flushAndCloseObservable : closeObservable;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void closeNow() {
|
|
||||||
close().subscribe(Actions.empty(), new Action1<Throwable>() {
|
|
||||||
@Override
|
|
||||||
public void call(Throwable throwable) {
|
|
||||||
logger.log(Level.SEVERE, "Error closing connection.", throwable);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> closeListener() {
|
|
||||||
return Observable.create(new OnSubscribe<Void>() {
|
|
||||||
@Override
|
|
||||||
public void call(final Subscriber<? super Void> subscriber) {
|
|
||||||
final SubscriberToChannelFutureBridge l = new SubscriberToChannelFutureBridge() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doOnSuccess(ChannelFuture future) {
|
|
||||||
subscriber.onCompleted();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doOnFailure(ChannelFuture future, Throwable cause) {
|
|
||||||
subscriber.onCompleted();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
l.bridge(nettyChannel.closeFuture(), subscriber);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private <X> Observable<Void> _write(final Observable<X> msgs, Func1<X, Boolean> flushSelector) {
|
|
||||||
return _write(msgs.lift(new FlushSelectorOperator<>(flushSelector, this)));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void _flushInEventloop(long startTimeNanos) {
|
|
||||||
assert nettyChannel.eventLoop().inEventLoop();
|
|
||||||
nettyChannel.flush(); // Flush is sync when from eventloop.
|
|
||||||
eventListener.onFlushComplete(Clock.onEndNanos(startTimeNanos), NANOSECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Observable<Void> _write(final Observable<?> msgs) {
|
|
||||||
return Observable.create(new OnSubscribe<Void>() {
|
|
||||||
@Override
|
|
||||||
public void call(final Subscriber<? super Void> subscriber) {
|
|
||||||
|
|
||||||
final long startTimeNanos = Clock.newStartTimeNanos();
|
|
||||||
|
|
||||||
if (eventPublisher.publishingEnabled()) {
|
|
||||||
eventListener.onWriteStart();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If a write happens from outside the eventloop, it does not wakeup the selector, till a flush happens.
|
|
||||||
* In absence of a selector wakeup, this write will be delayed by the selector sleep interval.
|
|
||||||
* The code below makes sure that the selector is woken up on a write (by executing a task that does
|
|
||||||
* the write)
|
|
||||||
*/
|
|
||||||
if (nettyChannel.eventLoop().inEventLoop()) {
|
|
||||||
_writeStreamToChannel(subscriber, startTimeNanos);
|
|
||||||
} else {
|
|
||||||
nettyChannel.eventLoop()
|
|
||||||
.execute(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
_writeStreamToChannel(subscriber, startTimeNanos);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void _writeStreamToChannel(final Subscriber<? super Void> subscriber, final long startTimeNanos) {
|
|
||||||
final ChannelFuture writeFuture = nettyChannel.write(msgs.doOnCompleted(new Action0() {
|
|
||||||
@Override
|
|
||||||
public void call() {
|
|
||||||
Boolean shdNotFlush = nettyChannel.attr(FLUSH_ONLY_ON_READ_COMPLETE).get();
|
|
||||||
if (null == shdNotFlush || !shdNotFlush) {
|
|
||||||
flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
subscriber.add(Subscriptions.create(new Action0() {
|
|
||||||
@Override
|
|
||||||
public void call() {
|
|
||||||
writeFuture.cancel(false); // cancel write on unsubscribe.
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
writeFuture.addListener(new ChannelFutureListener() {
|
|
||||||
@Override
|
|
||||||
public void operationComplete(ChannelFuture future) throws Exception {
|
|
||||||
if (subscriber.isUnsubscribed()) {
|
|
||||||
/*short-circuit if subscriber is unsubscribed*/
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (future.isSuccess()) {
|
|
||||||
if (eventPublisher.publishingEnabled()) {
|
|
||||||
eventListener.onWriteSuccess(Clock.onEndNanos(startTimeNanos), NANOSECONDS);
|
|
||||||
}
|
|
||||||
subscriber.onCompleted();
|
|
||||||
} else {
|
|
||||||
if (eventPublisher.publishingEnabled()) {
|
|
||||||
eventListener.onWriteFailed(Clock.onEndNanos(startTimeNanos), NANOSECONDS,
|
|
||||||
future.cause());
|
|
||||||
}
|
|
||||||
subscriber.onError(future.cause());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private class OnSubscribeForClose implements OnSubscribe<Void> {
|
|
||||||
|
|
||||||
private final Channel nettyChannel;
|
|
||||||
|
|
||||||
public OnSubscribeForClose(Channel nettyChannel) {
|
|
||||||
this.nettyChannel = nettyChannel;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public void call(final Subscriber<? super Void> subscriber) {
|
|
||||||
|
|
||||||
final long closeStartTimeNanos = Clock.newStartTimeNanos();
|
|
||||||
|
|
||||||
final ChannelCloseListener closeListener;
|
|
||||||
if (CLOSE_ISSUED_UPDATER.compareAndSet(DefaultChannelOperations.this, 0, 1)) {
|
|
||||||
if (eventPublisher.publishingEnabled()) {
|
|
||||||
eventListener.onConnectionCloseStart();
|
|
||||||
}
|
|
||||||
|
|
||||||
nettyChannel.close(); // close only once.
|
|
||||||
|
|
||||||
closeListener = new ChannelCloseListener(eventListener, eventPublisher, closeStartTimeNanos,
|
|
||||||
subscriber);
|
|
||||||
} else {
|
|
||||||
closeListener = new ChannelCloseListener(subscriber);
|
|
||||||
}
|
|
||||||
|
|
||||||
closeListener.bridge(nettyChannel.closeFuture(), subscriber);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ChannelCloseListener extends SubscriberToChannelFutureBridge {
|
|
||||||
|
|
||||||
private final long closeStartTimeNanos;
|
|
||||||
private final Subscriber<? super Void> subscriber;
|
|
||||||
private final ConnectionEventListener eventListener;
|
|
||||||
private final EventPublisher eventPublisher;
|
|
||||||
|
|
||||||
public ChannelCloseListener(ConnectionEventListener eventListener, EventPublisher eventPublisher,
|
|
||||||
long closeStartTimeNanos, Subscriber<? super Void> subscriber) {
|
|
||||||
this.eventListener = eventListener;
|
|
||||||
this.eventPublisher = eventPublisher;
|
|
||||||
this.closeStartTimeNanos = closeStartTimeNanos;
|
|
||||||
this.subscriber = subscriber;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChannelCloseListener(Subscriber<? super Void> subscriber) {
|
|
||||||
this(null, null, -1, subscriber);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doOnSuccess(ChannelFuture future) {
|
|
||||||
if (null != eventListener && eventPublisher.publishingEnabled()) {
|
|
||||||
eventListener.onConnectionCloseSuccess(Clock.onEndNanos(closeStartTimeNanos), NANOSECONDS);
|
|
||||||
}
|
|
||||||
if (!subscriber.isUnsubscribed()) {
|
|
||||||
subscriber.onCompleted();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doOnFailure(ChannelFuture future, Throwable cause) {
|
|
||||||
if (null != eventListener && eventPublisher.publishingEnabled()) {
|
|
||||||
eventListener.onConnectionCloseFailed(Clock.onEndNanos(closeStartTimeNanos), NANOSECONDS,
|
|
||||||
future.cause());
|
|
||||||
}
|
|
||||||
if (!subscriber.isUnsubscribed()) {
|
|
||||||
subscriber.onError(future.cause());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,387 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.channel;
|
|
||||||
|
|
||||||
import io.netty.channel.Channel;
|
|
||||||
import io.netty.channel.ChannelHandler;
|
|
||||||
import io.netty.channel.ChannelInitializer;
|
|
||||||
import io.netty.channel.ChannelPipeline;
|
|
||||||
import io.netty.util.concurrent.EventExecutorGroup;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
import rx.functions.Action1;
|
|
||||||
import rx.functions.Func0;
|
|
||||||
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.NoSuchElementException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An implementation of {@link ChannelPipeline} which is detached from a channel and provides a
|
|
||||||
* {@link #addToChannel(Channel)} method to be invoked when this pipeline handlers are to be added to an actual channel
|
|
||||||
* pipeline.
|
|
||||||
*
|
|
||||||
* This must NOT be used on an actual channel, it does not support any channel operations. It only supports pipeline
|
|
||||||
* modification operations.
|
|
||||||
*/
|
|
||||||
public class DetachedChannelPipeline {
|
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(DetachedChannelPipeline.class.getName());
|
|
||||||
|
|
||||||
private final LinkedList<HandlerHolder> holdersInOrder;
|
|
||||||
|
|
||||||
private final Action1<ChannelPipeline> nullableTail;
|
|
||||||
|
|
||||||
public DetachedChannelPipeline() {
|
|
||||||
this(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DetachedChannelPipeline(final Action1<ChannelPipeline> nullableTail) {
|
|
||||||
this.nullableTail = nullableTail;
|
|
||||||
holdersInOrder = new LinkedList<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
private DetachedChannelPipeline(final DetachedChannelPipeline copyFrom,
|
|
||||||
final Action1<ChannelPipeline> nullableTail) {
|
|
||||||
this.nullableTail = nullableTail;
|
|
||||||
holdersInOrder = new LinkedList<>();
|
|
||||||
synchronized (copyFrom.holdersInOrder) {
|
|
||||||
for (HandlerHolder handlerHolder : copyFrom.holdersInOrder) {
|
|
||||||
holdersInOrder.addLast(handlerHolder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChannelInitializer<Channel> getChannelInitializer() {
|
|
||||||
return new ChannelInitializer<Channel>() {
|
|
||||||
@Override
|
|
||||||
protected void initChannel(Channel ch) throws Exception {
|
|
||||||
final ChannelPipeline pipeline = ch.pipeline();
|
|
||||||
synchronized (holdersInOrder) {
|
|
||||||
unguardedCopyToPipeline(pipeline);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public void addToChannel(Channel channel) {
|
|
||||||
final ChannelPipeline pipeline = channel.pipeline();
|
|
||||||
synchronized (holdersInOrder) {
|
|
||||||
unguardedCopyToPipeline(pipeline);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public DetachedChannelPipeline copy() {
|
|
||||||
return copy(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DetachedChannelPipeline copy(Action1<ChannelPipeline> newTail) {
|
|
||||||
return new DetachedChannelPipeline(this, newTail);
|
|
||||||
}
|
|
||||||
|
|
||||||
public DetachedChannelPipeline addFirst(String name, Func0<ChannelHandler> handlerFactory) {
|
|
||||||
return _guardedAddFirst(new HandlerHolder(name, handlerFactory));
|
|
||||||
}
|
|
||||||
|
|
||||||
public DetachedChannelPipeline addFirst(EventExecutorGroup group,
|
|
||||||
String name, Func0<ChannelHandler> handlerFactory) {
|
|
||||||
return _guardedAddFirst(new HandlerHolder(name, handlerFactory, group));
|
|
||||||
}
|
|
||||||
|
|
||||||
public DetachedChannelPipeline addLast(String name, Func0<ChannelHandler> handlerFactory) {
|
|
||||||
return _guardedAddLast(new HandlerHolder(name, handlerFactory));
|
|
||||||
}
|
|
||||||
|
|
||||||
public DetachedChannelPipeline addLast(EventExecutorGroup group, String name, Func0<ChannelHandler> handlerFactory) {
|
|
||||||
return _guardedAddLast(new HandlerHolder(name, handlerFactory, group));
|
|
||||||
}
|
|
||||||
|
|
||||||
public DetachedChannelPipeline addBefore(String baseName, String name, Func0<ChannelHandler> handlerFactory) {
|
|
||||||
return _guardedAddBefore(baseName, new HandlerHolder(name, handlerFactory));
|
|
||||||
}
|
|
||||||
|
|
||||||
public DetachedChannelPipeline addBefore(EventExecutorGroup group, String baseName, String name, Func0<ChannelHandler> handlerFactory) {
|
|
||||||
return _guardedAddBefore(baseName, new HandlerHolder(name, handlerFactory, group));
|
|
||||||
}
|
|
||||||
|
|
||||||
public DetachedChannelPipeline addAfter(String baseName, String name, Func0<ChannelHandler> handlerFactory) {
|
|
||||||
return _guardedAddAfter(baseName, new HandlerHolder(name, handlerFactory));
|
|
||||||
}
|
|
||||||
|
|
||||||
public DetachedChannelPipeline addAfter(EventExecutorGroup group, String baseName, String name, Func0<ChannelHandler> handlerFactory) {
|
|
||||||
return _guardedAddAfter(baseName, new HandlerHolder(name, handlerFactory, group));
|
|
||||||
}
|
|
||||||
|
|
||||||
@SafeVarargs
|
|
||||||
public final DetachedChannelPipeline addFirst(Func0<ChannelHandler>... handlerFactories) {
|
|
||||||
synchronized (holdersInOrder) {
|
|
||||||
for (int i = handlerFactories.length - 1; i >= 0; i--) {
|
|
||||||
Func0<ChannelHandler> handlerFactory = handlerFactories[i];
|
|
||||||
holdersInOrder.addFirst(new HandlerHolder(handlerFactory));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SafeVarargs
|
|
||||||
public final DetachedChannelPipeline addFirst(EventExecutorGroup group, Func0<ChannelHandler>... handlerFactories) {
|
|
||||||
synchronized (holdersInOrder) {
|
|
||||||
for (int i = handlerFactories.length - 1; i >= 0; i--) {
|
|
||||||
Func0<ChannelHandler> handlerFactory = handlerFactories[i];
|
|
||||||
holdersInOrder.addFirst(new HandlerHolder(null, handlerFactory, group));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SafeVarargs
|
|
||||||
public final DetachedChannelPipeline addLast(Func0<ChannelHandler>... handlerFactories) {
|
|
||||||
for (Func0<ChannelHandler> handlerFactory : handlerFactories) {
|
|
||||||
_guardedAddLast(new HandlerHolder(handlerFactory));
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SafeVarargs
|
|
||||||
public final DetachedChannelPipeline addLast(EventExecutorGroup group, Func0<ChannelHandler>... handlerFactories) {
|
|
||||||
for (Func0<ChannelHandler> handlerFactory : handlerFactories) {
|
|
||||||
_guardedAddLast(new HandlerHolder(null, handlerFactory, group));
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DetachedChannelPipeline configure(Action1<ChannelPipeline> configurator) {
|
|
||||||
_guardedAddLast(new HandlerHolder(configurator));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void copyTo(ChannelPipeline pipeline) {
|
|
||||||
synchronized (holdersInOrder) {
|
|
||||||
unguardedCopyToPipeline(pipeline);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*Visible for testing*/ LinkedList<HandlerHolder> getHoldersInOrder() {
|
|
||||||
return holdersInOrder;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void unguardedCopyToPipeline(ChannelPipeline pipeline) { /*To be guarded by lock on holders*/
|
|
||||||
for (HandlerHolder holder : holdersInOrder) {
|
|
||||||
if (holder.hasPipelineConfigurator()) {
|
|
||||||
holder.getPipelineConfigurator().call(pipeline);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (holder.hasGroup()) {
|
|
||||||
if (holder.hasName()) {
|
|
||||||
pipeline.addLast(holder.getGroupIfConfigured(), holder.getNameIfConfigured(),
|
|
||||||
holder.getHandlerFactoryIfConfigured().call());
|
|
||||||
} else {
|
|
||||||
pipeline.addLast(holder.getGroupIfConfigured(), holder.getHandlerFactoryIfConfigured().call());
|
|
||||||
}
|
|
||||||
} else if (holder.hasName()) {
|
|
||||||
pipeline.addLast(holder.getNameIfConfigured(), holder.getHandlerFactoryIfConfigured().call());
|
|
||||||
} else {
|
|
||||||
pipeline.addLast(holder.getHandlerFactoryIfConfigured().call());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (null != nullableTail) {
|
|
||||||
nullableTail.call(pipeline); // This is the last handler to be added to the pipeline always.
|
|
||||||
}
|
|
||||||
|
|
||||||
if (logger.isLoggable(Level.FINE)) {
|
|
||||||
logger.log(Level.FINE, "Channel pipeline in initializer: " + pipelineToString(pipeline));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private HandlerHolder unguardedFindHandlerByName(String baseName, boolean leniant) {
|
|
||||||
for (HandlerHolder handlerHolder : holdersInOrder) {
|
|
||||||
if (handlerHolder.hasName() && handlerHolder.getNameIfConfigured().equals(baseName)) {
|
|
||||||
return handlerHolder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (leniant) {
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
throw new NoSuchElementException("No handler with name: " + baseName + " configured in the pipeline.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private DetachedChannelPipeline _guardedAddFirst(HandlerHolder toAdd) {
|
|
||||||
synchronized (holdersInOrder) {
|
|
||||||
holdersInOrder.addFirst(toAdd);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private DetachedChannelPipeline _guardedAddLast(HandlerHolder toAdd) {
|
|
||||||
synchronized (holdersInOrder) {
|
|
||||||
holdersInOrder.addLast(toAdd);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private DetachedChannelPipeline _guardedAddBefore(String baseName, HandlerHolder toAdd) {
|
|
||||||
synchronized (holdersInOrder) {
|
|
||||||
HandlerHolder before = unguardedFindHandlerByName(baseName, false);
|
|
||||||
final int indexOfBefore = holdersInOrder.indexOf(before);
|
|
||||||
holdersInOrder.add(indexOfBefore, toAdd);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private DetachedChannelPipeline _guardedAddAfter(String baseName, HandlerHolder toAdd) {
|
|
||||||
synchronized (holdersInOrder) {
|
|
||||||
HandlerHolder after = unguardedFindHandlerByName(baseName, false);
|
|
||||||
final int indexOfAfter = holdersInOrder.indexOf(after);
|
|
||||||
holdersInOrder.add(indexOfAfter + 1, toAdd);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String pipelineToString(ChannelPipeline pipeline) {
|
|
||||||
StringBuilder builder = new StringBuilder();
|
|
||||||
for (Entry<String, ChannelHandler> handlerEntry : pipeline) {
|
|
||||||
if (builder.length() == 0) {
|
|
||||||
builder.append("[\n");
|
|
||||||
} else {
|
|
||||||
builder.append(" ==> ");
|
|
||||||
}
|
|
||||||
builder.append("{ name =>")
|
|
||||||
.append(handlerEntry.getKey())
|
|
||||||
.append(", handler => ")
|
|
||||||
.append(handlerEntry.getValue())
|
|
||||||
.append("}\n")
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (builder.length() > 0) {
|
|
||||||
builder.append("}\n");
|
|
||||||
}
|
|
||||||
return builder.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A holder class for holding handler information, required to add handlers to the actual pipeline.
|
|
||||||
*/
|
|
||||||
/*Visible for testing*/ static class HandlerHolder {
|
|
||||||
|
|
||||||
private final String nameIfConfigured;
|
|
||||||
private final Func0<ChannelHandler> handlerFactoryIfConfigured;
|
|
||||||
private final Action1<ChannelPipeline> pipelineConfigurator;
|
|
||||||
private final EventExecutorGroup groupIfConfigured;
|
|
||||||
|
|
||||||
HandlerHolder(Action1<ChannelPipeline> pipelineConfigurator) {
|
|
||||||
this.pipelineConfigurator = pipelineConfigurator;
|
|
||||||
nameIfConfigured = null;
|
|
||||||
handlerFactoryIfConfigured = null;
|
|
||||||
groupIfConfigured = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
HandlerHolder(Func0<ChannelHandler> handlerFactory) {
|
|
||||||
this(null, handlerFactory);
|
|
||||||
}
|
|
||||||
|
|
||||||
HandlerHolder(String name, Func0<ChannelHandler> handlerFactory) {
|
|
||||||
this(name, handlerFactory, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
HandlerHolder(String name, Func0<ChannelHandler> handlerFactory, EventExecutorGroup group) {
|
|
||||||
nameIfConfigured = name;
|
|
||||||
handlerFactoryIfConfigured = handlerFactory;
|
|
||||||
groupIfConfigured = group;
|
|
||||||
pipelineConfigurator = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getNameIfConfigured() {
|
|
||||||
return nameIfConfigured;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasName() {
|
|
||||||
return null != nameIfConfigured;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Func0<ChannelHandler> getHandlerFactoryIfConfigured() {
|
|
||||||
return handlerFactoryIfConfigured;
|
|
||||||
}
|
|
||||||
|
|
||||||
public EventExecutorGroup getGroupIfConfigured() {
|
|
||||||
return groupIfConfigured;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasGroup() {
|
|
||||||
return null != groupIfConfigured;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Action1<ChannelPipeline> getPipelineConfigurator() {
|
|
||||||
return pipelineConfigurator;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasPipelineConfigurator() {
|
|
||||||
return null != pipelineConfigurator;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!(o instanceof HandlerHolder)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
HandlerHolder that = (HandlerHolder) o;
|
|
||||||
|
|
||||||
if (groupIfConfigured != null? !groupIfConfigured.equals(that.groupIfConfigured) :
|
|
||||||
that.groupIfConfigured != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (handlerFactoryIfConfigured != null?
|
|
||||||
!handlerFactoryIfConfigured.equals(that.handlerFactoryIfConfigured) :
|
|
||||||
that.handlerFactoryIfConfigured != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (nameIfConfigured != null? !nameIfConfigured.equals(that.nameIfConfigured) :
|
|
||||||
that.nameIfConfigured != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (pipelineConfigurator != null? !pipelineConfigurator.equals(that.pipelineConfigurator) :
|
|
||||||
that.pipelineConfigurator != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
int result = nameIfConfigured != null? nameIfConfigured.hashCode() : 0;
|
|
||||||
result = 31 * result + (handlerFactoryIfConfigured != null? handlerFactoryIfConfigured.hashCode() : 0);
|
|
||||||
result = 31 * result + (pipelineConfigurator != null? pipelineConfigurator.hashCode() : 0);
|
|
||||||
result = 31 * result + (groupIfConfigured != null? groupIfConfigured.hashCode() : 0);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "HandlerHolder{" + "nameIfConfigured='" + nameIfConfigured + '\'' + ", handlerFactoryIfConfigured=" +
|
|
||||||
handlerFactoryIfConfigured + ", pipelineConfigurator=" + pipelineConfigurator
|
|
||||||
+ ", groupIfConfigured=" + groupIfConfigured + '}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,134 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.channel;
|
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
|
||||||
import io.netty.buffer.ByteBufHolder;
|
|
||||||
import io.netty.util.ReferenceCountUtil;
|
|
||||||
import rx.Observable;
|
|
||||||
import rx.Subscriber;
|
|
||||||
import rx.functions.Action1;
|
|
||||||
import rx.observables.ConnectableObservable;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Similar to {@link ContentSource} but supports multicast to multiple subscriptions. This source, subscribes upstream
|
|
||||||
* once and then caches the content, till the time {@link #dispose()} is called.
|
|
||||||
*
|
|
||||||
* <h2>Managing {@link ByteBuf} lifecycle.</h2>
|
|
||||||
*
|
|
||||||
* If this source emits {@link ByteBuf} or a {@link ByteBufHolder}, using {@link #autoRelease()} will release the buffer
|
|
||||||
* after emitting it from this source.
|
|
||||||
*
|
|
||||||
* Every subscriber to this source must manage it's own lifecycle of the items it receives i.e. the buffers must be
|
|
||||||
* released by every subscriber post processing.
|
|
||||||
*
|
|
||||||
* <h2>Disposing the source</h2>
|
|
||||||
*
|
|
||||||
* It is mandatory to call {@link #dispose()} on this source when no more subscriptions are required. Failure to do so,
|
|
||||||
* will cause a buffer leak as this source, caches the contents till disposed.
|
|
||||||
*
|
|
||||||
* Typically, {@link #dispose()} can be called as an {@link Subscriber#unsubscribe()} action.
|
|
||||||
*
|
|
||||||
* @param <T> Type of objects emitted by this source.
|
|
||||||
*/
|
|
||||||
public final class DisposableContentSource<T> extends Observable<T> {
|
|
||||||
|
|
||||||
private final OnSubscribeImpl<T> onSubscribe;
|
|
||||||
|
|
||||||
private DisposableContentSource(final OnSubscribeImpl<T> onSubscribe) {
|
|
||||||
super(onSubscribe);
|
|
||||||
this.onSubscribe = onSubscribe;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If this source emits {@link ByteBuf} or {@link ByteBufHolder} then using this operator will release the buffer
|
|
||||||
* after it is emitted from this source.
|
|
||||||
*
|
|
||||||
* @return A new instance of the stream with auto-release enabled.
|
|
||||||
*/
|
|
||||||
public Observable<T> autoRelease() {
|
|
||||||
return this.lift(new AutoReleaseOperator<T>());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disposes this source.
|
|
||||||
*/
|
|
||||||
public void dispose() {
|
|
||||||
if (onSubscribe.disposed.compareAndSet(false, true)) {
|
|
||||||
for (Object chunk : onSubscribe.chunks) {
|
|
||||||
ReferenceCountUtil.release(chunk);
|
|
||||||
}
|
|
||||||
onSubscribe.chunks.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static <X> DisposableContentSource<X> createNew(Observable<X> source) {
|
|
||||||
final ArrayList<X> chunks = new ArrayList<>();
|
|
||||||
ConnectableObservable<X> replay = source.doOnNext(new Action1<X>() {
|
|
||||||
@Override
|
|
||||||
public void call(X x) {
|
|
||||||
chunks.add(x);
|
|
||||||
}
|
|
||||||
}).replay();
|
|
||||||
return new DisposableContentSource<>(new OnSubscribeImpl<X>(replay, chunks));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class OnSubscribeImpl<T> implements OnSubscribe<T> {
|
|
||||||
|
|
||||||
private final ConnectableObservable<T> source;
|
|
||||||
private final ArrayList<T> chunks;
|
|
||||||
private boolean subscribed;
|
|
||||||
private final AtomicBoolean disposed = new AtomicBoolean();
|
|
||||||
|
|
||||||
public OnSubscribeImpl(ConnectableObservable<T> source, ArrayList<T> chunks) {
|
|
||||||
this.source = source;
|
|
||||||
this.chunks = chunks;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void call(Subscriber<? super T> subscriber) {
|
|
||||||
|
|
||||||
if (disposed.get()) {
|
|
||||||
subscriber.onError(new IllegalStateException("Content source is already disposed."));
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean connectNow = false;
|
|
||||||
|
|
||||||
synchronized (this) {
|
|
||||||
if (!subscribed) {
|
|
||||||
connectNow = true;
|
|
||||||
subscribed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
source.doOnNext(new Action1<T>() {
|
|
||||||
@Override
|
|
||||||
public void call(T msg) {
|
|
||||||
ReferenceCountUtil.retain(msg);
|
|
||||||
}
|
|
||||||
}).unsafeSubscribe(subscriber);
|
|
||||||
|
|
||||||
if (connectNow) {
|
|
||||||
source.connect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.channel;
|
|
||||||
|
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
|
||||||
import io.netty.channel.ChannelInboundHandler;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An event to indicate to {@link AbstractConnectionToChannelBridge} that the channel is ready to emit a new
|
|
||||||
* {@link io.reactivex.netty.channel.Connection} to the subscriber as published by {@link ChannelSubscriberEvent}
|
|
||||||
*
|
|
||||||
* <h2>Why do we need this?</h2>
|
|
||||||
*
|
|
||||||
* Since, emitting a connection can include a handshake for protocols such as TLS/SSL, it is not so that a new
|
|
||||||
* {@link io.reactivex.netty.channel.Connection} should be emitted as soon as the channel is active (i.e. inside
|
|
||||||
* {@link ChannelInboundHandler#channelActive(ChannelHandlerContext)}).
|
|
||||||
* For this reason, this event leaves it to the pipeline or any other entity outside to decide, when is the rite time to
|
|
||||||
* emit a connection.
|
|
||||||
*/
|
|
||||||
public final class EmitConnectionEvent {
|
|
||||||
|
|
||||||
public static final EmitConnectionEvent INSTANCE = new EmitConnectionEvent();
|
|
||||||
|
|
||||||
private EmitConnectionEvent() {
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.channel;
|
|
||||||
|
|
||||||
import rx.Observable.Operator;
|
|
||||||
import rx.Subscriber;
|
|
||||||
import rx.functions.Func1;
|
|
||||||
|
|
||||||
public class FlushSelectorOperator<T> implements Operator<T, T> {
|
|
||||||
|
|
||||||
private final Func1<T, Boolean> flushSelector;
|
|
||||||
private final ChannelOperations<?> channelOps;
|
|
||||||
|
|
||||||
public FlushSelectorOperator(Func1<T, Boolean> flushSelector, ChannelOperations<?> channelOps) {
|
|
||||||
this.flushSelector = flushSelector;
|
|
||||||
this.channelOps = channelOps;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Subscriber<? super T> call(final Subscriber<? super T> subscriber) {
|
|
||||||
|
|
||||||
return new Subscriber<T>(subscriber) {
|
|
||||||
@Override
|
|
||||||
public void onCompleted() {
|
|
||||||
subscriber.onCompleted();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(Throwable e) {
|
|
||||||
subscriber.onError(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNext(T next) {
|
|
||||||
subscriber.onNext(next);
|
|
||||||
/*Call the selector _after_ writing an element*/
|
|
||||||
if (flushSelector.call(next)) {
|
|
||||||
channelOps.flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,471 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.channel;
|
|
||||||
|
|
||||||
import io.netty.channel.Channel;
|
|
||||||
import io.netty.channel.ChannelFuture;
|
|
||||||
import io.netty.channel.ChannelHandler;
|
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
|
||||||
import io.netty.channel.ChannelOutboundInvoker;
|
|
||||||
import io.netty.channel.ChannelPipeline;
|
|
||||||
import io.netty.channel.ChannelProgressivePromise;
|
|
||||||
import io.netty.channel.ChannelPromise;
|
|
||||||
import io.netty.util.concurrent.EventExecutorGroup;
|
|
||||||
|
|
||||||
import java.net.SocketAddress;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An implementation of {@link ChannelPipeline} that allows a mark-reset scheme for {@link ChannelHandler}s. This allows
|
|
||||||
* temporary modifications to the underlying {@link ChannelPipeline} instance for usecases like pooled connections,
|
|
||||||
* server response upgrades, etc.
|
|
||||||
*
|
|
||||||
* <b>This only supports a single mark at a time, although mark-reset-mark cycles can be repeated any number of times.</b>
|
|
||||||
*
|
|
||||||
* <h2>Usage:</h2>
|
|
||||||
*
|
|
||||||
* To start recording resetable changes, call {@link #mark()} and to reset back to the state before {@link #mark()} was
|
|
||||||
* called, call {@link #reset()}
|
|
||||||
*
|
|
||||||
* <h2>Thread safety</h2>
|
|
||||||
*
|
|
||||||
* All operations of {@link ChannelPipeline} are delegated to the passed {@link ChannelPipeline} instance while
|
|
||||||
* creation. {@link #mark()} and {@link #reset()} uses the same mutex as {@link ChannelPipeline} for synchronization
|
|
||||||
* across different method calls.
|
|
||||||
*/
|
|
||||||
public final class MarkAwarePipeline implements ChannelPipeline {
|
|
||||||
|
|
||||||
private boolean marked; // Guarded by this
|
|
||||||
|
|
||||||
private final ChannelPipeline delegate;
|
|
||||||
|
|
||||||
public MarkAwarePipeline(ChannelPipeline delegate) {
|
|
||||||
this.delegate = delegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Marks this pipeline and record further changes which can be reverted by calling {@link #reset()}
|
|
||||||
*
|
|
||||||
* @throws IllegalStateException If this method is called more than once without calling {@link #reset()} in
|
|
||||||
* between.
|
|
||||||
*/
|
|
||||||
public synchronized MarkAwarePipeline mark() {
|
|
||||||
if (marked) {
|
|
||||||
throw new IllegalStateException("Pipeline does not support nested marks.");
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Marks this pipeline and record further changes which can be reverted by calling {@link #reset()}
|
|
||||||
*
|
|
||||||
* @throws IllegalStateException If this method is called more than once without calling {@link #reset()} in
|
|
||||||
* between.
|
|
||||||
*/
|
|
||||||
public synchronized MarkAwarePipeline markIfNotYetMarked() {
|
|
||||||
if (!marked) {
|
|
||||||
return mark();
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* If {@link #mark()} was called before, resets the pipeline to the state it was before calling {@link #mark()}.
|
|
||||||
* Otherwise, ignores the reset.
|
|
||||||
*/
|
|
||||||
public synchronized MarkAwarePipeline reset() {
|
|
||||||
if (!marked) {
|
|
||||||
return this; /*If there is no mark, there is nothing to reset.*/
|
|
||||||
}
|
|
||||||
|
|
||||||
marked = false;
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized boolean isMarked() {
|
|
||||||
return marked;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelPipeline addFirst(String name, ChannelHandler handler) {
|
|
||||||
delegate.addFirst(name, handler);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelPipeline addFirst(EventExecutorGroup group,
|
|
||||||
String name, ChannelHandler handler) {
|
|
||||||
delegate.addFirst(group, name, handler);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelPipeline addLast(String name, ChannelHandler handler) {
|
|
||||||
delegate.addLast(name, handler);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelPipeline addLast(EventExecutorGroup group,
|
|
||||||
String name, ChannelHandler handler) {
|
|
||||||
delegate.addLast(group, name, handler);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelPipeline addBefore(String baseName, String name,
|
|
||||||
ChannelHandler handler) {
|
|
||||||
return delegate.addBefore(baseName, name, handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelPipeline addBefore(EventExecutorGroup group,
|
|
||||||
String baseName, String name,
|
|
||||||
ChannelHandler handler) {
|
|
||||||
delegate.addBefore(group, baseName, name, handler);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelPipeline addAfter(String baseName, String name,
|
|
||||||
ChannelHandler handler) {
|
|
||||||
delegate.addAfter(baseName, name, handler);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelPipeline addAfter(EventExecutorGroup group,
|
|
||||||
String baseName, String name,
|
|
||||||
ChannelHandler handler) {
|
|
||||||
delegate.addAfter(group, baseName, name, handler);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelPipeline addFirst(ChannelHandler... handlers) {
|
|
||||||
delegate.addFirst(handlers);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelPipeline addFirst(EventExecutorGroup group,
|
|
||||||
ChannelHandler... handlers) {
|
|
||||||
delegate.addFirst(group, handlers);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelPipeline addLast(ChannelHandler... handlers) {
|
|
||||||
delegate.addLast(handlers);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelPipeline addLast(EventExecutorGroup group,
|
|
||||||
ChannelHandler... handlers) {
|
|
||||||
delegate.addLast(group, handlers);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelPipeline remove(ChannelHandler handler) {
|
|
||||||
delegate.remove(handler);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelHandler remove(String name) {
|
|
||||||
return delegate.remove(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T extends ChannelHandler> T remove(Class<T> handlerType) {
|
|
||||||
return delegate.remove(handlerType);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelHandler removeFirst() {
|
|
||||||
return delegate.removeFirst();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelHandler removeLast() {
|
|
||||||
return delegate.removeLast();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelPipeline replace(ChannelHandler oldHandler,
|
|
||||||
String newName, ChannelHandler newHandler) {
|
|
||||||
delegate.replace(oldHandler, newName, newHandler);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelHandler replace(String oldName, String newName,
|
|
||||||
ChannelHandler newHandler) {
|
|
||||||
return delegate.replace(oldName, newName, newHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T extends ChannelHandler> T replace(Class<T> oldHandlerType, String newName,
|
|
||||||
ChannelHandler newHandler) {
|
|
||||||
return delegate.replace(oldHandlerType, newName, newHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelHandler first() {
|
|
||||||
return delegate.first();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelHandlerContext firstContext() {
|
|
||||||
return delegate.firstContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelHandler last() {
|
|
||||||
return delegate.last();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelHandlerContext lastContext() {
|
|
||||||
return delegate.lastContext();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelHandler get(String name) {
|
|
||||||
return delegate.get(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T extends ChannelHandler> T get(Class<T> handlerType) {
|
|
||||||
return delegate.get(handlerType);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelHandlerContext context(ChannelHandler handler) {
|
|
||||||
return delegate.context(handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelHandlerContext context(String name) {
|
|
||||||
return delegate.context(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelHandlerContext context(Class<? extends ChannelHandler> handlerType) {
|
|
||||||
return delegate.context(handlerType);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Channel channel() {
|
|
||||||
return delegate.channel();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<String> names() {
|
|
||||||
return delegate.names();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, ChannelHandler> toMap() {
|
|
||||||
return delegate.toMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelPipeline fireChannelRegistered() {
|
|
||||||
delegate.fireChannelRegistered();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelPipeline fireChannelUnregistered() {
|
|
||||||
delegate.fireChannelUnregistered();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelPipeline fireChannelActive() {
|
|
||||||
delegate.fireChannelActive();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelPipeline fireChannelInactive() {
|
|
||||||
delegate.fireChannelInactive();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelPipeline fireExceptionCaught(Throwable cause) {
|
|
||||||
delegate.fireExceptionCaught(cause);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelPipeline fireUserEventTriggered(Object event) {
|
|
||||||
delegate.fireUserEventTriggered(event);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelPipeline fireChannelRead(Object msg) {
|
|
||||||
delegate.fireChannelRead(msg);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelPipeline fireChannelReadComplete() {
|
|
||||||
delegate.fireChannelReadComplete();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelPipeline fireChannelWritabilityChanged() {
|
|
||||||
delegate.fireChannelWritabilityChanged();
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelFuture bind(SocketAddress localAddress) {
|
|
||||||
return delegate.bind(localAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelFuture connect(SocketAddress remoteAddress) {
|
|
||||||
return delegate.connect(remoteAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelFuture connect(SocketAddress remoteAddress,
|
|
||||||
SocketAddress localAddress) {
|
|
||||||
return delegate.connect(remoteAddress, localAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelFuture disconnect() {
|
|
||||||
return delegate.disconnect();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelFuture close() {
|
|
||||||
return delegate.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelFuture deregister() {
|
|
||||||
return delegate.deregister();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelFuture bind(SocketAddress localAddress,
|
|
||||||
ChannelPromise promise) {
|
|
||||||
return delegate.bind(localAddress, promise);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelFuture connect(SocketAddress remoteAddress,
|
|
||||||
ChannelPromise promise) {
|
|
||||||
return delegate.connect(remoteAddress, promise);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelFuture connect(SocketAddress remoteAddress,
|
|
||||||
SocketAddress localAddress,
|
|
||||||
ChannelPromise promise) {
|
|
||||||
return delegate.connect(remoteAddress, localAddress, promise);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelFuture disconnect(ChannelPromise promise) {
|
|
||||||
return delegate.disconnect(promise);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelFuture close(ChannelPromise promise) {
|
|
||||||
return delegate.close(promise);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelFuture deregister(ChannelPromise promise) {
|
|
||||||
return delegate.deregister(promise);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelOutboundInvoker read() {
|
|
||||||
return delegate.read();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelFuture write(Object msg) {
|
|
||||||
return delegate.write(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelFuture write(Object msg, ChannelPromise promise) {
|
|
||||||
return delegate.write(msg, promise);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelPipeline flush() {
|
|
||||||
return delegate.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) {
|
|
||||||
return delegate.writeAndFlush(msg, promise);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelFuture writeAndFlush(Object msg) {
|
|
||||||
return delegate.writeAndFlush(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelPromise newPromise() {
|
|
||||||
return delegate.newPromise();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelProgressivePromise newProgressivePromise() {
|
|
||||||
return delegate.newProgressivePromise();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelFuture newSucceededFuture() {
|
|
||||||
return delegate.newSucceededFuture();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelFuture newFailedFuture(Throwable cause) {
|
|
||||||
return delegate.newFailedFuture(cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelPromise voidPromise() {
|
|
||||||
return delegate.voidPromise();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Iterator<Entry<String, ChannelHandler>> iterator() {
|
|
||||||
return delegate.iterator();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,70 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.channel;
|
|
||||||
|
|
||||||
import io.netty.channel.Channel;
|
|
||||||
import io.netty.channel.ChannelFuture;
|
|
||||||
import io.netty.channel.ChannelFutureListener;
|
|
||||||
import rx.Subscriber;
|
|
||||||
import rx.functions.Action0;
|
|
||||||
import rx.subscriptions.Subscriptions;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A bridge to connect a {@link Subscriber} to a {@link ChannelFuture} so that when the {@code subscriber} is
|
|
||||||
* unsubscribed, the listener will get removed from the {@code future}. Failure to do so for futures that are long
|
|
||||||
* living, eg: {@link Channel#closeFuture()} will lead to a memory leak where the attached listener will be in the
|
|
||||||
* listener queue of the future till the channel closes.
|
|
||||||
*
|
|
||||||
* In order to bridge the future and subscriber, {@link #bridge(ChannelFuture, Subscriber)} must be called.
|
|
||||||
*/
|
|
||||||
public abstract class SubscriberToChannelFutureBridge implements ChannelFutureListener {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final void operationComplete(ChannelFuture future) throws Exception {
|
|
||||||
if (future.isSuccess()) {
|
|
||||||
doOnSuccess(future);
|
|
||||||
} else {
|
|
||||||
doOnFailure(future, future.cause());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract void doOnSuccess(ChannelFuture future);
|
|
||||||
|
|
||||||
protected abstract void doOnFailure(ChannelFuture future, Throwable cause);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bridges the passed subscriber and future, which means the following:
|
|
||||||
*
|
|
||||||
* <ul>
|
|
||||||
<li>Add this listener to the passed future.</li>
|
|
||||||
<li>Add a callback to the subscriber, such that on unsubscribe this listener is removed from the future.</li>
|
|
||||||
</ul>
|
|
||||||
*
|
|
||||||
* @param future Future to bridge.
|
|
||||||
* @param subscriber Subscriber to connect to the future.
|
|
||||||
*/
|
|
||||||
public void bridge(final ChannelFuture future, Subscriber<?> subscriber) {
|
|
||||||
future.addListener(this);
|
|
||||||
subscriber.add(Subscriptions.create(new Action0() {
|
|
||||||
@Override
|
|
||||||
public void call() {
|
|
||||||
future.removeListener(SubscriberToChannelFutureBridge.this);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,98 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.channel;
|
|
||||||
|
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
|
||||||
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A holder for all transformations that are applied on a channel. Out of the box, it comes with a {@code String} and
|
|
||||||
* {@code byte[]} transformer to {@code ByteBuf}. Additional transformations can be applied using
|
|
||||||
* {@link #appendTransformer(AllocatingTransformer)}.
|
|
||||||
*/
|
|
||||||
public class WriteTransformations {
|
|
||||||
|
|
||||||
private TransformerChain transformers;
|
|
||||||
|
|
||||||
public boolean transform(Object msg, ByteBufAllocator allocator, List<Object> out) {
|
|
||||||
|
|
||||||
boolean transformed = false;
|
|
||||||
|
|
||||||
if (msg instanceof String) {
|
|
||||||
out.add(allocator.buffer().writeBytes(((String) msg).getBytes()));
|
|
||||||
transformed = true;
|
|
||||||
} else if (msg instanceof byte[]) {
|
|
||||||
out.add(allocator.buffer().writeBytes((byte[]) msg));
|
|
||||||
transformed = true;
|
|
||||||
} else if (null != transformers && transformers.acceptMessage(msg)) {
|
|
||||||
out.addAll(transformers.transform(msg, allocator));
|
|
||||||
transformed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return transformed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T, TT> void appendTransformer(AllocatingTransformer<T, TT> transformer) {
|
|
||||||
transformers = new TransformerChain(transformer, transformers);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void resetTransformations() {
|
|
||||||
transformers = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean acceptMessage(Object msg) {
|
|
||||||
return msg instanceof String || msg instanceof byte[] || null != transformers && transformers.acceptMessage(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
|
||||||
private static class TransformerChain extends AllocatingTransformer {
|
|
||||||
|
|
||||||
private final AllocatingTransformer start;
|
|
||||||
private final AllocatingTransformer next;
|
|
||||||
|
|
||||||
public TransformerChain(AllocatingTransformer start, AllocatingTransformer next) {
|
|
||||||
this.start = start;
|
|
||||||
this.next = next;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List transform(Object toTransform, ByteBufAllocator allocator) {
|
|
||||||
if (null == next) {
|
|
||||||
return start.transform(toTransform, allocator);
|
|
||||||
}
|
|
||||||
|
|
||||||
List transformed = start.transform(toTransform, allocator);
|
|
||||||
if (transformed.size() == 1) {
|
|
||||||
return next.transform(transformed.get(0), allocator);
|
|
||||||
} else {
|
|
||||||
final LinkedList toReturn = new LinkedList();
|
|
||||||
for (Object nextItem : transformed) {
|
|
||||||
toReturn.addAll(next.transform(nextItem, allocator));
|
|
||||||
}
|
|
||||||
return toReturn;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean acceptMessage(Object msg) {
|
|
||||||
return start.acceptMessage(msg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,78 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.channel;
|
|
||||||
|
|
||||||
import io.netty.channel.ChannelHandler;
|
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
|
||||||
import io.netty.handler.codec.MessageToMessageCodec;
|
|
||||||
import io.netty.util.ReferenceCountUtil;
|
|
||||||
import io.reactivex.netty.client.ClientConnectionToChannelBridge.ConnectionReuseEvent;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A {@link ChannelHandler} that transforms objects written to this channel. <p>
|
|
||||||
*
|
|
||||||
* Any {@code String} or {@code byte[]} written to the channel are converted to {@code ByteBuf} if no other
|
|
||||||
* {@link AllocatingTransformer} is added that accepts these types.
|
|
||||||
*
|
|
||||||
* If the last added {@link AllocatingTransformer} accepts the written message, then invoke all added transformers and
|
|
||||||
* skip the primitive conversions.
|
|
||||||
*/
|
|
||||||
public class WriteTransformer extends MessageToMessageCodec<Object, Object> {
|
|
||||||
|
|
||||||
private final WriteTransformations transformations = new WriteTransformations();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean acceptInboundMessage(Object msg) throws Exception {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean acceptOutboundMessage(Object msg) throws Exception {
|
|
||||||
return true;// Always return true and let the encode do the checking as opposed to be done at both places.
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void encode(ChannelHandlerContext ctx, Object msg, List<Object> out) throws Exception {
|
|
||||||
if (!transformations.transform(msg, ctx.alloc(), out)) {
|
|
||||||
/*
|
|
||||||
* M2MCodec will release the passed message after encode but we are adding the same object to out.
|
|
||||||
* So, the message needs to be retained and subsequently released by the next consumer in the pipeline.
|
|
||||||
*/
|
|
||||||
out.add(ReferenceCountUtil.retain(msg));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void decode(ChannelHandlerContext ctx, Object msg, List<Object> out) throws Exception {
|
|
||||||
// Never decode (acceptInbound) always returns false.
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
|
||||||
if (evt instanceof AppendTransformerEvent) {
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
AppendTransformerEvent ate = (AppendTransformerEvent) evt;
|
|
||||||
transformations.appendTransformer(ate.getTransformer());
|
|
||||||
} else if(evt instanceof ConnectionReuseEvent) {
|
|
||||||
transformations.resetTransformations();
|
|
||||||
}
|
|
||||||
|
|
||||||
super.userEventTriggered(ctx, evt);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,124 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.channel.events;
|
|
||||||
|
|
||||||
import io.reactivex.netty.events.EventListener;
|
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An event listener for all events releated to a {@link io.reactivex.netty.channel.Connection}
|
|
||||||
*/
|
|
||||||
public abstract class ConnectionEventListener implements EventListener {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event whenever any bytes are read on any open connection.
|
|
||||||
*
|
|
||||||
* @param bytesRead Number of bytes read.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public void onByteRead(long bytesRead) { }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event whenever any bytes are successfully written on any open connection.
|
|
||||||
*
|
|
||||||
* @param bytesWritten Number of bytes written.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public void onByteWritten(long bytesWritten) { }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event whenever a flush is issued on a connection.
|
|
||||||
*/
|
|
||||||
public void onFlushStart() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event whenever flush completes.
|
|
||||||
*
|
|
||||||
* @param duration Duration between flush start and completion.
|
|
||||||
* @param timeUnit Timeunit for the duration.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public void onFlushComplete(long duration, TimeUnit timeUnit) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event whenever a write is issued on a connection.
|
|
||||||
*/
|
|
||||||
public void onWriteStart() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event whenever data is written successfully on a connection. Use {@link #onByteWritten(long)} to capture number
|
|
||||||
* of bytes written.
|
|
||||||
*
|
|
||||||
* @param duration Duration between write start and completion.
|
|
||||||
* @param timeUnit Timeunit for the duration.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public void onWriteSuccess(long duration, TimeUnit timeUnit) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event whenever a write failed on a connection.
|
|
||||||
*
|
|
||||||
* @param duration Duration between write start and failure.
|
|
||||||
* @param timeUnit Timeunit for the duration.
|
|
||||||
* @param throwable Error that caused the failure..
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public void onWriteFailed(long duration, TimeUnit timeUnit, Throwable throwable) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event whenever a close of any connection is issued. This event will only be fired when the physical connection
|
|
||||||
* is closed and not when a pooled connection is closed and put back in the pool.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public void onConnectionCloseStart() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event whenever a close of any connection is successful.
|
|
||||||
*
|
|
||||||
* @param duration Duration between close start and completion.
|
|
||||||
* @param timeUnit Timeunit for the duration.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public void onConnectionCloseSuccess(long duration, TimeUnit timeUnit) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event whenever a connection close failed.
|
|
||||||
*
|
|
||||||
* @param duration Duration between close start and failure.
|
|
||||||
* @param timeUnit Timeunit for the duration.
|
|
||||||
* @param throwable Error that caused the failure.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public void onConnectionCloseFailed(long duration, TimeUnit timeUnit, Throwable throwable) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCustomEvent(Object event) { }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCustomEvent(Object event, long duration, TimeUnit timeUnit) { }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCustomEvent(Object event, Throwable throwable) { }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCustomEvent(Object event, long duration, TimeUnit timeUnit, Throwable throwable) { }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCompleted() { }
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,238 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.channel.events;
|
|
||||||
|
|
||||||
import io.reactivex.netty.events.EventListener;
|
|
||||||
import io.reactivex.netty.events.EventPublisher;
|
|
||||||
import io.reactivex.netty.events.EventSource;
|
|
||||||
import io.reactivex.netty.events.ListenersHolder;
|
|
||||||
import rx.Subscription;
|
|
||||||
import rx.functions.Action1;
|
|
||||||
import rx.functions.Action2;
|
|
||||||
import rx.functions.Action3;
|
|
||||||
import rx.functions.Action4;
|
|
||||||
import rx.functions.Action5;
|
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A publisher which is both {@link EventSource} and {@link EventListener} for connection events.
|
|
||||||
*
|
|
||||||
* @param <T> Type of listener to expect.
|
|
||||||
*/
|
|
||||||
public final class ConnectionEventPublisher<T extends ConnectionEventListener> extends ConnectionEventListener
|
|
||||||
implements EventSource<T>, EventPublisher {
|
|
||||||
|
|
||||||
private final Action2<T, Long> bytesReadAction = new Action2<T, Long>() {
|
|
||||||
@Override
|
|
||||||
public void call(T l, Long bytesRead) {
|
|
||||||
l.onByteRead(bytesRead);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final Action2<T, Long> bytesWrittenAction = new Action2<T, Long>() {
|
|
||||||
@Override
|
|
||||||
public void call(T l, Long bytesWritten) {
|
|
||||||
l.onByteWritten(bytesWritten);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final Action1<T> flushStartAction = new Action1<T>() {
|
|
||||||
@Override
|
|
||||||
public void call(T l) {
|
|
||||||
l.onFlushStart();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final Action3<T, Long, TimeUnit> flushCompleteAction = new Action3<T, Long, TimeUnit>() {
|
|
||||||
@Override
|
|
||||||
public void call(T l, Long duration, TimeUnit timeUnit) {
|
|
||||||
l.onFlushComplete(duration, timeUnit);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final Action1<T> writeStartAction = new Action1<T>() {
|
|
||||||
@Override
|
|
||||||
public void call(T l) {
|
|
||||||
l.onWriteStart();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final Action3<T, Long, TimeUnit> writeSuccessAction = new Action3<T, Long, TimeUnit>() {
|
|
||||||
@Override
|
|
||||||
public void call(T l, Long duration, TimeUnit timeUnit) {
|
|
||||||
l.onWriteSuccess(duration, timeUnit);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final Action4<T, Long, TimeUnit, Throwable> writeFailedAction =
|
|
||||||
new Action4<T, Long, TimeUnit, Throwable>() {
|
|
||||||
@Override
|
|
||||||
public void call(T l, Long duration, TimeUnit timeUnit, Throwable t) {
|
|
||||||
l.onWriteFailed(duration, timeUnit, t);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final Action1<T> closeStartAction = new Action1<T>() {
|
|
||||||
@Override
|
|
||||||
public void call(T l) {
|
|
||||||
l.onConnectionCloseStart();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final Action3<T, Long, TimeUnit> closeSuccessAction = new Action3<T, Long, TimeUnit>() {
|
|
||||||
@Override
|
|
||||||
public void call(T l, Long duration, TimeUnit timeUnit) {
|
|
||||||
l.onConnectionCloseSuccess(duration, timeUnit);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final Action4<T, Long, TimeUnit, Throwable> closeFailedAction =
|
|
||||||
new Action4<T, Long, TimeUnit, Throwable>() {
|
|
||||||
@Override
|
|
||||||
public void call(T l, Long duration, TimeUnit timeUnit, Throwable t) {
|
|
||||||
l.onConnectionCloseFailed(duration, timeUnit, t);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final Action2<T, Object> customEventAction = new Action2<T, Object>() {
|
|
||||||
@Override
|
|
||||||
public void call(T l, Object event) {
|
|
||||||
l.onCustomEvent(event);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final Action3<T, Throwable, Object> customEventErrorAction = new Action3<T, Throwable, Object>() {
|
|
||||||
@Override
|
|
||||||
public void call(T l, Throwable throwable, Object event) {
|
|
||||||
l.onCustomEvent(event, throwable);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final Action4<T, Long, TimeUnit, Object> customEventDurationAction = new Action4<T, Long, TimeUnit, Object>() {
|
|
||||||
@Override
|
|
||||||
public void call(T l, Long duration, TimeUnit timeUnit, Object event) {
|
|
||||||
l.onCustomEvent(event, duration, timeUnit);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final Action5<T, Long, TimeUnit, Throwable, Object> customEventDurationErrAction =
|
|
||||||
new Action5<T, Long, TimeUnit, Throwable, Object>() {
|
|
||||||
@Override
|
|
||||||
public void call(T l, Long duration, TimeUnit timeUnit, Throwable throwable, Object event) {
|
|
||||||
l.onCustomEvent(event, duration, timeUnit, throwable);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final ListenersHolder<T> listeners;
|
|
||||||
|
|
||||||
public ConnectionEventPublisher() {
|
|
||||||
listeners = new ListenersHolder<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConnectionEventPublisher(ConnectionEventPublisher<T> toCopy) {
|
|
||||||
listeners = toCopy.listeners.copy();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onByteRead(final long bytesRead) {
|
|
||||||
listeners.invokeListeners(bytesReadAction, bytesRead);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onByteWritten(long bytesWritten) {
|
|
||||||
listeners.invokeListeners(bytesWrittenAction, bytesWritten);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFlushStart() {
|
|
||||||
listeners.invokeListeners(flushStartAction);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFlushComplete(final long duration, final TimeUnit timeUnit) {
|
|
||||||
listeners.invokeListeners(flushCompleteAction, duration, timeUnit);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onWriteStart() {
|
|
||||||
listeners.invokeListeners(writeStartAction);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onWriteSuccess(final long duration, final TimeUnit timeUnit) {
|
|
||||||
listeners.invokeListeners(writeSuccessAction, duration, timeUnit);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onWriteFailed(final long duration, final TimeUnit timeUnit, final Throwable throwable) {
|
|
||||||
listeners.invokeListeners(writeFailedAction, duration, timeUnit, throwable);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConnectionCloseStart() {
|
|
||||||
listeners.invokeListeners(closeStartAction);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConnectionCloseSuccess(final long duration, final TimeUnit timeUnit) {
|
|
||||||
listeners.invokeListeners(closeSuccessAction, duration, timeUnit);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConnectionCloseFailed(final long duration, final TimeUnit timeUnit, final Throwable throwable) {
|
|
||||||
listeners.invokeListeners(closeFailedAction, duration, timeUnit, throwable);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCustomEvent(Object event) {
|
|
||||||
listeners.invokeListeners(customEventAction, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCustomEvent(Object event, long duration, TimeUnit timeUnit) {
|
|
||||||
listeners.invokeListeners(customEventDurationAction, duration, timeUnit, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCustomEvent(Object event, long duration, TimeUnit timeUnit, Throwable throwable) {
|
|
||||||
listeners.invokeListeners(customEventDurationErrAction, duration, timeUnit, throwable, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCustomEvent(Object event, Throwable throwable) {
|
|
||||||
listeners.invokeListeners(customEventErrorAction, throwable, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Subscription subscribe(T listener) {
|
|
||||||
return listeners.subscribe(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean publishingEnabled() {
|
|
||||||
return listeners.publishingEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConnectionEventPublisher<T> copy() {
|
|
||||||
return new ConnectionEventPublisher<>(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*Visible for testing*/ ListenersHolder<T> getListeners() {
|
|
||||||
return listeners;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,27 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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 rx.Observable;
|
|
||||||
|
|
||||||
public interface ChannelProvider {
|
|
||||||
|
|
||||||
Observable<Channel> newChannel(Observable<Channel> input);
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,29 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.reactivex.netty.client.events.ClientEventListener;
|
|
||||||
import io.reactivex.netty.events.EventPublisher;
|
|
||||||
import io.reactivex.netty.events.EventSource;
|
|
||||||
|
|
||||||
public interface ChannelProviderFactory {
|
|
||||||
|
|
||||||
ChannelProvider newProvider(Host host, EventSource<? super ClientEventListener> eventSource,
|
|
||||||
EventPublisher publisher, ClientEventListener clientPublisher);
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,202 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
import rx.Subscriber;
|
|
||||||
import rx.functions.Action1;
|
|
||||||
import rx.functions.Actions;
|
|
||||||
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 = Logger.getLogger(ClientConnectionToChannelBridge.class.getName());
|
|
||||||
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.log(Level.SEVERE, "No Event publisher bound to the channel, closing channel.");
|
|
||||||
ctx.channel().close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (eventPublisher.publishingEnabled() && null == eventListener) {
|
|
||||||
logger.log(Level.SEVERE, "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 {
|
|
||||||
// If pooled connection not sent to the subscriber, release to the pool.
|
|
||||||
event.getPooledConnection().close(false).subscribe(Actions.empty(), new Action1<Throwable>() {
|
|
||||||
@Override
|
|
||||||
public void call(Throwable throwable) {
|
|
||||||
logger.log(Level.SEVERE, "Error closing connection.", throwable);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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() {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,505 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.bootstrap.Bootstrap;
|
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
|
||||||
import io.netty.channel.Channel;
|
|
||||||
import io.netty.channel.ChannelDuplexHandler;
|
|
||||||
import io.netty.channel.ChannelFuture;
|
|
||||||
import io.netty.channel.ChannelFutureListener;
|
|
||||||
import io.netty.channel.ChannelHandler;
|
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
|
||||||
import io.netty.channel.ChannelInitializer;
|
|
||||||
import io.netty.channel.ChannelOption;
|
|
||||||
import io.netty.channel.ChannelPipeline;
|
|
||||||
import io.netty.channel.ChannelPromise;
|
|
||||||
import io.netty.channel.EventLoopGroup;
|
|
||||||
import io.netty.channel.epoll.EpollSocketChannel;
|
|
||||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
|
||||||
import io.netty.handler.logging.LogLevel;
|
|
||||||
import io.netty.handler.logging.LoggingHandler;
|
|
||||||
import io.netty.handler.ssl.SslContextBuilder;
|
|
||||||
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
|
|
||||||
import io.netty.util.concurrent.EventExecutorGroup;
|
|
||||||
import io.reactivex.netty.RxNetty;
|
|
||||||
import io.reactivex.netty.channel.ChannelSubscriberEvent;
|
|
||||||
import io.reactivex.netty.channel.ConnectionCreationFailedEvent;
|
|
||||||
import io.reactivex.netty.channel.DetachedChannelPipeline;
|
|
||||||
import io.reactivex.netty.channel.WriteTransformer;
|
|
||||||
import io.reactivex.netty.client.events.ClientEventListener;
|
|
||||||
import io.reactivex.netty.events.Clock;
|
|
||||||
import io.reactivex.netty.events.EventPublisher;
|
|
||||||
import io.reactivex.netty.events.EventSource;
|
|
||||||
import io.reactivex.netty.ssl.DefaultSslCodec;
|
|
||||||
import io.reactivex.netty.ssl.SslCodec;
|
|
||||||
import io.reactivex.netty.util.LoggingHandlerFactory;
|
|
||||||
import rx.Observable;
|
|
||||||
import rx.exceptions.Exceptions;
|
|
||||||
import rx.functions.Action1;
|
|
||||||
import rx.functions.Func0;
|
|
||||||
import rx.functions.Func1;
|
|
||||||
|
|
||||||
import javax.net.ssl.SSLEngine;
|
|
||||||
import java.net.SocketAddress;
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
|
|
||||||
import static io.reactivex.netty.HandlerNames.*;
|
|
||||||
import static java.util.concurrent.TimeUnit.NANOSECONDS;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A collection of state that a client holds. This supports the copy-on-write semantics of clients.
|
|
||||||
*
|
|
||||||
* @param <W> The type of objects written to the client owning this state.
|
|
||||||
* @param <R> The type of objects read from the client owning this state.
|
|
||||||
*/
|
|
||||||
public class ClientState<W, R> {
|
|
||||||
|
|
||||||
private final Observable<Host> hostStream;
|
|
||||||
private final ConnectionProviderFactory<W, R> factory;
|
|
||||||
private final DetachedChannelPipeline detachedPipeline;
|
|
||||||
private final Map<ChannelOption<?>, Object> options;
|
|
||||||
private final boolean isSecure;
|
|
||||||
private final EventLoopGroup eventLoopGroup;
|
|
||||||
private final Class<? extends Channel> channelClass;
|
|
||||||
private final ChannelProviderFactory channelProviderFactory;
|
|
||||||
|
|
||||||
protected ClientState(Observable<Host> hostStream, ConnectionProviderFactory<W, R> factory,
|
|
||||||
DetachedChannelPipeline detachedPipeline, EventLoopGroup eventLoopGroup,
|
|
||||||
Class<? extends Channel> channelClass) {
|
|
||||||
this.eventLoopGroup = eventLoopGroup;
|
|
||||||
this.channelClass = channelClass;
|
|
||||||
options = new LinkedHashMap<>(); /// Same as netty bootstrap, order matters.
|
|
||||||
this.hostStream = hostStream;
|
|
||||||
this.factory = factory;
|
|
||||||
this.detachedPipeline = detachedPipeline;
|
|
||||||
isSecure = false;
|
|
||||||
channelProviderFactory = new ChannelProviderFactory() {
|
|
||||||
@Override
|
|
||||||
public ChannelProvider newProvider(Host host, EventSource<? super ClientEventListener> eventSource,
|
|
||||||
EventPublisher publisher, ClientEventListener clientPublisher) {
|
|
||||||
return new ChannelProvider() {
|
|
||||||
@Override
|
|
||||||
public Observable<Channel> newChannel(Observable<Channel> input) {
|
|
||||||
return input;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected ClientState(ClientState<W, R> toCopy, ChannelOption<?> option, Object value) {
|
|
||||||
options = new LinkedHashMap<>(toCopy.options); // Since, we are adding an option, copy it.
|
|
||||||
options.put(option, value);
|
|
||||||
detachedPipeline = toCopy.detachedPipeline;
|
|
||||||
hostStream = toCopy.hostStream;
|
|
||||||
factory = toCopy.factory;
|
|
||||||
eventLoopGroup = toCopy.eventLoopGroup;
|
|
||||||
channelClass = toCopy.channelClass;
|
|
||||||
isSecure = toCopy.isSecure;
|
|
||||||
channelProviderFactory = toCopy.channelProviderFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected ClientState(ClientState<?, ?> toCopy, DetachedChannelPipeline newPipeline, boolean secure) {
|
|
||||||
final ClientState<W, R> toCopyCast = toCopy.cast();
|
|
||||||
options = toCopy.options;
|
|
||||||
hostStream = toCopy.hostStream;
|
|
||||||
factory = toCopyCast.factory;
|
|
||||||
eventLoopGroup = toCopy.eventLoopGroup;
|
|
||||||
channelClass = toCopy.channelClass;
|
|
||||||
detachedPipeline = newPipeline;
|
|
||||||
isSecure = secure;
|
|
||||||
channelProviderFactory = toCopyCast.channelProviderFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected ClientState(ClientState<?, ?> toCopy, ChannelProviderFactory newFactory) {
|
|
||||||
final ClientState<W, R> toCopyCast = toCopy.cast();
|
|
||||||
options = toCopy.options;
|
|
||||||
hostStream = toCopy.hostStream;
|
|
||||||
factory = toCopyCast.factory;
|
|
||||||
eventLoopGroup = toCopy.eventLoopGroup;
|
|
||||||
channelClass = toCopy.channelClass;
|
|
||||||
detachedPipeline = toCopy.detachedPipeline;
|
|
||||||
channelProviderFactory = newFactory;
|
|
||||||
isSecure = toCopy.isSecure;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected ClientState(ClientState<?, ?> toCopy, SslCodec sslCodec) {
|
|
||||||
this(toCopy, toCopy.detachedPipeline.copy(new TailHandlerFactory(true)).configure(sslCodec), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> ClientState<W, R> channelOption(ChannelOption<T> option, T value) {
|
|
||||||
return new ClientState<>(this, option, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <WW, RR> ClientState<WW, RR> addChannelHandlerFirst(String name, Func0<ChannelHandler> handlerFactory) {
|
|
||||||
ClientState<WW, RR> copy = copy();
|
|
||||||
copy.detachedPipeline.addFirst(name, handlerFactory);
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public <WW, RR> ClientState<WW, RR> addChannelHandlerFirst(EventExecutorGroup group, String name,
|
|
||||||
Func0<ChannelHandler> handlerFactory) {
|
|
||||||
ClientState<WW, RR> copy = copy();
|
|
||||||
copy.detachedPipeline.addFirst(group, name, handlerFactory);
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public <WW, RR> ClientState<WW, RR> addChannelHandlerLast(String name, Func0<ChannelHandler> handlerFactory) {
|
|
||||||
ClientState<WW, RR> copy = copy();
|
|
||||||
copy.detachedPipeline.addLast(name, handlerFactory);
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public <WW, RR> ClientState<WW, RR> addChannelHandlerLast(EventExecutorGroup group, String name,
|
|
||||||
Func0<ChannelHandler> handlerFactory) {
|
|
||||||
ClientState<WW, RR> copy = copy();
|
|
||||||
copy.detachedPipeline.addLast(group, name, handlerFactory);
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public <WW, RR> ClientState<WW, RR> addChannelHandlerBefore(String baseName, String name,
|
|
||||||
Func0<ChannelHandler> handlerFactory) {
|
|
||||||
ClientState<WW, RR> copy = copy();
|
|
||||||
copy.detachedPipeline.addBefore(baseName, name, handlerFactory);
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public <WW, RR> ClientState<WW, RR> addChannelHandlerBefore(EventExecutorGroup group, String baseName,
|
|
||||||
String name, Func0<ChannelHandler> handlerFactory) {
|
|
||||||
ClientState<WW, RR> copy = copy();
|
|
||||||
copy.detachedPipeline.addBefore(group, baseName, name, handlerFactory);
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public <WW, RR> ClientState<WW, RR> addChannelHandlerAfter(String baseName, String name,
|
|
||||||
Func0<ChannelHandler> handlerFactory) {
|
|
||||||
ClientState<WW, RR> copy = copy();
|
|
||||||
copy.detachedPipeline.addAfter(baseName, name, handlerFactory);
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public <WW, RR> ClientState<WW, RR> addChannelHandlerAfter(EventExecutorGroup group, String baseName,
|
|
||||||
String name, Func0<ChannelHandler> handlerFactory) {
|
|
||||||
ClientState<WW, RR> copy = copy();
|
|
||||||
copy.detachedPipeline.addAfter(group, baseName, name, handlerFactory);
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public <WW, RR> ClientState<WW, RR> pipelineConfigurator(Action1<ChannelPipeline> pipelineConfigurator) {
|
|
||||||
ClientState<WW, RR> copy = copy();
|
|
||||||
copy.detachedPipeline.configure(pipelineConfigurator);
|
|
||||||
return copy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientState<W, R> enableWireLogging(final LogLevel wireLoggingLevel) {
|
|
||||||
return enableWireLogging(LoggingHandler.class.getName(), wireLoggingLevel);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientState<W, R> enableWireLogging(String name, final LogLevel wireLoggingLevel) {
|
|
||||||
return addChannelHandlerFirst(WireLogging.getName(),
|
|
||||||
LoggingHandlerFactory.getFactory(name, wireLoggingLevel));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <WW, RR> ClientState<WW, RR> create(ConnectionProviderFactory<WW, RR> factory,
|
|
||||||
Observable<Host> hostStream) {
|
|
||||||
return create(newChannelPipeline(new TailHandlerFactory(false)), factory, hostStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <WW, RR> ClientState<WW, RR> create(ConnectionProviderFactory<WW, RR> factory,
|
|
||||||
Observable<Host> hostStream,
|
|
||||||
EventLoopGroup eventLoopGroup,
|
|
||||||
Class<? extends Channel> channelClass) {
|
|
||||||
return new ClientState<>(hostStream, factory, newChannelPipeline(new TailHandlerFactory(false)), eventLoopGroup,
|
|
||||||
channelClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <WW, RR> ClientState<WW, RR> create(DetachedChannelPipeline detachedPipeline,
|
|
||||||
ConnectionProviderFactory<WW, RR> factory,
|
|
||||||
Observable<Host> hostStream) {
|
|
||||||
return create(detachedPipeline, factory, hostStream, defaultEventloopGroup(), defaultSocketChannelClass());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <WW, RR> ClientState<WW, RR> create(DetachedChannelPipeline detachedPipeline,
|
|
||||||
ConnectionProviderFactory<WW, RR> factory,
|
|
||||||
Observable<Host> hostStream,
|
|
||||||
EventLoopGroup eventLoopGroup,
|
|
||||||
Class<? extends Channel> channelClass) {
|
|
||||||
return new ClientState<>(hostStream, factory, detachedPipeline, eventLoopGroup, channelClass);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static DetachedChannelPipeline newChannelPipeline(TailHandlerFactory thf) {
|
|
||||||
return new DetachedChannelPipeline(thf)
|
|
||||||
.addLast(WriteTransformer.getName(), new Func0<ChannelHandler>() {
|
|
||||||
@Override
|
|
||||||
public ChannelHandler call() {
|
|
||||||
return new WriteTransformer();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public Bootstrap newBootstrap(final EventPublisher eventPublisher, final ClientEventListener eventListener) {
|
|
||||||
final Bootstrap nettyBootstrap = new Bootstrap().group(eventLoopGroup)
|
|
||||||
.channel(channelClass)
|
|
||||||
.option(ChannelOption.AUTO_READ, false);// by default do not read content unless asked.
|
|
||||||
|
|
||||||
for (Entry<ChannelOption<?>, Object> optionEntry : options.entrySet()) {
|
|
||||||
// Type is just for safety for user of ClientState, internally in Bootstrap, types are thrown on the floor.
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
ChannelOption<Object> key = (ChannelOption<Object>) optionEntry.getKey();
|
|
||||||
nettyBootstrap.option(key, optionEntry.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
nettyBootstrap.handler(new ChannelInitializer<Channel>() {
|
|
||||||
@Override
|
|
||||||
protected void initChannel(Channel ch) throws Exception {
|
|
||||||
ch.pipeline().addLast(ClientChannelActiveBufferingHandler.getName(),
|
|
||||||
new ChannelActivityBufferingHandler(eventPublisher, eventListener));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return nettyBootstrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DetachedChannelPipeline unsafeDetachedPipeline() {
|
|
||||||
return detachedPipeline;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<ChannelOption<?>, Object> unsafeChannelOptions() {
|
|
||||||
return options;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientState<W, R> channelProviderFactory(ChannelProviderFactory factory) {
|
|
||||||
return new ClientState<>(this, factory);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientState<W, R> secure(Func1<ByteBufAllocator, SSLEngine> sslEngineFactory) {
|
|
||||||
return secure(new DefaultSslCodec(sslEngineFactory));
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientState<W, R> secure(SSLEngine sslEngine) {
|
|
||||||
return secure(new DefaultSslCodec(sslEngine));
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientState<W, R> secure(SslCodec sslCodec) {
|
|
||||||
return new ClientState<>(this, sslCodec);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientState<W, R> unsafeSecure() {
|
|
||||||
return secure(new DefaultSslCodec(new Func1<ByteBufAllocator, SSLEngine>() {
|
|
||||||
@Override
|
|
||||||
public SSLEngine call(ByteBufAllocator allocator) {
|
|
||||||
try {
|
|
||||||
return SslContextBuilder.forClient()
|
|
||||||
.trustManager(InsecureTrustManagerFactory.INSTANCE)
|
|
||||||
.build()
|
|
||||||
.newEngine(allocator);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw Exceptions.propagate(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
private <WW, RR> ClientState<WW, RR> copy() {
|
|
||||||
TailHandlerFactory newTail = new TailHandlerFactory(isSecure);
|
|
||||||
return new ClientState<>(this, detachedPipeline.copy(newTail), isSecure);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConnectionProviderFactory<W, R> getFactory() {
|
|
||||||
return factory;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Observable<Host> getHostStream() {
|
|
||||||
return hostStream;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ChannelProviderFactory getChannelProviderFactory() {
|
|
||||||
return channelProviderFactory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private <WW, RR> ClientState<WW, RR> cast() {
|
|
||||||
return (ClientState<WW, RR>) this;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static class TailHandlerFactory implements Action1<ChannelPipeline> {
|
|
||||||
|
|
||||||
private final boolean isSecure;
|
|
||||||
|
|
||||||
public TailHandlerFactory(boolean isSecure) {
|
|
||||||
this.isSecure = isSecure;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void call(ChannelPipeline pipeline) {
|
|
||||||
ClientConnectionToChannelBridge.addToPipeline(pipeline, isSecure);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static EventLoopGroup defaultEventloopGroup() {
|
|
||||||
return RxNetty.getRxEventLoopProvider().globalClientEventLoop(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Class<? extends Channel> defaultSocketChannelClass() {
|
|
||||||
return RxNetty.isUsingNativeTransport() ? EpollSocketChannel.class : NioSocketChannel.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clients construct the pipeline, outside of the {@link ChannelInitializer} through {@link ChannelProvider}.
|
|
||||||
* Thus channel registration and activation events may be lost due to a race condition when the channel is active
|
|
||||||
* before the pipeline is configured.
|
|
||||||
* This handler buffers, the channel events till the time, a subscriber appears for channel establishment.
|
|
||||||
*/
|
|
||||||
private static class ChannelActivityBufferingHandler extends ChannelDuplexHandler {
|
|
||||||
|
|
||||||
private enum State {
|
|
||||||
Initialized,
|
|
||||||
Registered,
|
|
||||||
Active,
|
|
||||||
Inactive,
|
|
||||||
ChannelSubscribed
|
|
||||||
}
|
|
||||||
|
|
||||||
private State state = State.Initialized;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unregistered state will hide the active/inactive state, hence this is a different flag.
|
|
||||||
*/
|
|
||||||
private boolean unregistered;
|
|
||||||
private long connectStartTimeNanos;
|
|
||||||
private final EventPublisher eventPublisher;
|
|
||||||
private final ClientEventListener eventListener;
|
|
||||||
|
|
||||||
private ChannelActivityBufferingHandler(EventPublisher eventPublisher, ClientEventListener eventListener) {
|
|
||||||
this.eventPublisher = eventPublisher;
|
|
||||||
this.eventListener = eventListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
|
||||||
public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress,
|
|
||||||
ChannelPromise promise) throws Exception {
|
|
||||||
|
|
||||||
connectStartTimeNanos = Clock.newStartTimeNanos();
|
|
||||||
|
|
||||||
if (eventPublisher.publishingEnabled()) {
|
|
||||||
eventListener.onConnectStart();
|
|
||||||
promise.addListener(new ChannelFutureListener() {
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
|
||||||
public void operationComplete(ChannelFuture future) throws Exception {
|
|
||||||
if (eventPublisher.publishingEnabled()) {
|
|
||||||
long endTimeNanos = Clock.onEndNanos(connectStartTimeNanos);
|
|
||||||
if (!future.isSuccess()) {
|
|
||||||
eventListener.onConnectFailed(endTimeNanos, NANOSECONDS, future.cause());
|
|
||||||
} else {
|
|
||||||
eventListener.onConnectSuccess(endTimeNanos, NANOSECONDS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
super.connect(ctx, remoteAddress, localAddress, promise);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
|
|
||||||
if (State.ChannelSubscribed == state) {
|
|
||||||
super.channelRegistered(ctx);
|
|
||||||
} else {
|
|
||||||
state = State.Registered;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
|
|
||||||
if (State.ChannelSubscribed == state) {
|
|
||||||
super.channelUnregistered(ctx);
|
|
||||||
} else {
|
|
||||||
unregistered = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
|
||||||
if (State.ChannelSubscribed == state) {
|
|
||||||
super.channelActive(ctx);
|
|
||||||
} else {
|
|
||||||
state = State.Active;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
|
||||||
if (State.ChannelSubscribed == state) {
|
|
||||||
super.channelInactive(ctx);
|
|
||||||
} else {
|
|
||||||
state = State.Inactive;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
|
||||||
if (evt instanceof ChannelSubscriberEvent) {
|
|
||||||
final State existingState = state;
|
|
||||||
state = State.ChannelSubscribed;
|
|
||||||
super.userEventTriggered(ctx, evt);
|
|
||||||
final ChannelPipeline pipeline = ctx.channel().pipeline();
|
|
||||||
switch (existingState) {
|
|
||||||
case Initialized:
|
|
||||||
break;
|
|
||||||
case Registered:
|
|
||||||
pipeline.fireChannelRegistered();
|
|
||||||
break;
|
|
||||||
case Active:
|
|
||||||
pipeline.fireChannelRegistered();
|
|
||||||
pipeline.fireChannelActive();
|
|
||||||
break;
|
|
||||||
case Inactive:
|
|
||||||
pipeline.fireChannelRegistered();
|
|
||||||
pipeline.fireChannelActive();
|
|
||||||
pipeline.fireChannelInactive();
|
|
||||||
break;
|
|
||||||
case ChannelSubscribed:
|
|
||||||
// Duplicate event, ignore.
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (unregistered) {
|
|
||||||
pipeline.fireChannelUnregistered();
|
|
||||||
}
|
|
||||||
} else if (evt instanceof ConnectionCreationFailedEvent) {
|
|
||||||
ConnectionCreationFailedEvent failedEvent = (ConnectionCreationFailedEvent) evt;
|
|
||||||
onConnectFailedEvent(failedEvent);
|
|
||||||
super.userEventTriggered(ctx, evt);
|
|
||||||
} else {
|
|
||||||
super.userEventTriggered(ctx, evt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private void onConnectFailedEvent(ConnectionCreationFailedEvent event) {
|
|
||||||
if (eventPublisher.publishingEnabled()) {
|
|
||||||
eventListener.onConnectFailed(connectStartTimeNanos, NANOSECONDS, event.getThrowable());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.reactivex.netty.channel.Connection;
|
|
||||||
import rx.Observable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A contract to control how connections are established from a client.
|
|
||||||
*
|
|
||||||
* @param <W> The type of objects written on the connections created by this provider.
|
|
||||||
* @param <R> The type of objects read from the connections created by this provider.
|
|
||||||
*/
|
|
||||||
public interface ConnectionProvider<W, R> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an {@code Observable} that emits a single connection every time it is subscribed.
|
|
||||||
*
|
|
||||||
* @return An {@code Observable} that emits a single connection every time it is subscribed.
|
|
||||||
*/
|
|
||||||
Observable<Connection<R, W>> newConnectionRequest();
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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 rx.Observable;
|
|
||||||
|
|
||||||
public interface ConnectionProviderFactory<W, R> {
|
|
||||||
|
|
||||||
ConnectionProvider<W, R> newProvider(Observable<HostConnector<W, R>> hosts);
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,42 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.reactivex.netty.channel.Connection;
|
|
||||||
import rx.Observable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A connection request that is used to create connections for different protocols.
|
|
||||||
*
|
|
||||||
* <h2>Mutations</h2>
|
|
||||||
*
|
|
||||||
* All mutations to this request that creates a brand new instance.
|
|
||||||
*
|
|
||||||
* <h2> Inititating connections</h2>
|
|
||||||
*
|
|
||||||
* A new connection is initiated every time {@link ConnectionRequest#subscribe()} is called and is the only way of
|
|
||||||
* creating connections.
|
|
||||||
*
|
|
||||||
* @param <W> The type of the objects that are written to the connection created by this request.
|
|
||||||
* @param <R> The type of objects that are read from the connection created by this request.
|
|
||||||
*/
|
|
||||||
public abstract class ConnectionRequest<W, R> extends Observable<Connection<R, W>> {
|
|
||||||
|
|
||||||
protected ConnectionRequest(OnSubscribe<Connection<R, W>> f) {
|
|
||||||
super(f);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,70 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 rx.Observable;
|
|
||||||
|
|
||||||
import java.net.SocketAddress;
|
|
||||||
|
|
||||||
public final class Host {
|
|
||||||
|
|
||||||
private final SocketAddress host;
|
|
||||||
private final Observable<Void> closeNotifier;
|
|
||||||
|
|
||||||
public Host(SocketAddress host) {
|
|
||||||
this(host, Observable.<Void>never());
|
|
||||||
}
|
|
||||||
|
|
||||||
public Host(SocketAddress host, Observable<Void> closeNotifier) {
|
|
||||||
this.host = host;
|
|
||||||
this.closeNotifier = closeNotifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SocketAddress getHost() {
|
|
||||||
return host;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Observable<Void> getCloseNotifier() {
|
|
||||||
return closeNotifier;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!(o instanceof Host)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
Host host1 = (Host) o;
|
|
||||||
|
|
||||||
if (host != null? !host.equals(host1.host) : host1.host != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return closeNotifier != null? closeNotifier.equals(host1.closeNotifier) : host1.closeNotifier == null;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
int result = host != null? host.hashCode() : 0;
|
|
||||||
result = 31 * result + (closeNotifier != null? closeNotifier.hashCode() : 0);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,110 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.reactivex.netty.client.events.ClientEventListener;
|
|
||||||
import io.reactivex.netty.events.EventPublisher;
|
|
||||||
import io.reactivex.netty.events.EventSource;
|
|
||||||
import rx.Subscription;
|
|
||||||
|
|
||||||
public class HostConnector<W, R> implements EventSource<ClientEventListener> {
|
|
||||||
|
|
||||||
private final Host host;
|
|
||||||
private final ConnectionProvider<W, R> connectionProvider;
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
private final EventSource eventSource;
|
|
||||||
private final EventPublisher publisher;
|
|
||||||
private final ClientEventListener clientPublisher;
|
|
||||||
|
|
||||||
public HostConnector(Host host, ConnectionProvider<W, R> connectionProvider,
|
|
||||||
EventSource<? extends ClientEventListener> eventSource, EventPublisher publisher,
|
|
||||||
ClientEventListener clientPublisher) {
|
|
||||||
this.host = host;
|
|
||||||
this.connectionProvider = connectionProvider;
|
|
||||||
this.eventSource = eventSource;
|
|
||||||
this.publisher = publisher;
|
|
||||||
this.clientPublisher = clientPublisher;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HostConnector(HostConnector<W, R> source, ConnectionProvider<W, R> connectionProvider) {
|
|
||||||
this.connectionProvider = connectionProvider;
|
|
||||||
host = source.host;
|
|
||||||
eventSource = source.eventSource;
|
|
||||||
clientPublisher = source.clientPublisher;
|
|
||||||
publisher = source.publisher;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Host getHost() {
|
|
||||||
return host;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ConnectionProvider<W, R> getConnectionProvider() {
|
|
||||||
return connectionProvider;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientEventListener getClientPublisher() {
|
|
||||||
return clientPublisher;
|
|
||||||
}
|
|
||||||
|
|
||||||
public EventPublisher getEventPublisher() {
|
|
||||||
return publisher;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public Subscription subscribe(ClientEventListener listener) {
|
|
||||||
return eventSource.subscribe(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!(o instanceof HostConnector)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
HostConnector<?, ?> that = (HostConnector<?, ?>) o;
|
|
||||||
|
|
||||||
if (host != null? !host.equals(that.host) : that.host != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (connectionProvider != null? !connectionProvider.equals(that.connectionProvider) :
|
|
||||||
that.connectionProvider != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (eventSource != null? !eventSource.equals(that.eventSource) : that.eventSource != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (publisher != null? !publisher.equals(that.publisher) : that.publisher != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return clientPublisher != null? clientPublisher.equals(that.clientPublisher) : that.clientPublisher == null;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
int result = host != null? host.hashCode() : 0;
|
|
||||||
result = 31 * result + (connectionProvider != null? connectionProvider.hashCode() : 0);
|
|
||||||
result = 31 * result + (eventSource != null? eventSource.hashCode() : 0);
|
|
||||||
result = 31 * result + (publisher != null? publisher.hashCode() : 0);
|
|
||||||
result = 31 * result + (clientPublisher != null? clientPublisher.hashCode() : 0);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,111 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.events;
|
|
||||||
|
|
||||||
import io.reactivex.netty.channel.events.ConnectionEventListener;
|
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
public class ClientEventListener extends ConnectionEventListener {
|
|
||||||
/**
|
|
||||||
* Event whenever a new connection attempt is made.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public void onConnectStart() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event whenever a new connection is successfully established.
|
|
||||||
*
|
|
||||||
* @param duration Duration between connect start and completion.
|
|
||||||
* @param timeUnit Timeunit for the duration.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public void onConnectSuccess(long duration, TimeUnit timeUnit) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event whenever a connect attempt failed.
|
|
||||||
*
|
|
||||||
* @param duration Duration between connect start and failure.
|
|
||||||
* @param timeUnit Timeunit for the duration.
|
|
||||||
* @param throwable Error that caused the failure.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public void onConnectFailed(long duration, TimeUnit timeUnit, Throwable throwable) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event whenever a connection release to the pool is initiated (by closing the connection)
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public void onPoolReleaseStart() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event whenever a connection is successfully released to the pool.
|
|
||||||
*
|
|
||||||
* @param duration Duration between release start and completion.
|
|
||||||
* @param timeUnit Timeunit for the duration.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public void onPoolReleaseSuccess(long duration, TimeUnit timeUnit) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event whenever a connection release to pool fails.
|
|
||||||
*
|
|
||||||
* @param duration Duration between release start and failure.
|
|
||||||
* @param timeUnit Timeunit for the duration.
|
|
||||||
* @param throwable Error that caused the failure.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public void onPoolReleaseFailed(long duration, TimeUnit timeUnit, Throwable throwable) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event whenever an idle connection is removed/evicted from the pool.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public void onPooledConnectionEviction() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event whenever a connection is reused from the pool.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public void onPooledConnectionReuse() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event whenever an acquire from the pool is initiated.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public void onPoolAcquireStart() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event whenever an acquire from the pool is successful.
|
|
||||||
*
|
|
||||||
* @param duration Duration between acquire start and completion.
|
|
||||||
* @param timeUnit Timeunit for the duration.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public void onPoolAcquireSuccess(long duration, TimeUnit timeUnit) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event whenever an acquire from the pool failed.
|
|
||||||
*
|
|
||||||
* @param duration Duration between acquire start and failure.
|
|
||||||
* @param timeUnit Timeunit for the duration.
|
|
||||||
* @param throwable Error that caused the failure.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public void onPoolAcquireFailed(long duration, TimeUnit timeUnit, Throwable throwable) {}
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.internal;
|
|
||||||
|
|
||||||
import io.reactivex.netty.channel.Connection;
|
|
||||||
import io.reactivex.netty.client.ConnectionProvider;
|
|
||||||
import io.reactivex.netty.client.HostConnector;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
import rx.Observable;
|
|
||||||
import rx.functions.Action1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A connection provider that only ever fetches a single host from the host stream provided to it.
|
|
||||||
*
|
|
||||||
* @param <W> The type of objects written on the connections created by this provider.
|
|
||||||
* @param <R> The type of objects read from the connections created by this provider.
|
|
||||||
*/
|
|
||||||
public class SingleHostConnectionProvider<W, R> implements ConnectionProvider<W, R> {
|
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(SingleHostConnectionProvider.class.getName());
|
|
||||||
|
|
||||||
private volatile ConnectionProvider<W, R> provider;
|
|
||||||
|
|
||||||
public SingleHostConnectionProvider(Observable<HostConnector<W, R>> connectors) {
|
|
||||||
connectors.toSingle()
|
|
||||||
.subscribe(connector -> provider = connector.getConnectionProvider(),
|
|
||||||
t -> logger.log(Level.SEVERE, "Failed while fetching a host connector from a scalar host source", t));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Connection<R, W>> newConnectionRequest() {
|
|
||||||
return null != provider ? provider.newConnectionRequest()
|
|
||||||
: Observable.<Connection<R, W>>error(new IllegalStateException("No hosts available."));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,113 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.loadbalancer;
|
|
||||||
|
|
||||||
import io.reactivex.netty.channel.Connection;
|
|
||||||
import io.reactivex.netty.client.ConnectionProvider;
|
|
||||||
import io.reactivex.netty.client.Host;
|
|
||||||
import io.reactivex.netty.client.HostConnector;
|
|
||||||
import io.reactivex.netty.client.events.ClientEventListener;
|
|
||||||
import rx.Observable;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.ThreadLocalRandom;
|
|
||||||
|
|
||||||
public abstract class AbstractP2CStrategy<W, R, L extends ClientEventListener> implements LoadBalancingStrategy<W, R> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ConnectionProvider<W, R> newStrategy(final List<HostHolder<W, R>> hosts) {
|
|
||||||
newHostsList(hosts.size());
|
|
||||||
return new ConnectionProvider<W, R>() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Connection<R, W>> newConnectionRequest() {
|
|
||||||
HostHolder<W, R> selected = null;
|
|
||||||
if (hosts.isEmpty()) {
|
|
||||||
noUsableHostsFound();
|
|
||||||
return Observable.error(NoHostsAvailableException.EMPTY_INSTANCE);
|
|
||||||
} else if (hosts.size() == 1) {
|
|
||||||
HostHolder<W, R> holder = hosts.get(0);
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
L eventListener = (L) holder.getEventListener();
|
|
||||||
double weight = getWeight(eventListener);
|
|
||||||
if (isUnusable(weight)) {
|
|
||||||
noUsableHostsFound();
|
|
||||||
return Observable.error(new NoHostsAvailableException("No usable hosts found."));
|
|
||||||
}
|
|
||||||
selected = holder;
|
|
||||||
} else {
|
|
||||||
ThreadLocalRandom rand = ThreadLocalRandom.current();
|
|
||||||
for (int i = 0; i < 5; i++) {
|
|
||||||
int pos = rand.nextInt(hosts.size());
|
|
||||||
HostHolder<W, R> first = hosts.get(pos);
|
|
||||||
int pos2 = (rand.nextInt(hosts.size() - 1) + pos + 1) % hosts.size();
|
|
||||||
HostHolder<W, R> second = hosts.get(pos2);
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
double w1 = getWeight((L) first.getEventListener());
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
double w2 = getWeight((L) second.getEventListener());
|
|
||||||
|
|
||||||
if (w1 > w2) {
|
|
||||||
selected = first;
|
|
||||||
break;
|
|
||||||
} else if (w1 < w2) {
|
|
||||||
selected = second;
|
|
||||||
break;
|
|
||||||
} else if (!isUnusable(w1)) {
|
|
||||||
selected = first;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
foundTwoUnusableHosts();
|
|
||||||
}
|
|
||||||
if (null == selected) {
|
|
||||||
noUsableHostsFound();
|
|
||||||
return Observable.error(new NoHostsAvailableException("No usable hosts found after 5 tries."));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return selected.getConnector().getConnectionProvider().newConnectionRequest();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean isUnusable(double weight) {
|
|
||||||
return weight < 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HostHolder<W, R> toHolder(HostConnector<W, R> connector) {
|
|
||||||
return new HostHolder<>(connector, newListener(connector.getHost()));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract L newListener(Host host);
|
|
||||||
|
|
||||||
protected abstract double getWeight(L listener);
|
|
||||||
|
|
||||||
protected void noUsableHostsFound() {
|
|
||||||
// No Op by default
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void foundTwoUnusableHosts() {
|
|
||||||
// No Op by default
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void newHostsList(int size) {
|
|
||||||
// No Op by default
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.loadbalancer;
|
|
||||||
|
|
||||||
import rx.Single;
|
|
||||||
import rx.functions.Func1;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public interface HostCollector {
|
|
||||||
|
|
||||||
<W, R> Func1<HostUpdate<W, R>, Single<List<HostHolder<W, R>>>> newCollector();
|
|
||||||
|
|
||||||
final class HostUpdate<W, R> {
|
|
||||||
|
|
||||||
public enum Action{ Add, Remove }
|
|
||||||
|
|
||||||
private final Action action;
|
|
||||||
private final HostHolder<W, R> hostHolder;
|
|
||||||
|
|
||||||
public HostUpdate(Action action, HostHolder<W, R> hostHolder) {
|
|
||||||
this.action = action;
|
|
||||||
this.hostHolder = hostHolder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Action getAction() {
|
|
||||||
return action;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HostHolder<W, R> getHostHolder() {
|
|
||||||
return hostHolder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.loadbalancer;
|
|
||||||
|
|
||||||
import io.reactivex.netty.client.HostConnector;
|
|
||||||
import io.reactivex.netty.client.events.ClientEventListener;
|
|
||||||
|
|
||||||
public class HostHolder<W, R> {
|
|
||||||
|
|
||||||
private final HostConnector<W, R> connector;
|
|
||||||
private final ClientEventListener eventListener;
|
|
||||||
|
|
||||||
public HostHolder(HostConnector<W, R> connector, ClientEventListener eventListener) {
|
|
||||||
this.connector = connector;
|
|
||||||
this.eventListener = eventListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HostConnector<W, R> getConnector() {
|
|
||||||
return connector;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ClientEventListener getEventListener() {
|
|
||||||
return eventListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!(o instanceof HostHolder)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
HostHolder<?, ?> that = (HostHolder<?, ?>) o;
|
|
||||||
|
|
||||||
if (connector != null? !connector.equals(that.connector) : that.connector != null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return eventListener != null? eventListener.equals(that.eventListener) : that.eventListener == null;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
int result = connector != null? connector.hashCode() : 0;
|
|
||||||
result = 31 * result + (eventListener != null? eventListener.hashCode() : 0);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,94 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.loadbalancer;
|
|
||||||
|
|
||||||
import io.reactivex.netty.channel.Connection;
|
|
||||||
import io.reactivex.netty.client.ConnectionProvider;
|
|
||||||
import io.reactivex.netty.client.ConnectionProviderFactory;
|
|
||||||
import io.reactivex.netty.client.HostConnector;
|
|
||||||
import io.reactivex.netty.client.loadbalancer.HostCollector.HostUpdate;
|
|
||||||
import io.reactivex.netty.client.loadbalancer.HostCollector.HostUpdate.Action;
|
|
||||||
import io.reactivex.netty.internal.VoidToAnythingCast;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
import rx.Observable;
|
|
||||||
import rx.Single;
|
|
||||||
import rx.functions.Func1;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class LoadBalancerFactory<W, R> implements ConnectionProviderFactory<W, R> {
|
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(LoadBalancerFactory.class.getName());
|
|
||||||
|
|
||||||
private final LoadBalancingStrategy<W, R> strategy;
|
|
||||||
private final HostCollector collector;
|
|
||||||
|
|
||||||
private LoadBalancerFactory(LoadBalancingStrategy<W, R> strategy, HostCollector collector) {
|
|
||||||
this.strategy = strategy;
|
|
||||||
this.collector = collector;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ConnectionProvider<W, R> newProvider(Observable<HostConnector<W, R>> hosts) {
|
|
||||||
|
|
||||||
return new ConnectionProviderImpl(hosts.map(connector -> {
|
|
||||||
HostHolder<W, R> newHolder = strategy.toHolder(connector);
|
|
||||||
connector.subscribe(newHolder.getEventListener());
|
|
||||||
return newHolder;
|
|
||||||
}).flatMap((Func1<HostHolder<W, R>, Observable<HostUpdate<W, R>>>) holder -> holder.getConnector()
|
|
||||||
.getHost()
|
|
||||||
.getCloseNotifier()
|
|
||||||
.map(new VoidToAnythingCast<HostUpdate<W, R>>())
|
|
||||||
.ignoreElements()
|
|
||||||
.onErrorResumeNext(Observable.<HostUpdate<W, R>>empty())
|
|
||||||
.concatWith(Observable.just(new HostUpdate<>(Action.Remove, holder)))
|
|
||||||
.mergeWith(Observable.just(new HostUpdate<>(Action.Add, holder)))).flatMap(newCollector(collector.<W, R>newCollector()), 1).distinctUntilChanged());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <WW, RR> LoadBalancerFactory<WW, RR> create(LoadBalancingStrategy<WW, RR> strategy) {
|
|
||||||
return create(strategy, new NoBufferHostCollector());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <WW, RR> LoadBalancerFactory<WW, RR> create(LoadBalancingStrategy<WW, RR> strategy,
|
|
||||||
HostCollector collector) {
|
|
||||||
return new LoadBalancerFactory<>(strategy, collector);
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ConnectionProviderImpl implements ConnectionProvider<W, R> {
|
|
||||||
|
|
||||||
private volatile ConnectionProvider<W, R> currentProvider = () ->
|
|
||||||
Observable.error(NoHostsAvailableException.EMPTY_INSTANCE);
|
|
||||||
|
|
||||||
public ConnectionProviderImpl(Observable<List<HostHolder<W, R>>> hosts) {
|
|
||||||
hosts.subscribe(hostHolders -> currentProvider = strategy.newStrategy(hostHolders),
|
|
||||||
throwable -> logger.log(Level.SEVERE, "Error while listening on the host stream. Hosts will not be refreshed.", throwable));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Connection<R, W>> newConnectionRequest() {
|
|
||||||
return currentProvider.newConnectionRequest();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Func1<? super HostUpdate<W, R>, ? extends Observable<List<HostHolder<W, R>>>>
|
|
||||||
newCollector(final Func1<HostUpdate<W, R>, Single<List<HostHolder<W, R>>>> f) {
|
|
||||||
return (Func1<HostUpdate<W, R>, Observable<List<HostHolder<W, R>>>>) holder ->
|
|
||||||
f.call(holder).toObservable();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.loadbalancer;
|
|
||||||
|
|
||||||
import io.reactivex.netty.client.ConnectionProvider;
|
|
||||||
import io.reactivex.netty.client.HostConnector;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public interface LoadBalancingStrategy<W, R> {
|
|
||||||
|
|
||||||
ConnectionProvider<W, R> newStrategy(List<HostHolder<W, R>> hosts);
|
|
||||||
|
|
||||||
HostHolder<W, R> toHolder(HostConnector<W, R> connector);
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,74 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.loadbalancer;
|
|
||||||
|
|
||||||
import rx.Single;
|
|
||||||
import rx.functions.Func1;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A {@link HostCollector} implementation that does not buffer any updates and hence emits a new list for every new
|
|
||||||
* host received or a host removed.
|
|
||||||
*/
|
|
||||||
public class NoBufferHostCollector implements HostCollector {
|
|
||||||
|
|
||||||
private final boolean allowDuplicates;
|
|
||||||
|
|
||||||
public NoBufferHostCollector() {
|
|
||||||
this(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
public NoBufferHostCollector(boolean allowDuplicates) {
|
|
||||||
this.allowDuplicates = allowDuplicates;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <W, R> Func1<HostUpdate<W, R>, Single<List<HostHolder<W, R>>>> newCollector() {
|
|
||||||
return new Func1<HostUpdate<W, R>, Single<List<HostHolder<W, R>>>>() {
|
|
||||||
|
|
||||||
private volatile List<HostHolder<W, R>> currentList = Collections.emptyList();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Single<List<HostHolder<W, R>>> call(HostUpdate<W, R> update) {
|
|
||||||
List<HostHolder<W, R>> newList = null;
|
|
||||||
|
|
||||||
switch (update.getAction()) {
|
|
||||||
case Add:
|
|
||||||
if (allowDuplicates || !currentList.contains(update.getHostHolder())) {
|
|
||||||
newList = new ArrayList<>(currentList);
|
|
||||||
newList.add(update.getHostHolder());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case Remove:
|
|
||||||
newList = new ArrayList<>(currentList);
|
|
||||||
newList.remove(update.getHostHolder());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (null != newList) {
|
|
||||||
currentList = newList;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Single.just(currentList);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,55 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.loadbalancer;
|
|
||||||
|
|
||||||
import io.netty.util.internal.EmptyArrays;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Exception raised when there are no eligible hosts available to a load balancer.
|
|
||||||
*/
|
|
||||||
public class NoHostsAvailableException extends RuntimeException {
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 7993688893506534768L;
|
|
||||||
|
|
||||||
@SuppressWarnings("ThrowableInstanceNeverThrown")
|
|
||||||
public static final NoHostsAvailableException EMPTY_INSTANCE = new NoHostsAvailableException();
|
|
||||||
|
|
||||||
static {
|
|
||||||
EMPTY_INSTANCE.setStackTrace(EmptyArrays.EMPTY_STACK_TRACE);
|
|
||||||
}
|
|
||||||
|
|
||||||
public NoHostsAvailableException() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public NoHostsAvailableException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public NoHostsAvailableException(String message, Throwable cause) {
|
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
public NoHostsAvailableException(Throwable cause) {
|
|
||||||
super(cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
public NoHostsAvailableException(String message, Throwable cause, boolean enableSuppression,
|
|
||||||
boolean writableStackTrace) {
|
|
||||||
super(message, cause, enableSuppression, writableStackTrace);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,75 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.pool;
|
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
public class CompositePoolLimitDeterminationStrategy implements PoolLimitDeterminationStrategy {
|
|
||||||
|
|
||||||
private final PoolLimitDeterminationStrategy[] strategies;
|
|
||||||
|
|
||||||
public CompositePoolLimitDeterminationStrategy(PoolLimitDeterminationStrategy... strategies) {
|
|
||||||
if (null == strategies || strategies.length == 0) {
|
|
||||||
throw new IllegalArgumentException("Strategies can not be null or empty.");
|
|
||||||
}
|
|
||||||
for (PoolLimitDeterminationStrategy strategy : strategies) {
|
|
||||||
if (null == strategy) {
|
|
||||||
throw new IllegalArgumentException("No strategy can be null.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.strategies = strategies;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean acquireCreationPermit(long acquireStartTime, TimeUnit timeUnit) {
|
|
||||||
for (int i = 0; i < strategies.length; i++) {
|
|
||||||
PoolLimitDeterminationStrategy strategy = strategies[i];
|
|
||||||
if (!strategy.acquireCreationPermit(acquireStartTime, timeUnit)) {
|
|
||||||
if (i > 0) {
|
|
||||||
for (int j = i - 1; j >= 0; j--) {
|
|
||||||
strategies[j].releasePermit(); // release all permits acquired before this failure.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true; // nothing failed and hence it is OK to create a new connection.
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the minimum number of permits available across all strategies.
|
|
||||||
*
|
|
||||||
* @return The minimum number of permits available across all strategies.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public int getAvailablePermits() {
|
|
||||||
int minPermits = Integer.MAX_VALUE;
|
|
||||||
for (PoolLimitDeterminationStrategy strategy : strategies) {
|
|
||||||
int availablePermits = strategy.getAvailablePermits();
|
|
||||||
minPermits = Math.min(minPermits, availablePermits);
|
|
||||||
}
|
|
||||||
return minPermits; // If will atleast be one strategy (invariant in constructor) and hence this should be the value provided by that strategy.
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void releasePermit() {
|
|
||||||
for (PoolLimitDeterminationStrategy strategy : strategies) {
|
|
||||||
strategy.releasePermit();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,75 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.pool;
|
|
||||||
|
|
||||||
import rx.Observable;
|
|
||||||
import rx.Observable.OnSubscribe;
|
|
||||||
import rx.Subscriber;
|
|
||||||
|
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An implementation of {@link IdleConnectionsHolder} with a FIFO strategy.
|
|
||||||
*
|
|
||||||
* @param <W> Type of object that is written to the client using this holder.
|
|
||||||
* @param <R> Type of object that is read from the the client using this holder.
|
|
||||||
*/
|
|
||||||
public class FIFOIdleConnectionsHolder<W, R> extends IdleConnectionsHolder<W, R> {
|
|
||||||
|
|
||||||
private final ConcurrentLinkedQueue<PooledConnection<R, W>> idleConnections;
|
|
||||||
private final Observable<PooledConnection<R, W>> pollObservable;
|
|
||||||
private final Observable<PooledConnection<R, W>> peekObservable;
|
|
||||||
|
|
||||||
public FIFOIdleConnectionsHolder() {
|
|
||||||
idleConnections = new ConcurrentLinkedQueue<>();
|
|
||||||
|
|
||||||
pollObservable = Observable.create(new OnSubscribe<PooledConnection<R, W>>() {
|
|
||||||
@Override
|
|
||||||
public void call(Subscriber<? super PooledConnection<R, W>> subscriber) {
|
|
||||||
PooledConnection<R, W> idleConnection;
|
|
||||||
while (!subscriber.isUnsubscribed() && (idleConnection = idleConnections.poll()) != null) {
|
|
||||||
subscriber.onNext(idleConnection);
|
|
||||||
}
|
|
||||||
if (!subscriber.isUnsubscribed()) {
|
|
||||||
subscriber.onCompleted();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
peekObservable = Observable.from(idleConnections);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<PooledConnection<R, W>> poll() {
|
|
||||||
return pollObservable;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<PooledConnection<R, W>> peek() {
|
|
||||||
return peekObservable;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void add(PooledConnection<R, W> toAdd) {
|
|
||||||
idleConnections.add(toAdd);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean remove(PooledConnection<R, W> toRemove) {
|
|
||||||
return idleConnections.remove(toRemove);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,72 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.pool;
|
|
||||||
|
|
||||||
import io.netty.channel.EventLoop;
|
|
||||||
import rx.Observable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A holder of idle {@link PooledConnection} used by {@link PooledConnectionProvider}
|
|
||||||
*
|
|
||||||
* @param <W> Type of object that is written to the client using this holder.
|
|
||||||
* @param <R> Type of object that is read from the the client using this holder.
|
|
||||||
*/
|
|
||||||
public abstract class IdleConnectionsHolder<W, R> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a stream of idle connections where every item sent on to the stream is removed from the underlying
|
|
||||||
* idle connections pool.
|
|
||||||
*
|
|
||||||
* @return A stream of idle connections.
|
|
||||||
*/
|
|
||||||
public abstract Observable<PooledConnection<R, W>> poll();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a stream of idle connections where every item sent on to the stream is removed from the underlying
|
|
||||||
* idle connections pool.
|
|
||||||
* This method will only poll connections if the calling thread is an {@link EventLoop} known to this holder.
|
|
||||||
* Otherwise, it should return an empty stream.
|
|
||||||
*
|
|
||||||
* @return A stream of idle connections.
|
|
||||||
*/
|
|
||||||
public Observable<PooledConnection<R, W>> pollThisEventLoopConnections() {
|
|
||||||
return poll(); /*Override if the holder is aware of eventloops*/
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a stream of idle connections where every item sent on to the stream is NOT removed from the
|
|
||||||
* underlying idle connections pool. If the connection is to be removed, {@link #remove(PooledConnection)} must
|
|
||||||
* be called for that connection.
|
|
||||||
*
|
|
||||||
* @return A stream of idle connections.
|
|
||||||
*/
|
|
||||||
public abstract Observable<PooledConnection<R, W>> peek();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds the passed connection to this holder.
|
|
||||||
*
|
|
||||||
* @param toAdd Connection to add.
|
|
||||||
*/
|
|
||||||
public abstract void add(PooledConnection<R, W> toAdd);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the passed connection from this holder.
|
|
||||||
*
|
|
||||||
* @param toRemove Connection to remove.
|
|
||||||
*/
|
|
||||||
public abstract boolean remove(PooledConnection<R, W> toRemove);
|
|
||||||
}
|
|
|
@ -1,107 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.pool;
|
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An implementation of {@link PoolLimitDeterminationStrategy} that limits the pool based on a maximum connections limit.
|
|
||||||
* This limit can be increased or decreased at runtime.
|
|
||||||
*/
|
|
||||||
public class MaxConnectionsBasedStrategy implements PoolLimitDeterminationStrategy {
|
|
||||||
|
|
||||||
public static final int DEFAULT_MAX_CONNECTIONS = 1000;
|
|
||||||
|
|
||||||
private final AtomicInteger limitEnforcer;
|
|
||||||
private final AtomicInteger maxConnections;
|
|
||||||
|
|
||||||
public MaxConnectionsBasedStrategy() {
|
|
||||||
this(DEFAULT_MAX_CONNECTIONS);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MaxConnectionsBasedStrategy(int maxConnections) {
|
|
||||||
this.maxConnections = new AtomicInteger(maxConnections);
|
|
||||||
limitEnforcer = new AtomicInteger();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean acquireCreationPermit(long acquireStartTime, TimeUnit timeUnit) {
|
|
||||||
/**
|
|
||||||
* As opposed to limitEnforcer.incrementAndGet() we follow this model as this does not change the limitEnforcer
|
|
||||||
* value unless there are enough permits.
|
|
||||||
* If we were to use incrementAndGet(), in case of overflow (from max allowed limit) we would have to decrement
|
|
||||||
* the limitEnforcer. This may show temporary overflows in getMaxConnections() which may be disturbing for a
|
|
||||||
* user. However, even if we use incrementAndGet() the counter corrects itself over time.
|
|
||||||
* This is just a more semantically correct implementation with similar performance characterstics as
|
|
||||||
* incrementAndGet()
|
|
||||||
*/
|
|
||||||
for (;;) {
|
|
||||||
final int currentValue = limitEnforcer.get();
|
|
||||||
final int newValue = currentValue + 1;
|
|
||||||
final int maxAllowedConnections = maxConnections.get();
|
|
||||||
if (newValue <= maxAllowedConnections) {
|
|
||||||
if (limitEnforcer.compareAndSet(currentValue, newValue)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int incrementMaxConnections(int incrementBy) {
|
|
||||||
return maxConnections.addAndGet(incrementBy);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int decrementMaxConnections(int decrementBy) {
|
|
||||||
return maxConnections.addAndGet(-1 * decrementBy);
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getMaxConnections() {
|
|
||||||
return maxConnections.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getAvailablePermits() {
|
|
||||||
return maxConnections.get() - limitEnforcer.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void releasePermit() {
|
|
||||||
/**
|
|
||||||
* As opposed to limitEnforcer.decrementAndGet() we follow this model as this does not change the limitEnforcer
|
|
||||||
* value unless there are enough permits.
|
|
||||||
* If we were to use decrementAndGet(), in case of overflow (from max allowed limit) we would have to decrement
|
|
||||||
* the limitEnforcer. This may show temporary overflows in getMaxConnections() which may be disturbing for a
|
|
||||||
* user. However, even if we use decrementAndGet() the counter corrects itself over time.
|
|
||||||
* This is just a more semantically correct implementation with similar performance characterstics as
|
|
||||||
* decrementAndGet()
|
|
||||||
*/
|
|
||||||
for (;;) {
|
|
||||||
final int currentValue = limitEnforcer.get();
|
|
||||||
final int newValue = currentValue - 1;
|
|
||||||
if (newValue >= 0) {
|
|
||||||
if (!limitEnforcer.compareAndSet(currentValue, newValue)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,93 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.pool;
|
|
||||||
|
|
||||||
import rx.Observable;
|
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A configuration for connection pooling for a client.
|
|
||||||
*
|
|
||||||
* @param <W> Type of object that is written to the client using this pool config.
|
|
||||||
* @param <R> Type of object that is read from the the client using this pool config.
|
|
||||||
*/
|
|
||||||
public class PoolConfig<W, R> {
|
|
||||||
|
|
||||||
public static final long DEFAULT_MAX_IDLE_TIME_MILLIS = TimeUnit.MILLISECONDS.convert(30, TimeUnit.SECONDS);
|
|
||||||
|
|
||||||
private Observable<Long> idleConnCleanupTicker;
|
|
||||||
private PoolLimitDeterminationStrategy limitDeterminationStrategy;
|
|
||||||
private IdleConnectionsHolder<W, R> idleConnectionsHolder;
|
|
||||||
private long maxIdleTimeMillis;
|
|
||||||
|
|
||||||
public PoolConfig() {
|
|
||||||
maxIdleTimeMillis = DEFAULT_MAX_IDLE_TIME_MILLIS;
|
|
||||||
idleConnCleanupTicker = Observable.interval(maxIdleTimeMillis, maxIdleTimeMillis, TimeUnit.MILLISECONDS);
|
|
||||||
idleConnectionsHolder = new FIFOIdleConnectionsHolder<>();
|
|
||||||
limitDeterminationStrategy = UnboundedPoolLimitDeterminationStrategy.INSTANCE;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getMaxIdleTimeMillis() {
|
|
||||||
return maxIdleTimeMillis;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Observable<Long> getIdleConnectionsCleanupTimer() {
|
|
||||||
return idleConnCleanupTicker;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PoolLimitDeterminationStrategy getPoolLimitDeterminationStrategy() {
|
|
||||||
return limitDeterminationStrategy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PoolConfig<W, R> maxConnections(int maxConnections) {
|
|
||||||
limitDeterminationStrategy = new MaxConnectionsBasedStrategy(maxConnections);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PoolConfig<W, R> maxIdleTimeoutMillis(long maxIdleTimeoutMillis) {
|
|
||||||
maxIdleTimeMillis = maxIdleTimeoutMillis;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PoolConfig<W, R> limitDeterminationStrategy(PoolLimitDeterminationStrategy strategy) {
|
|
||||||
limitDeterminationStrategy = strategy;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PoolLimitDeterminationStrategy getLimitDeterminationStrategy() {
|
|
||||||
return limitDeterminationStrategy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PoolConfig<W, R> idleConnectionsHolder(IdleConnectionsHolder<W, R> holder) {
|
|
||||||
idleConnectionsHolder = holder;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public IdleConnectionsHolder<W, R> getIdleConnectionsHolder() {
|
|
||||||
return idleConnectionsHolder;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PoolConfig<W, R> idleConnectionsCleanupTimer(Observable<Long> timer) {
|
|
||||||
idleConnCleanupTicker = timer;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Observable<Long> getIdleConnCleanupTicker() {
|
|
||||||
return idleConnCleanupTicker;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.pool;
|
|
||||||
|
|
||||||
public class PoolExhaustedException extends Exception {
|
|
||||||
|
|
||||||
private static final long serialVersionUID = -6299997509113653123L;
|
|
||||||
|
|
||||||
public PoolExhaustedException() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public PoolExhaustedException(Throwable cause) {
|
|
||||||
super(cause);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PoolExhaustedException(String message) {
|
|
||||||
super(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
public PoolExhaustedException(String message, Throwable cause) {
|
|
||||||
super(message, cause);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.pool;
|
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A strategy to delegate the decision pertaining to connection pool size limits.
|
|
||||||
*/
|
|
||||||
public interface PoolLimitDeterminationStrategy {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts to acquire a creation permit.
|
|
||||||
*
|
|
||||||
* @param acquireStartTime The start time for the acquire process in milliseconds since epoch.
|
|
||||||
* @param timeUnit The timeunit for the acquire start time.
|
|
||||||
*
|
|
||||||
* @return {@code true} if the permit was acquired, {@code false} otherwise.
|
|
||||||
*/
|
|
||||||
boolean acquireCreationPermit(long acquireStartTime, TimeUnit timeUnit);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the number of creation permits available.
|
|
||||||
*
|
|
||||||
* @return The number of creation permits available.
|
|
||||||
*/
|
|
||||||
int getAvailablePermits();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Release a previously acquired permit.
|
|
||||||
*/
|
|
||||||
void releasePermit();
|
|
||||||
}
|
|
|
@ -1,373 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.pool;
|
|
||||||
|
|
||||||
import io.netty.channel.Channel;
|
|
||||||
import io.netty.channel.ChannelHandler;
|
|
||||||
import io.netty.channel.ChannelPipeline;
|
|
||||||
import io.netty.channel.FileRegion;
|
|
||||||
import io.netty.util.AttributeKey;
|
|
||||||
import io.netty.util.concurrent.EventExecutorGroup;
|
|
||||||
import io.reactivex.netty.channel.AllocatingTransformer;
|
|
||||||
import io.reactivex.netty.channel.Connection;
|
|
||||||
import io.reactivex.netty.client.ClientConnectionToChannelBridge;
|
|
||||||
import io.reactivex.netty.client.ClientConnectionToChannelBridge.ConnectionReuseEvent;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
import rx.Observable;
|
|
||||||
import rx.Observable.OnSubscribe;
|
|
||||||
import rx.Observable.Transformer;
|
|
||||||
import rx.Subscriber;
|
|
||||||
import rx.functions.Action0;
|
|
||||||
import rx.functions.Action1;
|
|
||||||
import rx.functions.Actions;
|
|
||||||
import rx.functions.Func1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An implementation of {@link Connection} which is pooled and reused.
|
|
||||||
*
|
|
||||||
* It is required to call {@link #reuse(Subscriber)} for reusing this connection.
|
|
||||||
*
|
|
||||||
* @param <R> Type of object that is read from this connection.
|
|
||||||
* @param <W> Type of object that is written to this connection.
|
|
||||||
*/
|
|
||||||
public class PooledConnection<R, W> extends Connection<R, W> {
|
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(PooledConnection.class.getName());
|
|
||||||
|
|
||||||
public static final AttributeKey<Long> DYNAMIC_CONN_KEEP_ALIVE_TIMEOUT_MS =
|
|
||||||
AttributeKey.valueOf("rxnetty_conn_keep_alive_timeout_millis");
|
|
||||||
|
|
||||||
private final Owner owner;
|
|
||||||
private final Connection<R, W> unpooledDelegate;
|
|
||||||
|
|
||||||
private volatile long lastReturnToPoolTimeMillis;
|
|
||||||
private volatile boolean releasedAtLeastOnce;
|
|
||||||
private volatile long maxIdleTimeMillis;
|
|
||||||
private final Observable<Void> releaseObservable;
|
|
||||||
|
|
||||||
private PooledConnection(Owner owner, long maxIdleTimeMillis, Connection<R, W> unpooledDelegate) {
|
|
||||||
super(unpooledDelegate);
|
|
||||||
if (null == owner) {
|
|
||||||
throw new IllegalArgumentException("Pooled connection owner can not be null");
|
|
||||||
}
|
|
||||||
if (null == unpooledDelegate) {
|
|
||||||
throw new IllegalArgumentException("Connection delegate can not be null");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.owner = owner;
|
|
||||||
this.unpooledDelegate = unpooledDelegate;
|
|
||||||
this.maxIdleTimeMillis = maxIdleTimeMillis;
|
|
||||||
lastReturnToPoolTimeMillis = System.currentTimeMillis();
|
|
||||||
releaseObservable = Observable.create(new OnSubscribe<Void>() {
|
|
||||||
@Override
|
|
||||||
public void call(Subscriber<? super Void> subscriber) {
|
|
||||||
if (!isUsable()) {
|
|
||||||
PooledConnection.this.owner.discard(PooledConnection.this)
|
|
||||||
.unsafeSubscribe(subscriber);
|
|
||||||
} else {
|
|
||||||
Long keepAliveTimeout = unsafeNettyChannel().attr(DYNAMIC_CONN_KEEP_ALIVE_TIMEOUT_MS).get();
|
|
||||||
if (null != keepAliveTimeout) {
|
|
||||||
PooledConnection.this.maxIdleTimeMillis = keepAliveTimeout;
|
|
||||||
}
|
|
||||||
markAwarePipeline.reset(); // Reset pipeline state, if changed, on release.
|
|
||||||
PooledConnection.this.owner.release(PooledConnection.this)
|
|
||||||
.doOnCompleted(new Action0() {
|
|
||||||
@Override
|
|
||||||
public void call() {
|
|
||||||
releasedAtLeastOnce = true;
|
|
||||||
lastReturnToPoolTimeMillis = System.currentTimeMillis();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unsafeSubscribe(subscriber);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}).onErrorResumeNext(discard());
|
|
||||||
}
|
|
||||||
|
|
||||||
private PooledConnection(PooledConnection<?, ?> toCopy, Connection<R, W> unpooledDelegate) {
|
|
||||||
super(unpooledDelegate);
|
|
||||||
owner = toCopy.owner;
|
|
||||||
this.unpooledDelegate = unpooledDelegate;
|
|
||||||
lastReturnToPoolTimeMillis = toCopy.lastReturnToPoolTimeMillis;
|
|
||||||
releasedAtLeastOnce = toCopy.releasedAtLeastOnce;
|
|
||||||
maxIdleTimeMillis = toCopy.maxIdleTimeMillis;
|
|
||||||
releaseObservable = toCopy.releaseObservable;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> write(Observable<W> msgs) {
|
|
||||||
return unpooledDelegate.write(msgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> write(Observable<W> msgs, Func1<W, Boolean> flushSelector) {
|
|
||||||
return unpooledDelegate.write(msgs, flushSelector);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> writeAndFlushOnEach(Observable<W> msgs) {
|
|
||||||
return unpooledDelegate.writeAndFlushOnEach(msgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> writeString(Observable<String> msgs) {
|
|
||||||
return unpooledDelegate.writeString(msgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> writeString(Observable<String> msgs,
|
|
||||||
Func1<String, Boolean> flushSelector) {
|
|
||||||
return unpooledDelegate.writeString(msgs, flushSelector);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> writeStringAndFlushOnEach(Observable<String> msgs) {
|
|
||||||
return unpooledDelegate.writeStringAndFlushOnEach(msgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> writeBytes(Observable<byte[]> msgs) {
|
|
||||||
return unpooledDelegate.writeBytes(msgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> writeBytes(Observable<byte[]> msgs,
|
|
||||||
Func1<byte[], Boolean> flushSelector) {
|
|
||||||
return unpooledDelegate.writeBytes(msgs, flushSelector);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> writeBytesAndFlushOnEach(Observable<byte[]> msgs) {
|
|
||||||
return unpooledDelegate.writeBytesAndFlushOnEach(msgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> writeFileRegion(Observable<FileRegion> msgs) {
|
|
||||||
return unpooledDelegate.writeFileRegion(msgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> writeFileRegion(Observable<FileRegion> msgs,
|
|
||||||
Func1<FileRegion, Boolean> flushSelector) {
|
|
||||||
return unpooledDelegate.writeFileRegion(msgs, flushSelector);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> writeFileRegionAndFlushOnEach(Observable<FileRegion> msgs) {
|
|
||||||
return unpooledDelegate.writeFileRegionAndFlushOnEach(msgs);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void flush() {
|
|
||||||
unpooledDelegate.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> close() {
|
|
||||||
return close(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> close(boolean flush) {
|
|
||||||
if (flush) {
|
|
||||||
return releaseObservable.doOnSubscribe(new Action0() {
|
|
||||||
@Override
|
|
||||||
public void call() {
|
|
||||||
unpooledDelegate.flush();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return releaseObservable;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void closeNow() {
|
|
||||||
close().subscribe(Actions.empty(), new Action1<Throwable>() {
|
|
||||||
@Override
|
|
||||||
public void call(Throwable throwable) {
|
|
||||||
logger.log(Level.SEVERE, "Error closing connection.", throwable);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> closeListener() {
|
|
||||||
return unpooledDelegate.closeListener();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <RR, WW> Connection<RR, WW> addChannelHandlerAfter(String baseName, String name,
|
|
||||||
ChannelHandler handler) {
|
|
||||||
return new PooledConnection<>(this, unpooledDelegate.<RR, WW>addChannelHandlerAfter(baseName, name, handler));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <RR, WW> Connection<RR, WW> addChannelHandlerAfter(EventExecutorGroup group,
|
|
||||||
String baseName, String name,
|
|
||||||
ChannelHandler handler) {
|
|
||||||
return new PooledConnection<>(this, unpooledDelegate.<RR, WW>addChannelHandlerAfter(group, baseName, name,
|
|
||||||
handler));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <RR, WW> Connection<RR, WW> addChannelHandlerBefore(String baseName, String name,
|
|
||||||
ChannelHandler handler) {
|
|
||||||
return new PooledConnection<>(this, unpooledDelegate.<RR, WW>addChannelHandlerBefore(baseName, name, handler));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <RR, WW> Connection<RR, WW> addChannelHandlerBefore(EventExecutorGroup group,
|
|
||||||
String baseName, String name,
|
|
||||||
ChannelHandler handler) {
|
|
||||||
return new PooledConnection<>(this, unpooledDelegate.<RR, WW>addChannelHandlerBefore(group, baseName, name,
|
|
||||||
handler));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <RR, WW> Connection<RR, WW> addChannelHandlerFirst(EventExecutorGroup group,
|
|
||||||
String name, ChannelHandler handler) {
|
|
||||||
return new PooledConnection<>(this, unpooledDelegate.<RR, WW>addChannelHandlerFirst(group, name, handler));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <RR, WW> Connection<RR, WW> addChannelHandlerFirst(String name, ChannelHandler handler) {
|
|
||||||
return new PooledConnection<>(this, unpooledDelegate.<RR, WW>addChannelHandlerFirst(name, handler));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <RR, WW> Connection<RR, WW> addChannelHandlerLast(EventExecutorGroup group,
|
|
||||||
String name, ChannelHandler handler) {
|
|
||||||
return new PooledConnection<>(this, unpooledDelegate.<RR, WW>addChannelHandlerLast(group, name, handler));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <RR, WW> Connection<RR, WW> addChannelHandlerLast(String name, ChannelHandler handler) {
|
|
||||||
return new PooledConnection<>(this, unpooledDelegate.<RR, WW>addChannelHandlerLast(name, handler));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <RR, WW> Connection<RR, WW> pipelineConfigurator(Action1<ChannelPipeline> pipelineConfigurator) {
|
|
||||||
return new PooledConnection<>(this, unpooledDelegate.<RR, WW>pipelineConfigurator(pipelineConfigurator));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <RR> Connection<RR, W> transformRead(Transformer<R, RR> transformer) {
|
|
||||||
return new PooledConnection<>(this, unpooledDelegate.transformRead(transformer));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <WW> Connection<R, WW> transformWrite(AllocatingTransformer<WW, W> transformer) {
|
|
||||||
return new PooledConnection<>(this, unpooledDelegate.transformWrite(transformer));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Discards this connection, to be called when this connection will never be used again.
|
|
||||||
*
|
|
||||||
* @return {@link Observable} representing the result of the discard, this will typically be resulting in a close
|
|
||||||
* on the underlying {@link Connection}.
|
|
||||||
*/
|
|
||||||
/*package private, externally shouldn't be discardable.*/Observable<Void> discard() {
|
|
||||||
return unpooledDelegate.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns whether this connection is safe to be used at this moment.
|
|
||||||
* This makes sure that the underlying netty's channel is active as returned by
|
|
||||||
* {@link Channel#isActive()} and it has not passed the maximum idle time in the pool.
|
|
||||||
*
|
|
||||||
* @return {@code true} if the connection is usable.
|
|
||||||
*/
|
|
||||||
public boolean isUsable() {
|
|
||||||
final Channel nettyChannel = unsafeNettyChannel();
|
|
||||||
Boolean discardConn = nettyChannel.attr(ClientConnectionToChannelBridge.DISCARD_CONNECTION).get();
|
|
||||||
|
|
||||||
if (!nettyChannel.isActive() || Boolean.TRUE == discardConn) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
long nowMillis = System.currentTimeMillis();
|
|
||||||
long idleTime = nowMillis - lastReturnToPoolTimeMillis;
|
|
||||||
return idleTime < maxIdleTimeMillis;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This method must be called for reusing the connection i.e. for sending this connection to the passed subscriber.
|
|
||||||
*
|
|
||||||
* @param connectionSubscriber Subscriber for the pooled connection for reuse.
|
|
||||||
*/
|
|
||||||
public void reuse(Subscriber<? super PooledConnection<R, W>> connectionSubscriber) {
|
|
||||||
unsafeNettyChannel().pipeline().fireUserEventTriggered(new ConnectionReuseEvent<>(connectionSubscriber, this));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <R, W> PooledConnection<R, W> create(Owner owner, long maxIdleTimeMillis,
|
|
||||||
Connection<R, W> unpooledDelegate) {
|
|
||||||
final PooledConnection<R, W> toReturn = new PooledConnection<>(owner, maxIdleTimeMillis, unpooledDelegate
|
|
||||||
);
|
|
||||||
toReturn.connectCloseToChannelClose();
|
|
||||||
return toReturn;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} if this connection is reused at least once.
|
|
||||||
*
|
|
||||||
* @return {@code true} if this connection is reused at least once.
|
|
||||||
*/
|
|
||||||
public boolean isReused() {
|
|
||||||
return releasedAtLeastOnce;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelPipeline getChannelPipeline() {
|
|
||||||
return markAwarePipeline; // Always return mark aware as, we always have to reset state on release to pool.
|
|
||||||
}
|
|
||||||
|
|
||||||
/*Visible for testin*/ void setLastReturnToPoolTimeMillis(long lastReturnToPoolTimeMillis) {
|
|
||||||
this.lastReturnToPoolTimeMillis = lastReturnToPoolTimeMillis;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A contract for the owner of the {@link PooledConnection} to which any instance of {@link PooledConnection} must
|
|
||||||
* be returned after use.
|
|
||||||
*/
|
|
||||||
public interface Owner {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Releases the passed connection back to the owner, for reuse.
|
|
||||||
*
|
|
||||||
* @param connection Connection to be released.
|
|
||||||
*
|
|
||||||
* @return {@link Observable} representing result of the release. Every subscription to this, releases the
|
|
||||||
* connection.
|
|
||||||
*/
|
|
||||||
Observable<Void> release(PooledConnection<?, ?> connection);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Discards the passed connection from the pool. This is usually called due to an external event like closing of
|
|
||||||
* a connection that the pool may not know.
|
|
||||||
* <b> This operation is idempotent and hence can be called multiple times with no side effects</b>
|
|
||||||
*
|
|
||||||
* @param connection The connection to discard.
|
|
||||||
*
|
|
||||||
* @return {@link Observable} indicating the result of the discard (which usually results in a close()).
|
|
||||||
* Every subscription to this {@link Observable} will discard the connection.
|
|
||||||
*/
|
|
||||||
Observable<Void> discard(PooledConnection<?, ?> connection);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.pool;
|
|
||||||
|
|
||||||
import io.reactivex.netty.client.ConnectionProvider;
|
|
||||||
import io.reactivex.netty.client.ConnectionProviderFactory;
|
|
||||||
import io.reactivex.netty.client.HostConnector;
|
|
||||||
import io.reactivex.netty.client.pool.PooledConnection.Owner;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An implementation of {@link PooledConnectionProvider} that pools connections.
|
|
||||||
*
|
|
||||||
* Following are the key parameters:
|
|
||||||
*
|
|
||||||
* <ul>
|
|
||||||
<li>{@link PoolLimitDeterminationStrategy}: A strategy to determine whether a new physical connection should be
|
|
||||||
created as part of the user request.</li>
|
|
||||||
<li>{@link PoolConfig#getIdleConnectionsCleanupTimer()}: The schedule for cleaning up idle connections in the pool.</li>
|
|
||||||
<li>{@link PoolConfig#getMaxIdleTimeMillis()}: Maximum time a connection can be idle in this pool.</li>
|
|
||||||
</ul>
|
|
||||||
*
|
|
||||||
* <h2>Usage</h2>
|
|
||||||
*
|
|
||||||
* <h3>Complementing a {@link ConnectionProviderFactory}</h3>
|
|
||||||
*
|
|
||||||
* For employing better host selection strategies, this provider can be used to complement the default
|
|
||||||
* {@link ConnectionProvider} provided by a {@link HostConnector}.
|
|
||||||
*
|
|
||||||
* <h3>Standalone</h3>
|
|
||||||
*
|
|
||||||
* For clients that do not use a pool of hosts can use {@link SingleHostPoolingProviderFactory} that will only ever pick
|
|
||||||
* a single host but will pool connections.
|
|
||||||
*/
|
|
||||||
public abstract class PooledConnectionProvider<W, R> implements ConnectionProvider<W, R> , Owner {
|
|
||||||
|
|
||||||
public static <W, R> PooledConnectionProvider<W, R> createUnbounded(final HostConnector<W, R> delegate) {
|
|
||||||
return create(new PoolConfig<W, R>(), delegate);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <W, R> PooledConnectionProvider<W, R> createBounded(int maxConnections,
|
|
||||||
final HostConnector<W, R> delegate) {
|
|
||||||
return create(new PoolConfig<W, R>().maxConnections(maxConnections), delegate);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <W, R> PooledConnectionProvider<W, R> create(final PoolConfig<W, R> config,
|
|
||||||
final HostConnector<W, R> delegate) {
|
|
||||||
return new PooledConnectionProviderImpl<>(config, delegate);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,431 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.pool;
|
|
||||||
|
|
||||||
import io.reactivex.netty.channel.Connection;
|
|
||||||
import io.reactivex.netty.client.ClientConnectionToChannelBridge.PooledConnectionReleaseEvent;
|
|
||||||
import io.reactivex.netty.client.HostConnector;
|
|
||||||
import io.reactivex.netty.client.events.ClientEventListener;
|
|
||||||
import io.reactivex.netty.events.Clock;
|
|
||||||
import io.reactivex.netty.events.EventPublisher;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
import rx.Observable;
|
|
||||||
import rx.Observable.OnSubscribe;
|
|
||||||
import rx.Observable.Operator;
|
|
||||||
import rx.Subscriber;
|
|
||||||
import rx.Subscription;
|
|
||||||
import rx.functions.Action0;
|
|
||||||
import rx.functions.Action1;
|
|
||||||
import rx.functions.Actions;
|
|
||||||
import rx.functions.Func1;
|
|
||||||
|
|
||||||
import static io.reactivex.netty.events.EventAttributeKeys.*;
|
|
||||||
import static java.util.concurrent.TimeUnit.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An implementation of {@link PooledConnectionProvider} that pools connections.
|
|
||||||
*
|
|
||||||
* Following are the key parameters:
|
|
||||||
*
|
|
||||||
* <ul>
|
|
||||||
<li>{@link PoolLimitDeterminationStrategy}: A strategy to determine whether a new physical connection should be
|
|
||||||
created as part of the user request.</li>
|
|
||||||
<li>{@link PoolConfig#getIdleConnectionsCleanupTimer()}: The schedule for cleaning up idle connections in the pool.</li>
|
|
||||||
<li>{@link PoolConfig#getMaxIdleTimeMillis()}: Maximum time a connection can be idle in this pool.</li>
|
|
||||||
</ul>
|
|
||||||
*
|
|
||||||
* @param <W> Type of object that is written to the client using this factory.
|
|
||||||
* @param <R> Type of object that is read from the the client using this factory.
|
|
||||||
*/
|
|
||||||
public final class PooledConnectionProviderImpl<W, R> extends PooledConnectionProvider<W, R> {
|
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(PooledConnectionProviderImpl.class.getName());
|
|
||||||
|
|
||||||
private final Subscription idleConnCleanupSubscription;
|
|
||||||
private final IdleConnectionsHolder<W, R> idleConnectionsHolder;
|
|
||||||
|
|
||||||
private final PoolLimitDeterminationStrategy limitDeterminationStrategy;
|
|
||||||
private final long maxIdleTimeMillis;
|
|
||||||
private final HostConnector<W, R> hostConnector;
|
|
||||||
private volatile boolean isShutdown;
|
|
||||||
|
|
||||||
public PooledConnectionProviderImpl(PoolConfig<W, R> poolConfig, HostConnector<W, R> hostConnector) {
|
|
||||||
this.hostConnector = hostConnector;
|
|
||||||
idleConnectionsHolder = poolConfig.getIdleConnectionsHolder();
|
|
||||||
limitDeterminationStrategy = poolConfig.getPoolLimitDeterminationStrategy();
|
|
||||||
maxIdleTimeMillis = poolConfig.getMaxIdleTimeMillis();
|
|
||||||
// In case, there is no cleanup required, this observable should never give a tick.
|
|
||||||
idleConnCleanupSubscription = poolConfig.getIdleConnCleanupTicker()
|
|
||||||
.doOnError(LogErrorAction.INSTANCE)
|
|
||||||
.retry() // Retry when there is an error in timer.
|
|
||||||
.concatMap(new IdleConnectionCleanupTask())
|
|
||||||
.doOnError(new Action1<Throwable>() {
|
|
||||||
@Override
|
|
||||||
public void call(Throwable throwable) {
|
|
||||||
logger.log(Level.SEVERE, "Ignoring error cleaning up idle connections.",
|
|
||||||
throwable);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.retry()
|
|
||||||
.subscribe();
|
|
||||||
|
|
||||||
hostConnector.getHost()
|
|
||||||
.getCloseNotifier()
|
|
||||||
.doOnTerminate(new Action0() {
|
|
||||||
@Override
|
|
||||||
public void call() {
|
|
||||||
isShutdown = true;
|
|
||||||
idleConnCleanupSubscription.unsubscribe();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.onErrorResumeNext(new Func1<Throwable, Observable<Void>>() {
|
|
||||||
@Override
|
|
||||||
public Observable<Void> call(Throwable throwable) {
|
|
||||||
logger.log(Level.SEVERE, "Error listening to Host close notifications. Shutting down the pool.",
|
|
||||||
throwable);
|
|
||||||
return Observable.empty();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.subscribe(Actions.empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Connection<R, W>> newConnectionRequest() {
|
|
||||||
return Observable.create(new OnSubscribe<Connection<R, W>>() {
|
|
||||||
@Override
|
|
||||||
public void call(Subscriber<? super Connection<R, W>> subscriber) {
|
|
||||||
if (isShutdown) {
|
|
||||||
subscriber.onError(new IllegalStateException("Connection provider is shutdown."));
|
|
||||||
}
|
|
||||||
idleConnectionsHolder.pollThisEventLoopConnections()
|
|
||||||
.concatWith(connectIfAllowed())
|
|
||||||
.filter(new Func1<PooledConnection<R, W>, Boolean>() {
|
|
||||||
@Override
|
|
||||||
public Boolean call(PooledConnection<R, W> c) {
|
|
||||||
boolean isUsable = c.isUsable();
|
|
||||||
if (!isUsable) {
|
|
||||||
discardNow(c);
|
|
||||||
}
|
|
||||||
return isUsable;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.take(1)
|
|
||||||
.lift(new ReuseSubscriberLinker())
|
|
||||||
.lift(new ConnectMetricsOperator())
|
|
||||||
.unsafeSubscribe(subscriber);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> release(final PooledConnection<?, ?> connection) {
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
final PooledConnection<R, W> c = (PooledConnection<R, W>) connection;
|
|
||||||
return Observable.create(new OnSubscribe<Void>() {
|
|
||||||
@Override
|
|
||||||
public void call(Subscriber<? super Void> subscriber) {
|
|
||||||
if (null == c) {
|
|
||||||
subscriber.onCompleted();
|
|
||||||
} else {
|
|
||||||
/**
|
|
||||||
* Executing the release on the eventloop to avoid race-conditions between code cleaning up
|
|
||||||
* connection in the pipeline and the connecting being released to the pool.
|
|
||||||
*/
|
|
||||||
c.unsafeNettyChannel()
|
|
||||||
.eventLoop()
|
|
||||||
.submit(new ReleaseTask(c, subscriber));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> discard(final PooledConnection<?, ?> connection) {
|
|
||||||
return connection.discard().doOnSubscribe(new Action0() {
|
|
||||||
@Override
|
|
||||||
public void call() {
|
|
||||||
EventPublisher eventPublisher = connection.unsafeNettyChannel().attr(EVENT_PUBLISHER).get();
|
|
||||||
if (eventPublisher.publishingEnabled()) {
|
|
||||||
ClientEventListener eventListener = connection.unsafeNettyChannel()
|
|
||||||
.attr(CLIENT_EVENT_LISTENER).get();
|
|
||||||
eventListener.onPooledConnectionEviction();
|
|
||||||
}
|
|
||||||
limitDeterminationStrategy.releasePermit();/*Since, an idle connection took a permit*/
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private Observable<PooledConnection<R, W>> connectIfAllowed() {
|
|
||||||
return Observable.create(new OnSubscribe<PooledConnection<R, W>>() {
|
|
||||||
@Override
|
|
||||||
public void call(Subscriber<? super PooledConnection<R, W>> subscriber) {
|
|
||||||
final long startTimeNanos = Clock.newStartTimeNanos();
|
|
||||||
if (limitDeterminationStrategy.acquireCreationPermit(startTimeNanos, NANOSECONDS)) {
|
|
||||||
Observable<Connection<R, W>> newConnObsv = hostConnector.getConnectionProvider()
|
|
||||||
.newConnectionRequest();
|
|
||||||
newConnObsv.map(new Func1<Connection<R, W>, PooledConnection<R, W>>() {
|
|
||||||
@Override
|
|
||||||
public PooledConnection<R, W> call(Connection<R, W> connection) {
|
|
||||||
return PooledConnection.create(PooledConnectionProviderImpl.this,
|
|
||||||
maxIdleTimeMillis, connection);
|
|
||||||
}
|
|
||||||
}).doOnError(new Action1<Throwable>() {
|
|
||||||
@Override
|
|
||||||
public void call(Throwable throwable) {
|
|
||||||
limitDeterminationStrategy.releasePermit(); /*Before connect we acquired.*/
|
|
||||||
}
|
|
||||||
}).unsafeSubscribe(subscriber);
|
|
||||||
} else {
|
|
||||||
idleConnectionsHolder.poll()
|
|
||||||
.switchIfEmpty(Observable.<PooledConnection<R, W>>error(
|
|
||||||
new PoolExhaustedException("Client connection pool exhausted.")))
|
|
||||||
.unsafeSubscribe(subscriber);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void discardNow(PooledConnection<R, W> toDiscard) {
|
|
||||||
discard(toDiscard).subscribe(Actions.empty(), new Action1<Throwable>() {
|
|
||||||
@Override
|
|
||||||
public void call(Throwable throwable) {
|
|
||||||
logger.log(Level.SEVERE, "Error discarding connection.", throwable);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class LogErrorAction implements Action1<Throwable> {
|
|
||||||
|
|
||||||
public static final LogErrorAction INSTANCE = new LogErrorAction();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void call(Throwable throwable) {
|
|
||||||
logger.log(Level.SEVERE, "Error from idle connection cleanup timer. This will be retried.", throwable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class IdleConnectionCleanupTask implements Func1<Long, Observable<Void>> {
|
|
||||||
@Override
|
|
||||||
public Observable<Void> call(Long aLong) {
|
|
||||||
return idleConnectionsHolder.peek()
|
|
||||||
.map((Func1<PooledConnection<R, W>, Void>) connection -> {
|
|
||||||
if (!connection.isUsable()) {
|
|
||||||
idleConnectionsHolder.remove(connection);
|
|
||||||
discardNow(connection);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}).ignoreElements();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ReleaseTask implements Runnable {
|
|
||||||
|
|
||||||
private final PooledConnection<R, W> connection;
|
|
||||||
private final Subscriber<? super Void> subscriber;
|
|
||||||
private final long releaseStartTimeNanos;
|
|
||||||
private final EventPublisher eventPublisher;
|
|
||||||
private final ClientEventListener eventListener;
|
|
||||||
|
|
||||||
private ReleaseTask(PooledConnection<R, W> connection, Subscriber<? super Void> subscriber) {
|
|
||||||
this.connection = connection;
|
|
||||||
this.subscriber = subscriber;
|
|
||||||
releaseStartTimeNanos = Clock.newStartTimeNanos();
|
|
||||||
eventPublisher = connection.unsafeNettyChannel().attr(EVENT_PUBLISHER).get();
|
|
||||||
eventListener = connection.unsafeNettyChannel().attr(CLIENT_EVENT_LISTENER).get();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
try {
|
|
||||||
connection.unsafeNettyChannel().pipeline().fireUserEventTriggered(PooledConnectionReleaseEvent.INSTANCE);
|
|
||||||
if (eventPublisher.publishingEnabled()) {
|
|
||||||
eventListener.onPoolReleaseStart();
|
|
||||||
}
|
|
||||||
if (isShutdown || !connection.isUsable()) {
|
|
||||||
discardNow(connection);
|
|
||||||
} else {
|
|
||||||
idleConnectionsHolder.add(connection);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (eventPublisher.publishingEnabled()) {
|
|
||||||
eventListener.onPoolReleaseSuccess(Clock.onEndNanos(releaseStartTimeNanos), NANOSECONDS);
|
|
||||||
}
|
|
||||||
subscriber.onCompleted();
|
|
||||||
} catch (Throwable throwable) {
|
|
||||||
if (eventPublisher.publishingEnabled()) {
|
|
||||||
eventListener.onPoolReleaseFailed(Clock.onEndNanos(releaseStartTimeNanos), NANOSECONDS, throwable);
|
|
||||||
}
|
|
||||||
subscriber.onError(throwable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ConnectMetricsOperator implements Operator<Connection<R, W>, PooledConnection<R, W>> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Subscriber<? super PooledConnection<R, W>> call(final Subscriber<? super Connection<R, W>> o) {
|
|
||||||
final long startTimeNanos = Clock.newStartTimeNanos();
|
|
||||||
|
|
||||||
return new Subscriber<>(o) {
|
|
||||||
|
|
||||||
private volatile boolean publishingEnabled;
|
|
||||||
private volatile ClientEventListener eventListener;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCompleted() {
|
|
||||||
if (publishingEnabled) {
|
|
||||||
eventListener.onPoolAcquireStart();
|
|
||||||
eventListener.onPoolAcquireSuccess(Clock.onEndNanos(startTimeNanos), NANOSECONDS);
|
|
||||||
}
|
|
||||||
o.onCompleted();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(Throwable e) {
|
|
||||||
if (publishingEnabled) {
|
|
||||||
/*Error means no connection was received, as it always every gets at most one connection*/
|
|
||||||
eventListener.onPoolAcquireStart();
|
|
||||||
eventListener.onPoolAcquireFailed(Clock.onEndNanos(startTimeNanos), NANOSECONDS, e);
|
|
||||||
}
|
|
||||||
o.onError(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNext(PooledConnection<R, W> c) {
|
|
||||||
EventPublisher eventPublisher = c.unsafeNettyChannel().attr(EVENT_PUBLISHER).get();
|
|
||||||
if (eventPublisher.publishingEnabled()) {
|
|
||||||
publishingEnabled = true;
|
|
||||||
eventListener = c.unsafeNettyChannel().attr(CLIENT_EVENT_LISTENER).get();
|
|
||||||
}
|
|
||||||
o.onNext(c);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ReuseSubscriberLinker implements Operator<PooledConnection<R, W>, PooledConnection<R, W>> {
|
|
||||||
|
|
||||||
private ScalarAsyncSubscriber<R, W> onReuseSubscriber;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Subscriber<? super PooledConnection<R, W>> call(final Subscriber<? super PooledConnection<R, W>> o) {
|
|
||||||
return new Subscriber<>(o) {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCompleted() {
|
|
||||||
/*This subscriber is not invoked by different threads, so don't need sychronization*/
|
|
||||||
if (null != onReuseSubscriber) {
|
|
||||||
onReuseSubscriber.onCompleted();
|
|
||||||
} else {
|
|
||||||
o.onCompleted();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(Throwable e) {
|
|
||||||
/*This subscriber is not invoked by different threads, so don't need sychronization*/
|
|
||||||
if (null != onReuseSubscriber) {
|
|
||||||
onReuseSubscriber.onError(e);
|
|
||||||
} else {
|
|
||||||
o.onError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNext(PooledConnection<R, W> c) {
|
|
||||||
if (c.isReused()) {
|
|
||||||
EventPublisher eventPublisher = c.unsafeNettyChannel().attr(EVENT_PUBLISHER).get();
|
|
||||||
if (eventPublisher.publishingEnabled()) {
|
|
||||||
ClientEventListener eventListener = c.unsafeNettyChannel()
|
|
||||||
.attr(CLIENT_EVENT_LISTENER).get();
|
|
||||||
eventListener.onPooledConnectionReuse();
|
|
||||||
}
|
|
||||||
onReuseSubscriber = new ScalarAsyncSubscriber<>(o);
|
|
||||||
c.reuse(onReuseSubscriber); /*Reuse will on next to the subscriber*/
|
|
||||||
} else {
|
|
||||||
o.onNext(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class ScalarAsyncSubscriber<R, W> extends Subscriber<PooledConnection<R, W>> {
|
|
||||||
|
|
||||||
private boolean terminated; /*Guarded by this*/
|
|
||||||
private Throwable error; /*Guarded by this*/
|
|
||||||
private boolean onNextArrived; /*Guarded by this*/
|
|
||||||
private final Subscriber<? super PooledConnection<R, W>> delegate;
|
|
||||||
|
|
||||||
private ScalarAsyncSubscriber(Subscriber<? super PooledConnection<R, W>> delegate) {
|
|
||||||
this.delegate = delegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCompleted() {
|
|
||||||
boolean _onNextArrived;
|
|
||||||
|
|
||||||
synchronized (this) {
|
|
||||||
_onNextArrived = onNextArrived;
|
|
||||||
}
|
|
||||||
|
|
||||||
terminated = true;
|
|
||||||
|
|
||||||
if (_onNextArrived) {
|
|
||||||
delegate.onCompleted();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(Throwable e) {
|
|
||||||
boolean _onNextArrived;
|
|
||||||
|
|
||||||
synchronized (this) {
|
|
||||||
_onNextArrived = onNextArrived;
|
|
||||||
}
|
|
||||||
terminated = true;
|
|
||||||
error = e;
|
|
||||||
|
|
||||||
if (_onNextArrived) {
|
|
||||||
delegate.onError(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNext(PooledConnection<R, W> conn) {
|
|
||||||
boolean _terminated;
|
|
||||||
Throwable _error;
|
|
||||||
synchronized (this) {
|
|
||||||
onNextArrived = true;
|
|
||||||
_terminated = terminated;
|
|
||||||
_error = error;
|
|
||||||
delegate.onNext(conn);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (_terminated) {
|
|
||||||
if (null != error) {
|
|
||||||
delegate.onError(_error);
|
|
||||||
} else {
|
|
||||||
delegate.onCompleted();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,168 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.pool;
|
|
||||||
|
|
||||||
import io.netty.channel.EventLoop;
|
|
||||||
import io.netty.util.concurrent.EventExecutor;
|
|
||||||
import io.netty.util.concurrent.FastThreadLocal;
|
|
||||||
import io.reactivex.netty.client.ClientConnectionToChannelBridge;
|
|
||||||
import io.reactivex.netty.threads.PreferCurrentEventLoopGroup;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
import rx.Observable;
|
|
||||||
import rx.Observable.OnSubscribe;
|
|
||||||
import rx.Subscriber;
|
|
||||||
import rx.functions.Action1;
|
|
||||||
import rx.functions.Actions;
|
|
||||||
import rx.functions.Func0;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An {@link IdleConnectionsHolder} implementation that can identify if the calling thread is an {@link EventLoop} in
|
|
||||||
* the provided {@link PreferCurrentEventLoopGroup} and prefers a connection registered with the calling
|
|
||||||
* {@link EventLoop}.
|
|
||||||
*
|
|
||||||
* If the calling thread is not an {@link EventLoop} in the provided {@link PreferCurrentEventLoopGroup} then
|
|
||||||
* {@link #poll()} and {@link #peek()} will iterate over connections from all {@link EventLoop}s however
|
|
||||||
* {@link #add(PooledConnection)} will attempt to find the {@link EventLoop} of the added {@link PooledConnection}. If
|
|
||||||
* the {@link EventLoop} of the connection does not belong to the provided {@link PreferCurrentEventLoopGroup} then the
|
|
||||||
* connection will be discarded.
|
|
||||||
*
|
|
||||||
* @param <W> Type of object that is written to the client using this holder.
|
|
||||||
* @param <R> Type of object that is read from the the client using this holder.
|
|
||||||
*/
|
|
||||||
public class PreferCurrentEventLoopHolder<W, R> extends IdleConnectionsHolder<W, R> {
|
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(PreferCurrentEventLoopHolder.class.getName());
|
|
||||||
|
|
||||||
private final FastThreadLocal<IdleConnectionsHolder<W, R>> perElHolder = new FastThreadLocal<>();
|
|
||||||
private final ArrayList<IdleConnectionsHolder<W, R>> allElHolders;
|
|
||||||
private final Observable<PooledConnection<R, W>> pollObservable;
|
|
||||||
private final Observable<PooledConnection<R, W>> peekObservable;
|
|
||||||
|
|
||||||
PreferCurrentEventLoopHolder(PreferCurrentEventLoopGroup eventLoopGroup) {
|
|
||||||
this(eventLoopGroup, new FIFOIdleConnectionsHolderFactory<W, R>());
|
|
||||||
}
|
|
||||||
|
|
||||||
PreferCurrentEventLoopHolder(PreferCurrentEventLoopGroup eventLoopGroup,
|
|
||||||
final IdleConnectionsHolderFactory<W, R> holderFactory) {
|
|
||||||
final ArrayList<IdleConnectionsHolder<W, R>> _allElHolders = new ArrayList<>();
|
|
||||||
allElHolders = _allElHolders;
|
|
||||||
for (final EventExecutor child : eventLoopGroup) {
|
|
||||||
final IdleConnectionsHolder<W, R> newHolder = holderFactory.call();
|
|
||||||
allElHolders.add(newHolder);
|
|
||||||
child.submit(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
perElHolder.set(newHolder);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Observable<PooledConnection<R, W>> pollOverAllHolders = Observable.empty();
|
|
||||||
Observable<PooledConnection<R, W>> peekOverAllHolders = Observable.empty();
|
|
||||||
|
|
||||||
for (IdleConnectionsHolder<W, R> anElHolder : allElHolders) {
|
|
||||||
pollOverAllHolders = pollOverAllHolders.concatWith(anElHolder.poll());
|
|
||||||
peekOverAllHolders = peekOverAllHolders.concatWith(anElHolder.peek());
|
|
||||||
}
|
|
||||||
|
|
||||||
pollObservable = pollOverAllHolders;
|
|
||||||
peekObservable = peekOverAllHolders;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<PooledConnection<R, W>> poll() {
|
|
||||||
return pollObservable;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<PooledConnection<R, W>> pollThisEventLoopConnections() {
|
|
||||||
|
|
||||||
return Observable.create(new OnSubscribe<PooledConnection<R, W>>() {
|
|
||||||
@Override
|
|
||||||
public void call(Subscriber<? super PooledConnection<R, W>> subscriber) {
|
|
||||||
final IdleConnectionsHolder<W, R> holderForThisEL = perElHolder.get();
|
|
||||||
if (null == holderForThisEL) {
|
|
||||||
/*Caller is not an eventloop*/
|
|
||||||
PreferCurrentEventLoopHolder.super.pollThisEventLoopConnections().unsafeSubscribe(subscriber);
|
|
||||||
} else {
|
|
||||||
holderForThisEL.poll().unsafeSubscribe(subscriber);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<PooledConnection<R, W>> peek() {
|
|
||||||
return peekObservable;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void add(final PooledConnection<R, W> toAdd) {
|
|
||||||
final IdleConnectionsHolder<W, R> holderForThisEL = perElHolder.get();
|
|
||||||
if (null != holderForThisEL) {
|
|
||||||
holderForThisEL.add(toAdd);
|
|
||||||
} else {
|
|
||||||
/*
|
|
||||||
* This should not happen as the code generally adds the connection from within an eventloop.
|
|
||||||
* By executing the add on the eventloop, the owner eventloop is correctly discovered for this eventloop.
|
|
||||||
*/
|
|
||||||
toAdd.unsafeNettyChannel().eventLoop().execute(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
IdleConnectionsHolder<W, R> holderForThisEl = perElHolder.get();
|
|
||||||
if (null == holderForThisEl) {
|
|
||||||
logger.log(Level.SEVERE, "Unrecognized eventloop: " + Thread.currentThread().getName() +
|
|
||||||
". Returned connection can not be added to the pool. Closing the connection.");
|
|
||||||
toAdd.unsafeNettyChannel().attr(ClientConnectionToChannelBridge.DISCARD_CONNECTION).set(true);
|
|
||||||
toAdd.close().subscribe(Actions.empty(), new Action1<Throwable>() {
|
|
||||||
@Override
|
|
||||||
public void call(Throwable throwable) {
|
|
||||||
logger.log(Level.SEVERE, "Failed to discard connection.", throwable);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
holderForThisEl.add(toAdd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean remove(PooledConnection<R, W> toRemove) {
|
|
||||||
for (IdleConnectionsHolder<W, R> anElHolder : allElHolders) {
|
|
||||||
if (anElHolder.remove(toRemove)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface IdleConnectionsHolderFactory<W, R> extends Func0<IdleConnectionsHolder<W, R>> {
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class FIFOIdleConnectionsHolderFactory<W, R> implements IdleConnectionsHolderFactory<W, R> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public IdleConnectionsHolder<W, R> call() {
|
|
||||||
return new FIFOIdleConnectionsHolder<>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.pool;
|
|
||||||
|
|
||||||
import io.reactivex.netty.client.ConnectionProvider;
|
|
||||||
import io.reactivex.netty.client.ConnectionProviderFactory;
|
|
||||||
import io.reactivex.netty.client.HostConnector;
|
|
||||||
import io.reactivex.netty.client.internal.SingleHostConnectionProvider;
|
|
||||||
import rx.Observable;
|
|
||||||
import rx.functions.Func1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A {@link ConnectionProviderFactory} that must only be used for a client that operates on a single host.
|
|
||||||
*/
|
|
||||||
public class SingleHostPoolingProviderFactory<W, R> implements ConnectionProviderFactory<W, R> {
|
|
||||||
|
|
||||||
private final PoolConfig<W, R> config;
|
|
||||||
|
|
||||||
private SingleHostPoolingProviderFactory(PoolConfig<W, R> config) {
|
|
||||||
this.config = config;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ConnectionProvider<W, R> newProvider(Observable<HostConnector<W, R>> hosts) {
|
|
||||||
return new SingleHostConnectionProvider<>(hosts.map(new Func1<HostConnector<W, R>, HostConnector<W, R>>() {
|
|
||||||
@Override
|
|
||||||
public HostConnector<W, R> call(HostConnector<W, R> hc) {
|
|
||||||
return new HostConnector<>(hc, PooledConnectionProvider.create(config, hc));
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <W, R> SingleHostPoolingProviderFactory<W, R> createUnbounded() {
|
|
||||||
return create(new PoolConfig<W, R>());
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <W, R> SingleHostPoolingProviderFactory<W, R> createBounded(int maxConnections) {
|
|
||||||
return create(new PoolConfig<W, R>().maxConnections(maxConnections));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <W, R> SingleHostPoolingProviderFactory<W, R> create(final PoolConfig<W, R> config) {
|
|
||||||
return new SingleHostPoolingProviderFactory<>(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.pool;
|
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
public class UnboundedPoolLimitDeterminationStrategy implements PoolLimitDeterminationStrategy {
|
|
||||||
|
|
||||||
public static final PoolLimitDeterminationStrategy INSTANCE = new UnboundedPoolLimitDeterminationStrategy();
|
|
||||||
|
|
||||||
private UnboundedPoolLimitDeterminationStrategy() { }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean acquireCreationPermit(long acquireStartTime, TimeUnit timeUnit) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getAvailablePermits() {
|
|
||||||
return Integer.MAX_VALUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void releasePermit() {
|
|
||||||
// No Op, no limit.
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,140 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.events;
|
|
||||||
|
|
||||||
import io.reactivex.netty.RxNetty;
|
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A simple utility to wrap start and end times of a call.
|
|
||||||
*
|
|
||||||
* <h2>Thread Safety</h2>
|
|
||||||
*
|
|
||||||
* This class is <b>NOT</b> threadsafe.
|
|
||||||
* <h2>Memory overhead</h2>
|
|
||||||
*
|
|
||||||
* One of the major concerns in publishing events is the object allocation overhead and having a Clock instance
|
|
||||||
* can attribute to such overheads. This is the reason why this class also provides static convenience methods to mark
|
|
||||||
* start and end of times to reduce some boiler plate code.
|
|
||||||
*/
|
|
||||||
public class Clock {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The value returned by all static methods in this class, viz.,
|
|
||||||
* <ul>
|
|
||||||
<li>{@link #newStartTime(TimeUnit)}</li>
|
|
||||||
<li>{@link #newStartTimeNanos()}</li>
|
|
||||||
<li>{@link #onEndNanos(long)}</li>
|
|
||||||
<li>{@link #onEnd(long, TimeUnit)}</li>
|
|
||||||
</ul>
|
|
||||||
* after calling {@link RxNetty#disableEventPublishing()}
|
|
||||||
*/
|
|
||||||
public static final long SYSTEM_TIME_DISABLED_TIME = -1;
|
|
||||||
|
|
||||||
private final long startTimeNanos = System.nanoTime();
|
|
||||||
private long endTimeNanos = -1;
|
|
||||||
private long durationNanos = -1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stops this clock. This method is idempotent, so, after invoking this method, the duration of the clock is
|
|
||||||
* immutable. Hence, you can call this method multiple times with no side-effects.
|
|
||||||
*
|
|
||||||
* @return The duration in nanoseconds for which the clock was running.
|
|
||||||
*/
|
|
||||||
public long stop() {
|
|
||||||
if (-1 != endTimeNanos) {
|
|
||||||
endTimeNanos = System.nanoTime();
|
|
||||||
durationNanos = endTimeNanos - startTimeNanos;
|
|
||||||
}
|
|
||||||
return durationNanos;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getStartTimeNanos() {
|
|
||||||
return startTimeNanos;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getStartTime(TimeUnit targetUnit) {
|
|
||||||
return targetUnit.convert(startTimeNanos, TimeUnit.NANOSECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the duration for which this clock was running in nanoseconds.
|
|
||||||
*
|
|
||||||
* @return The duration for which this clock was running in nanoseconds.
|
|
||||||
*
|
|
||||||
* @throws IllegalStateException If the clock is not yet stopped.
|
|
||||||
*/
|
|
||||||
public long getDurationInNanos() {
|
|
||||||
if (isRunning()) {
|
|
||||||
throw new IllegalStateException("The clock is not yet stopped.");
|
|
||||||
}
|
|
||||||
return durationNanos;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the duration for which this clock was running in the given timeunit.
|
|
||||||
*
|
|
||||||
* @return The duration for which this clock was running in the given timeunit.
|
|
||||||
*
|
|
||||||
* @throws IllegalStateException If the clock is not yet stopped.
|
|
||||||
*/
|
|
||||||
public long getDuration(TimeUnit targetUnit) {
|
|
||||||
if (isRunning()) {
|
|
||||||
throw new IllegalStateException("The clock is not yet stopped.");
|
|
||||||
}
|
|
||||||
return targetUnit.convert(durationNanos, TimeUnit.NANOSECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isRunning() {
|
|
||||||
return -1 != durationNanos;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long newStartTimeNanos() {
|
|
||||||
return RxNetty.isEventPublishingDisabled() ? SYSTEM_TIME_DISABLED_TIME : System.nanoTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long newStartTime(TimeUnit timeUnit) {
|
|
||||||
if (RxNetty.isEventPublishingDisabled() ) {
|
|
||||||
return SYSTEM_TIME_DISABLED_TIME;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (TimeUnit.NANOSECONDS == timeUnit) {
|
|
||||||
return newStartTimeNanos();
|
|
||||||
}
|
|
||||||
return timeUnit.convert(newStartTimeNanos(), TimeUnit.NANOSECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long onEnd(long startTime, TimeUnit timeUnit) {
|
|
||||||
if (RxNetty.isEventPublishingDisabled() ) {
|
|
||||||
return SYSTEM_TIME_DISABLED_TIME;
|
|
||||||
}
|
|
||||||
if (TimeUnit.NANOSECONDS == timeUnit) {
|
|
||||||
return onEndNanos(startTime);
|
|
||||||
}
|
|
||||||
long startTimeNanos = TimeUnit.NANOSECONDS.convert(startTime, timeUnit);
|
|
||||||
return timeUnit.convert(onEndNanos(startTimeNanos), TimeUnit.NANOSECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long onEndNanos(long startTimeNanos) {
|
|
||||||
if (RxNetty.isEventPublishingDisabled() ) {
|
|
||||||
return SYSTEM_TIME_DISABLED_TIME;
|
|
||||||
}
|
|
||||||
return System.nanoTime() - startTimeNanos;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.events;
|
|
||||||
|
|
||||||
import io.netty.util.AttributeKey;
|
|
||||||
import io.reactivex.netty.channel.events.ConnectionEventListener;
|
|
||||||
import io.reactivex.netty.client.events.ClientEventListener;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A set of {@link AttributeKey} that are used by the event infrastructure.
|
|
||||||
*/
|
|
||||||
public final class EventAttributeKeys {
|
|
||||||
|
|
||||||
private EventAttributeKeys() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final AttributeKey<EventPublisher> EVENT_PUBLISHER =
|
|
||||||
AttributeKey.valueOf("rxnetty_client_event_publisher");
|
|
||||||
public static final AttributeKey<ClientEventListener> CLIENT_EVENT_LISTENER =
|
|
||||||
AttributeKey.valueOf("rxnetty_client_event_listener");
|
|
||||||
public static final AttributeKey<ConnectionEventListener> CONNECTION_EVENT_LISTENER =
|
|
||||||
AttributeKey.valueOf("rxnetty_client_conn_event_listener");
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,105 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.events;
|
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A listener to subscribe to events published by an {@link EventSource}
|
|
||||||
*/
|
|
||||||
public interface EventListener {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Marks the end of all event callbacks. No methods on this listener will ever be called once this method is called.
|
|
||||||
*/
|
|
||||||
void onCompleted();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Typically specific instances on {@link EventListener} will provide events that are fired by RxNetty, however,
|
|
||||||
* there may be cases, where a user would want to emit custom events. eg: Creating a custom protocol on top of an
|
|
||||||
* existing protocol like TCP, would perhaps require some additional events. In such a case, this callback can be
|
|
||||||
* utilized.
|
|
||||||
*
|
|
||||||
* @param event Event published.
|
|
||||||
*
|
|
||||||
* @see #onCustomEvent(Object, long, TimeUnit)
|
|
||||||
* @see #onCustomEvent(Object, Throwable)
|
|
||||||
* @see #onCustomEvent(Object, long, TimeUnit, Throwable)
|
|
||||||
*/
|
|
||||||
void onCustomEvent(Object event);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Typically specific instances on {@link EventListener} will provide events that are fired by RxNetty, however,
|
|
||||||
* there may be cases, where a user would want to emit custom events. eg: Creating a custom protocol on top of an
|
|
||||||
* existing protocol like TCP, would perhaps require some additional events. In such a case, this callback can be
|
|
||||||
* utilized.
|
|
||||||
*
|
|
||||||
* One should use this overload as opposed to {@link #onCustomEvent(Object)} if the custom event need not be created
|
|
||||||
* per invocation but has to be associated with a duration. This is a simple optimization to reduce event creation
|
|
||||||
* overhead.
|
|
||||||
*
|
|
||||||
* @param event Event published.
|
|
||||||
* @param duration Duration associated with this event. The semantics of this duration is totally upto the published
|
|
||||||
* event.
|
|
||||||
* @param timeUnit Timeunit for the duration.
|
|
||||||
*
|
|
||||||
* @see #onCustomEvent(Object, long, TimeUnit)
|
|
||||||
* @see #onCustomEvent(Object, Throwable)
|
|
||||||
* @see #onCustomEvent(Object, long, TimeUnit, Throwable)
|
|
||||||
*/
|
|
||||||
void onCustomEvent(Object event, long duration, TimeUnit timeUnit);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Typically specific instances on {@link EventListener} will provide events that are fired by RxNetty, however,
|
|
||||||
* there may be cases, where a user would want to emit custom events. eg: Creating a custom protocol on top of an
|
|
||||||
* existing protocol like TCP, would perhaps require some additional events. In such a case, this callback can be
|
|
||||||
* utilized.
|
|
||||||
*
|
|
||||||
* One should use this overload as opposed to {@link #onCustomEvent(Object)} if the custom event need not be created
|
|
||||||
* per invocation but has to be associated with an error. This is a simple optimization to reduce event creation
|
|
||||||
* overhead.
|
|
||||||
*
|
|
||||||
* @param event Event published.
|
|
||||||
*
|
|
||||||
* @see #onCustomEvent(Object, long, TimeUnit)
|
|
||||||
* @see #onCustomEvent(Object, Throwable)
|
|
||||||
* @see #onCustomEvent(Object, long, TimeUnit, Throwable)
|
|
||||||
*/
|
|
||||||
void onCustomEvent(Object event, Throwable throwable);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Typically specific instances on {@link EventListener} will provide events that are fired by RxNetty, however,
|
|
||||||
* there may be cases, where a user would want to emit custom events. eg: Creating a custom protocol on top of an
|
|
||||||
* existing protocol like TCP, would perhaps require some additional events. In such a case, this callback can be
|
|
||||||
* utilized.
|
|
||||||
*
|
|
||||||
* One should use this overload as opposed to {@link #onCustomEvent(Object)} if the custom event need not be created
|
|
||||||
* per invocation but has to be associated with a duration and an error. This is a simple optimization to reduce
|
|
||||||
* event creation overhead.
|
|
||||||
*
|
|
||||||
* @param event Event published.
|
|
||||||
* @param duration Duration associated with this event. The semantics of this duration is totally upto the published
|
|
||||||
* event.
|
|
||||||
* @param timeUnit Timeunit for the duration.
|
|
||||||
* @param throwable Error associated with the event.
|
|
||||||
*
|
|
||||||
* @see #onCustomEvent(Object, long, TimeUnit)
|
|
||||||
* @see #onCustomEvent(Object, Throwable)
|
|
||||||
* @see #onCustomEvent(Object, long, TimeUnit, Throwable)
|
|
||||||
*/
|
|
||||||
void onCustomEvent(Object event, long duration, TimeUnit timeUnit, Throwable throwable);
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.events;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A contract for any publisher of events.
|
|
||||||
*/
|
|
||||||
public interface EventPublisher {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} if event publishing is enabled. This is primarily used to short-circuit event publishing
|
|
||||||
* if the publishing is not enabled. Event publishing will be disabled if there are no active listeners or has
|
|
||||||
* been explicitly disabled using {@link io.reactivex.netty.RxNetty#disableEventPublishing()}
|
|
||||||
*
|
|
||||||
* @return {@code true} if event publishing is enabled.
|
|
||||||
*/
|
|
||||||
boolean publishingEnabled();
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.events;
|
|
||||||
|
|
||||||
import rx.Subscription;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An event source to which {@link EventListener}s can subscribe to receive events.
|
|
||||||
*/
|
|
||||||
public interface EventSource<T extends EventListener> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Subscribes the passed {@code listener} for events published by this source.
|
|
||||||
*
|
|
||||||
* @param listener Listener for events published by this source.
|
|
||||||
*
|
|
||||||
* @return Subscription, from which one can unsubscribe to stop receiving events.
|
|
||||||
*/
|
|
||||||
Subscription subscribe(T listener);
|
|
||||||
}
|
|
|
@ -1,65 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.events;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.PrintStream;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class ListenerInvocationException extends RuntimeException {
|
|
||||||
|
|
||||||
private Map<EventListener, Throwable> exceptions;
|
|
||||||
private String message;
|
|
||||||
|
|
||||||
private static final long serialVersionUID = -4381062024201397997L;
|
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
protected ListenerInvocationException() {
|
|
||||||
super("Metric event listener invocation failed.");
|
|
||||||
exceptions = new HashMap<>();
|
|
||||||
message = super.getMessage();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void addException(EventListener listener, Throwable error) {
|
|
||||||
exceptions.put(listener, error);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void finish() {
|
|
||||||
exceptions = Collections.unmodifiableMap(exceptions);
|
|
||||||
StringBuilder msgBuilder = new StringBuilder(getMessage()).append(". Errors: \n");
|
|
||||||
for (Map.Entry<EventListener, Throwable> exceptionEntry : exceptions.entrySet()) {
|
|
||||||
msgBuilder.append("Listener: ");
|
|
||||||
msgBuilder.append(exceptionEntry.getKey().getClass().getSimpleName());
|
|
||||||
msgBuilder.append("\n Error:");
|
|
||||||
ByteArrayOutputStream stackTraceStream = new ByteArrayOutputStream();
|
|
||||||
exceptionEntry.getValue().printStackTrace(new PrintStream(stackTraceStream));
|
|
||||||
msgBuilder.append(stackTraceStream.toString());
|
|
||||||
}
|
|
||||||
message = msgBuilder.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<EventListener, Throwable> getExceptions() {
|
|
||||||
return exceptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getMessage() {
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,406 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.events;
|
|
||||||
|
|
||||||
import io.reactivex.netty.RxNetty;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
import rx.Subscription;
|
|
||||||
import rx.exceptions.Exceptions;
|
|
||||||
import rx.functions.Action0;
|
|
||||||
import rx.functions.Action1;
|
|
||||||
import rx.functions.Action2;
|
|
||||||
import rx.functions.Action3;
|
|
||||||
import rx.functions.Action4;
|
|
||||||
import rx.functions.Action5;
|
|
||||||
import rx.subscriptions.CompositeSubscription;
|
|
||||||
import rx.subscriptions.Subscriptions;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.concurrent.CopyOnWriteArraySet;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A holder for storing {@link EventListener} providing utility methods for any {@link EventSource} implementation that
|
|
||||||
* requires storing and invoking listeners.
|
|
||||||
*
|
|
||||||
* @param <T> Type of listener to store.
|
|
||||||
*/
|
|
||||||
public final class ListenersHolder<T extends EventListener> implements EventSource<T>, EventPublisher {
|
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(ListenersHolder.class.getName());
|
|
||||||
|
|
||||||
private final CopyOnWriteArraySet<ListenerHolder<T>> listeners;
|
|
||||||
|
|
||||||
public ListenersHolder() {
|
|
||||||
listeners = new CopyOnWriteArraySet<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public ListenersHolder(ListenersHolder<T> toCopy) {
|
|
||||||
|
|
||||||
listeners = new CopyOnWriteArraySet<>(toCopy.listeners);
|
|
||||||
|
|
||||||
for (final ListenerHolder<T> holder : listeners) {
|
|
||||||
// Add the subscription to the existing holder, so that on unsubscribe, it is also removed from this list.
|
|
||||||
holder.subscription.add(Subscriptions.create(new Action0() {
|
|
||||||
@Override
|
|
||||||
public void call() {
|
|
||||||
listeners.remove(holder);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Subscription subscribe(final T listener) {
|
|
||||||
final CompositeSubscription cs = new CompositeSubscription();
|
|
||||||
|
|
||||||
ListenerHolder.configureRemoval(cs, listener, listeners);
|
|
||||||
|
|
||||||
final ListenerHolder<T> holder = new ListenerHolder<>(listener, cs);
|
|
||||||
listeners.add(holder);
|
|
||||||
return cs;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean publishingEnabled() {
|
|
||||||
return !RxNetty.isEventPublishingDisabled() && !listeners.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void dispose() {
|
|
||||||
ListenerInvocationException exception = null;
|
|
||||||
for (ListenerHolder<T> listener : listeners) {
|
|
||||||
try {
|
|
||||||
listener.onCompleted();
|
|
||||||
} catch (Throwable e) {
|
|
||||||
exception = handleListenerError(exception, listener, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (null != exception) {
|
|
||||||
exception.finish();
|
|
||||||
throw exception;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoke listeners with an action expressed by the passed {@code invocationAction}. This method does the necessary
|
|
||||||
* validations required for invoking a listener and also guards against a listener throwing exceptions on invocation.
|
|
||||||
*
|
|
||||||
* @param invocationAction The action to perform on all listeners.
|
|
||||||
*/
|
|
||||||
public void invokeListeners(Action1<T> invocationAction) {
|
|
||||||
ListenerInvocationException exception = null;
|
|
||||||
for (final ListenerHolder<T> listener : listeners) {
|
|
||||||
if (!listener.subscription.isUnsubscribed()) {
|
|
||||||
try {
|
|
||||||
invocationAction.call(listener.delegate);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
exception = handleListenerError(exception, listener, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (null != exception) {
|
|
||||||
exception.finish();
|
|
||||||
/*Do not bubble event notification errors to the caller, event notifications are best effort.*/
|
|
||||||
logger.log(Level.SEVERE, "Error occured while invoking event listeners.", exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoke listeners with an action expressed by the passed {@code invocationAction}. This method does the necessary
|
|
||||||
* validations required for invoking a listener and also guards against a listener throwing exceptions on invocation.
|
|
||||||
*
|
|
||||||
* @param invocationAction The action to perform on all listeners.
|
|
||||||
* @param duration Duration.
|
|
||||||
* @param timeUnit Time unit for the duration.
|
|
||||||
*/
|
|
||||||
public void invokeListeners(Action3<T, Long, TimeUnit> invocationAction, long duration, TimeUnit timeUnit) {
|
|
||||||
ListenerInvocationException exception = null;
|
|
||||||
for (ListenerHolder<T> listener : listeners) {
|
|
||||||
if (!listener.subscription.isUnsubscribed()) {
|
|
||||||
try {
|
|
||||||
invocationAction.call(listener.delegate, duration, timeUnit);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
exception = handleListenerError(exception, listener, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (null != exception) {
|
|
||||||
exception.finish();
|
|
||||||
/*Do not bubble event notification errors to the caller, event notifications are best effort.*/
|
|
||||||
logger.log(Level.SEVERE, "Error occured while invoking event listeners.", exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoke listeners with an action expressed by the passed {@code invocationAction}. This method does the necessary
|
|
||||||
* validations required for invoking a listener and also guards against a listener throwing exceptions on invocation.
|
|
||||||
*
|
|
||||||
* @param invocationAction The action to perform on all listeners.
|
|
||||||
* @param duration Duration.
|
|
||||||
* @param timeUnit Time unit for the duration.
|
|
||||||
* @param throwable An error.
|
|
||||||
*/
|
|
||||||
public void invokeListeners(Action4<T, Long, TimeUnit, Throwable> invocationAction, long duration,
|
|
||||||
TimeUnit timeUnit, Throwable throwable) {
|
|
||||||
ListenerInvocationException exception = null;
|
|
||||||
for (ListenerHolder<T> listener : listeners) {
|
|
||||||
if (!listener.subscription.isUnsubscribed()) {
|
|
||||||
try {
|
|
||||||
invocationAction.call(listener.delegate, duration, timeUnit, throwable);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
exception = handleListenerError(exception, listener, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (null != exception) {
|
|
||||||
exception.finish();
|
|
||||||
/*Do not bubble event notification errors to the caller, event notifications are best effort.*/
|
|
||||||
logger.log(Level.SEVERE, "Error occured while invoking event listeners.", exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoke listeners with an action expressed by the passed {@code invocationAction}. This method does the necessary
|
|
||||||
* validations required for invoking a listener and also guards against a listener throwing exceptions on invocation.
|
|
||||||
*
|
|
||||||
* @param invocationAction The action to perform on all listeners.
|
|
||||||
* @param duration Duration.
|
|
||||||
* @param timeUnit Time unit for the duration.
|
|
||||||
* @param arg Any arbitrary argument
|
|
||||||
*/
|
|
||||||
public <A> void invokeListeners(Action4<T, Long, TimeUnit, A> invocationAction, long duration,
|
|
||||||
TimeUnit timeUnit, A arg) {
|
|
||||||
ListenerInvocationException exception = null;
|
|
||||||
for (ListenerHolder<T> listener : listeners) {
|
|
||||||
if (!listener.subscription.isUnsubscribed()) {
|
|
||||||
try {
|
|
||||||
invocationAction.call(listener.delegate, duration, timeUnit, arg);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
exception = handleListenerError(exception, listener, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (null != exception) {
|
|
||||||
exception.finish();
|
|
||||||
/*Do not bubble event notification errors to the caller, event notifications are best effort.*/
|
|
||||||
logger.log(Level.SEVERE, "Error occured while invoking event listeners.", exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoke listeners with an action expressed by the passed {@code invocationAction}. This method does the necessary
|
|
||||||
* validations required for invoking a listener and also guards against a listener throwing exceptions on invocation.
|
|
||||||
*
|
|
||||||
* @param invocationAction The action to perform on all listeners.
|
|
||||||
* @param duration Duration.
|
|
||||||
* @param timeUnit Time unit for the duration.
|
|
||||||
* @param throwable An error.
|
|
||||||
* @param arg Any arbitrary argument
|
|
||||||
*/
|
|
||||||
public <A> void invokeListeners(Action5<T, Long, TimeUnit, Throwable, A> invocationAction, long duration,
|
|
||||||
TimeUnit timeUnit, Throwable throwable, A arg) {
|
|
||||||
ListenerInvocationException exception = null;
|
|
||||||
for (ListenerHolder<T> listener : listeners) {
|
|
||||||
if (!listener.subscription.isUnsubscribed()) {
|
|
||||||
try {
|
|
||||||
invocationAction.call(listener.delegate, duration, timeUnit, throwable, arg);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
exception = handleListenerError(exception, listener, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (null != exception) {
|
|
||||||
exception.finish();
|
|
||||||
/*Do not bubble event notification errors to the caller, event notifications are best effort.*/
|
|
||||||
logger.log(Level.SEVERE, "Error occured while invoking event listeners.", exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoke listeners with an action expressed by the passed {@code invocationAction}. This method does the necessary
|
|
||||||
* validations required for invoking a listener and also guards against a listener throwing exceptions on invocation.
|
|
||||||
*
|
|
||||||
* @param invocationAction The action to perform on all listeners.
|
|
||||||
* @param arg Any arbitrary argument
|
|
||||||
*/
|
|
||||||
public <A> void invokeListeners(Action2<T, A> invocationAction, A arg) {
|
|
||||||
ListenerInvocationException exception = null;
|
|
||||||
for (ListenerHolder<T> listener : listeners) {
|
|
||||||
if (!listener.subscription.isUnsubscribed()) {
|
|
||||||
try {
|
|
||||||
invocationAction.call(listener.delegate, arg);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
exception = handleListenerError(exception, listener, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (null != exception) {
|
|
||||||
exception.finish();
|
|
||||||
/*Do not bubble event notification errors to the caller, event notifications are best effort.*/
|
|
||||||
logger.log(Level.SEVERE, "Error occured while invoking event listeners.", exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoke listeners with an action expressed by the passed {@code invocationAction}. This method does the necessary
|
|
||||||
* validations required for invoking a listener and also guards against a listener throwing exceptions on invocation.
|
|
||||||
*
|
|
||||||
* @param invocationAction The action to perform on all listeners.
|
|
||||||
* @param throwable An error.
|
|
||||||
* @param arg Any arbitrary argument
|
|
||||||
*/
|
|
||||||
public <A> void invokeListeners(Action3<T, Throwable, A> invocationAction, Throwable throwable, A arg) {
|
|
||||||
ListenerInvocationException exception = null;
|
|
||||||
for (ListenerHolder<T> listener : listeners) {
|
|
||||||
if (!listener.subscription.isUnsubscribed()) {
|
|
||||||
try {
|
|
||||||
invocationAction.call(listener.delegate, throwable, arg);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
exception = handleListenerError(exception, listener, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (null != exception) {
|
|
||||||
exception.finish();
|
|
||||||
/*Do not bubble event notification errors to the caller, event notifications are best effort.*/
|
|
||||||
logger.log(Level.SEVERE, "Error occured while invoking event listeners.", exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ListenerInvocationException handleListenerError(ListenerInvocationException exception,
|
|
||||||
ListenerHolder<T> listener, Throwable e) {
|
|
||||||
Exceptions.throwIfFatal(e);
|
|
||||||
if (null == exception) {
|
|
||||||
exception = new ListenerInvocationException();
|
|
||||||
}
|
|
||||||
exception.addException(listener.delegate, e);
|
|
||||||
return exception;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ListenersHolder<T> copy() {
|
|
||||||
return new ListenersHolder<>(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*Visible for testing*/Collection<T> getAllListeners() {
|
|
||||||
final Collection<T> toReturn = new ArrayList<>();
|
|
||||||
for (ListenerHolder<T> listener : listeners) {
|
|
||||||
toReturn.add(listener.delegate);
|
|
||||||
}
|
|
||||||
return toReturn;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*Visible for testing*/CopyOnWriteArraySet<ListenerHolder<T>> getActualListenersList() {
|
|
||||||
return listeners;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void subscribeAllTo(EventSource<T> lazySource) {
|
|
||||||
for (ListenerHolder<T> listener : listeners) {
|
|
||||||
listener.subscription.add(lazySource.subscribe(listener.delegate));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class ListenerHolder<T extends EventListener> implements EventListener {
|
|
||||||
|
|
||||||
private static final CompositeSubscription EMPTY_SUB_FOR_REMOVAL = new CompositeSubscription();
|
|
||||||
|
|
||||||
private final T delegate;
|
|
||||||
private final CompositeSubscription subscription;
|
|
||||||
|
|
||||||
public ListenerHolder(T delegate, CompositeSubscription subscription) {
|
|
||||||
this.delegate = delegate;
|
|
||||||
this.subscription = subscription;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCompleted() {
|
|
||||||
if (!subscription.isUnsubscribed()) {
|
|
||||||
try {
|
|
||||||
delegate.onCompleted();
|
|
||||||
} finally {
|
|
||||||
subscription.unsubscribe();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCustomEvent(Object event) { }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCustomEvent(Object event, long duration, TimeUnit timeUnit) { }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCustomEvent(Object event, Throwable throwable) { }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCustomEvent(Object event, long duration, TimeUnit timeUnit, Throwable throwable) { }
|
|
||||||
|
|
||||||
public static <X extends EventListener> ListenerHolder<X> forRemoval(X listenerToRemove) {
|
|
||||||
return new ListenerHolder<>(listenerToRemove, EMPTY_SUB_FOR_REMOVAL);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <X extends EventListener> void configureRemoval(CompositeSubscription cs,
|
|
||||||
final X listenerToRemove,
|
|
||||||
final CopyOnWriteArraySet<ListenerHolder<X>> removeFrom) {
|
|
||||||
cs.add(Subscriptions.create(new Action0() {
|
|
||||||
@Override
|
|
||||||
public void call() {
|
|
||||||
/**
|
|
||||||
* Why do we add {@link ListenerHolder} but remove {@link X}?
|
|
||||||
* Since {@link ListenerHolder} requires the associated {@link Subscription}, and then
|
|
||||||
* {@link Subscription} will require the {@link ListenerHolder}, there will be a circular dependency.
|
|
||||||
*
|
|
||||||
* Instead, by having {@link ListenerHolder} implement equals/hashcode to only look for the
|
|
||||||
* enclosing {@link X} instance, it is possible to add {@link ListenerHolder} but remove {@link X}
|
|
||||||
*/
|
|
||||||
removeFrom.remove(forRemoval(listenerToRemove));
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(o instanceof ListenerHolder)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
ListenerHolder that = (ListenerHolder) o;
|
|
||||||
|
|
||||||
return delegate.equals(that.delegate);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return delegate.hashCode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.events.internal;
|
|
||||||
|
|
||||||
import io.reactivex.netty.events.EventListener;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A marker interface to indicate that the {@link EventListener} is safe to be invoked multiple times but the wrapped
|
|
||||||
* listener will only be invoked once.
|
|
||||||
*/
|
|
||||||
public interface SafeEventListener extends EventListener {
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.internal;
|
|
||||||
|
|
||||||
import io.netty.channel.Channel;
|
|
||||||
import rx.functions.Action0;
|
|
||||||
|
|
||||||
public abstract class ExecuteInEventloopAction implements Action0, Runnable {
|
|
||||||
|
|
||||||
private final Channel channel;
|
|
||||||
|
|
||||||
protected ExecuteInEventloopAction(Channel channel) {
|
|
||||||
this.channel = channel;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void call() {
|
|
||||||
if (channel.eventLoop().inEventLoop()) {
|
|
||||||
run();
|
|
||||||
} else {
|
|
||||||
channel.eventLoop().execute(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,247 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.internal;
|
|
||||||
|
|
||||||
import io.netty.channel.ChannelDuplexHandler;
|
|
||||||
import io.netty.channel.ChannelFuture;
|
|
||||||
import io.netty.channel.ChannelFutureListener;
|
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
|
||||||
import io.netty.channel.ChannelPromise;
|
|
||||||
import io.netty.handler.timeout.ReadTimeoutException;
|
|
||||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
|
||||||
import io.reactivex.netty.client.ClientConnectionToChannelBridge.PooledConnectionReleaseEvent;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import java.util.concurrent.ScheduledFuture;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A copy of netty's {@link ReadTimeoutHandler}. This is required because {@link ReadTimeoutHandler} does not allow
|
|
||||||
* reuse in the same pipeline, which is required for connection pooling.
|
|
||||||
* See issue https://github.com/ReactiveX/RxNetty/issues/344
|
|
||||||
*/
|
|
||||||
public class InternalReadTimeoutHandler extends ChannelDuplexHandler {
|
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(InternalReadTimeoutHandler.class.getName());
|
|
||||||
|
|
||||||
private static final long MIN_TIMEOUT_NANOS = TimeUnit.MILLISECONDS.toNanos(1);
|
|
||||||
|
|
||||||
private final long timeoutNanos;
|
|
||||||
|
|
||||||
private volatile ScheduledFuture<?> timeout;
|
|
||||||
private volatile long lastReadTime;
|
|
||||||
|
|
||||||
private enum State {
|
|
||||||
Created,
|
|
||||||
Active,
|
|
||||||
Paused,
|
|
||||||
Destroyed
|
|
||||||
}
|
|
||||||
|
|
||||||
private volatile State state = State.Created;
|
|
||||||
|
|
||||||
private boolean closed;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new instance.
|
|
||||||
*
|
|
||||||
* @param timeout
|
|
||||||
* read timeout
|
|
||||||
* @param unit
|
|
||||||
* the {@link TimeUnit} of {@code timeout}
|
|
||||||
*/
|
|
||||||
public InternalReadTimeoutHandler(long timeout, TimeUnit unit) {
|
|
||||||
if (unit == null) {
|
|
||||||
throw new NullPointerException("unit");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timeout <= 0) {
|
|
||||||
timeoutNanos = 0;
|
|
||||||
} else {
|
|
||||||
timeoutNanos = Math.max(unit.toNanos(timeout), MIN_TIMEOUT_NANOS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
|
|
||||||
if (ctx.channel().isActive() && ctx.channel().isRegistered()) {
|
|
||||||
// channelActive() event has been fired already, which means this.channelActive() will
|
|
||||||
// not be invoked. We have to scheduleAfresh here instead.
|
|
||||||
scheduleAfresh(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
// channelActive() event has not been fired yet. this.channelActive() will be invoked
|
|
||||||
// and initialization will occur there.
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
|
|
||||||
destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
|
|
||||||
// Initialize early if channel is active already.
|
|
||||||
if (ctx.channel().isActive()) {
|
|
||||||
scheduleAfresh(ctx);
|
|
||||||
}
|
|
||||||
super.channelRegistered(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
|
||||||
// This method will be invoked only if this handler was added
|
|
||||||
// before channelActive() event is fired. If a user adds this handler
|
|
||||||
// after the channelActive() event, scheduleAfresh() will be called by beforeAdd().
|
|
||||||
scheduleAfresh(ctx);
|
|
||||||
super.channelActive(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
|
||||||
destroy();
|
|
||||||
super.channelInactive(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
|
||||||
lastReadTime = System.nanoTime();
|
|
||||||
ctx.fireChannelRead(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(final ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
|
||||||
if (State.Paused == state) {
|
|
||||||
// Add the timeout handler when write is complete.
|
|
||||||
promise.addListener(new ChannelFutureListener() {
|
|
||||||
@Override
|
|
||||||
public void operationComplete(ChannelFuture future) throws Exception {
|
|
||||||
if (State.Paused == state) {
|
|
||||||
/*
|
|
||||||
* Multiple writes can all add a listener, till it is active again (on write success), so it is
|
|
||||||
* required to only schedule next when the state is actually paused.
|
|
||||||
*/
|
|
||||||
scheduleAfresh(ctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
super.write(ctx, msg, promise);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
|
||||||
if (evt instanceof PooledConnectionReleaseEvent) {
|
|
||||||
cancelTimeoutSchedule(ctx);
|
|
||||||
}
|
|
||||||
super.userEventTriggered(ctx, evt);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void cancelTimeoutSchedule(ChannelHandlerContext ctx) {
|
|
||||||
assert ctx.channel().eventLoop().inEventLoop(); /*should only be called from the owner eventloop*/
|
|
||||||
if (State.Active == state) {
|
|
||||||
state = State.Paused;
|
|
||||||
timeout.cancel(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void scheduleAfresh(ChannelHandlerContext ctx) {
|
|
||||||
// Avoid the case where destroy() is called before scheduling timeouts.
|
|
||||||
// See: https://github.com/netty/netty/issues/143
|
|
||||||
switch (state) {
|
|
||||||
case Created:
|
|
||||||
break;
|
|
||||||
case Active:
|
|
||||||
return;
|
|
||||||
case Paused:
|
|
||||||
break;
|
|
||||||
case Destroyed:
|
|
||||||
logger.log(Level.WARNING, "Not scheduling next read timeout task as the channel handler is removed.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
state = State.Active;
|
|
||||||
|
|
||||||
lastReadTime = System.nanoTime();
|
|
||||||
if (timeoutNanos > 0) {
|
|
||||||
timeout = _scheduleNextTask(ctx, new ReadTimeoutTask(ctx), timeoutNanos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ScheduledFuture<?> _scheduleNextTask(ChannelHandlerContext ctx, ReadTimeoutTask task, long timeoutNanos) {
|
|
||||||
try {
|
|
||||||
return ctx.executor().schedule(task, timeoutNanos, TimeUnit.NANOSECONDS);
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.log(Level.SEVERE, "Failed to schedule read timeout task. Read timeout will not work on channel: "
|
|
||||||
+ ctx.channel(), e);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void destroy() {
|
|
||||||
state = State.Destroyed;
|
|
||||||
|
|
||||||
if (timeout != null) {
|
|
||||||
timeout.cancel(false);
|
|
||||||
timeout = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Is called when a read timeout was detected.
|
|
||||||
*/
|
|
||||||
protected void readTimedOut(ChannelHandlerContext ctx) throws Exception {
|
|
||||||
if (!closed) {
|
|
||||||
ctx.fireExceptionCaught(ReadTimeoutException.INSTANCE);
|
|
||||||
ctx.close();
|
|
||||||
closed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class ReadTimeoutTask implements Runnable {
|
|
||||||
|
|
||||||
private final ChannelHandlerContext ctx;
|
|
||||||
|
|
||||||
ReadTimeoutTask(ChannelHandlerContext ctx) {
|
|
||||||
this.ctx = ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
if (!ctx.channel().isOpen()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
long currentTime = System.nanoTime();
|
|
||||||
long nextDelay = timeoutNanos - (currentTime - lastReadTime);
|
|
||||||
if (nextDelay <= 0) {
|
|
||||||
// Read timed out - set a new timeout and notify the callback.
|
|
||||||
timeout = ctx.executor().schedule(this, timeoutNanos, TimeUnit.NANOSECONDS);
|
|
||||||
try {
|
|
||||||
readTimedOut(ctx);
|
|
||||||
} catch (Throwable t) {
|
|
||||||
ctx.fireExceptionCaught(t);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Read occurred before the timeout - set a new timeout with shorter delay.
|
|
||||||
timeout = ctx.executor().schedule(this, nextDelay, TimeUnit.NANOSECONDS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.internal;
|
|
||||||
|
|
||||||
import rx.Observable;
|
|
||||||
import rx.functions.Func1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A function to be used in place of {@link Observable#cast(Class)} to support nested generics.
|
|
||||||
*
|
|
||||||
* @param <T> Target type.
|
|
||||||
*/
|
|
||||||
public class VoidToAnythingCast<T> implements Func1<Void, T> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public T call(Void aVoid) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,95 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.http;
|
|
||||||
|
|
||||||
import io.netty.handler.codec.http.HttpHeaders;
|
|
||||||
import io.netty.handler.codec.http.cookie.ClientCookieDecoder;
|
|
||||||
import io.netty.handler.codec.http.cookie.Cookie;
|
|
||||||
import io.netty.handler.codec.http.cookie.ServerCookieDecoder;
|
|
||||||
import io.netty.util.AsciiString;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import static io.netty.handler.codec.http.HttpHeaderNames.*;
|
|
||||||
/**
|
|
||||||
* A holder of cookies parsed from the Http headers.
|
|
||||||
*/
|
|
||||||
public class CookiesHolder {
|
|
||||||
|
|
||||||
private final HttpHeaders nettyHeaders;
|
|
||||||
private final AsciiString cookiesHeaderName;
|
|
||||||
private final boolean isClientDecoder;
|
|
||||||
private Map<String, Set<Cookie>> allCookies;
|
|
||||||
private boolean cookiesParsed;
|
|
||||||
|
|
||||||
private CookiesHolder(HttpHeaders nettyHeaders, AsciiString cookiesHeaderName, boolean isClientDecoder) {
|
|
||||||
this.nettyHeaders = nettyHeaders;
|
|
||||||
this.cookiesHeaderName = cookiesHeaderName;
|
|
||||||
this.isClientDecoder = isClientDecoder;
|
|
||||||
allCookies = Collections.emptyMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, Set<Cookie>> getAllCookies() {
|
|
||||||
return _parseIfNeededAndGet();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static CookiesHolder newClientResponseHolder(HttpHeaders headers) {
|
|
||||||
return new CookiesHolder(headers, SET_COOKIE, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static CookiesHolder newServerRequestHolder(HttpHeaders headers) {
|
|
||||||
return new CookiesHolder(headers, COOKIE, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
private synchronized Map<String, Set<Cookie>> _parseIfNeededAndGet() {
|
|
||||||
if (cookiesParsed) { // This method is synchronized, a memory barrier for this variable to be refreshed.
|
|
||||||
return allCookies;
|
|
||||||
}
|
|
||||||
List<String> allCookieHeaders = nettyHeaders.getAll(cookiesHeaderName);
|
|
||||||
Map<String, Set<Cookie>> cookies = new HashMap<>();
|
|
||||||
for (String aCookieHeader : allCookieHeaders) {
|
|
||||||
Set<Cookie> decode;
|
|
||||||
if (isClientDecoder) {
|
|
||||||
Cookie decoded = ClientCookieDecoder.STRICT.decode(aCookieHeader);
|
|
||||||
Set<Cookie> existingCookiesOfName = cookies.get(decoded.name());
|
|
||||||
if (null == existingCookiesOfName) {
|
|
||||||
existingCookiesOfName = new HashSet<>();
|
|
||||||
cookies.put(decoded.name(), existingCookiesOfName);
|
|
||||||
}
|
|
||||||
existingCookiesOfName.add(decoded);
|
|
||||||
} else {
|
|
||||||
decode = ServerCookieDecoder.STRICT.decode(aCookieHeader);
|
|
||||||
for (Cookie cookie : decode) {
|
|
||||||
Set<Cookie> existingCookiesOfName = cookies.get(cookie.name());
|
|
||||||
if (null == existingCookiesOfName) {
|
|
||||||
existingCookiesOfName = new HashSet<>();
|
|
||||||
cookies.put(cookie.name(), existingCookiesOfName);
|
|
||||||
}
|
|
||||||
existingCookiesOfName.add(cookie);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
allCookies = Collections.unmodifiableMap(cookies);
|
|
||||||
cookiesParsed = true;
|
|
||||||
return allCookies;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.http;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A list of all handler names added by for HTTP. This is just to ensure consistency in naming.
|
|
||||||
*/
|
|
||||||
public enum HttpHandlerNames {
|
|
||||||
|
|
||||||
HttpClientCodec("http-client-codec"),
|
|
||||||
HttpServerDecoder("http-server-request-decoder"),
|
|
||||||
HttpServerEncoder("http-server-response-encoder"),
|
|
||||||
WsServerDecoder("ws-server-request-decoder"),
|
|
||||||
WsServerEncoder("ws-server-response-encoder"),
|
|
||||||
WsServerUpgradeHandler("ws-server-upgrade-handler"),
|
|
||||||
WsClientDecoder("ws-client-request-decoder"),
|
|
||||||
WsClientEncoder("ws-client-response-encoder"),
|
|
||||||
WsClientUpgradeHandler("ws-client-upgrade-handler"),
|
|
||||||
SseClientCodec("sse-client-codec"),
|
|
||||||
SseServerCodec("sse-server-codec"),
|
|
||||||
;
|
|
||||||
|
|
||||||
private final String name;
|
|
||||||
|
|
||||||
HttpHandlerNames(String name) {
|
|
||||||
this.name = qualify(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String qualify(String name) {
|
|
||||||
return "_rx_netty_" + name;
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,116 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.http;
|
|
||||||
|
|
||||||
import io.netty.handler.codec.http.DefaultLastHttpContent;
|
|
||||||
import io.netty.handler.codec.http.LastHttpContent;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A mutable stateful entity containing trailing headers.
|
|
||||||
*
|
|
||||||
* <b>This class is not thread safe</b>
|
|
||||||
*/
|
|
||||||
public class TrailingHeaders {
|
|
||||||
|
|
||||||
private final LastHttpContent lastHttpContent;
|
|
||||||
|
|
||||||
public TrailingHeaders() {
|
|
||||||
lastHttpContent = LastHttpContent.EMPTY_LAST_CONTENT;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TrailingHeaders(LastHttpContent lastHttpContent) {
|
|
||||||
this.lastHttpContent = lastHttpContent;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds an HTTP trailing header with the passed {@code name} and {@code value} to this request.
|
|
||||||
*
|
|
||||||
* @param name Name of the header.
|
|
||||||
* @param value Value for the header.
|
|
||||||
*
|
|
||||||
* @return {@code this}.
|
|
||||||
*/
|
|
||||||
public TrailingHeaders addHeader(CharSequence name, Object value) {
|
|
||||||
lastHttpContent.trailingHeaders().add(name, value);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds an HTTP trailing header with the passed {@code name} and {@code values} to this request.
|
|
||||||
*
|
|
||||||
* @param name Name of the header.
|
|
||||||
* @param values Values for the header.
|
|
||||||
*
|
|
||||||
* @return {@code this}.
|
|
||||||
*/
|
|
||||||
public TrailingHeaders addHeader(CharSequence name, Iterable<Object> values) {
|
|
||||||
lastHttpContent.trailingHeaders().add(name, values);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overwrites the current value, if any, of the passed trailing header to the passed value for this request.
|
|
||||||
*
|
|
||||||
* @param name Name of the header.
|
|
||||||
* @param value Value of the header.
|
|
||||||
*
|
|
||||||
* @return {@code this}.
|
|
||||||
*/
|
|
||||||
public TrailingHeaders setHeader(CharSequence name, Object value) {
|
|
||||||
lastHttpContent.trailingHeaders().set(name, value);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overwrites the current value, if any, of the passed trailing header to the passed values for this request.
|
|
||||||
*
|
|
||||||
* @param name Name of the header.
|
|
||||||
* @param values Values of the header.
|
|
||||||
*
|
|
||||||
* @return {@code this}.
|
|
||||||
*/
|
|
||||||
public TrailingHeaders setHeader(CharSequence name, Iterable<Object> values) {
|
|
||||||
lastHttpContent.trailingHeaders().set(name, values);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the value of a header with the specified name. If there are more than one values for the specified name,
|
|
||||||
* the first value is returned.
|
|
||||||
*
|
|
||||||
* @param name The name of the header to search
|
|
||||||
*
|
|
||||||
* @return The first header value or {@code null} if there is no such header
|
|
||||||
*/
|
|
||||||
public String getHeader(CharSequence name) {
|
|
||||||
return lastHttpContent.trailingHeaders().get(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the values of headers with the specified name
|
|
||||||
*
|
|
||||||
* @param name The name of the headers to search
|
|
||||||
*
|
|
||||||
* @return A {@link List} of header values which will be empty if no values are found
|
|
||||||
*/
|
|
||||||
public List<String> getAllHeaderValues(CharSequence name) {
|
|
||||||
return lastHttpContent.trailingHeaders().getAll(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,363 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.http.client;
|
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
|
||||||
import io.netty.channel.ChannelHandler;
|
|
||||||
import io.netty.channel.ChannelOption;
|
|
||||||
import io.netty.channel.ChannelPipeline;
|
|
||||||
import io.netty.handler.logging.LogLevel;
|
|
||||||
import io.netty.handler.logging.LoggingHandler;
|
|
||||||
import io.netty.handler.ssl.SslHandler;
|
|
||||||
import io.netty.util.concurrent.EventExecutorGroup;
|
|
||||||
import io.reactivex.netty.client.ChannelProviderFactory;
|
|
||||||
import io.reactivex.netty.client.ConnectionProviderFactory;
|
|
||||||
import io.reactivex.netty.client.Host;
|
|
||||||
import io.reactivex.netty.ssl.SslCodec;
|
|
||||||
import rx.Observable;
|
|
||||||
import rx.functions.Action1;
|
|
||||||
import rx.functions.Func0;
|
|
||||||
import rx.functions.Func1;
|
|
||||||
|
|
||||||
import javax.net.ssl.SSLEngine;
|
|
||||||
import javax.net.ssl.TrustManagerFactory;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.net.SocketAddress;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An HTTP client for executing HTTP requests.
|
|
||||||
*
|
|
||||||
* <h2>Immutability</h2>
|
|
||||||
* An instance of this client is immutable and all mutations produce a new client instance. For this reason it is
|
|
||||||
* recommended that the mutations are done during client creation and not during request processing to avoid repeated
|
|
||||||
* object creation overhead.
|
|
||||||
*
|
|
||||||
* @param <I> The type of the content of request.
|
|
||||||
* @param <O> The type of the content of response.
|
|
||||||
*/
|
|
||||||
public abstract class HttpClient<I, O> extends InterceptingHttpClient<I,O> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new client instances, inheriting all configurations from this client and adding the passed read timeout
|
|
||||||
* for all requests created by the newly created client instance.
|
|
||||||
*
|
|
||||||
* @param timeOut Read timeout duration.
|
|
||||||
* @param timeUnit Timeunit for the timeout.
|
|
||||||
*
|
|
||||||
* @return A new {@link HttpClient} instance.
|
|
||||||
*/
|
|
||||||
public abstract HttpClient<I, O> readTimeOut(int timeOut, TimeUnit timeUnit);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new client instances, inheriting all configurations from this client and adding a
|
|
||||||
* {@link ChannelOption} for the connections created by the newly created client instance.
|
|
||||||
*
|
|
||||||
* @param option Option to add.
|
|
||||||
* @param value Value for the option.
|
|
||||||
*
|
|
||||||
* @return A new {@link HttpClient} instance.
|
|
||||||
*/
|
|
||||||
public abstract <T> HttpClient<I, O> channelOption(ChannelOption<T> option, T value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a {@link ChannelHandler} to {@link ChannelPipeline} for all connections created by this client. The specified
|
|
||||||
* handler is added at the first position of the pipeline as specified by
|
|
||||||
* {@link ChannelPipeline#addFirst(String, ChannelHandler)}
|
|
||||||
*
|
|
||||||
* <em>For better flexibility of pipeline modification, the method {@link #pipelineConfigurator(Action1)} will be more
|
|
||||||
* convenient.</em>
|
|
||||||
*
|
|
||||||
* @param name Name of the handler.
|
|
||||||
* @param handlerFactory Factory to create handler instance to add.
|
|
||||||
*
|
|
||||||
* @return A new {@link HttpClient} instance.
|
|
||||||
*/
|
|
||||||
public abstract <II, OO> HttpClient<II, OO> addChannelHandlerFirst(String name, Func0<ChannelHandler> handlerFactory);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a {@link ChannelHandler} to {@link ChannelPipeline} for all connections created by this client. The specified
|
|
||||||
* handler is added at the first position of the pipeline as specified by
|
|
||||||
* {@link ChannelPipeline#addFirst(EventExecutorGroup, String, ChannelHandler)}
|
|
||||||
*
|
|
||||||
* <em>For better flexibility of pipeline modification, the method {@link #pipelineConfigurator(Action1)} will be more
|
|
||||||
* convenient.</em>
|
|
||||||
*
|
|
||||||
* @param group the {@link EventExecutorGroup} which will be used to execute the {@link ChannelHandler}
|
|
||||||
* methods
|
|
||||||
* @param name the name of the handler to append
|
|
||||||
* @param handlerFactory Factory to create handler instance to add.
|
|
||||||
*
|
|
||||||
* @return A new {@link HttpClient} instance.
|
|
||||||
*/
|
|
||||||
public abstract <II, OO> HttpClient<II, OO> addChannelHandlerFirst(EventExecutorGroup group, String name,
|
|
||||||
Func0<ChannelHandler> handlerFactory);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a {@link ChannelHandler} to {@link ChannelPipeline} for all connections created by this client. The specified
|
|
||||||
* handler is added at the last position of the pipeline as specified by
|
|
||||||
* {@link ChannelPipeline#addLast(String, ChannelHandler)}
|
|
||||||
*
|
|
||||||
* <em>For better flexibility of pipeline modification, the method {@link #pipelineConfigurator(Action1)} will be more
|
|
||||||
* convenient.</em>
|
|
||||||
*
|
|
||||||
* @param name Name of the handler.
|
|
||||||
* @param handlerFactory Factory to create handler instance to add.
|
|
||||||
*
|
|
||||||
* @return A new {@link HttpClient} instance.
|
|
||||||
*/
|
|
||||||
public abstract <II, OO> HttpClient<II, OO> addChannelHandlerLast(String name, Func0<ChannelHandler> handlerFactory);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a {@link ChannelHandler} to {@link ChannelPipeline} for all connections created by this client. The specified
|
|
||||||
* handler is added at the last position of the pipeline as specified by
|
|
||||||
* {@link ChannelPipeline#addLast(EventExecutorGroup, String, ChannelHandler)}
|
|
||||||
*
|
|
||||||
* <em>For better flexibility of pipeline modification, the method {@link #pipelineConfigurator(Action1)} will be more
|
|
||||||
* convenient.</em>
|
|
||||||
*
|
|
||||||
* @param group the {@link EventExecutorGroup} which will be used to execute the {@link ChannelHandler}
|
|
||||||
* methods
|
|
||||||
* @param name the name of the handler to append
|
|
||||||
* @param handlerFactory Factory to create handler instance to add.
|
|
||||||
*
|
|
||||||
* @return A new {@link HttpClient} instance.
|
|
||||||
*/
|
|
||||||
public abstract <II, OO> HttpClient<II, OO> addChannelHandlerLast(EventExecutorGroup group, String name,
|
|
||||||
Func0<ChannelHandler> handlerFactory);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a {@link ChannelHandler} to {@link ChannelPipeline} for all connections created by this client. The specified
|
|
||||||
* handler is added before an existing handler with the passed {@code baseName} in the pipeline as specified by
|
|
||||||
* {@link ChannelPipeline#addBefore(String, String, ChannelHandler)}
|
|
||||||
*
|
|
||||||
* <em>For better flexibility of pipeline modification, the method {@link #pipelineConfigurator(Action1)} will be more
|
|
||||||
* convenient.</em>
|
|
||||||
*
|
|
||||||
* @param baseName the name of the existing handler
|
|
||||||
* @param name Name of the handler.
|
|
||||||
* @param handlerFactory Factory to create handler instance to add.
|
|
||||||
*
|
|
||||||
* @return A new {@link HttpClient} instance.
|
|
||||||
*/
|
|
||||||
public abstract <II, OO> HttpClient<II, OO> addChannelHandlerBefore(String baseName, String name,
|
|
||||||
Func0<ChannelHandler> handlerFactory);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a {@link ChannelHandler} to {@link ChannelPipeline} for all connections created by this client. The specified
|
|
||||||
* handler is added before an existing handler with the passed {@code baseName} in the pipeline as specified by
|
|
||||||
* {@link ChannelPipeline#addBefore(EventExecutorGroup, String, String, ChannelHandler)}
|
|
||||||
*
|
|
||||||
* <em>For better flexibility of pipeline modification, the method {@link #pipelineConfigurator(Action1)} will be more
|
|
||||||
* convenient.</em>
|
|
||||||
*
|
|
||||||
* @param group the {@link EventExecutorGroup} which will be used to execute the {@link ChannelHandler}
|
|
||||||
* methods
|
|
||||||
* @param baseName the name of the existing handler
|
|
||||||
* @param name the name of the handler to append
|
|
||||||
* @param handlerFactory Factory to create handler instance to add.
|
|
||||||
*
|
|
||||||
* @return A new {@link HttpClient} instance.
|
|
||||||
*/
|
|
||||||
public abstract <II, OO> HttpClient<II, OO> addChannelHandlerBefore(EventExecutorGroup group, String baseName,
|
|
||||||
String name, Func0<ChannelHandler> handlerFactory);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a {@link ChannelHandler} to {@link ChannelPipeline} for all connections created by this client. The specified
|
|
||||||
* handler is added after an existing handler with the passed {@code baseName} in the pipeline as specified by
|
|
||||||
* {@link ChannelPipeline#addAfter(String, String, ChannelHandler)}
|
|
||||||
*
|
|
||||||
* @param baseName the name of the existing handler
|
|
||||||
* @param name Name of the handler.
|
|
||||||
* @param handlerFactory Factory to create handler instance to add.
|
|
||||||
*
|
|
||||||
* @return A new {@link HttpClient} instance.
|
|
||||||
*/
|
|
||||||
public abstract <II, OO> HttpClient<II, OO> addChannelHandlerAfter(String baseName, String name,
|
|
||||||
Func0<ChannelHandler> handlerFactory);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a {@link ChannelHandler} to {@link ChannelPipeline} for all connections created by this client. The specified
|
|
||||||
* handler is added after an existing handler with the passed {@code baseName} in the pipeline as specified by
|
|
||||||
* {@link ChannelPipeline#addAfter(EventExecutorGroup, String, String, ChannelHandler)}
|
|
||||||
*
|
|
||||||
* <em>For better flexibility of pipeline modification, the method {@link #pipelineConfigurator(Action1)} will be more
|
|
||||||
* convenient.</em>
|
|
||||||
*
|
|
||||||
* @param group the {@link EventExecutorGroup} which will be used to execute the {@link ChannelHandler}
|
|
||||||
* methods
|
|
||||||
* @param baseName the name of the existing handler
|
|
||||||
* @param name the name of the handler to append
|
|
||||||
* @param handlerFactory Factory to create handler instance to add.
|
|
||||||
*
|
|
||||||
* @return A new {@link HttpClient} instance.
|
|
||||||
*/
|
|
||||||
public abstract <II, OO> HttpClient<II, OO> addChannelHandlerAfter(EventExecutorGroup group, String baseName,
|
|
||||||
String name, Func0<ChannelHandler> handlerFactory);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new client instances, inheriting all configurations from this client and using the passed
|
|
||||||
* action to configure all the connections created by the newly created client instance.
|
|
||||||
*
|
|
||||||
* @param pipelineConfigurator Action to configure {@link ChannelPipeline}.
|
|
||||||
*
|
|
||||||
* @return A new {@link HttpClient} instance.
|
|
||||||
*/
|
|
||||||
public abstract <II, OO> HttpClient<II, OO> pipelineConfigurator(Action1<ChannelPipeline> pipelineConfigurator);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new client instance, inheriting all configurations from this client and using the passed
|
|
||||||
* {@code sslEngineFactory} for all secured connections created by the newly created client instance.
|
|
||||||
*
|
|
||||||
* If the {@link SSLEngine} instance can be statically, created, {@link #secure(SSLEngine)} can be used.
|
|
||||||
*
|
|
||||||
* @param sslEngineFactory Factory for all secured connections created by the newly created client instance.
|
|
||||||
*
|
|
||||||
* @return A new {@link HttpClient} instance.
|
|
||||||
*/
|
|
||||||
public abstract HttpClient<I, O> secure(Func1<ByteBufAllocator, SSLEngine> sslEngineFactory);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new client instance, inheriting all configurations from this client and using the passed
|
|
||||||
* {@code sslEngine} for all secured connections created by the newly created client instance.
|
|
||||||
*
|
|
||||||
* If the {@link SSLEngine} instance can not be statically, created, {@link #secure(Func1)} )} can be used.
|
|
||||||
*
|
|
||||||
* @param sslEngine {@link SSLEngine} for all secured connections created by the newly created client instance.
|
|
||||||
*
|
|
||||||
* @return A new {@link HttpClient} instance.
|
|
||||||
*/
|
|
||||||
public abstract HttpClient<I, O> secure(SSLEngine sslEngine);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new client instance, inheriting all configurations from this client and using the passed
|
|
||||||
* {@code sslCodec} for all secured connections created by the newly created client instance.
|
|
||||||
*
|
|
||||||
* This is required only when the {@link SslHandler} used by {@link SslCodec} is to be modified before adding to
|
|
||||||
* the {@link ChannelPipeline}. For most of the cases, {@link #secure(Func1)} or {@link #secure(SSLEngine)} will be
|
|
||||||
* enough.
|
|
||||||
*
|
|
||||||
* @param sslCodec {@link SslCodec} for all secured connections created by the newly created client instance.
|
|
||||||
*
|
|
||||||
* @return A new {@link HttpClient} instance.
|
|
||||||
*/
|
|
||||||
public abstract HttpClient<I, O> secure(SslCodec sslCodec);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new client instance, inheriting all configurations from this client and using a trust-all
|
|
||||||
* {@link TrustManagerFactory}for all secured connections created by the newly created client instance.
|
|
||||||
*
|
|
||||||
* <b>This is only for testing and should not be used for real production clients.</b>
|
|
||||||
*
|
|
||||||
* @return A new {@link HttpClient} instance.
|
|
||||||
*/
|
|
||||||
public abstract HttpClient<I, O> unsafeSecure();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new client instances, inheriting all configurations from this client and enabling wire logging at the
|
|
||||||
* passed level for the newly created client instance.
|
|
||||||
*
|
|
||||||
* @param wireLoggingLevel Logging level at which the wire logs will be logged. The wire logging will only be done if
|
|
||||||
* logging is enabled at this level for {@link LoggingHandler}
|
|
||||||
*
|
|
||||||
* @return A new {@link HttpClient} instance.
|
|
||||||
*
|
|
||||||
* @deprecated Use {@link #enableWireLogging(String, LogLevel)} instead.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
public abstract HttpClient<I, O> enableWireLogging(LogLevel wireLoggingLevel);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new client instances, inheriting all configurations from this client and enabling wire logging at the
|
|
||||||
* passed level for the newly created client instance.
|
|
||||||
*
|
|
||||||
* @param name Name of the logger that can be used to control the logging dynamically.
|
|
||||||
* @param wireLoggingLevel Logging level at which the wire logs will be logged. The wire logging will only be done if
|
|
||||||
* logging is enabled at this level for {@link LoggingHandler}
|
|
||||||
*
|
|
||||||
* @return A new {@link HttpClient} instance.
|
|
||||||
*/
|
|
||||||
public abstract HttpClient<I, O> enableWireLogging(String name, LogLevel wireLoggingLevel);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new client instance, inheriting all configurations from this client and using the passed
|
|
||||||
* {@code providerFactory}.
|
|
||||||
*
|
|
||||||
* @param providerFactory Channel provider factory.
|
|
||||||
*
|
|
||||||
* @return A new {@link HttpClient} instance.
|
|
||||||
*/
|
|
||||||
public abstract HttpClient<I, O> channelProvider(ChannelProviderFactory providerFactory);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new HTTP client instance with the passed host and port for the target server.
|
|
||||||
*
|
|
||||||
* @param host Hostname for the target server.
|
|
||||||
* @param port Port for the target server.
|
|
||||||
*
|
|
||||||
* @return A new {@code HttpClient} instance.
|
|
||||||
*/
|
|
||||||
public static HttpClient<ByteBuf, ByteBuf> newClient(String host, int port) {
|
|
||||||
return newClient(new InetSocketAddress(host, port));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new HTTP client instance with the passed address of the target server.
|
|
||||||
*
|
|
||||||
* @param serverAddress Socket address for the target server.
|
|
||||||
*
|
|
||||||
* @return A new {@code HttpClient} instance.
|
|
||||||
*/
|
|
||||||
public static HttpClient<ByteBuf, ByteBuf> newClient(SocketAddress serverAddress) {
|
|
||||||
return HttpClientImpl.create(serverAddress);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new HTTP client instance using the supplied connection provider.
|
|
||||||
*
|
|
||||||
* @param providerFactory {@link ConnectionProviderFactory} for the client.
|
|
||||||
* @param hostStream Stream of hosts for the client.
|
|
||||||
*
|
|
||||||
* @return A new {@code HttpClient} instance.
|
|
||||||
*/
|
|
||||||
public static HttpClient<ByteBuf, ByteBuf> newClient(ConnectionProviderFactory<ByteBuf, ByteBuf> providerFactory,
|
|
||||||
Observable<Host> hostStream) {
|
|
||||||
return HttpClientImpl.create(providerFactory, hostStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new client instances, inheriting all configurations from this client and following the passed number of
|
|
||||||
* max HTTP redirects.
|
|
||||||
*
|
|
||||||
* @param maxRedirects Maximum number of redirects to follow for any request.
|
|
||||||
*
|
|
||||||
* @return A new {@link HttpClient} instance.
|
|
||||||
*/
|
|
||||||
public abstract HttpClient<I, O> followRedirects(int maxRedirects);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new client instances, inheriting all configurations from this client and enabling/disabling redirects
|
|
||||||
* for all requests created by the newly created client instance.
|
|
||||||
*
|
|
||||||
* @param follow {@code true} to follow redirects. {@code false} to disable any redirects.
|
|
||||||
*
|
|
||||||
* @return A new {@link HttpClient} instance.
|
|
||||||
*/
|
|
||||||
public abstract HttpClient<I, O> followRedirects(boolean follow);
|
|
||||||
}
|
|
|
@ -1,312 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.http.client;
|
|
||||||
|
|
||||||
import io.netty.buffer.ByteBuf;
|
|
||||||
import io.netty.buffer.ByteBufAllocator;
|
|
||||||
import io.netty.channel.ChannelHandler;
|
|
||||||
import io.netty.channel.ChannelOption;
|
|
||||||
import io.netty.channel.ChannelPipeline;
|
|
||||||
import io.netty.handler.codec.http.HttpClientCodec;
|
|
||||||
import io.netty.handler.codec.http.HttpMethod;
|
|
||||||
import io.netty.handler.codec.http.HttpVersion;
|
|
||||||
import io.netty.handler.logging.LogLevel;
|
|
||||||
import io.netty.util.concurrent.EventExecutorGroup;
|
|
||||||
import io.reactivex.netty.client.ChannelProviderFactory;
|
|
||||||
import io.reactivex.netty.client.ConnectionProvider;
|
|
||||||
import io.reactivex.netty.client.ConnectionProviderFactory;
|
|
||||||
import io.reactivex.netty.client.Host;
|
|
||||||
import io.reactivex.netty.client.HostConnector;
|
|
||||||
import io.reactivex.netty.protocol.http.HttpHandlerNames;
|
|
||||||
import io.reactivex.netty.protocol.http.client.events.HttpClientEventPublisher;
|
|
||||||
import io.reactivex.netty.protocol.http.client.events.HttpClientEventsListener;
|
|
||||||
import io.reactivex.netty.protocol.http.client.internal.HttpChannelProviderFactory;
|
|
||||||
import io.reactivex.netty.protocol.http.client.internal.HttpClientRequestImpl;
|
|
||||||
import io.reactivex.netty.protocol.http.client.internal.HttpClientToConnectionBridge;
|
|
||||||
import io.reactivex.netty.protocol.http.client.internal.Redirector;
|
|
||||||
import io.reactivex.netty.protocol.http.ws.client.Ws7To13UpgradeHandler;
|
|
||||||
import io.reactivex.netty.protocol.tcp.client.TcpClient;
|
|
||||||
import io.reactivex.netty.protocol.tcp.client.TcpClientImpl;
|
|
||||||
import io.reactivex.netty.ssl.SslCodec;
|
|
||||||
import rx.Observable;
|
|
||||||
import rx.Subscription;
|
|
||||||
import rx.functions.Action1;
|
|
||||||
import rx.functions.Func0;
|
|
||||||
import rx.functions.Func1;
|
|
||||||
|
|
||||||
import javax.net.ssl.SSLEngine;
|
|
||||||
import java.net.SocketAddress;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import static io.reactivex.netty.protocol.http.client.internal.HttpClientRequestImpl.*;
|
|
||||||
|
|
||||||
public final class HttpClientImpl<I, O> extends HttpClient<I, O> {
|
|
||||||
|
|
||||||
private final TcpClient<?, HttpClientResponse<O>> client;
|
|
||||||
private final int maxRedirects;
|
|
||||||
private final HttpClientEventPublisher clientEventPublisher;
|
|
||||||
private final RequestProvider<I, O> requestProvider;
|
|
||||||
|
|
||||||
private HttpClientImpl(final TcpClient<?, HttpClientResponse<O>> client, final int maxRedirects,
|
|
||||||
HttpClientEventPublisher clientEventPublisher) {
|
|
||||||
this.client = client;
|
|
||||||
this.maxRedirects = maxRedirects;
|
|
||||||
this.clientEventPublisher = clientEventPublisher;
|
|
||||||
requestProvider = new RequestProvider<I, O>() {
|
|
||||||
@Override
|
|
||||||
public HttpClientRequest<I, O> createRequest(HttpVersion version, HttpMethod method, String uri) {
|
|
||||||
return HttpClientRequestImpl.create(version, method, uri, client, maxRedirects);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequest<I, O> createGet(String uri) {
|
|
||||||
return createRequest(HttpMethod.GET, uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequest<I, O> createPost(String uri) {
|
|
||||||
return createRequest(HttpMethod.POST, uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequest<I, O> createPut(String uri) {
|
|
||||||
return createRequest(HttpMethod.PUT, uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequest<I, O> createDelete(String uri) {
|
|
||||||
return createRequest(HttpMethod.DELETE, uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequest<I, O> createHead(String uri) {
|
|
||||||
return createRequest(HttpMethod.HEAD, uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequest<I, O> createOptions(String uri) {
|
|
||||||
return createRequest(HttpMethod.OPTIONS, uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequest<I, O> createPatch(String uri) {
|
|
||||||
return createRequest(HttpMethod.PATCH, uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequest<I, O> createTrace(String uri) {
|
|
||||||
return createRequest(HttpMethod.TRACE, uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequest<I, O> createConnect(String uri) {
|
|
||||||
return createRequest(HttpMethod.CONNECT, uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequest<I, O> createRequest(HttpMethod method, String uri) {
|
|
||||||
return createRequest(HttpVersion.HTTP_1_1, method, uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequest<I, O> createRequest(HttpVersion version, HttpMethod method, String uri) {
|
|
||||||
return requestProvider.createRequest(version, method, uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientInterceptorChain<I, O> intercept() {
|
|
||||||
return new HttpClientInterceptorChainImpl<>(requestProvider, clientEventPublisher);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientImpl<I, O> readTimeOut(int timeOut, TimeUnit timeUnit) {
|
|
||||||
return _copy(client.readTimeOut(timeOut, timeUnit), maxRedirects);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientImpl<I, O> followRedirects(int maxRedirects) {
|
|
||||||
return _copy(client, maxRedirects);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientImpl<I, O> followRedirects(boolean follow) {
|
|
||||||
return _copy(client, follow ? Redirector.DEFAULT_MAX_REDIRECTS : NO_REDIRECTS);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T> HttpClientImpl<I, O> channelOption(ChannelOption<T> option, T value) {
|
|
||||||
return _copy(client.channelOption(option, value), maxRedirects);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <II, OO> HttpClientImpl<II, OO> addChannelHandlerFirst(String name, Func0<ChannelHandler> handlerFactory) {
|
|
||||||
return _copy(HttpClientImpl.<OO>castClient(client.addChannelHandlerFirst(name, handlerFactory)),
|
|
||||||
maxRedirects);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <II, OO> HttpClientImpl<II, OO> addChannelHandlerFirst(EventExecutorGroup group, String name,
|
|
||||||
Func0<ChannelHandler> handlerFactory) {
|
|
||||||
return _copy(HttpClientImpl.<OO>castClient(client.addChannelHandlerFirst(group, name, handlerFactory)),
|
|
||||||
maxRedirects);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <II, OO> HttpClientImpl<II, OO> addChannelHandlerLast(String name, Func0<ChannelHandler> handlerFactory) {
|
|
||||||
return _copy(HttpClientImpl.<OO>castClient(client.addChannelHandlerLast(name, handlerFactory)),
|
|
||||||
maxRedirects);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <II, OO> HttpClientImpl<II, OO> addChannelHandlerLast(EventExecutorGroup group, String name,
|
|
||||||
Func0<ChannelHandler> handlerFactory) {
|
|
||||||
return _copy(HttpClientImpl.<OO>castClient(client.addChannelHandlerLast(group, name, handlerFactory)),
|
|
||||||
maxRedirects);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <II, OO> HttpClientImpl<II, OO> addChannelHandlerBefore(String baseName, String name,
|
|
||||||
Func0<ChannelHandler> handlerFactory) {
|
|
||||||
return _copy(HttpClientImpl.<OO>castClient(client.addChannelHandlerBefore(baseName, name, handlerFactory)),
|
|
||||||
maxRedirects);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <II, OO> HttpClientImpl<II, OO> addChannelHandlerBefore(EventExecutorGroup group, String baseName, String name,
|
|
||||||
Func0<ChannelHandler> handlerFactory) {
|
|
||||||
return _copy(HttpClientImpl.<OO>castClient(client.addChannelHandlerBefore(group, baseName, name,
|
|
||||||
handlerFactory)),
|
|
||||||
maxRedirects);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <II, OO> HttpClientImpl<II, OO> addChannelHandlerAfter(String baseName, String name,
|
|
||||||
Func0<ChannelHandler> handlerFactory) {
|
|
||||||
return _copy(HttpClientImpl.<OO>castClient(client.addChannelHandlerAfter(baseName, name, handlerFactory)),
|
|
||||||
maxRedirects);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <II, OO> HttpClientImpl<II, OO> addChannelHandlerAfter(EventExecutorGroup group, String baseName, String name,
|
|
||||||
Func0<ChannelHandler> handlerFactory) {
|
|
||||||
return _copy(HttpClientImpl.<OO>castClient(client.addChannelHandlerAfter(group, baseName, name,
|
|
||||||
handlerFactory)),
|
|
||||||
maxRedirects);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <II, OO> HttpClientImpl<II, OO> pipelineConfigurator(Action1<ChannelPipeline> pipelineConfigurator) {
|
|
||||||
return _copy(HttpClientImpl.<OO>castClient(client.pipelineConfigurator(pipelineConfigurator)),
|
|
||||||
maxRedirects);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientImpl<I, O> secure(Func1<ByteBufAllocator, SSLEngine> sslEngineFactory) {
|
|
||||||
return _copy(client.secure(sslEngineFactory), maxRedirects);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientImpl<I, O> secure(SSLEngine sslEngine) {
|
|
||||||
return _copy(client.secure(sslEngine), maxRedirects);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientImpl<I, O> secure(SslCodec sslCodec) {
|
|
||||||
return _copy(client.secure(sslCodec), maxRedirects);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientImpl<I, O> unsafeSecure() {
|
|
||||||
return _copy(client.unsafeSecure(), maxRedirects);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Deprecated
|
|
||||||
public HttpClientImpl<I, O> enableWireLogging(LogLevel wireLoggingLevel) {
|
|
||||||
return _copy(client.enableWireLogging(wireLoggingLevel), maxRedirects);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClient<I, O> enableWireLogging(String name, LogLevel wireLoggingLevel) {
|
|
||||||
return _copy(client.enableWireLogging(name, wireLoggingLevel), maxRedirects);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientImpl<I, O> channelProvider(ChannelProviderFactory providerFactory) {
|
|
||||||
return _copy(client.channelProvider(new HttpChannelProviderFactory(clientEventPublisher, providerFactory)),
|
|
||||||
maxRedirects);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Subscription subscribe(HttpClientEventsListener listener) {
|
|
||||||
return clientEventPublisher.subscribe(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static HttpClient<ByteBuf, ByteBuf> create(final ConnectionProviderFactory<ByteBuf, ByteBuf> providerFactory,
|
|
||||||
Observable<Host> hostStream) {
|
|
||||||
ConnectionProviderFactory<ByteBuf, ByteBuf> cpf = new ConnectionProviderFactory<ByteBuf, ByteBuf>() {
|
|
||||||
@Override
|
|
||||||
public ConnectionProvider<ByteBuf, ByteBuf> newProvider(Observable<HostConnector<ByteBuf, ByteBuf>> hosts) {
|
|
||||||
return providerFactory.newProvider(hosts.map(
|
|
||||||
new Func1<HostConnector<ByteBuf, ByteBuf>, HostConnector<ByteBuf, ByteBuf>>() {
|
|
||||||
@Override
|
|
||||||
public HostConnector<ByteBuf, ByteBuf> call(HostConnector<ByteBuf, ByteBuf> hc) {
|
|
||||||
HttpClientEventPublisher hcep = new HttpClientEventPublisher();
|
|
||||||
hc.subscribe(hcep);
|
|
||||||
return new HostConnector<>(hc.getHost(), hc.getConnectionProvider(), hcep, hcep, hcep);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return _newClient(TcpClientImpl.create(cpf, hostStream));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static HttpClient<ByteBuf, ByteBuf> create(SocketAddress socketAddress) {
|
|
||||||
return _newClient(TcpClientImpl.<ByteBuf, ByteBuf>create(socketAddress));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static HttpClient<ByteBuf, ByteBuf> _newClient(TcpClient<ByteBuf, ByteBuf> tcpClient) {
|
|
||||||
|
|
||||||
HttpClientEventPublisher clientEventPublisher = new HttpClientEventPublisher();
|
|
||||||
|
|
||||||
TcpClient<Object, HttpClientResponse<ByteBuf>> client =
|
|
||||||
tcpClient.<Object, HttpClientResponse<ByteBuf>>pipelineConfigurator(new Action1<ChannelPipeline>() {
|
|
||||||
@Override
|
|
||||||
public void call(ChannelPipeline pipeline) {
|
|
||||||
pipeline.addLast(HttpHandlerNames.HttpClientCodec.getName(), new HttpClientCodec());
|
|
||||||
pipeline.addLast(new HttpClientToConnectionBridge<>());
|
|
||||||
pipeline.addLast(HttpHandlerNames.WsClientUpgradeHandler.getName(),
|
|
||||||
new Ws7To13UpgradeHandler());
|
|
||||||
}
|
|
||||||
}).channelProvider(new HttpChannelProviderFactory(clientEventPublisher));
|
|
||||||
|
|
||||||
client.subscribe(clientEventPublisher);
|
|
||||||
|
|
||||||
return new HttpClientImpl<>(client, NO_REDIRECTS, clientEventPublisher);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private static <OO> TcpClient<?, HttpClientResponse<OO>> castClient(TcpClient<?, ?> rawTypes) {
|
|
||||||
return (TcpClient<?, HttpClientResponse<OO>>) rawTypes;
|
|
||||||
}
|
|
||||||
|
|
||||||
private <II, OO> HttpClientImpl<II, OO> _copy(TcpClient<?, HttpClientResponse<OO>> newClient, int maxRedirects) {
|
|
||||||
return new HttpClientImpl<>(newClient, maxRedirects, clientEventPublisher);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,82 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.http.client;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interceptor chain for {@link HttpClient}, obtained via {@link HttpClient#intercept()}. <p>
|
|
||||||
*
|
|
||||||
* Multiple interceptors can be added to this chain by using the various {@code next*()} methods available, before
|
|
||||||
* calling {@link #finish()} that returns a new {@link HttpClient} which inherits all the configuration from the parent
|
|
||||||
* client (from which this chain was created) and adds these interceptors.
|
|
||||||
*
|
|
||||||
* <h2>Order of execution</h2>
|
|
||||||
*
|
|
||||||
* Interceptors are executed in the order in which they are added.
|
|
||||||
*
|
|
||||||
* @param <I> The type of the content of request.
|
|
||||||
* @param <O> The type of the content of response.
|
|
||||||
*/
|
|
||||||
public interface HttpClientInterceptorChain<I, O> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a simple interceptor that does not change the type of objects read/written to a connection.
|
|
||||||
*
|
|
||||||
* @param interceptor Interceptor to add.
|
|
||||||
*
|
|
||||||
* @return {@code this}
|
|
||||||
*/
|
|
||||||
HttpClientInterceptorChain<I, O> next(Interceptor<I, O> interceptor);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds an interceptor that changes the type of objects read from the connections created by the client provided by
|
|
||||||
* this chain.
|
|
||||||
*
|
|
||||||
* @param interceptor Interceptor to add.
|
|
||||||
*
|
|
||||||
* @return A new chain instance.
|
|
||||||
*/
|
|
||||||
<OO> HttpClientInterceptorChain<I, OO> nextWithReadTransform(TransformingInterceptor<I, O, I, OO> interceptor);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds an interceptor that changes the type of objects written to the connections created by the client provided by
|
|
||||||
* this chain.
|
|
||||||
*
|
|
||||||
* @param interceptor Interceptor to add.
|
|
||||||
*
|
|
||||||
* @return A new chain instance.
|
|
||||||
*/
|
|
||||||
<II> HttpClientInterceptorChain<II, O> nextWithWriteTransform(TransformingInterceptor<I, O, II, O> interceptor);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds an interceptor that changes the type of objects read and written to the connections created by the client
|
|
||||||
* provided by this chain.
|
|
||||||
*
|
|
||||||
* @param interceptor Interceptor to add.
|
|
||||||
*
|
|
||||||
* @return A new chain instance.
|
|
||||||
*/
|
|
||||||
<II, OO> HttpClientInterceptorChain<II, OO> nextWithTransform(TransformingInterceptor<I, O, II, OO> interceptor);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finish the addition of interceptors and create a new client instance.
|
|
||||||
*
|
|
||||||
* @return New client instance which inherits all the configuration from the parent client
|
|
||||||
* (from which this chain was created) and adds these interceptors.
|
|
||||||
*/
|
|
||||||
InterceptingHttpClient<I, O> finish();
|
|
||||||
}
|
|
|
@ -1,56 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.http.client;
|
|
||||||
|
|
||||||
import io.reactivex.netty.protocol.http.client.events.HttpClientEventPublisher;
|
|
||||||
|
|
||||||
final class HttpClientInterceptorChainImpl<I, O> implements HttpClientInterceptorChain<I, O> {
|
|
||||||
|
|
||||||
private final RequestProvider<I, O> rp;
|
|
||||||
private final HttpClientEventPublisher cep;
|
|
||||||
|
|
||||||
HttpClientInterceptorChainImpl(RequestProvider<I, O> rp, HttpClientEventPublisher cep) {
|
|
||||||
this.rp = rp;
|
|
||||||
this.cep = cep;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientInterceptorChain<I, O> next(Interceptor<I, O> i) {
|
|
||||||
return new HttpClientInterceptorChainImpl<>(i.intercept(rp), cep);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <OO> HttpClientInterceptorChain<I, OO> nextWithReadTransform(TransformingInterceptor<I, O, I, OO> i) {
|
|
||||||
return new HttpClientInterceptorChainImpl<>(i.intercept(rp), cep);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <II> HttpClientInterceptorChain<II, O> nextWithWriteTransform(TransformingInterceptor<I, O, II, O> i) {
|
|
||||||
return new HttpClientInterceptorChainImpl<>(i.intercept(rp), cep);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <II, OO> HttpClientInterceptorChain<II, OO> nextWithTransform(TransformingInterceptor<I, O, II, OO> i) {
|
|
||||||
return new HttpClientInterceptorChainImpl<>(i.intercept(rp), cep);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public InterceptingHttpClient<I, O> finish() {
|
|
||||||
return new InterceptingHttpClientImpl<>(rp, cep);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,575 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.http.client;
|
|
||||||
|
|
||||||
import io.netty.handler.codec.http.HttpHeaders;
|
|
||||||
import io.netty.handler.codec.http.HttpMessage;
|
|
||||||
import io.netty.handler.codec.http.HttpMethod;
|
|
||||||
import io.netty.handler.codec.http.HttpVersion;
|
|
||||||
import io.netty.handler.codec.http.cookie.Cookie;
|
|
||||||
import io.reactivex.netty.channel.AllocatingTransformer;
|
|
||||||
import io.reactivex.netty.protocol.http.TrailingHeaders;
|
|
||||||
import io.reactivex.netty.protocol.http.ws.client.WebSocketRequest;
|
|
||||||
import rx.Observable;
|
|
||||||
import rx.annotations.Experimental;
|
|
||||||
import rx.functions.Func0;
|
|
||||||
import rx.functions.Func1;
|
|
||||||
import rx.functions.Func2;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An HTTP request. An instance of a request can only be created from an associated {@link HttpClient} and can be
|
|
||||||
* modified after creation.
|
|
||||||
*
|
|
||||||
* <h2>Request URIs</h2>
|
|
||||||
*
|
|
||||||
* While creating a request, the user should provide a URI to be used for the request. The URI can be relative or
|
|
||||||
* absolute. If the URI is relative (missing host and port information), the target host and port are inferred from the
|
|
||||||
* {@link HttpClient} that created the request. If the URI is absolute, the host and port are used from the URI.
|
|
||||||
*
|
|
||||||
* <h2>Mutations</h2>
|
|
||||||
*
|
|
||||||
* All mutations to this request creates a brand new instance.
|
|
||||||
|
|
||||||
* <h2>Trailing headers</h2>
|
|
||||||
*
|
|
||||||
* One can write HTTP trailing headers by using
|
|
||||||
*
|
|
||||||
* <h2> Executing request</h2>
|
|
||||||
*
|
|
||||||
* The request is executed every time {@link HttpClientRequest}, or {@link Observable} returned by
|
|
||||||
* {@code write*Content} is subscribed and is the only way of executing the request.
|
|
||||||
*
|
|
||||||
* @param <I> The type of objects read from the request content.
|
|
||||||
* @param <O> The type of objects read from the response content.
|
|
||||||
*/
|
|
||||||
public abstract class HttpClientRequest<I, O> extends Observable<HttpClientResponse<O>> {
|
|
||||||
|
|
||||||
protected HttpClientRequest(OnSubscribe<HttpClientResponse<O>> onSubscribe) {
|
|
||||||
super(onSubscribe);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uses the passed {@link Observable} as the source of content for this request.
|
|
||||||
*
|
|
||||||
* @param contentSource Content source for the request.
|
|
||||||
*
|
|
||||||
* @return An new instance of {@link Observable} which can be subscribed to execute the request.
|
|
||||||
*/
|
|
||||||
public abstract Observable<HttpClientResponse<O>> writeContent(Observable<I> contentSource);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uses the passed {@link Observable} as the source of content for this request. Every item is written and flushed
|
|
||||||
* immediately.
|
|
||||||
*
|
|
||||||
* @param contentSource Content source for the request.
|
|
||||||
*
|
|
||||||
* @return An new instance of {@link Observable} which can be subscribed to execute the request.
|
|
||||||
*/
|
|
||||||
public abstract Observable<HttpClientResponse<O>> writeContentAndFlushOnEach(Observable<I> contentSource);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uses the passed {@link Observable} as the source of content for this request.
|
|
||||||
*
|
|
||||||
* @param contentSource Content source for the request.
|
|
||||||
* @param flushSelector A {@link Func1} which is invoked for every item emitted from {@code msgs}. All pending
|
|
||||||
* writes are flushed, iff this function returns, {@code true}.
|
|
||||||
*
|
|
||||||
* @return An new instance of {@link Observable} which can be subscribed to execute the request.
|
|
||||||
*/
|
|
||||||
public abstract Observable<HttpClientResponse<O>> writeContent(Observable<I> contentSource,
|
|
||||||
Func1<I, Boolean> flushSelector);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uses the passed {@link Observable} as the source of content for this request. This method provides a way to
|
|
||||||
* write trailing headers.
|
|
||||||
*
|
|
||||||
* A new instance of {@link TrailingHeaders} will be created using the passed {@code trailerFactory} and the passed
|
|
||||||
* {@code trailerMutator} will be invoked for every item emitted from the content source, giving a chance to modify
|
|
||||||
* the trailing headers instance.
|
|
||||||
*
|
|
||||||
* @param contentSource Content source for the request.
|
|
||||||
* @param trailerFactory A factory function to create a new {@link TrailingHeaders} per subscription of the content.
|
|
||||||
* @param trailerMutator A function to mutate the trailing header on each item emitted from the content source.
|
|
||||||
*
|
|
||||||
* @return An new instance of {@link Observable} which can be subscribed to execute the request.
|
|
||||||
*/
|
|
||||||
@Experimental
|
|
||||||
public abstract <T extends TrailingHeaders> Observable<HttpClientResponse<O>> writeContent(Observable<I> contentSource,
|
|
||||||
Func0<T> trailerFactory,
|
|
||||||
Func2<T, I, T> trailerMutator);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uses the passed {@link Observable} as the source of content for this request. This method provides a way to
|
|
||||||
* write trailing headers.
|
|
||||||
*
|
|
||||||
* A new instance of {@link TrailingHeaders} will be created using the passed {@code trailerFactory} and the passed
|
|
||||||
* {@code trailerMutator} will be invoked for every item emitted from the content source, giving a chance to modify
|
|
||||||
* the trailing headers instance.
|
|
||||||
*
|
|
||||||
* @param contentSource Content source for the request.
|
|
||||||
* @param trailerFactory A factory function to create a new {@link TrailingHeaders} per subscription of the content.
|
|
||||||
* @param trailerMutator A function to mutate the trailing header on each item emitted from the content source.
|
|
||||||
* @param flushSelector A {@link Func1} which is invoked for every item emitted from {@code msgs}. All pending
|
|
||||||
* writes are flushed, iff this function returns, {@code true}.
|
|
||||||
*
|
|
||||||
* @return An new instance of {@link Observable} which can be subscribed to execute the request.
|
|
||||||
*/
|
|
||||||
@Experimental
|
|
||||||
public abstract <T extends TrailingHeaders> Observable<HttpClientResponse<O>> writeContent(Observable<I> contentSource,
|
|
||||||
Func0<T> trailerFactory,
|
|
||||||
Func2<T, I, T> trailerMutator,
|
|
||||||
Func1<I, Boolean> flushSelector);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uses the passed {@link Observable} as the source of content for this request.
|
|
||||||
*
|
|
||||||
* @param contentSource Content source for the request.
|
|
||||||
*
|
|
||||||
* @return An new instance of {@link Observable} which can be subscribed to execute the request.
|
|
||||||
*/
|
|
||||||
public abstract Observable<HttpClientResponse<O>> writeStringContent(Observable<String> contentSource);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uses the passed {@link Observable} as the source of content for this request.
|
|
||||||
*
|
|
||||||
* @param contentSource Content source for the request.
|
|
||||||
* @param flushSelector A {@link Func1} which is invoked for every item emitted from {@code msgs}. All pending
|
|
||||||
* writes are flushed, iff this function returns, {@code true}.
|
|
||||||
*
|
|
||||||
* @return An new instance of {@link Observable} which can be subscribed to execute the request.
|
|
||||||
*/
|
|
||||||
public abstract Observable<HttpClientResponse<O>> writeStringContent(Observable<String> contentSource,
|
|
||||||
Func1<String, Boolean> flushSelector);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uses the passed {@link Observable} as the source of content for this request. This method provides a way to
|
|
||||||
* write trailing headers.
|
|
||||||
*
|
|
||||||
* A new instance of {@link TrailingHeaders} will be created using the passed {@code trailerFactory} and the passed
|
|
||||||
* {@code trailerMutator} will be invoked for every item emitted from the content source, giving a chance to modify
|
|
||||||
* the trailing headers instance.
|
|
||||||
*
|
|
||||||
* @param contentSource Content source for the request.
|
|
||||||
* @param trailerFactory A factory function to create a new {@link TrailingHeaders} per subscription of the content.
|
|
||||||
* @param trailerMutator A function to mutate the trailing header on each item emitted from the content source.
|
|
||||||
*
|
|
||||||
* @return An new instance of {@link Observable} which can be subscribed to execute the request.
|
|
||||||
*/
|
|
||||||
@Experimental
|
|
||||||
public abstract <T extends TrailingHeaders> Observable<HttpClientResponse<O>> writeStringContent(Observable<String> contentSource,
|
|
||||||
Func0<T> trailerFactory,
|
|
||||||
Func2<T, String, T> trailerMutator);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uses the passed {@link Observable} as the source of content for this request. This method provides a way to
|
|
||||||
* write trailing headers.
|
|
||||||
*
|
|
||||||
* A new instance of {@link TrailingHeaders} will be created using the passed {@code trailerFactory} and the passed
|
|
||||||
* {@code trailerMutator} will be invoked for every item emitted from the content source, giving a chance to modify
|
|
||||||
* the trailing headers instance.
|
|
||||||
*
|
|
||||||
* @param contentSource Content source for the request.
|
|
||||||
* @param trailerFactory A factory function to create a new {@link TrailingHeaders} per subscription of the content.
|
|
||||||
* @param trailerMutator A function to mutate the trailing header on each item emitted from the content source.
|
|
||||||
* @param flushSelector A {@link Func1} which is invoked for every item emitted from {@code msgs}. All pending
|
|
||||||
* writes are flushed, iff this function returns, {@code true}.
|
|
||||||
*
|
|
||||||
* @return An new instance of {@link Observable} which can be subscribed to execute the request.
|
|
||||||
*/
|
|
||||||
@Experimental
|
|
||||||
public abstract <T extends TrailingHeaders> Observable<HttpClientResponse<O>> writeStringContent(Observable<String> contentSource,
|
|
||||||
Func0<T> trailerFactory,
|
|
||||||
Func2<T, String, T> trailerMutator,
|
|
||||||
Func1<String, Boolean> flushSelector);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uses the passed {@link Observable} as the source of content for this request.
|
|
||||||
*
|
|
||||||
* @param contentSource Content source for the request.
|
|
||||||
*
|
|
||||||
* @return An new instance of {@link Observable} which can be subscribed to execute the request.
|
|
||||||
*/
|
|
||||||
public abstract Observable<HttpClientResponse<O>> writeBytesContent(Observable<byte[]> contentSource);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uses the passed {@link Observable} as the source of content for this request.
|
|
||||||
*
|
|
||||||
* @param contentSource Content source for the request.
|
|
||||||
* @param flushSelector A {@link Func1} which is invoked for every item emitted from {@code msgs}. All pending
|
|
||||||
* writes are flushed, iff this function returns, {@code true}.
|
|
||||||
*
|
|
||||||
* @return An new instance of {@link Observable} which can be subscribed to execute the request.
|
|
||||||
*/
|
|
||||||
public abstract Observable<HttpClientResponse<O>> writeBytesContent(Observable<byte[]> contentSource,
|
|
||||||
Func1<byte[], Boolean> flushSelector);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uses the passed {@link Observable} as the source of content for this request. This method provides a way to
|
|
||||||
* write trailing headers.
|
|
||||||
*
|
|
||||||
* A new instance of {@link TrailingHeaders} will be created using the passed {@code trailerFactory} and the passed
|
|
||||||
* {@code trailerMutator} will be invoked for every item emitted from the content source, giving a chance to modify
|
|
||||||
* the trailing headers instance.
|
|
||||||
*
|
|
||||||
* @param contentSource Content source for the request.
|
|
||||||
* @param trailerFactory A factory function to create a new {@link TrailingHeaders} per subscription of the content.
|
|
||||||
* @param trailerMutator A function to mutate the trailing header on each item emitted from the content source.
|
|
||||||
*
|
|
||||||
* @return An new instance of {@link Observable} which can be subscribed to execute the request.
|
|
||||||
*/
|
|
||||||
@Experimental
|
|
||||||
public abstract <T extends TrailingHeaders> Observable<HttpClientResponse<O>> writeBytesContent(Observable<byte[]> contentSource,
|
|
||||||
Func0<T> trailerFactory,
|
|
||||||
Func2<T, byte[], T> trailerMutator);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uses the passed {@link Observable} as the source of content for this request. This method provides a way to
|
|
||||||
* write trailing headers.
|
|
||||||
*
|
|
||||||
* A new instance of {@link TrailingHeaders} will be created using the passed {@code trailerFactory} and the passed
|
|
||||||
* {@code trailerMutator} will be invoked for every item emitted from the content source, giving a chance to modify
|
|
||||||
* the trailing headers instance.
|
|
||||||
*
|
|
||||||
* @param contentSource Content source for the request.
|
|
||||||
* @param trailerFactory A factory function to create a new {@link TrailingHeaders} per subscription of the content.
|
|
||||||
* @param trailerMutator A function to mutate the trailing header on each item emitted from the content source.
|
|
||||||
* @param flushSelector A {@link Func1} which is invoked for every item emitted from {@code msgs}. All pending
|
|
||||||
* writes are flushed, iff this function returns, {@code true}.
|
|
||||||
*
|
|
||||||
* @return An new instance of {@link Observable} which can be subscribed to execute the request.
|
|
||||||
*/
|
|
||||||
@Experimental
|
|
||||||
public abstract <T extends TrailingHeaders> Observable<HttpClientResponse<O>> writeBytesContent(Observable<byte[]> contentSource,
|
|
||||||
Func0<T> trailerFactory,
|
|
||||||
Func2<T, byte[], T> trailerMutator,
|
|
||||||
Func1<byte[], Boolean> flushSelector);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enables read timeout for the response of the newly created and returned request.
|
|
||||||
*
|
|
||||||
* @param timeOut Read timeout duration.
|
|
||||||
* @param timeUnit Read timeout time unit.
|
|
||||||
*
|
|
||||||
* @return A new instance of the {@link HttpClientRequest} sharing all existing state from this request.
|
|
||||||
*/
|
|
||||||
public abstract HttpClientRequest<I, O> readTimeOut(int timeOut, TimeUnit timeUnit);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enables following HTTP redirects for the newly created and returned request.
|
|
||||||
*
|
|
||||||
* @param maxRedirects Maximum number of redirects allowed.
|
|
||||||
*
|
|
||||||
* @return A new instance of the {@link HttpClientRequest} sharing all existing state from this request.
|
|
||||||
*/
|
|
||||||
public abstract HttpClientRequest<I, O> followRedirects(int maxRedirects);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enables/disables following HTTP redirects for the newly created and returned request.
|
|
||||||
*
|
|
||||||
* @param follow {@code true} for enabling redirects, {@code false} to disable.
|
|
||||||
*
|
|
||||||
* @return A new instance of the {@link HttpClientRequest} sharing all existing state from this request.
|
|
||||||
*/
|
|
||||||
public abstract HttpClientRequest<I, O> followRedirects(boolean follow);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the HTTP method of the request and creates a new {@link HttpClientRequest} instance.
|
|
||||||
*
|
|
||||||
* @param method New HTTP method to use.
|
|
||||||
*
|
|
||||||
* @return A new instance of the {@link HttpClientRequest} sharing all existing state from this request.
|
|
||||||
*/
|
|
||||||
public abstract HttpClientRequest<I, O> setMethod(HttpMethod method);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the URI of the request and creates a new {@link HttpClientRequest} instance.
|
|
||||||
*
|
|
||||||
* @param newUri New URI to use.
|
|
||||||
*
|
|
||||||
* @return A new instance of the {@link HttpClientRequest} sharing all existing state from this request.
|
|
||||||
*/
|
|
||||||
public abstract HttpClientRequest<I, O> setUri(String newUri);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds an HTTP header with the passed {@code name} and {@code value} to this request.
|
|
||||||
*
|
|
||||||
* @param name Name of the header.
|
|
||||||
* @param value Value for the header.
|
|
||||||
*
|
|
||||||
* @return A new instance of the {@link HttpClientRequest} sharing all existing state from this request.
|
|
||||||
*/
|
|
||||||
public abstract HttpClientRequest<I, O> addHeader(CharSequence name, Object value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds the HTTP headers from the passed {@code headers} to this request.
|
|
||||||
*
|
|
||||||
* @param headers Map of the headers.
|
|
||||||
*
|
|
||||||
* @return A new instance of the {@link HttpClientRequest} sharing all existing state from this request.
|
|
||||||
*/
|
|
||||||
public abstract HttpClientRequest<I, O> addHeaders(Map<? extends CharSequence, ? extends Iterable<Object>> headers);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds the passed {@code cookie} to this request.
|
|
||||||
*
|
|
||||||
* @param cookie Cookie to add.
|
|
||||||
*
|
|
||||||
* @return A new instance of the {@link HttpClientRequest} sharing all existing state from this request.
|
|
||||||
*/
|
|
||||||
public abstract HttpClientRequest<I, O> addCookie(Cookie cookie);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds the passed header as a date value to this request. The date is formatted using netty's {@link
|
|
||||||
* HttpHeaders#addDateHeader(HttpMessage, CharSequence, Date)} which formats the date as per the <a
|
|
||||||
* href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1">HTTP specifications</a> into the format:
|
|
||||||
*
|
|
||||||
* <PRE>"E, dd MMM yyyy HH:mm:ss z"</PRE>
|
|
||||||
*
|
|
||||||
* @param name Name of the header.
|
|
||||||
* @param value Value of the header.
|
|
||||||
*
|
|
||||||
* @return A new instance of the {@link HttpClientRequest} sharing all existing state from this request.
|
|
||||||
*/
|
|
||||||
public abstract HttpClientRequest<I, O> addDateHeader(CharSequence name, Date value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds multiple date values for the passed header name to this request. The date values are formatted using netty's
|
|
||||||
* {@link HttpHeaders#addDateHeader(HttpMessage, CharSequence, Date)} which formats the date as per the <a
|
|
||||||
* href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1">HTTP specifications</a> into the format:
|
|
||||||
*
|
|
||||||
* <PRE>"E, dd MMM yyyy HH:mm:ss z"</PRE>
|
|
||||||
*
|
|
||||||
* @param name Name of the header.
|
|
||||||
* @param values Values for the header.
|
|
||||||
*
|
|
||||||
* @return A new instance of the {@link HttpClientRequest} sharing all existing state from this request.
|
|
||||||
*/
|
|
||||||
public abstract HttpClientRequest<I, O> addDateHeader(CharSequence name, Iterable<Date> values);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds an HTTP header with the passed {@code name} and {@code values} to this request.
|
|
||||||
*
|
|
||||||
* @param name Name of the header.
|
|
||||||
* @param values Values for the header.
|
|
||||||
*
|
|
||||||
* @return A new instance of the {@link HttpClientRequest} sharing all existing state from this request.
|
|
||||||
*/
|
|
||||||
public abstract HttpClientRequest<I, O> addHeaderValues(CharSequence name, Iterable<Object> values);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overwrites the current value, if any, of the passed header to the passed date value for this request. The date is
|
|
||||||
* formatted using netty's {@link HttpHeaders#addDateHeader(HttpMessage, CharSequence, Date)} which formats the date
|
|
||||||
* as per the <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1">HTTP specifications</a> into
|
|
||||||
* the format:
|
|
||||||
*
|
|
||||||
* <PRE>"E, dd MMM yyyy HH:mm:ss z"</PRE>
|
|
||||||
*
|
|
||||||
* @param name Name of the header.
|
|
||||||
* @param value Value of the header.
|
|
||||||
*
|
|
||||||
* @return A new instance of the {@link HttpClientRequest} sharing all existing state from this request.
|
|
||||||
*/
|
|
||||||
public abstract HttpClientRequest<I, O> setDateHeader(CharSequence name, Date value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overwrites the current value, if any, of the passed header to the passed value for this request.
|
|
||||||
*
|
|
||||||
* @param name Name of the header.
|
|
||||||
* @param value Value of the header.
|
|
||||||
*
|
|
||||||
* @return A new instance of the {@link HttpClientRequest} sharing all existing state from this request.
|
|
||||||
*/
|
|
||||||
public abstract HttpClientRequest<I, O> setHeader(CharSequence name, Object value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overwrites the current values, if any, of the passed headers for this request.
|
|
||||||
*
|
|
||||||
* @param headers Map of the headers.
|
|
||||||
*
|
|
||||||
* @return A new instance of the {@link HttpClientRequest} sharing all existing state from this request.
|
|
||||||
*/
|
|
||||||
public abstract HttpClientRequest<I, O> setHeaders(Map<? extends CharSequence, ? extends Iterable<Object>> headers);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overwrites the current value, if any, of the passed header to the passed date values for this request. The date
|
|
||||||
* is formatted using netty's {@link HttpHeaders#addDateHeader(HttpMessage, CharSequence, Date)} which formats the
|
|
||||||
* date as per the <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1">HTTP specifications</a>
|
|
||||||
* into the format:
|
|
||||||
*
|
|
||||||
* <PRE>"E, dd MMM yyyy HH:mm:ss z"</PRE>
|
|
||||||
*
|
|
||||||
* @param name Name of the header.
|
|
||||||
* @param values Values of the header.
|
|
||||||
*
|
|
||||||
* @return A new instance of the {@link HttpClientRequest} sharing all existing state from this request.
|
|
||||||
*/
|
|
||||||
public abstract HttpClientRequest<I, O> setDateHeader(CharSequence name, Iterable<Date> values);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overwrites the current value, if any, of the passed header to the passed values for this request.
|
|
||||||
*
|
|
||||||
* @param name Name of the header.
|
|
||||||
* @param values Values of the header.
|
|
||||||
*
|
|
||||||
* @return A new instance of the {@link HttpClientRequest} sharing all existing state from this request.
|
|
||||||
*/
|
|
||||||
public abstract HttpClientRequest<I, O> setHeaderValues(CharSequence name, Iterable<Object> values);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the passed header from this request.
|
|
||||||
*
|
|
||||||
* @param name Name of the header.
|
|
||||||
*
|
|
||||||
* @return A new instance of the {@link HttpClientRequest} sharing all existing state from this request.
|
|
||||||
*/
|
|
||||||
public abstract HttpClientRequest<I, O> removeHeader(CharSequence name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets HTTP Connection header to the appropriate value for HTTP keep-alive. This delegates to {@link
|
|
||||||
* HttpHeaders#setKeepAlive(HttpMessage, boolean)}
|
|
||||||
*
|
|
||||||
* @param keepAlive {@code true} to enable keep alive.
|
|
||||||
*
|
|
||||||
* @return A new instance of the {@link HttpClientRequest} sharing all existing state from this request.
|
|
||||||
*/
|
|
||||||
public abstract HttpClientRequest<I, O> setKeepAlive(boolean keepAlive);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the HTTP transfer encoding to chunked for this request. This delegates to {@link
|
|
||||||
* HttpHeaders#setTransferEncodingChunked(HttpMessage)}
|
|
||||||
*
|
|
||||||
* @return A new instance of the {@link HttpClientRequest} sharing all existing state from this request.
|
|
||||||
*/
|
|
||||||
public abstract HttpClientRequest<I, O> setTransferEncodingChunked();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new {@code HttpClientRequest} instance modifying the content type using the passed {@code transformer}.
|
|
||||||
*
|
|
||||||
* @param transformer Transformer to transform the content stream.
|
|
||||||
*
|
|
||||||
* @param <II> New type of the content.
|
|
||||||
*
|
|
||||||
* @return A new instance of {@link HttpClientRequest} with the transformed content stream.
|
|
||||||
*/
|
|
||||||
public abstract <II> HttpClientRequest<II, O> transformContent(AllocatingTransformer<II, I> transformer);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new {@code HttpClientRequest} instance modifying the content type of the response using the
|
|
||||||
* passed {@code transformer}.
|
|
||||||
*
|
|
||||||
* @param transformer Transformer to transform the content stream.
|
|
||||||
*
|
|
||||||
* @param <OO> New type of the content.
|
|
||||||
*
|
|
||||||
* @return A new instance of {@link HttpClientRequest} with the transformed response content stream.
|
|
||||||
*/
|
|
||||||
public abstract <OO> HttpClientRequest<I, OO> transformResponseContent(Transformer<O, OO> transformer);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new {@link WebSocketRequest}, inheriting all configurations from this request, that will request an
|
|
||||||
* upgrade to websockets from the server.
|
|
||||||
*
|
|
||||||
* @return A new {@link WebSocketRequest}.
|
|
||||||
*/
|
|
||||||
public abstract WebSocketRequest<O> requestWebSocketUpgrade();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether a header with the passed name exists for this request.
|
|
||||||
*
|
|
||||||
* @param name Header name.
|
|
||||||
*
|
|
||||||
* @return {@code true} if the header exists.
|
|
||||||
*/
|
|
||||||
public abstract boolean containsHeader(CharSequence name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether a header with the passed name and value exists for this request.
|
|
||||||
*
|
|
||||||
* @param name Header name.
|
|
||||||
* @param value Value to check.
|
|
||||||
* @param caseInsensitiveValueMatch If the value has to be matched ignoring case.
|
|
||||||
*
|
|
||||||
* @return {@code true} if the header with the passed value exists.
|
|
||||||
*/
|
|
||||||
public abstract boolean containsHeaderWithValue(CharSequence name, CharSequence value,
|
|
||||||
boolean caseInsensitiveValueMatch);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches the value of a header, if exists, for this request.
|
|
||||||
*
|
|
||||||
* @param name Name of the header.
|
|
||||||
*
|
|
||||||
* @return The value of the header, if it exists, {@code null} otherwise. If there are multiple values for this
|
|
||||||
* header, the first value is returned.
|
|
||||||
*/
|
|
||||||
public abstract String getHeader(CharSequence name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches all values of a header, if exists, for this request.
|
|
||||||
*
|
|
||||||
* @param name Name of the header.
|
|
||||||
*
|
|
||||||
* @return All values of the header, if it exists, {@code null} otherwise.
|
|
||||||
*/
|
|
||||||
public abstract List<String> getAllHeaders(CharSequence name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an iterator over the header entries. Multiple values for the same header appear as separate entries in
|
|
||||||
* the returned iterator.
|
|
||||||
*
|
|
||||||
* @return An iterator over the header entries
|
|
||||||
*/
|
|
||||||
public abstract Iterator<Entry<CharSequence, CharSequence>> headerIterator();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new {@link Set} that contains the names of all headers in this request. Note that modifying the
|
|
||||||
* returned {@link Set} will not affect the state of this response.
|
|
||||||
*/
|
|
||||||
public abstract Set<String> getHeaderNames();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the HTTP version of this request.
|
|
||||||
*
|
|
||||||
* @return The HTTP version of this request.
|
|
||||||
*/
|
|
||||||
public abstract HttpVersion getHttpVersion();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the HTTP method for this request.
|
|
||||||
*
|
|
||||||
* @return The HTTP method for this request.
|
|
||||||
*/
|
|
||||||
public abstract HttpMethod getMethod();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the URI for this request. The returned URI does <em>not</em> contain the scheme, host and port portion of
|
|
||||||
* the URI.
|
|
||||||
*
|
|
||||||
* @return The URI for this request.
|
|
||||||
*/
|
|
||||||
public abstract String getUri();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,410 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.http.client;
|
|
||||||
|
|
||||||
import io.netty.channel.Channel;
|
|
||||||
import io.netty.handler.codec.http.HttpHeaders;
|
|
||||||
import io.netty.handler.codec.http.HttpMessage;
|
|
||||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
|
||||||
import io.netty.handler.codec.http.HttpVersion;
|
|
||||||
import io.netty.handler.codec.http.cookie.Cookie;
|
|
||||||
import io.reactivex.netty.channel.Connection;
|
|
||||||
import io.reactivex.netty.channel.ContentSource;
|
|
||||||
import io.reactivex.netty.protocol.http.internal.HttpMessageFormatter;
|
|
||||||
import io.reactivex.netty.protocol.http.sse.ServerSentEvent;
|
|
||||||
import rx.Observable;
|
|
||||||
import rx.Observable.Transformer;
|
|
||||||
import rx.Subscriber;
|
|
||||||
|
|
||||||
import java.text.ParseException;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HTTP response for {@link HttpClient}
|
|
||||||
*
|
|
||||||
* <h2>Thread safety</h2>
|
|
||||||
*
|
|
||||||
* This object is not thread-safe and must not be used by multiple threads.
|
|
||||||
*
|
|
||||||
* <h2>Mutability</h2>
|
|
||||||
*
|
|
||||||
* Headers and trailing headers can be mutated for this response.
|
|
||||||
*/
|
|
||||||
public abstract class HttpClientResponse<T> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the HTTP version for this response.
|
|
||||||
*
|
|
||||||
* @return The HTTP version for this response.
|
|
||||||
*/
|
|
||||||
public abstract HttpVersion getHttpVersion();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the HTTP status for this response.
|
|
||||||
*
|
|
||||||
* @return The HTTP status for this response.
|
|
||||||
*/
|
|
||||||
public abstract HttpResponseStatus getStatus();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an immutable map of cookie names and cookies contained in this response.
|
|
||||||
*
|
|
||||||
* @return An immutable map of cookie names and cookies contained in this response.
|
|
||||||
*/
|
|
||||||
public abstract Map<String, Set<Cookie>> getCookies();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if there is a header with the passed name in this response.
|
|
||||||
*
|
|
||||||
* @param name Name of the header.
|
|
||||||
*
|
|
||||||
* @return {@code true} if there is a header with the passed name in this response.
|
|
||||||
*/
|
|
||||||
public abstract boolean containsHeader(CharSequence name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if there is a header with the passed name and value in this response.
|
|
||||||
*
|
|
||||||
* @param name Name of the header.
|
|
||||||
* @param value Value of the header.
|
|
||||||
* @param ignoreCaseValue {@code true} then the value comparision is done ignoring case.
|
|
||||||
*
|
|
||||||
* @return {@code true} if there is a header with the passed name and value in this response.
|
|
||||||
*/
|
|
||||||
public abstract boolean containsHeader(CharSequence name, CharSequence value, boolean ignoreCaseValue);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an iterator over the header entries. Multiple values for the same header appear as separate entries in
|
|
||||||
* the returned iterator.
|
|
||||||
*
|
|
||||||
* @return An iterator over the header entries
|
|
||||||
*/
|
|
||||||
public abstract Iterator<Entry<CharSequence, CharSequence>> headerIterator();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the value of a header with the specified name. If there are more than one values for the specified name,
|
|
||||||
* the first value is returned.
|
|
||||||
*
|
|
||||||
* @param name The name of the header to search
|
|
||||||
* @return The first header value or {@code null} if there is no such header
|
|
||||||
*/
|
|
||||||
public abstract String getHeader(CharSequence name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the value of a header with the specified name. If there are more than one values for the specified name,
|
|
||||||
* the first value is returned.
|
|
||||||
*
|
|
||||||
* @param name The name of the header to search
|
|
||||||
* @param defaultValue Default if the header does not exist.
|
|
||||||
*
|
|
||||||
* @return The first header value or {@code defaultValue} if there is no such header
|
|
||||||
*/
|
|
||||||
public abstract String getHeader(CharSequence name, String defaultValue);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the values of headers with the specified name
|
|
||||||
*
|
|
||||||
* @param name The name of the headers to search
|
|
||||||
*
|
|
||||||
* @return A {@link List} of header values which will be empty if no values are found
|
|
||||||
*/
|
|
||||||
public abstract List<String> getAllHeaderValues(CharSequence name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the length of the content.
|
|
||||||
*
|
|
||||||
* @return the content length
|
|
||||||
*
|
|
||||||
* @throws NumberFormatException if the message does not have the {@code "Content-Length"} header or its value is
|
|
||||||
* not a number.
|
|
||||||
*/
|
|
||||||
public abstract long getContentLength();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the length of the content.
|
|
||||||
*
|
|
||||||
* @param defaultValue Default value if the message does not have a {@code "Content-Length"} header or its value is
|
|
||||||
* not a number
|
|
||||||
*
|
|
||||||
* @return the content length or {@code defaultValue} if this message does not have the {@code "Content-Length"}
|
|
||||||
* header or its value is not a number
|
|
||||||
*/
|
|
||||||
public abstract long getContentLength(long defaultValue);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the date header value with the specified header name. If there are more than one header value for the
|
|
||||||
* specified header name, the first value is returned.
|
|
||||||
* The value is parsed as per the
|
|
||||||
* <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1">HTTP specifications</a> using the format:
|
|
||||||
* <PRE>"E, dd MMM yyyy HH:mm:ss z"</PRE>
|
|
||||||
*
|
|
||||||
* @param name The name of the header to search
|
|
||||||
*
|
|
||||||
* @return the header value
|
|
||||||
*
|
|
||||||
* @throws ParseException if there is no such header or the header value is not a formatted date
|
|
||||||
*/
|
|
||||||
public abstract long getDateHeader(CharSequence name) throws ParseException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the date header value with the specified header name. If there are more than one header value for the
|
|
||||||
* specified header name, the first value is returned.
|
|
||||||
* The value is parsed as per the
|
|
||||||
* <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1">HTTP specifications</a> using the format:
|
|
||||||
* <PRE>"E, dd MMM yyyy HH:mm:ss z"</PRE>
|
|
||||||
*
|
|
||||||
* @param name The name of the header to search
|
|
||||||
* @param defaultValue Default value if there is no header with this name.
|
|
||||||
*
|
|
||||||
* @return the header value or {@code defaultValue} if there is no header with this name.
|
|
||||||
*/
|
|
||||||
public abstract long getDateHeader(CharSequence name, long defaultValue);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the value of the {@code "Host"} header.
|
|
||||||
*/
|
|
||||||
public abstract String getHostHeader();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the value of the {@code "Host"} header.
|
|
||||||
*
|
|
||||||
* @param defaultValue Default if the header does not exist.
|
|
||||||
*
|
|
||||||
* @return The value of the {@code "Host"} header or {@code defaultValue} if there is no such header.
|
|
||||||
*/
|
|
||||||
public abstract String getHost(String defaultValue);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the integer header value with the specified header name. If there are more than one header value for
|
|
||||||
* the specified header name, the first value is returned.
|
|
||||||
*
|
|
||||||
* @param name The name of the header to search
|
|
||||||
*
|
|
||||||
* @return the header value
|
|
||||||
*
|
|
||||||
* @throws NumberFormatException if there is no such header or the header value is not a number
|
|
||||||
*/
|
|
||||||
public abstract int getIntHeader(CharSequence name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the integer header value with the specified header name. If there are more than one header value for
|
|
||||||
* the specified header name, the first value is returned.
|
|
||||||
*
|
|
||||||
* @param name The name of the header to search
|
|
||||||
* @param defaultValue Default if the header does not exist.
|
|
||||||
*
|
|
||||||
* @return the header value or the {@code defaultValue} if there is no such header or the header value is not a
|
|
||||||
* number
|
|
||||||
*/
|
|
||||||
public abstract int getIntHeader(CharSequence name, int defaultValue);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} if and only if this response has the content-length header set.
|
|
||||||
*/
|
|
||||||
public abstract boolean isContentLengthSet();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns {@code true} if and only if the connection can remain open and thus 'kept alive'. This methods respects
|
|
||||||
* the value of the {@code "Connection"} header first and then the return value of
|
|
||||||
* {@link HttpVersion#isKeepAliveDefault()}.
|
|
||||||
*/
|
|
||||||
public abstract boolean isKeepAlive();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks to see if the transfer encoding of this response is chunked
|
|
||||||
*
|
|
||||||
* @return True if transfer encoding is chunked, otherwise false
|
|
||||||
*/
|
|
||||||
public abstract boolean isTransferEncodingChunked();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new {@link Set} that contains the names of all headers in this response. Note that modifying the
|
|
||||||
* returned {@link Set} will not affect the state of this response.
|
|
||||||
*/
|
|
||||||
public abstract Set<String> getHeaderNames();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds an HTTP header with the passed {@code name} and {@code value} to this response.
|
|
||||||
*
|
|
||||||
* @param name Name of the header.
|
|
||||||
* @param value Value for the header.
|
|
||||||
*
|
|
||||||
* @return {@code this}
|
|
||||||
*/
|
|
||||||
public abstract HttpClientResponse<T> addHeader(CharSequence name, Object value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds the passed {@code cookie} to this response.
|
|
||||||
*
|
|
||||||
* @param cookie Cookie to add.
|
|
||||||
*
|
|
||||||
* @return {@code this}
|
|
||||||
*/
|
|
||||||
public abstract HttpClientResponse<T> addCookie(Cookie cookie);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds the passed header as a date value to this response. The date is formatted using netty's
|
|
||||||
* {@link HttpHeaders#addDateHeader(HttpMessage, CharSequence, Date)} which formats the date as per the
|
|
||||||
* <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1">HTTP specifications</a> into the format:
|
|
||||||
* <PRE>"E, dd MMM yyyy HH:mm:ss z"</PRE>
|
|
||||||
*
|
|
||||||
* @param name Name of the header.
|
|
||||||
* @param value Value of the header.
|
|
||||||
*
|
|
||||||
* @return {@code this}
|
|
||||||
*/
|
|
||||||
public abstract HttpClientResponse<T> addDateHeader(CharSequence name, Date value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds multiple date values for the passed header name to this response. The date values are formatted using netty's
|
|
||||||
* {@link HttpHeaders#addDateHeader(HttpMessage, CharSequence, Date)} which formats the date as per the
|
|
||||||
* <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1">HTTP specifications</a> into the format:
|
|
||||||
*
|
|
||||||
* <PRE>"E, dd MMM yyyy HH:mm:ss z"</PRE>
|
|
||||||
*
|
|
||||||
* @param name Name of the header.
|
|
||||||
* @param values Values for the header.
|
|
||||||
*
|
|
||||||
* @return {@code this}
|
|
||||||
*/
|
|
||||||
public abstract HttpClientResponse<T> addDateHeader(CharSequence name, Iterable<Date> values);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds an HTTP header with the passed {@code name} and {@code values} to this response.
|
|
||||||
*
|
|
||||||
* @param name Name of the header.
|
|
||||||
* @param values Values for the header.
|
|
||||||
*
|
|
||||||
* @return {@code this}
|
|
||||||
*/
|
|
||||||
public abstract HttpClientResponse<T> addHeader(CharSequence name, Iterable<Object> values);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overwrites the current value, if any, of the passed header to the passed date value for this response. The date is
|
|
||||||
* formatted using netty's {@link HttpHeaders#addDateHeader(HttpMessage, CharSequence, Date)} which formats the date
|
|
||||||
* as per the <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1">HTTP specifications</a> into
|
|
||||||
* the format:
|
|
||||||
*
|
|
||||||
* <PRE>"E, dd MMM yyyy HH:mm:ss z"</PRE>
|
|
||||||
*
|
|
||||||
* @param name Name of the header.
|
|
||||||
* @param value Value of the header.
|
|
||||||
*
|
|
||||||
* @return {@code this}
|
|
||||||
*/
|
|
||||||
public abstract HttpClientResponse<T> setDateHeader(CharSequence name, Date value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overwrites the current value, if any, of the passed header to the passed value for this response.
|
|
||||||
*
|
|
||||||
* @param name Name of the header.
|
|
||||||
* @param value Value of the header.
|
|
||||||
*
|
|
||||||
* @return {@code this}
|
|
||||||
*/
|
|
||||||
public abstract HttpClientResponse<T> setHeader(CharSequence name, Object value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overwrites the current value, if any, of the passed header to the passed date values for this response. The date
|
|
||||||
* is formatted using netty's {@link HttpHeaders#addDateHeader(HttpMessage, CharSequence, Date)} which formats the
|
|
||||||
* date as per the <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1">HTTP specifications</a>
|
|
||||||
* into the format:
|
|
||||||
*
|
|
||||||
* <PRE>"E, dd MMM yyyy HH:mm:ss z"</PRE>
|
|
||||||
*
|
|
||||||
* @param name Name of the header.
|
|
||||||
* @param values Values of the header.
|
|
||||||
*
|
|
||||||
* @return {@code this}
|
|
||||||
*/
|
|
||||||
public abstract HttpClientResponse<T> setDateHeader(CharSequence name, Iterable<Date> values);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overwrites the current value, if any, of the passed header to the passed values for this response.
|
|
||||||
*
|
|
||||||
* @param name Name of the header.
|
|
||||||
* @param values Values of the header.
|
|
||||||
*
|
|
||||||
* @return {@code this}
|
|
||||||
*/
|
|
||||||
public abstract HttpClientResponse<T> setHeader(CharSequence name, Iterable<Object> values);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the passed header from this response.
|
|
||||||
*
|
|
||||||
* @param name Name of the header.
|
|
||||||
*
|
|
||||||
* @return {@code this}
|
|
||||||
*/
|
|
||||||
public abstract HttpClientResponse<T> removeHeader(CharSequence name);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the content as a stream of <a href="http://www.w3.org/TR/eventsource/">Server sent events</a>.
|
|
||||||
* There can only be one {@link Subscriber} to the returned {@link Observable}, any subsequent subscriptions will
|
|
||||||
* get an error.
|
|
||||||
*
|
|
||||||
* @return Stream of content as {@link ServerSentEvent} messages.
|
|
||||||
*/
|
|
||||||
public abstract ContentSource<ServerSentEvent> getContentAsServerSentEvents();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the content as a stream. There can only be one {@link Subscriber} to the returned {@link Observable}, any
|
|
||||||
* subsequent subscriptions will get an error.
|
|
||||||
*
|
|
||||||
* @return Stream of content.
|
|
||||||
*/
|
|
||||||
public abstract ContentSource<T> getContent();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Marks the content to be discarded. This means that the content can not be read from this response from now.
|
|
||||||
*
|
|
||||||
* @return An {@link Observable}, subscription to which will discard the content. This {@code Observable} will
|
|
||||||
* error/complete when the content errors/completes and unsubscription from here will unsubscribe from the content.
|
|
||||||
*/
|
|
||||||
public abstract Observable<Void> discardContent();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transforms the type of objects read from the content of this response, using the supplied {@code transformer}.
|
|
||||||
*
|
|
||||||
* @return A new instance of {@code HttpClientResponse} with transformed content.
|
|
||||||
*/
|
|
||||||
public abstract <TT> HttpClientResponse<TT> transformContent(Transformer<T, TT> transformer);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the underlying channel on which this response was received.
|
|
||||||
*
|
|
||||||
* @return The underlying channel on which this response was received.
|
|
||||||
*/
|
|
||||||
public abstract Channel unsafeNettyChannel();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the underlying connection on which this response was received.
|
|
||||||
*
|
|
||||||
* @return The underlying connection on which this response was received.
|
|
||||||
*/
|
|
||||||
public abstract Connection<?, ?> unsafeConnection();
|
|
||||||
|
|
||||||
public String toString() {
|
|
||||||
return HttpMessageFormatter.formatResponse(getHttpVersion(), getStatus(), headerIterator());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,64 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.http.client;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An exception signifying a failed HTTP redirects. Every exception has an encapsulated {@link Reason} retrievable via
|
|
||||||
* {@link #getReason()}
|
|
||||||
*/
|
|
||||||
public class HttpRedirectException extends RuntimeException {
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 612647744832660373L;
|
|
||||||
private final Reason reason;
|
|
||||||
|
|
||||||
public enum Reason {
|
|
||||||
RedirectLoop,
|
|
||||||
TooManyRedirects,
|
|
||||||
InvalidRedirect
|
|
||||||
}
|
|
||||||
|
|
||||||
public HttpRedirectException(Reason reason) {
|
|
||||||
this.reason = reason;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HttpRedirectException(Reason reason, Throwable cause) {
|
|
||||||
super(getMsgWithReason(reason), cause);
|
|
||||||
this.reason = reason;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HttpRedirectException(Reason reason, String message) {
|
|
||||||
super(getMsgWithReason(reason, message));
|
|
||||||
this.reason = reason;
|
|
||||||
}
|
|
||||||
|
|
||||||
public HttpRedirectException(Reason reason, String message, Throwable cause) {
|
|
||||||
super(getMsgWithReason(reason, message), cause);
|
|
||||||
this.reason = reason;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Reason getReason() {
|
|
||||||
return reason;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getMsgWithReason(Reason reason) {
|
|
||||||
return "Redirect failed. Reason: " + reason;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String getMsgWithReason(Reason reason, String message) {
|
|
||||||
return getMsgWithReason(reason) + ". Error: " + message;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,136 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.http.client;
|
|
||||||
|
|
||||||
import io.netty.handler.codec.http.HttpMethod;
|
|
||||||
import io.netty.handler.codec.http.HttpVersion;
|
|
||||||
import io.reactivex.netty.events.EventSource;
|
|
||||||
import io.reactivex.netty.protocol.http.client.events.HttpClientEventsListener;
|
|
||||||
|
|
||||||
public abstract class InterceptingHttpClient<I, O> implements EventSource<HttpClientEventsListener> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a GET request for the passed URI.
|
|
||||||
*
|
|
||||||
* @param uri The URI for the request. The URI should be absolute and should not contain the scheme, host and port.
|
|
||||||
*
|
|
||||||
* @return New {@link HttpClientRequest}.
|
|
||||||
*/
|
|
||||||
public abstract HttpClientRequest<I, O> createGet(String uri);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a POST request for the passed URI.
|
|
||||||
*
|
|
||||||
* @param uri The URI for the request. The URI should be absolute and should not contain the scheme, host and port.
|
|
||||||
*
|
|
||||||
* @return New {@link HttpClientRequest}.
|
|
||||||
*/
|
|
||||||
public abstract HttpClientRequest<I, O> createPost(String uri);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a PUT request for the passed URI.
|
|
||||||
*
|
|
||||||
* @param uri The URI for the request. The URI should be absolute and should not contain the scheme, host and port.
|
|
||||||
*
|
|
||||||
* @return New {@link HttpClientRequest}.
|
|
||||||
*/
|
|
||||||
public abstract HttpClientRequest<I, O> createPut(String uri);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a DELETE request for the passed URI.
|
|
||||||
*
|
|
||||||
* @param uri The URI for the request. The URI should be absolute and should not contain the scheme, host and port.
|
|
||||||
*
|
|
||||||
* @return New {@link HttpClientRequest}.
|
|
||||||
*/
|
|
||||||
public abstract HttpClientRequest<I, O> createDelete(String uri);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a HEAD request for the passed URI.
|
|
||||||
*
|
|
||||||
* @param uri The URI for the request. The URI should be absolute and should not contain the scheme, host and port.
|
|
||||||
*
|
|
||||||
* @return New {@link HttpClientRequest}.
|
|
||||||
*/
|
|
||||||
public abstract HttpClientRequest<I, O> createHead(String uri);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an OPTIONS request for the passed URI.
|
|
||||||
*
|
|
||||||
* @param uri The URI for the request. The URI should be absolute and should not contain the scheme, host and port.
|
|
||||||
*
|
|
||||||
* @return New {@link HttpClientRequest}.
|
|
||||||
*/
|
|
||||||
public abstract HttpClientRequest<I, O> createOptions(String uri);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a PATCH request for the passed URI.
|
|
||||||
*
|
|
||||||
* @param uri The URI for the request. The URI should be absolute and should not contain the scheme, host and port.
|
|
||||||
*
|
|
||||||
* @return New {@link HttpClientRequest}.
|
|
||||||
*/
|
|
||||||
public abstract HttpClientRequest<I, O> createPatch(String uri);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a TRACE request for the passed URI.
|
|
||||||
*
|
|
||||||
* @param uri The URI for the request. The URI should be absolute and should not contain the scheme, host and port.
|
|
||||||
*
|
|
||||||
* @return New {@link HttpClientRequest}.
|
|
||||||
*/
|
|
||||||
public abstract HttpClientRequest<I, O> createTrace(String uri);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a CONNECT request for the passed URI.
|
|
||||||
*
|
|
||||||
* @param uri The URI for the request. The URI should be absolute and should not contain the scheme, host and port.
|
|
||||||
*
|
|
||||||
* @return New {@link HttpClientRequest}.
|
|
||||||
*/
|
|
||||||
public abstract HttpClientRequest<I, O> createConnect(String uri);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a request for the passed HTTP method and URI.
|
|
||||||
*
|
|
||||||
* @param method Http Method.
|
|
||||||
* @param uri The URI for the request. The URI should be absolute and should not contain the scheme, host and port.
|
|
||||||
*
|
|
||||||
* @return New {@link HttpClientRequest}.
|
|
||||||
*/
|
|
||||||
public abstract HttpClientRequest<I, O> createRequest(HttpMethod method, String uri);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a request for the passed HTTP version, method and URI.
|
|
||||||
*
|
|
||||||
* @param version HTTP version
|
|
||||||
* @param method Http Method.
|
|
||||||
* @param uri The URI for the request. The URI should be absolute and should not contain the scheme, host and port.
|
|
||||||
*
|
|
||||||
* @return New {@link HttpClientRequest}.
|
|
||||||
*/
|
|
||||||
public abstract HttpClientRequest<I, O> createRequest(HttpVersion version, HttpMethod method, String uri);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Starts the process of adding interceptors to this client. Interceptors help in achieving various usecases of
|
|
||||||
* instrumenting and transforming connections.
|
|
||||||
*
|
|
||||||
* @return A new interceptor chain to add the various interceptors.
|
|
||||||
*/
|
|
||||||
public abstract HttpClientInterceptorChain<I, O> intercept();
|
|
||||||
}
|
|
|
@ -1,100 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.http.client;
|
|
||||||
|
|
||||||
import io.netty.handler.codec.http.HttpMethod;
|
|
||||||
import io.netty.handler.codec.http.HttpVersion;
|
|
||||||
import io.reactivex.netty.protocol.http.client.events.HttpClientEventPublisher;
|
|
||||||
import io.reactivex.netty.protocol.http.client.events.HttpClientEventsListener;
|
|
||||||
import rx.Subscription;
|
|
||||||
|
|
||||||
class InterceptingHttpClientImpl<I, O> extends InterceptingHttpClient<I, O> {
|
|
||||||
|
|
||||||
private final RequestProvider<I, O> requestProvider;
|
|
||||||
private final HttpClientEventPublisher cep;
|
|
||||||
|
|
||||||
public InterceptingHttpClientImpl(RequestProvider<I, O> requestProvider, HttpClientEventPublisher cep) {
|
|
||||||
this.requestProvider = requestProvider;
|
|
||||||
this.cep = cep;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequest<I, O> createGet(String uri) {
|
|
||||||
return createRequest(HttpMethod.GET, uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequest<I, O> createPost(String uri) {
|
|
||||||
return createRequest(HttpMethod.POST, uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequest<I, O> createPut(String uri) {
|
|
||||||
return createRequest(HttpMethod.PUT, uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequest<I, O> createDelete(String uri) {
|
|
||||||
return createRequest(HttpMethod.DELETE, uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequest<I, O> createHead(String uri) {
|
|
||||||
return createRequest(HttpMethod.HEAD, uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequest<I, O> createOptions(String uri) {
|
|
||||||
return createRequest(HttpMethod.OPTIONS, uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequest<I, O> createPatch(String uri) {
|
|
||||||
return createRequest(HttpMethod.PATCH, uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequest<I, O> createTrace(String uri) {
|
|
||||||
return createRequest(HttpMethod.TRACE, uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequest<I, O> createConnect(String uri) {
|
|
||||||
return createRequest(HttpMethod.CONNECT, uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequest<I, O> createRequest(HttpMethod method, String uri) {
|
|
||||||
return createRequest(HttpVersion.HTTP_1_1, method, uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequest<I, O> createRequest(HttpVersion version, HttpMethod method, String uri) {
|
|
||||||
return requestProvider.createRequest(version, method, uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientInterceptorChain<I, O> intercept() {
|
|
||||||
return new HttpClientInterceptorChainImpl<>(requestProvider, cep);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Subscription subscribe(HttpClientEventsListener listener) {
|
|
||||||
return cep.subscribe(listener);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.http.client;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An interceptor that preserves the type of client request and response content.
|
|
||||||
*
|
|
||||||
* @param <I> The type of the content of request.
|
|
||||||
* @param <O> The type of the content of response.
|
|
||||||
*/
|
|
||||||
public interface Interceptor<I, O> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Intercepts and optionally changes the passed {@code RequestProvider}.
|
|
||||||
*
|
|
||||||
* @param provider Provider to intercept.
|
|
||||||
*
|
|
||||||
* @return Provider to use after this transformation.
|
|
||||||
*/
|
|
||||||
RequestProvider<I, O> intercept(RequestProvider<I, O> provider);
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.http.client;
|
|
||||||
|
|
||||||
import io.netty.handler.codec.http.HttpMethod;
|
|
||||||
import io.netty.handler.codec.http.HttpVersion;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An abstraction that creates new instance of {@link HttpClientRequest}.
|
|
||||||
*
|
|
||||||
* @param <I> The type of the content of request.
|
|
||||||
* @param <O> The type of the content of response.
|
|
||||||
*/
|
|
||||||
public interface RequestProvider<I, O> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new {@link HttpClientRequest} with the provided {@code version}, {@code method} and {@code uri}
|
|
||||||
*
|
|
||||||
* @param version HTTP version.
|
|
||||||
* @param method HTTP method.
|
|
||||||
* @param uri URI.
|
|
||||||
*
|
|
||||||
* @return A new instance of {@code HttpClientRequest}
|
|
||||||
*/
|
|
||||||
HttpClientRequest<I, O> createRequest(HttpVersion version, HttpMethod method, String uri);
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.http.client;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An interceptor that preserves the type of client request and response content.
|
|
||||||
*
|
|
||||||
* @param <I> The type of the content of request.
|
|
||||||
* @param <O> The type of the content of response.
|
|
||||||
*/
|
|
||||||
public interface TransformingInterceptor<I, O, II, OO> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Intercepts and changes the passed {@code RequestProvider}.
|
|
||||||
*
|
|
||||||
* @param provider Provider to intercept.
|
|
||||||
*
|
|
||||||
* @return Provider to use after this transformation.
|
|
||||||
*/
|
|
||||||
RequestProvider<II, OO> intercept(RequestProvider<I, O> provider);
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,343 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.http.client.events;
|
|
||||||
|
|
||||||
import io.reactivex.netty.events.EventPublisher;
|
|
||||||
import io.reactivex.netty.events.EventSource;
|
|
||||||
import io.reactivex.netty.events.ListenersHolder;
|
|
||||||
import io.reactivex.netty.events.internal.SafeEventListener;
|
|
||||||
import io.reactivex.netty.protocol.tcp.client.events.TcpClientEventListener;
|
|
||||||
import io.reactivex.netty.protocol.tcp.client.events.TcpClientEventPublisher;
|
|
||||||
import rx.Subscription;
|
|
||||||
import rx.functions.Action1;
|
|
||||||
import rx.functions.Action2;
|
|
||||||
import rx.functions.Action3;
|
|
||||||
import rx.functions.Action4;
|
|
||||||
import rx.subscriptions.CompositeSubscription;
|
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
public final class HttpClientEventPublisher extends HttpClientEventsListener
|
|
||||||
implements EventSource<HttpClientEventsListener>, EventPublisher {
|
|
||||||
|
|
||||||
private static final Action1<HttpClientEventsListener> REQUEST_SUBMIT_ACTION =
|
|
||||||
new Action1<HttpClientEventsListener>() {
|
|
||||||
@Override
|
|
||||||
public void call(HttpClientEventsListener listener) {
|
|
||||||
listener.onRequestSubmitted();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private static final Action1<HttpClientEventsListener> REQUEST_WRITE_START_ACTION =
|
|
||||||
new Action1<HttpClientEventsListener>() {
|
|
||||||
@Override
|
|
||||||
public void call(HttpClientEventsListener listener) {
|
|
||||||
listener.onRequestWriteStart();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private static final Action3<HttpClientEventsListener, Long, TimeUnit> REQUEST_WRITE_COMPLETE_ACTION =
|
|
||||||
new Action3<HttpClientEventsListener, Long, TimeUnit>() {
|
|
||||||
@Override
|
|
||||||
public void call(HttpClientEventsListener listener, Long duration, TimeUnit timeUnit) {
|
|
||||||
listener.onRequestWriteComplete(duration, timeUnit);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private static final Action4<HttpClientEventsListener, Long, TimeUnit, Throwable> REQUEST_WRITE_FAILED_ACTION =
|
|
||||||
new Action4<HttpClientEventsListener, Long, TimeUnit, Throwable>() {
|
|
||||||
@Override
|
|
||||||
public void call(HttpClientEventsListener listener, Long duration, TimeUnit timeUnit, Throwable t) {
|
|
||||||
listener.onRequestWriteFailed(duration, timeUnit, t);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private static final Action4<HttpClientEventsListener, Long, TimeUnit, Integer> RESP_HEADER_RECIEVED_ACTION =
|
|
||||||
new Action4<HttpClientEventsListener, Long, TimeUnit, Integer>() {
|
|
||||||
@Override
|
|
||||||
public void call(HttpClientEventsListener listener, Long duration, TimeUnit timeUnit,
|
|
||||||
Integer responseCode) {
|
|
||||||
listener.onResponseHeadersReceived(responseCode, duration, timeUnit);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private static final Action1<HttpClientEventsListener> RESP_CONTENT_RECIEVED_ACTION =
|
|
||||||
new Action1<HttpClientEventsListener>() {
|
|
||||||
@Override
|
|
||||||
public void call(HttpClientEventsListener listener) {
|
|
||||||
listener.onResponseContentReceived();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private static final Action3<HttpClientEventsListener, Long, TimeUnit> RESP_RECIEVE_COMPLETE_ACTION =
|
|
||||||
new Action3<HttpClientEventsListener, Long, TimeUnit>() {
|
|
||||||
@Override
|
|
||||||
public void call(HttpClientEventsListener listener, Long duration, TimeUnit timeUnit) {
|
|
||||||
listener.onResponseReceiveComplete(duration, timeUnit);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private static final Action2<HttpClientEventsListener, Throwable> RESP_FAILED_ACTION =
|
|
||||||
new Action2<HttpClientEventsListener, Throwable>() {
|
|
||||||
@Override
|
|
||||||
public void call(HttpClientEventsListener listener, Throwable t) {
|
|
||||||
listener.onResponseFailed(t);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private static final Action3<HttpClientEventsListener, Long, TimeUnit> PROCESSING_COMPLETE_ACTION =
|
|
||||||
new Action3<HttpClientEventsListener, Long, TimeUnit>() {
|
|
||||||
@Override
|
|
||||||
public void call(HttpClientEventsListener listener, Long duration, TimeUnit timeUnit) {
|
|
||||||
listener.onRequestProcessingComplete(duration, timeUnit);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final ListenersHolder<HttpClientEventsListener> listeners;
|
|
||||||
private final TcpClientEventPublisher tcpDelegate;
|
|
||||||
|
|
||||||
public HttpClientEventPublisher() {
|
|
||||||
listeners = new ListenersHolder<>();
|
|
||||||
tcpDelegate = new TcpClientEventPublisher();
|
|
||||||
}
|
|
||||||
|
|
||||||
private HttpClientEventPublisher(ListenersHolder<HttpClientEventsListener> l, TcpClientEventPublisher tcpDelegate) {
|
|
||||||
listeners = new ListenersHolder<>(l);
|
|
||||||
this.tcpDelegate = tcpDelegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRequestSubmitted() {
|
|
||||||
listeners.invokeListeners(REQUEST_SUBMIT_ACTION);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRequestWriteStart() {
|
|
||||||
listeners.invokeListeners(REQUEST_WRITE_START_ACTION);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRequestWriteComplete(final long duration, final TimeUnit timeUnit) {
|
|
||||||
listeners.invokeListeners(REQUEST_WRITE_COMPLETE_ACTION, duration, timeUnit);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRequestWriteFailed(final long duration, final TimeUnit timeUnit, final Throwable throwable) {
|
|
||||||
listeners.invokeListeners(REQUEST_WRITE_FAILED_ACTION, duration, timeUnit, throwable);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResponseHeadersReceived(final int responseCode, long duration, TimeUnit timeUnit) {
|
|
||||||
listeners.invokeListeners(RESP_HEADER_RECIEVED_ACTION, duration, timeUnit, responseCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResponseContentReceived() {
|
|
||||||
listeners.invokeListeners(RESP_CONTENT_RECIEVED_ACTION);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResponseReceiveComplete(final long duration, final TimeUnit timeUnit) {
|
|
||||||
listeners.invokeListeners(RESP_RECIEVE_COMPLETE_ACTION, duration, timeUnit);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResponseFailed(final Throwable throwable) {
|
|
||||||
listeners.invokeListeners(RESP_FAILED_ACTION, throwable);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRequestProcessingComplete(final long duration, final TimeUnit timeUnit) {
|
|
||||||
listeners.invokeListeners(PROCESSING_COMPLETE_ACTION, duration, timeUnit);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConnectionCloseFailed(long duration, TimeUnit timeUnit,
|
|
||||||
Throwable throwable) {
|
|
||||||
tcpDelegate.onConnectionCloseFailed(duration, timeUnit, throwable);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConnectStart() {
|
|
||||||
tcpDelegate.onConnectStart();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConnectSuccess(long duration, TimeUnit timeUnit) {
|
|
||||||
tcpDelegate.onConnectSuccess(duration, timeUnit);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConnectFailed(long duration, TimeUnit timeUnit,
|
|
||||||
Throwable throwable) {
|
|
||||||
tcpDelegate.onConnectFailed(duration, timeUnit, throwable);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPoolReleaseStart() {
|
|
||||||
tcpDelegate.onPoolReleaseStart();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPoolReleaseSuccess(long duration, TimeUnit timeUnit) {
|
|
||||||
tcpDelegate.onPoolReleaseSuccess(duration, timeUnit);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPoolReleaseFailed(long duration, TimeUnit timeUnit,
|
|
||||||
Throwable throwable) {
|
|
||||||
tcpDelegate.onPoolReleaseFailed(duration, timeUnit, throwable);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPooledConnectionEviction() {
|
|
||||||
tcpDelegate.onPooledConnectionEviction();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPooledConnectionReuse() {
|
|
||||||
tcpDelegate.onPooledConnectionReuse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPoolAcquireStart() {
|
|
||||||
tcpDelegate.onPoolAcquireStart();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPoolAcquireSuccess(long duration, TimeUnit timeUnit) {
|
|
||||||
tcpDelegate.onPoolAcquireSuccess(duration, timeUnit);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPoolAcquireFailed(long duration, TimeUnit timeUnit,
|
|
||||||
Throwable throwable) {
|
|
||||||
tcpDelegate.onPoolAcquireFailed(duration, timeUnit, throwable);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onByteRead(long bytesRead) {
|
|
||||||
tcpDelegate.onByteRead(bytesRead);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onByteWritten(long bytesWritten) {
|
|
||||||
tcpDelegate.onByteWritten(bytesWritten);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFlushStart() {
|
|
||||||
tcpDelegate.onFlushStart();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFlushComplete(long duration, TimeUnit timeUnit) {
|
|
||||||
tcpDelegate.onFlushComplete(duration, timeUnit);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onWriteStart() {
|
|
||||||
tcpDelegate.onWriteStart();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onWriteSuccess(long duration, TimeUnit timeUnit) {
|
|
||||||
tcpDelegate.onWriteSuccess(duration, timeUnit);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onWriteFailed(long duration, TimeUnit timeUnit, Throwable throwable) {
|
|
||||||
tcpDelegate.onWriteFailed(duration, timeUnit, throwable);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConnectionCloseStart() {
|
|
||||||
tcpDelegate.onConnectionCloseStart();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConnectionCloseSuccess(long duration, TimeUnit timeUnit) {
|
|
||||||
tcpDelegate.onConnectionCloseSuccess(duration, timeUnit);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCustomEvent(Object event) {
|
|
||||||
tcpDelegate.onCustomEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCustomEvent(Object event, long duration, TimeUnit timeUnit) {
|
|
||||||
tcpDelegate.onCustomEvent(event, duration, timeUnit);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCustomEvent(Object event, long duration, TimeUnit timeUnit, Throwable throwable) {
|
|
||||||
tcpDelegate.onCustomEvent(event, duration, timeUnit, throwable);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCustomEvent(Object event, Throwable throwable) {
|
|
||||||
tcpDelegate.onCustomEvent(event, throwable);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean publishingEnabled() {
|
|
||||||
return listeners.publishingEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Subscription subscribe(HttpClientEventsListener listener) {
|
|
||||||
if (!SafeEventListener.class.isAssignableFrom(listener.getClass())) {
|
|
||||||
listener = new SafeHttpClientEventsListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
CompositeSubscription cs = new CompositeSubscription();
|
|
||||||
cs.add(listeners.subscribe(listener));
|
|
||||||
|
|
||||||
TcpClientEventListener tcpListener = listener;
|
|
||||||
if (listener instanceof SafeHttpClientEventsListener) {
|
|
||||||
tcpListener = ((SafeHttpClientEventsListener) listener).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
cs.add(tcpDelegate.subscribe(tcpListener));
|
|
||||||
return cs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public EventSource<TcpClientEventListener> asTcpEventSource() {
|
|
||||||
return new EventSource<TcpClientEventListener>() {
|
|
||||||
@Override
|
|
||||||
public Subscription subscribe(TcpClientEventListener listener) {
|
|
||||||
if (listener instanceof HttpClientEventsListener) {
|
|
||||||
return HttpClientEventPublisher.this.subscribe((HttpClientEventsListener) listener);
|
|
||||||
}
|
|
||||||
return tcpDelegate.subscribe(listener);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public HttpClientEventPublisher copy() {
|
|
||||||
return new HttpClientEventPublisher(listeners.copy(), tcpDelegate.copy());
|
|
||||||
}
|
|
||||||
|
|
||||||
/*Visible for testing*/ListenersHolder<HttpClientEventsListener> getListeners() {
|
|
||||||
return listeners;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*Visible for testing*/TcpClientEventListener getTcpDelegate() {
|
|
||||||
return tcpDelegate;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,99 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.http.client.events;
|
|
||||||
|
|
||||||
import io.reactivex.netty.protocol.http.client.HttpClient;
|
|
||||||
import io.reactivex.netty.protocol.tcp.client.events.TcpClientEventListener;
|
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A listener for all events published by {@link HttpClient}
|
|
||||||
*/
|
|
||||||
public abstract class HttpClientEventsListener extends TcpClientEventListener {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event when a new request is submitted for the client.
|
|
||||||
*/
|
|
||||||
public void onRequestSubmitted() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event when the write of request started.
|
|
||||||
*/
|
|
||||||
public void onRequestWriteStart() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event when a request write is completed.
|
|
||||||
*
|
|
||||||
* @param duration Time taken from the start of write to completion.
|
|
||||||
* @param timeUnit Time unit for the duration.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public void onRequestWriteComplete(long duration, TimeUnit timeUnit) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event when a request write failed.
|
|
||||||
*
|
|
||||||
* @param duration Time taken from the start of write to failure.
|
|
||||||
* @param timeUnit Time unit for the duration.
|
|
||||||
* @param throwable Error that caused the failure.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public void onRequestWriteFailed(long duration, TimeUnit timeUnit, Throwable throwable) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event when the response headers are received.
|
|
||||||
*
|
|
||||||
* @param responseCode The HTTP response code.
|
|
||||||
* @param duration The time between the request write completion and response header recieve.
|
|
||||||
* @param timeUnit Timeunit for the duration.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public void onResponseHeadersReceived(int responseCode, long duration, TimeUnit timeUnit) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event whenever an HTTP response content is received (an HTTP response can have multiple content chunks, in which
|
|
||||||
* case this event will be fired as many times for the same response).
|
|
||||||
*/
|
|
||||||
public void onResponseContentReceived() {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event when the response receive is completed.
|
|
||||||
*
|
|
||||||
* @param duration Time taken between receiving the response headers and completion of response.
|
|
||||||
* @param timeUnit Time unit for the duration.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public void onResponseReceiveComplete(long duration, TimeUnit timeUnit) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event when the response failed (either it did not arrive or not arrived completely)
|
|
||||||
*
|
|
||||||
* @param throwable Error that caused the failure.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public void onResponseFailed(Throwable throwable) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Event when the entire request processing (request header write to response failed/complete) is completed.
|
|
||||||
*
|
|
||||||
* @param duration Time taken from start of write of request to response receive completion.
|
|
||||||
* @param timeUnit Time unit for the duration.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public void onRequestProcessingComplete(long duration, TimeUnit timeUnit) {}
|
|
||||||
}
|
|
|
@ -1,306 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2015 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.http.client.events;
|
|
||||||
|
|
||||||
import io.reactivex.netty.events.internal.SafeEventListener;
|
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
final class SafeHttpClientEventsListener extends HttpClientEventsListener implements SafeEventListener {
|
|
||||||
|
|
||||||
private final HttpClientEventsListener delegate;
|
|
||||||
|
|
||||||
private final AtomicBoolean completed = new AtomicBoolean();
|
|
||||||
|
|
||||||
public SafeHttpClientEventsListener(HttpClientEventsListener delegate) {
|
|
||||||
this.delegate = delegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCompleted() {
|
|
||||||
if (completed.compareAndSet(false, true)) {
|
|
||||||
delegate.onCompleted();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRequestSubmitted() {
|
|
||||||
if (!completed.get()) {
|
|
||||||
delegate.onRequestSubmitted();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRequestWriteStart() {
|
|
||||||
if (!completed.get()) {
|
|
||||||
delegate.onRequestWriteStart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRequestWriteComplete(long duration, TimeUnit timeUnit) {
|
|
||||||
if (!completed.get()) {
|
|
||||||
delegate.onRequestWriteComplete(duration, timeUnit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRequestWriteFailed(long duration, TimeUnit timeUnit,
|
|
||||||
Throwable throwable) {
|
|
||||||
if (!completed.get()) {
|
|
||||||
delegate.onRequestWriteFailed(duration, timeUnit, throwable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResponseHeadersReceived(int responseCode, long duration, TimeUnit timeUnit) {
|
|
||||||
if (!completed.get()) {
|
|
||||||
delegate.onResponseHeadersReceived(responseCode, duration, timeUnit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResponseContentReceived() {
|
|
||||||
if (!completed.get()) {
|
|
||||||
delegate.onResponseContentReceived();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResponseReceiveComplete(long duration, TimeUnit timeUnit) {
|
|
||||||
if (!completed.get()) {
|
|
||||||
delegate.onResponseReceiveComplete(duration, timeUnit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onResponseFailed(Throwable throwable) {
|
|
||||||
if (!completed.get()) {
|
|
||||||
delegate.onResponseFailed(throwable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRequestProcessingComplete(long duration, TimeUnit timeUnit) {
|
|
||||||
if (!completed.get()) {
|
|
||||||
delegate.onRequestProcessingComplete(duration, timeUnit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConnectStart() {
|
|
||||||
if (!completed.get()) {
|
|
||||||
delegate.onConnectStart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConnectSuccess(long duration, TimeUnit timeUnit) {
|
|
||||||
if (!completed.get()) {
|
|
||||||
delegate.onConnectSuccess(duration, timeUnit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConnectFailed(long duration, TimeUnit timeUnit, Throwable throwable) {
|
|
||||||
if (!completed.get()) {
|
|
||||||
delegate.onConnectFailed(duration, timeUnit, throwable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPoolReleaseStart() {
|
|
||||||
if (!completed.get()) {
|
|
||||||
delegate.onPoolReleaseStart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPoolReleaseSuccess(long duration, TimeUnit timeUnit) {
|
|
||||||
if (!completed.get()) {
|
|
||||||
delegate.onPoolReleaseSuccess(duration, timeUnit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPoolReleaseFailed(long duration, TimeUnit timeUnit,
|
|
||||||
Throwable throwable) {
|
|
||||||
if (!completed.get()) {
|
|
||||||
delegate.onPoolReleaseFailed(duration, timeUnit, throwable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPooledConnectionEviction() {
|
|
||||||
if (!completed.get()) {
|
|
||||||
delegate.onPooledConnectionEviction();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPooledConnectionReuse() {
|
|
||||||
if (!completed.get()) {
|
|
||||||
delegate.onPooledConnectionReuse();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPoolAcquireStart() {
|
|
||||||
if (!completed.get()) {
|
|
||||||
delegate.onPoolAcquireStart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPoolAcquireSuccess(long duration, TimeUnit timeUnit) {
|
|
||||||
if (!completed.get()) {
|
|
||||||
delegate.onPoolAcquireSuccess(duration, timeUnit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPoolAcquireFailed(long duration, TimeUnit timeUnit,
|
|
||||||
Throwable throwable) {
|
|
||||||
if (!completed.get()) {
|
|
||||||
delegate.onPoolAcquireFailed(duration, timeUnit, throwable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onByteRead(long bytesRead) {
|
|
||||||
if (!completed.get()) {
|
|
||||||
delegate.onByteRead(bytesRead);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onByteWritten(long bytesWritten) {
|
|
||||||
if (!completed.get()) {
|
|
||||||
delegate.onByteWritten(bytesWritten);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFlushStart() {
|
|
||||||
if (!completed.get()) {
|
|
||||||
delegate.onFlushStart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onFlushComplete(long duration, TimeUnit timeUnit) {
|
|
||||||
if (!completed.get()) {
|
|
||||||
delegate.onFlushComplete(duration, timeUnit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onWriteStart() {
|
|
||||||
if (!completed.get()) {
|
|
||||||
delegate.onWriteStart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onWriteSuccess(long duration, TimeUnit timeUnit) {
|
|
||||||
if (!completed.get()) {
|
|
||||||
delegate.onWriteSuccess(duration, timeUnit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onWriteFailed(long duration, TimeUnit timeUnit, Throwable throwable) {
|
|
||||||
if (!completed.get()) {
|
|
||||||
delegate.onWriteFailed(duration, timeUnit, throwable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConnectionCloseStart() {
|
|
||||||
if (!completed.get()) {
|
|
||||||
delegate.onConnectionCloseStart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConnectionCloseSuccess(long duration, TimeUnit timeUnit) {
|
|
||||||
if (!completed.get()) {
|
|
||||||
delegate.onConnectionCloseSuccess(duration, timeUnit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConnectionCloseFailed(long duration, TimeUnit timeUnit,
|
|
||||||
Throwable throwable) {
|
|
||||||
if (!completed.get()) {
|
|
||||||
delegate.onConnectionCloseFailed(duration, timeUnit, throwable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCustomEvent(Object event) {
|
|
||||||
if (!completed.get()) {
|
|
||||||
delegate.onCustomEvent(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCustomEvent(Object event, long duration, TimeUnit timeUnit) {
|
|
||||||
if (!completed.get()) {
|
|
||||||
delegate.onCustomEvent(event, duration, timeUnit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCustomEvent(Object event, long duration, TimeUnit timeUnit, Throwable throwable) {
|
|
||||||
if (!completed.get()) {
|
|
||||||
delegate.onCustomEvent(event, duration, timeUnit, throwable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public HttpClientEventsListener unwrap() {
|
|
||||||
return delegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCustomEvent(Object event, Throwable throwable) {
|
|
||||||
if (!completed.get()) {
|
|
||||||
delegate.onCustomEvent(event, throwable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(Object o) {
|
|
||||||
if (this == o) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (!(o instanceof SafeHttpClientEventsListener)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
SafeHttpClientEventsListener that = (SafeHttpClientEventsListener) o;
|
|
||||||
|
|
||||||
return !(delegate != null? !delegate.equals(that.delegate) : that.delegate != null);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return delegate != null? delegate.hashCode() : 0;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.http.client.internal;
|
|
||||||
|
|
||||||
import io.netty.channel.Channel;
|
|
||||||
import io.netty.util.AttributeKey;
|
|
||||||
import io.reactivex.netty.client.ChannelProvider;
|
|
||||||
import io.reactivex.netty.protocol.http.client.events.HttpClientEventPublisher;
|
|
||||||
import io.reactivex.netty.protocol.http.client.events.HttpClientEventsListener;
|
|
||||||
import rx.Observable;
|
|
||||||
import rx.functions.Func1;
|
|
||||||
|
|
||||||
public class HttpChannelProvider implements ChannelProvider {
|
|
||||||
|
|
||||||
public static final AttributeKey<HttpClientEventsListener> HTTP_CLIENT_EVENT_LISTENER =
|
|
||||||
AttributeKey.valueOf("rxnetty_http_client_event_listener");
|
|
||||||
|
|
||||||
private final HttpClientEventPublisher hostEventPublisher;
|
|
||||||
private final ChannelProvider delegate;
|
|
||||||
|
|
||||||
public HttpChannelProvider(HttpClientEventPublisher hostEventPublisher, ChannelProvider delegate) {
|
|
||||||
this.hostEventPublisher = hostEventPublisher;
|
|
||||||
this.delegate = delegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Channel> newChannel(Observable<Channel> input) {
|
|
||||||
if (null != delegate) {
|
|
||||||
input = delegate.newChannel(input);
|
|
||||||
}
|
|
||||||
return input.map(new Func1<Channel, Channel>() {
|
|
||||||
@Override
|
|
||||||
public Channel call(Channel channel) {
|
|
||||||
channel.attr(HTTP_CLIENT_EVENT_LISTENER).set(hostEventPublisher);
|
|
||||||
return channel;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,55 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.http.client.internal;
|
|
||||||
|
|
||||||
import io.reactivex.netty.client.ChannelProvider;
|
|
||||||
import io.reactivex.netty.client.ChannelProviderFactory;
|
|
||||||
import io.reactivex.netty.client.Host;
|
|
||||||
import io.reactivex.netty.client.events.ClientEventListener;
|
|
||||||
import io.reactivex.netty.events.EventPublisher;
|
|
||||||
import io.reactivex.netty.events.EventSource;
|
|
||||||
import io.reactivex.netty.protocol.http.client.events.HttpClientEventPublisher;
|
|
||||||
|
|
||||||
public class HttpChannelProviderFactory implements ChannelProviderFactory {
|
|
||||||
|
|
||||||
private final HttpClientEventPublisher clientEventPublisher;
|
|
||||||
private final ChannelProviderFactory delegate;
|
|
||||||
|
|
||||||
public HttpChannelProviderFactory(HttpClientEventPublisher clientEventPublisher) {
|
|
||||||
this(clientEventPublisher, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public HttpChannelProviderFactory(HttpClientEventPublisher clientEventPublisher, ChannelProviderFactory delegate) {
|
|
||||||
this.clientEventPublisher = clientEventPublisher;
|
|
||||||
this.delegate = delegate instanceof HttpChannelProviderFactory
|
|
||||||
? ((HttpChannelProviderFactory) delegate).delegate : delegate;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ChannelProvider newProvider(Host host, EventSource<? super ClientEventListener> eventSource,
|
|
||||||
EventPublisher publisher, ClientEventListener clientPublisher) {
|
|
||||||
final HttpClientEventPublisher hostPublisher = new HttpClientEventPublisher();
|
|
||||||
ChannelProvider delegate = null;
|
|
||||||
if (null != this.delegate) {
|
|
||||||
delegate = this.delegate.newProvider(host, eventSource, publisher, clientPublisher);
|
|
||||||
}
|
|
||||||
hostPublisher.subscribe(clientEventPublisher);
|
|
||||||
eventSource.subscribe(hostPublisher);
|
|
||||||
return new HttpChannelProvider(hostPublisher, delegate);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,542 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.http.client.internal;
|
|
||||||
|
|
||||||
import io.netty.handler.codec.http.HttpMethod;
|
|
||||||
import io.netty.handler.codec.http.HttpVersion;
|
|
||||||
import io.netty.handler.codec.http.cookie.Cookie;
|
|
||||||
import io.reactivex.netty.channel.AllocatingTransformer;
|
|
||||||
import io.reactivex.netty.channel.AppendTransformerEvent;
|
|
||||||
import io.reactivex.netty.channel.Connection;
|
|
||||||
import io.reactivex.netty.events.Clock;
|
|
||||||
import io.reactivex.netty.events.EventAttributeKeys;
|
|
||||||
import io.reactivex.netty.events.EventPublisher;
|
|
||||||
import io.reactivex.netty.internal.VoidToAnythingCast;
|
|
||||||
import io.reactivex.netty.protocol.http.TrailingHeaders;
|
|
||||||
import io.reactivex.netty.protocol.http.client.HttpClientRequest;
|
|
||||||
import io.reactivex.netty.protocol.http.client.HttpClientResponse;
|
|
||||||
import io.reactivex.netty.protocol.http.client.events.HttpClientEventsListener;
|
|
||||||
import io.reactivex.netty.protocol.http.internal.OperatorTrailer;
|
|
||||||
import io.reactivex.netty.protocol.http.ws.client.internal.WebSocketRequestImpl;
|
|
||||||
import io.reactivex.netty.protocol.tcp.client.TcpClient;
|
|
||||||
import rx.Observable;
|
|
||||||
import rx.Subscriber;
|
|
||||||
import rx.functions.Func0;
|
|
||||||
import rx.functions.Func1;
|
|
||||||
import rx.functions.Func2;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import static java.util.concurrent.TimeUnit.*;
|
|
||||||
|
|
||||||
public final class HttpClientRequestImpl<I, O> extends HttpClientRequest<I, O> {
|
|
||||||
|
|
||||||
public static final int NO_REDIRECTS = -1;
|
|
||||||
|
|
||||||
private final List<AppendTransformerEvent> immutableTransformers;
|
|
||||||
private final List<Transformer> immutableResponseTransformers;
|
|
||||||
private final RawRequest<I, O> rawRequest;
|
|
||||||
private final TcpClient<?, HttpClientResponse<O>> client;
|
|
||||||
private final Func1<I, Boolean> flushOnEachSelector = new Func1<I, Boolean>() {
|
|
||||||
@Override
|
|
||||||
public Boolean call(I next) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private HttpClientRequestImpl(final RawRequest<I, O> rawRequest, final TcpClient<?, HttpClientResponse<O>> client,
|
|
||||||
List<AppendTransformerEvent> immutableTransformers,
|
|
||||||
List<Transformer> immutableResponseTransformers) {
|
|
||||||
super(new OnSubscribeFuncImpl<>(client, rawRequest, immutableResponseTransformers, immutableTransformers));
|
|
||||||
this.rawRequest = rawRequest;
|
|
||||||
this.client = client;
|
|
||||||
this.immutableTransformers = immutableTransformers;
|
|
||||||
this.immutableResponseTransformers = immutableResponseTransformers;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<HttpClientResponse<O>> writeContent(Observable<I> contentSource) {
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
Observable rawObservable = contentSource;
|
|
||||||
return _writeContentRaw(rawObservable, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<HttpClientResponse<O>> writeContentAndFlushOnEach(Observable<I> contentSource) {
|
|
||||||
return writeContent(contentSource, flushOnEachSelector);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<HttpClientResponse<O>> writeStringContent(Observable<String> contentSource) {
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
Observable rawObservable = contentSource;
|
|
||||||
return _writeContentRaw(rawObservable, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<HttpClientResponse<O>> writeBytesContent(Observable<byte[]> contentSource) {
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
Observable rawObservable = contentSource;
|
|
||||||
return _writeContentRaw(rawObservable, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<HttpClientResponse<O>> writeContent(Observable<I> contentSource,
|
|
||||||
Func1<I, Boolean> flushSelector) {
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
Observable rawObservable = contentSource;
|
|
||||||
return _writeContentRaw(rawObservable, flushSelector, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<HttpClientResponse<O>> writeStringContent(Observable<String> contentSource,
|
|
||||||
Func1<String, Boolean> flushSelector) {
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
Observable rawObservable = contentSource;
|
|
||||||
return _writeContentRaw(rawObservable, flushSelector, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<HttpClientResponse<O>> writeBytesContent(Observable<byte[]> contentSource,
|
|
||||||
Func1<byte[], Boolean> flushSelector) {
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
Observable rawObservable = contentSource;
|
|
||||||
return _writeContentRaw(rawObservable, flushSelector, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T extends TrailingHeaders> Observable<HttpClientResponse<O>> writeContent(Observable<I> contentSource,
|
|
||||||
final Func0<T> trailerFactory,
|
|
||||||
final Func2<T, I, T> trailerMutator) {
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
Observable rawObservable = contentSource;
|
|
||||||
return _writeContentRaw(OperatorTrailer.liftFrom(rawObservable, trailerFactory, trailerMutator), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T extends TrailingHeaders> Observable<HttpClientResponse<O>> writeStringContent(Observable<String> contentSource,
|
|
||||||
Func0<T> trailerFactory,
|
|
||||||
Func2<T, String, T> trailerMutator) {
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
Observable rawObservable = contentSource;
|
|
||||||
return _writeContentRaw(OperatorTrailer.liftFrom(rawObservable, trailerFactory, trailerMutator), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T extends TrailingHeaders> Observable<HttpClientResponse<O>> writeBytesContent(Observable<byte[]> contentSource,
|
|
||||||
Func0<T> trailerFactory,
|
|
||||||
Func2<T, byte[], T> trailerMutator) {
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
Observable rawObservable = contentSource;
|
|
||||||
return _writeContentRaw(OperatorTrailer.liftFrom(rawObservable, trailerFactory, trailerMutator), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T extends TrailingHeaders> Observable<HttpClientResponse<O>> writeContent(Observable<I> contentSource,
|
|
||||||
Func0<T> trailerFactory,
|
|
||||||
Func2<T, I, T> trailerMutator,
|
|
||||||
Func1<I, Boolean> flushSelector) {
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
Observable rawObservable = contentSource;
|
|
||||||
return _writeContentRaw(OperatorTrailer.liftFrom(rawObservable, trailerFactory, trailerMutator), flushSelector,
|
|
||||||
true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T extends TrailingHeaders> Observable<HttpClientResponse<O>> writeStringContent(
|
|
||||||
Observable<String> contentSource, Func0<T> trailerFactory, Func2<T, String, T> trailerMutator,
|
|
||||||
Func1<String, Boolean> flushSelector) {
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
Observable rawObservable = contentSource;
|
|
||||||
return _writeContentRaw(OperatorTrailer.liftFrom(rawObservable, trailerFactory, trailerMutator), flushSelector,
|
|
||||||
true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <T extends TrailingHeaders> Observable<HttpClientResponse<O>> writeBytesContent(
|
|
||||||
Observable<byte[]> contentSource, Func0<T> trailerFactory, Func2<T, byte[], T> trailerMutator,
|
|
||||||
Func1<byte[], Boolean> flushSelector) {
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
Observable rawObservable = contentSource;
|
|
||||||
return _writeContentRaw(OperatorTrailer.liftFrom(rawObservable, trailerFactory, trailerMutator), flushSelector,
|
|
||||||
true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequestImpl<I, O> readTimeOut(int timeOut, TimeUnit timeUnit) {
|
|
||||||
return _copy(client.readTimeOut(timeOut, timeUnit));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequestImpl<I, O> followRedirects(int maxRedirects) {
|
|
||||||
final Redirector<I, O> redirector = new Redirector<>(maxRedirects, client);
|
|
||||||
HttpClientRequestImpl<I, O> toReturn = _copy(client, rawRequest.followRedirect(redirector));
|
|
||||||
redirector.setOriginalRequest(toReturn.rawRequest);
|
|
||||||
return toReturn;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequestImpl<I, O> followRedirects(boolean follow) {
|
|
||||||
return follow ? followRedirects(Redirector.DEFAULT_MAX_REDIRECTS) : followRedirects(NO_REDIRECTS);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequestImpl<I, O> setMethod(HttpMethod method) {
|
|
||||||
return _copy(client, rawRequest.setMethod(method));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequestImpl<I, O> setUri(String newUri) {
|
|
||||||
return _copy(client, rawRequest.setUri(newUri));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequestImpl<I, O> addHeader(CharSequence name, Object value) {
|
|
||||||
return _copy(client, rawRequest.addHeader(name, value));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequest<I, O> addHeaders(Map<? extends CharSequence, ? extends Iterable<Object>> headers) {
|
|
||||||
return _copy(client, rawRequest.addHeaders(headers));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequestImpl<I, O> addCookie(Cookie cookie) {
|
|
||||||
return _copy(client, rawRequest.addCookie(cookie));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequestImpl<I, O> addDateHeader(CharSequence name, Date value) {
|
|
||||||
return _copy(client, rawRequest.addDateHeader(name, value));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequestImpl<I, O> addDateHeader(CharSequence name, Iterable<Date> values) {
|
|
||||||
return _copy(client, rawRequest.addDateHeader(name, values));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequestImpl<I, O> addHeaderValues(CharSequence name, Iterable<Object> values) {
|
|
||||||
return _copy(client, rawRequest.addHeaderValues(name, values));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequestImpl<I, O> setDateHeader(CharSequence name, Date value) {
|
|
||||||
return _copy(client, rawRequest.setDateHeader(name, value));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequestImpl<I, O> setHeader(CharSequence name, Object value) {
|
|
||||||
return _copy(client, rawRequest.setHeader(name, value));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequest<I, O> setHeaders(Map<? extends CharSequence, ? extends Iterable<Object>> headers) {
|
|
||||||
return _copy(client, rawRequest.setHeaders(headers));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequestImpl<I, O> setDateHeader(CharSequence name, Iterable<Date> values) {
|
|
||||||
return _copy(client, rawRequest.setDateHeader(name, values));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequestImpl<I, O> setHeaderValues(CharSequence name, Iterable<Object> values) {
|
|
||||||
return _copy(client, rawRequest.setHeaderValues(name, values));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequestImpl<I, O> removeHeader(CharSequence name) {
|
|
||||||
return _copy(client, rawRequest.removeHeader(name));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequestImpl<I, O> setKeepAlive(boolean keepAlive) {
|
|
||||||
return _copy(client, rawRequest.setKeepAlive(keepAlive));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientRequestImpl<I, O> setTransferEncodingChunked() {
|
|
||||||
return _copy(client, rawRequest.setTransferEncodingChunked());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <II> HttpClientRequestImpl<II, O> transformContent(AllocatingTransformer<II, I> transformer) {
|
|
||||||
final List<AppendTransformerEvent> newTransformers = new ArrayList<>(immutableTransformers);
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
AppendTransformerEvent e = new AppendTransformerEvent(transformer);
|
|
||||||
newTransformers.add(e);
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
RawRequest<II, O> cast = (RawRequest<II, O>) this.rawRequest;
|
|
||||||
return new HttpClientRequestImpl<>(cast, client, newTransformers, immutableResponseTransformers);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <OO> HttpClientRequestImpl<I, OO> transformResponseContent(Transformer<O, OO> transformer) {
|
|
||||||
final List<Transformer> newTransformers = new ArrayList<>(immutableResponseTransformers);
|
|
||||||
newTransformers.add(transformer);
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
RawRequest<I, OO> cast = (RawRequest<I, OO>) this.rawRequest;
|
|
||||||
TcpClient rawClient = client;
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
TcpClient<?, HttpClientResponse<OO>> _client = (TcpClient<?, HttpClientResponse<OO>>)rawClient;
|
|
||||||
return new HttpClientRequestImpl<>(cast, _client, immutableTransformers, newTransformers);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public WebSocketRequestImpl<O> requestWebSocketUpgrade() {
|
|
||||||
return WebSocketRequestImpl.createNew(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean containsHeader(CharSequence name) {
|
|
||||||
return rawRequest.getHeaders().headers().contains(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean containsHeaderWithValue(CharSequence name, CharSequence value, boolean caseInsensitiveValueMatch) {
|
|
||||||
return rawRequest.getHeaders().headers().contains(name, value, caseInsensitiveValueMatch);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getHeader(CharSequence name) {
|
|
||||||
return rawRequest.getHeaders().headers().get(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<String> getAllHeaders(CharSequence name) {
|
|
||||||
return rawRequest.getHeaders().headers().getAll(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Iterator<Entry<CharSequence, CharSequence>> headerIterator() {
|
|
||||||
return rawRequest.getHeaders().headers().iteratorCharSequence();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<String> getHeaderNames() {
|
|
||||||
return rawRequest.getHeaders().headers().names();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpVersion getHttpVersion() {
|
|
||||||
return rawRequest.getHeaders().protocolVersion();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpMethod getMethod() {
|
|
||||||
return rawRequest.getHeaders().method();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getUri() {
|
|
||||||
return rawRequest.getHeaders().uri();
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <I, O> HttpClientRequestImpl<I, O> create(final HttpVersion version, final HttpMethod httpMethod,
|
|
||||||
final String uri,
|
|
||||||
final TcpClient<?, HttpClientResponse<O>> client,
|
|
||||||
int maxRedirects) {
|
|
||||||
Redirector<I, O> redirector = NO_REDIRECTS == maxRedirects
|
|
||||||
? null
|
|
||||||
: new Redirector<I, O>(maxRedirects, client
|
|
||||||
);
|
|
||||||
|
|
||||||
final RawRequest<I, O> rawRequest = RawRequest.create(version, httpMethod, uri, redirector);
|
|
||||||
|
|
||||||
if (null != redirector) {
|
|
||||||
redirector.setOriginalRequest(rawRequest);
|
|
||||||
}
|
|
||||||
|
|
||||||
return create(rawRequest, client);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <I, O> HttpClientRequestImpl<I, O> create(final HttpVersion version, final HttpMethod httpMethod,
|
|
||||||
final String uri,
|
|
||||||
final TcpClient<?, HttpClientResponse<O>> client) {
|
|
||||||
return create(version, httpMethod, uri, client, NO_REDIRECTS);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <I, O> HttpClientRequestImpl<I, O> create(final RawRequest<I, O> rawRequest,
|
|
||||||
final TcpClient<?, HttpClientResponse<O>> client) {
|
|
||||||
return new HttpClientRequestImpl<>(rawRequest, client, Collections.<AppendTransformerEvent>emptyList(),
|
|
||||||
Collections.<Transformer>emptyList());
|
|
||||||
}
|
|
||||||
|
|
||||||
public TcpClient<?, HttpClientResponse<O>> getClient() {
|
|
||||||
return client;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private <II, OO> HttpClientRequestImpl<II, OO> _copy(TcpClient<?, HttpClientResponse<OO>> c) {
|
|
||||||
return _copy(c, (RawRequest<II, OO>)rawRequest);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private <II, OO> HttpClientRequestImpl<II, OO> _copy(TcpClient<?, HttpClientResponse<OO>> c,
|
|
||||||
RawRequest<II, OO> rawRequest) {
|
|
||||||
return new HttpClientRequestImpl<>(rawRequest, c, immutableTransformers, immutableResponseTransformers);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
private Observable<HttpClientResponse<O>> _writeContentRaw(Observable rawContent, boolean hasTrailers) {
|
|
||||||
return _writeContentRaw(rawContent, null, hasTrailers);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
private Observable<HttpClientResponse<O>> _writeContentRaw(Observable rawContent,
|
|
||||||
Func1<?, Boolean> flushSelector, boolean hasTrailers) {
|
|
||||||
final RawRequest<I, O> r = RawRequest.create(rawRequest.getHeaders(), rawContent, flushSelector, hasTrailers,
|
|
||||||
rawRequest.getRedirector());
|
|
||||||
return new HttpClientRequestImpl<>(r, client, immutableTransformers, immutableResponseTransformers);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RawRequest<I, O> unsafeRawRequest() {
|
|
||||||
return rawRequest;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class OnSubscribeFuncImpl<I, O> implements OnSubscribe<HttpClientResponse<O>> {
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
private final Observable source;
|
|
||||||
private final TcpClient<?, HttpClientResponse<O>> client;
|
|
||||||
|
|
||||||
public OnSubscribeFuncImpl(final TcpClient<?, HttpClientResponse<O>> client, RawRequest<I, O> rawRequest,
|
|
||||||
List<Transformer> responseTransformers,
|
|
||||||
List<AppendTransformerEvent> requestTransformers) {
|
|
||||||
this.client = client;
|
|
||||||
ConnToResponseFunc<I, O> connToResponseFunc = new ConnToResponseFunc<>(rawRequest, responseTransformers,
|
|
||||||
requestTransformers);
|
|
||||||
Observable<HttpClientResponse<O>> source = this.client.createConnectionRequest()
|
|
||||||
.take(1)
|
|
||||||
.switchMap(connToResponseFunc);
|
|
||||||
|
|
||||||
if (null != rawRequest.getRedirector()) {
|
|
||||||
source = source.switchMap(rawRequest.getRedirector());
|
|
||||||
}
|
|
||||||
|
|
||||||
this.source = source;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public void call(Subscriber<? super HttpClientResponse<O>> subscriber) {
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
final Subscriber rawSub = subscriber;
|
|
||||||
source.unsafeSubscribe(rawSub);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class ConnToResponseFunc<I, O>
|
|
||||||
implements Func1<Connection<HttpClientResponse<O>, ?>, Observable<HttpClientResponse<O>>> {
|
|
||||||
|
|
||||||
private final RawRequest<I, O> rawRequest;
|
|
||||||
private List<Transformer> responseTransformers;
|
|
||||||
private List<AppendTransformerEvent> requestTransformers;
|
|
||||||
|
|
||||||
public ConnToResponseFunc(RawRequest<I, O> rawRequest, List<Transformer> responseTransformers,
|
|
||||||
List<AppendTransformerEvent> requestTransformers) {
|
|
||||||
this.rawRequest = rawRequest;
|
|
||||||
this.responseTransformers = responseTransformers;
|
|
||||||
this.requestTransformers = requestTransformers;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<HttpClientResponse<O>> call(final Connection<HttpClientResponse<O>, ?> conn) {
|
|
||||||
for (AppendTransformerEvent requestTransformer : requestTransformers) {
|
|
||||||
conn.unsafeNettyChannel().pipeline().fireUserEventTriggered(requestTransformer);
|
|
||||||
}
|
|
||||||
|
|
||||||
final Observable<HttpClientResponse<O>> input = conn.getInput();
|
|
||||||
|
|
||||||
final HttpClientEventsListener eventsListener =
|
|
||||||
conn.unsafeNettyChannel().attr(HttpChannelProvider.HTTP_CLIENT_EVENT_LISTENER).get();
|
|
||||||
final EventPublisher eventPublisher =
|
|
||||||
conn.unsafeNettyChannel().attr(EventAttributeKeys.EVENT_PUBLISHER).get();
|
|
||||||
|
|
||||||
return writeRequest(conn).lift(new RequestWriteMetricsOperator(eventsListener, eventPublisher))
|
|
||||||
.map(new VoidToAnythingCast<HttpClientResponse<O>>())
|
|
||||||
.ignoreElements()
|
|
||||||
.concatWith(input.take(1))
|
|
||||||
.map(new Func1<HttpClientResponse<O>, HttpClientResponse<O>>() {
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
@Override
|
|
||||||
public HttpClientResponse<O> call(HttpClientResponse<O> r) {
|
|
||||||
HttpClientResponse rp = HttpClientResponseImpl.newInstance(r, conn);
|
|
||||||
for (Transformer transformer : responseTransformers) {
|
|
||||||
rp = rp.transformContent(transformer);
|
|
||||||
}
|
|
||||||
return (HttpClientResponse<O>) rp;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
protected Observable<Void> writeRequest(Connection<HttpClientResponse<O>, ?> conn) {
|
|
||||||
return conn.write(rawRequest.asObservable(conn));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class RequestWriteMetricsOperator implements Operator<Void, Void> {
|
|
||||||
|
|
||||||
private final EventPublisher eventPublisher;
|
|
||||||
private final HttpClientEventsListener eventsListener;
|
|
||||||
|
|
||||||
public RequestWriteMetricsOperator(HttpClientEventsListener eventsListener, EventPublisher eventPublisher) {
|
|
||||||
this.eventPublisher = eventPublisher;
|
|
||||||
this.eventsListener = eventsListener;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Subscriber<? super Void> call(final Subscriber<? super Void> o) {
|
|
||||||
final long startTimeNanos = eventPublisher.publishingEnabled() ? Clock.newStartTimeNanos() : -1;
|
|
||||||
if (eventPublisher.publishingEnabled()) {
|
|
||||||
eventsListener.onRequestSubmitted();
|
|
||||||
}
|
|
||||||
return new Subscriber<Void>(o) {
|
|
||||||
@Override
|
|
||||||
public void onCompleted() {
|
|
||||||
if (eventPublisher.publishingEnabled()) {
|
|
||||||
eventsListener.onRequestWriteComplete(Clock.onEndNanos(startTimeNanos), NANOSECONDS);
|
|
||||||
}
|
|
||||||
o.onCompleted();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onError(Throwable e) {
|
|
||||||
if (eventPublisher.publishingEnabled()) {
|
|
||||||
eventsListener.onRequestWriteFailed(Clock.onEndNanos(startTimeNanos), NANOSECONDS, e);
|
|
||||||
}
|
|
||||||
o.onError(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onNext(Void aVoid) {
|
|
||||||
o.onNext(aVoid);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,353 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.http.client.internal;
|
|
||||||
|
|
||||||
import io.netty.channel.Channel;
|
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
|
||||||
import io.netty.channel.ChannelPipeline;
|
|
||||||
import io.netty.handler.codec.http.HttpResponse;
|
|
||||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
|
||||||
import io.netty.handler.codec.http.HttpUtil;
|
|
||||||
import io.netty.handler.codec.http.HttpVersion;
|
|
||||||
import io.netty.handler.codec.http.cookie.ClientCookieEncoder;
|
|
||||||
import io.netty.handler.codec.http.cookie.Cookie;
|
|
||||||
import io.netty.util.ReferenceCountUtil;
|
|
||||||
import io.reactivex.netty.channel.Connection;
|
|
||||||
import io.reactivex.netty.channel.ContentSource;
|
|
||||||
import io.reactivex.netty.protocol.http.CookiesHolder;
|
|
||||||
import io.reactivex.netty.protocol.http.HttpHandlerNames;
|
|
||||||
import io.reactivex.netty.protocol.http.client.HttpClientResponse;
|
|
||||||
import io.reactivex.netty.protocol.http.internal.HttpContentSubscriberEvent;
|
|
||||||
import io.reactivex.netty.protocol.http.sse.ServerSentEvent;
|
|
||||||
import io.reactivex.netty.protocol.http.sse.client.ServerSentEventDecoder;
|
|
||||||
import rx.Observable;
|
|
||||||
import rx.Observable.Transformer;
|
|
||||||
import rx.Subscriber;
|
|
||||||
import rx.functions.Func1;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
import static io.netty.handler.codec.http.HttpHeaderNames.*;
|
|
||||||
|
|
||||||
public final class HttpClientResponseImpl<T> extends HttpClientResponse<T> {
|
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(HttpClientResponseImpl.class.getName());
|
|
||||||
|
|
||||||
public static final String KEEP_ALIVE_HEADER_NAME = "Keep-Alive";
|
|
||||||
private static final Pattern PATTERN_COMMA = Pattern.compile(",");
|
|
||||||
private static final Pattern PATTERN_EQUALS = Pattern.compile("=");
|
|
||||||
public static final String KEEP_ALIVE_TIMEOUT_HEADER_ATTR = "timeout";
|
|
||||||
|
|
||||||
private final HttpResponse nettyResponse;
|
|
||||||
private final Connection<?, ?> connection;
|
|
||||||
private final CookiesHolder cookiesHolder;
|
|
||||||
private final ContentSource<T> contentSource;
|
|
||||||
|
|
||||||
private HttpClientResponseImpl(HttpResponse nettyResponse) {
|
|
||||||
this(nettyResponse, UnusableConnection.create());
|
|
||||||
}
|
|
||||||
|
|
||||||
private HttpClientResponseImpl(HttpResponse nettyResponse, Connection<?, ?> connection) {
|
|
||||||
this.nettyResponse = nettyResponse;
|
|
||||||
this.connection = connection;
|
|
||||||
cookiesHolder = CookiesHolder.newClientResponseHolder(nettyResponse.headers());
|
|
||||||
contentSource = new ContentSource<>(unsafeNettyChannel(), new ContentSourceSubscriptionFactory<T>());
|
|
||||||
}
|
|
||||||
|
|
||||||
private HttpClientResponseImpl(HttpClientResponseImpl<?> toCopy, ContentSource<T> newSource) {
|
|
||||||
nettyResponse = toCopy.nettyResponse;
|
|
||||||
connection = toCopy.connection;
|
|
||||||
cookiesHolder = toCopy.cookiesHolder;
|
|
||||||
contentSource = newSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpVersion getHttpVersion() {
|
|
||||||
return nettyResponse.protocolVersion();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpResponseStatus getStatus() {
|
|
||||||
return nettyResponse.status();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, Set<Cookie>> getCookies() {
|
|
||||||
return cookiesHolder.getAllCookies();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean containsHeader(CharSequence name) {
|
|
||||||
return nettyResponse.headers().contains(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean containsHeader(CharSequence name, CharSequence value, boolean ignoreCaseValue) {
|
|
||||||
return nettyResponse.headers().contains(name, value, ignoreCaseValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Iterator<Entry<CharSequence, CharSequence>> headerIterator() {
|
|
||||||
return nettyResponse.headers().iteratorCharSequence();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getHeader(CharSequence name) {
|
|
||||||
return nettyResponse.headers().get(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getHeader(CharSequence name, String defaultValue) {
|
|
||||||
return nettyResponse.headers().get(name, defaultValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<String> getAllHeaderValues(CharSequence name) {
|
|
||||||
return nettyResponse.headers().getAll(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getContentLength() {
|
|
||||||
return HttpUtil.getContentLength(nettyResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getContentLength(long defaultValue) {
|
|
||||||
return HttpUtil.getContentLength(nettyResponse, defaultValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getDateHeader(CharSequence name) {
|
|
||||||
return nettyResponse.headers().getTimeMillis(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getDateHeader(CharSequence name, long defaultValue) {
|
|
||||||
return nettyResponse.headers().getTimeMillis(name, defaultValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getHostHeader() {
|
|
||||||
return nettyResponse.headers().get(HOST);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getHost(String defaultValue) {
|
|
||||||
return nettyResponse.headers().get(HOST, defaultValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getIntHeader(CharSequence name) {
|
|
||||||
return nettyResponse.headers().getInt(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getIntHeader(CharSequence name, int defaultValue) {
|
|
||||||
return nettyResponse.headers().getInt(name, defaultValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isContentLengthSet() {
|
|
||||||
return HttpUtil.isContentLengthSet(nettyResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isKeepAlive() {
|
|
||||||
return HttpUtil.isKeepAlive(nettyResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isTransferEncodingChunked() {
|
|
||||||
return HttpUtil.isTransferEncodingChunked(nettyResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Set<String> getHeaderNames() {
|
|
||||||
return nettyResponse.headers().names();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientResponse<T> addHeader(CharSequence name, Object value) {
|
|
||||||
nettyResponse.headers().add(name, value);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientResponse<T> addCookie(Cookie cookie) {
|
|
||||||
nettyResponse.headers().add(SET_COOKIE, ClientCookieEncoder.STRICT.encode(cookie));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientResponse<T> addDateHeader(CharSequence name, Date value) {
|
|
||||||
nettyResponse.headers().set(name, value);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientResponse<T> addDateHeader(CharSequence name, Iterable<Date> values) {
|
|
||||||
for (Date value : values) {
|
|
||||||
nettyResponse.headers().add(name, value);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientResponse<T> addHeader(CharSequence name, Iterable<Object> values) {
|
|
||||||
nettyResponse.headers().add(name, values);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientResponse<T> setDateHeader(CharSequence name, Date value) {
|
|
||||||
nettyResponse.headers().set(name, value);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientResponse<T> setHeader(CharSequence name, Object value) {
|
|
||||||
nettyResponse.headers().set(name, value);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientResponse<T> setDateHeader(CharSequence name, Iterable<Date> values) {
|
|
||||||
for (Date value : values) {
|
|
||||||
nettyResponse.headers().set(name, value);
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientResponse<T> setHeader(CharSequence name, Iterable<Object> values) {
|
|
||||||
nettyResponse.headers().set(name, values);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HttpClientResponse<T> removeHeader(CharSequence name) {
|
|
||||||
nettyResponse.headers().remove(name);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ContentSource<ServerSentEvent> getContentAsServerSentEvents() {
|
|
||||||
if (containsHeader(CONTENT_TYPE) && getHeader(CONTENT_TYPE).startsWith("text/event-stream")) {
|
|
||||||
ChannelPipeline pipeline = unsafeNettyChannel().pipeline();
|
|
||||||
ChannelHandlerContext decoderCtx = pipeline.context(HttpHandlerNames.HttpClientCodec.getName());
|
|
||||||
if (null != decoderCtx) {
|
|
||||||
pipeline.addAfter(decoderCtx.name(), HttpHandlerNames.SseClientCodec.getName(),
|
|
||||||
new ServerSentEventDecoder());
|
|
||||||
}
|
|
||||||
return new ContentSource<>(unsafeNettyChannel(), new ContentSourceSubscriptionFactory<ServerSentEvent>());
|
|
||||||
}
|
|
||||||
|
|
||||||
return new ContentSource<>(new IllegalStateException("Response is not a server sent event response."));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ContentSource<T> getContent() {
|
|
||||||
return contentSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> discardContent() {
|
|
||||||
return getContent().map(new Func1<T, Void>() {
|
|
||||||
@Override
|
|
||||||
public Void call(T t) {
|
|
||||||
ReferenceCountUtil.release(t);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}).ignoreElements();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <TT> HttpClientResponse<TT> transformContent(Transformer<T, TT> transformer) {
|
|
||||||
return new HttpClientResponseImpl<>(this, contentSource.transform(transformer));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Channel unsafeNettyChannel() {
|
|
||||||
return unsafeConnection().unsafeNettyChannel();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Connection<?, ?> unsafeConnection() {
|
|
||||||
return connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses the timeout value from the HTTP keep alive header (with name {@link #KEEP_ALIVE_HEADER_NAME}) as described in
|
|
||||||
* <a href="http://tools.ietf.org/id/draft-thomson-hybi-http-timeout-01.html">this spec</a>
|
|
||||||
*
|
|
||||||
* @return The keep alive timeout or {@code null} if this response does not define the appropriate header value.
|
|
||||||
*/
|
|
||||||
public Long getKeepAliveTimeoutSeconds() {
|
|
||||||
String keepAliveHeader = nettyResponse.headers().get(KEEP_ALIVE_HEADER_NAME);
|
|
||||||
if (null != keepAliveHeader && !keepAliveHeader.isEmpty()) {
|
|
||||||
String[] pairs = PATTERN_COMMA.split(keepAliveHeader);
|
|
||||||
if (pairs != null) {
|
|
||||||
for (String pair: pairs) {
|
|
||||||
String[] nameValue = PATTERN_EQUALS.split(pair.trim());
|
|
||||||
if (nameValue != null && nameValue.length >= 2) {
|
|
||||||
String name = nameValue[0].trim().toLowerCase();
|
|
||||||
String value = nameValue[1].trim();
|
|
||||||
if (KEEP_ALIVE_TIMEOUT_HEADER_ATTR.equals(name)) {
|
|
||||||
try {
|
|
||||||
return Long.valueOf(value);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
logger.log(Level.INFO, "Invalid HTTP keep alive timeout value. Keep alive header: "
|
|
||||||
+ keepAliveHeader + ", timeout attribute value: " + nameValue[1], e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*Visible for the client bridge*/static <C> HttpClientResponseImpl<C> unsafeCreate(HttpResponse nettyResponse) {
|
|
||||||
return new HttpClientResponseImpl<>(nettyResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <C> HttpClientResponse<C> newInstance(HttpClientResponse<C> unsafeInstance,
|
|
||||||
Connection<?, ?> connection) {
|
|
||||||
HttpClientResponseImpl<C> cast = (HttpClientResponseImpl<C>) unsafeInstance;
|
|
||||||
return new HttpClientResponseImpl<>(cast.nettyResponse, connection);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <C> HttpClientResponse<C> newInstance(HttpResponse nettyResponse, Connection<?, ?> connection) {
|
|
||||||
return new HttpClientResponseImpl<>(nettyResponse, connection);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class ContentSourceSubscriptionFactory<T> implements Func1<Subscriber<? super T>, Object> {
|
|
||||||
@Override
|
|
||||||
public Object call(Subscriber<? super T> subscriber) {
|
|
||||||
return new HttpContentSubscriberEvent<>(subscriber);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,193 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.http.client.internal;
|
|
||||||
|
|
||||||
import io.netty.channel.Channel;
|
|
||||||
import io.netty.channel.ChannelFuture;
|
|
||||||
import io.netty.channel.ChannelFutureListener;
|
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
|
||||||
import io.netty.channel.ChannelPromise;
|
|
||||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
|
||||||
import io.netty.handler.codec.http.HttpMessage;
|
|
||||||
import io.netty.handler.codec.http.HttpRequest;
|
|
||||||
import io.netty.handler.codec.http.HttpResponse;
|
|
||||||
import io.netty.handler.codec.http.LastHttpContent;
|
|
||||||
import io.netty.util.AttributeKey;
|
|
||||||
import io.reactivex.netty.client.ClientConnectionToChannelBridge;
|
|
||||||
import io.reactivex.netty.client.ClientConnectionToChannelBridge.ConnectionReuseEvent;
|
|
||||||
import io.reactivex.netty.client.ClientConnectionToChannelBridge.PooledConnectionReleaseEvent;
|
|
||||||
import io.reactivex.netty.client.pool.PooledConnection;
|
|
||||||
import io.reactivex.netty.events.Clock;
|
|
||||||
import io.reactivex.netty.events.EventAttributeKeys;
|
|
||||||
import io.reactivex.netty.events.EventPublisher;
|
|
||||||
import io.reactivex.netty.protocol.http.client.events.HttpClientEventsListener;
|
|
||||||
import io.reactivex.netty.protocol.http.internal.AbstractHttpConnectionBridge;
|
|
||||||
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.net.SocketAddress;
|
|
||||||
|
|
||||||
import static java.util.concurrent.TimeUnit.*;
|
|
||||||
|
|
||||||
public class HttpClientToConnectionBridge<C> extends AbstractHttpConnectionBridge<C> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This attribute stores the value of any dynamic idle timeout value sent via an HTTP keep alive header.
|
|
||||||
* This follows the proposal specified here: http://tools.ietf.org/id/draft-thomson-hybi-http-timeout-01.html
|
|
||||||
* The attribute can be extracted from an HTTP response header using the helper method
|
|
||||||
* {@link HttpClientResponseImpl#getKeepAliveTimeoutSeconds()}
|
|
||||||
*/
|
|
||||||
public static final AttributeKey<Long> KEEP_ALIVE_TIMEOUT_MILLIS_ATTR =
|
|
||||||
PooledConnection.DYNAMIC_CONN_KEEP_ALIVE_TIMEOUT_MS;
|
|
||||||
|
|
||||||
private HttpClientEventsListener eventsListener;
|
|
||||||
private EventPublisher eventPublisher;
|
|
||||||
private String hostHeader;
|
|
||||||
private long requestWriteCompletionTimeNanos;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
|
|
||||||
eventsListener = ctx.channel().attr(HttpChannelProvider.HTTP_CLIENT_EVENT_LISTENER).get();
|
|
||||||
eventPublisher = ctx.channel().attr(EventAttributeKeys.EVENT_PUBLISHER).get();
|
|
||||||
super.handlerAdded(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
|
||||||
SocketAddress remoteAddr = ctx.channel().remoteAddress();
|
|
||||||
if (remoteAddr instanceof InetSocketAddress) {
|
|
||||||
InetSocketAddress inetSock = (InetSocketAddress) remoteAddr;
|
|
||||||
String hostString = inetSock.getHostString(); // Don't use hostname that does a DNS lookup.
|
|
||||||
hostHeader = hostString + ':' + inetSock.getPort();
|
|
||||||
}
|
|
||||||
super.channelActive(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void beforeOutboundHeaderWrite(HttpMessage httpMsg, ChannelPromise promise, long startTimeNanos) {
|
|
||||||
/*Reset on every request write, we do not currently support pipelining, otherwise, this should be stored in a
|
|
||||||
queue.*/
|
|
||||||
requestWriteCompletionTimeNanos = -1;
|
|
||||||
if (null != hostHeader) {
|
|
||||||
if (!httpMsg.headers().contains(HttpHeaderNames.HOST)) {
|
|
||||||
httpMsg.headers().set(HttpHeaderNames.HOST, hostHeader);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (eventPublisher.publishingEnabled()) {
|
|
||||||
eventsListener.onRequestWriteStart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onOutboundLastContentWrite(LastHttpContent msg, ChannelPromise promise,
|
|
||||||
final long headerWriteStartTimeNanos) {
|
|
||||||
if (eventPublisher.publishingEnabled()) {
|
|
||||||
promise.addListener(new ChannelFutureListener() {
|
|
||||||
@Override
|
|
||||||
public void operationComplete(ChannelFuture future) throws Exception {
|
|
||||||
if (eventPublisher.publishingEnabled()) {
|
|
||||||
requestWriteCompletionTimeNanos = Clock.newStartTimeNanos();
|
|
||||||
if (future.isSuccess()) {
|
|
||||||
eventsListener.onRequestWriteComplete(Clock.onEndNanos(headerWriteStartTimeNanos),
|
|
||||||
NANOSECONDS);
|
|
||||||
} else {
|
|
||||||
eventsListener.onRequestWriteFailed(Clock.onEndNanos(headerWriteStartTimeNanos),
|
|
||||||
NANOSECONDS, future.cause());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
|
||||||
if (evt instanceof ConnectionReuseEvent) {
|
|
||||||
resetSubscriptionState(connectionInputSubscriber);
|
|
||||||
connectionInputSubscriber = null;
|
|
||||||
} else if (PooledConnectionReleaseEvent.INSTANCE == evt) {
|
|
||||||
onPooledConnectionRelease(connectionInputSubscriber);
|
|
||||||
}
|
|
||||||
super.userEventTriggered(ctx, evt);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onClosedBeforeReceiveComplete(Channel channel) {
|
|
||||||
if (channel.isActive()) {
|
|
||||||
/*
|
|
||||||
* If the close is triggerred by the user, the channel will be active.
|
|
||||||
* If the response, isn't complete, then the connection can not be used.
|
|
||||||
*/
|
|
||||||
channel.attr(ClientConnectionToChannelBridge.DISCARD_CONNECTION).set(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean isInboundHeader(Object nextItem) {
|
|
||||||
return nextItem instanceof HttpResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected boolean isOutboundHeader(Object nextItem) {
|
|
||||||
return nextItem instanceof HttpRequest;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Object newHttpObject(Object nextItem, Channel channel) {
|
|
||||||
final HttpResponse nettyResponse = (HttpResponse) nextItem;
|
|
||||||
|
|
||||||
if (eventPublisher.publishingEnabled()) {
|
|
||||||
long duration = -1;
|
|
||||||
if (requestWriteCompletionTimeNanos != -1) {
|
|
||||||
duration = Clock.onEndNanos(requestWriteCompletionTimeNanos);
|
|
||||||
}
|
|
||||||
eventsListener.onResponseHeadersReceived(nettyResponse.status().code(), duration, NANOSECONDS);
|
|
||||||
}
|
|
||||||
|
|
||||||
final HttpClientResponseImpl<C> rxResponse = HttpClientResponseImpl.unsafeCreate(nettyResponse);
|
|
||||||
Long keepAliveTimeoutSeconds = rxResponse.getKeepAliveTimeoutSeconds();
|
|
||||||
if (null != keepAliveTimeoutSeconds) {
|
|
||||||
channel.attr(KEEP_ALIVE_TIMEOUT_MILLIS_ATTR).set(keepAliveTimeoutSeconds * 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!rxResponse.isKeepAlive()) {
|
|
||||||
channel.attr(ClientConnectionToChannelBridge.DISCARD_CONNECTION).set(true); /*Discard connection when done with this response.*/
|
|
||||||
}
|
|
||||||
|
|
||||||
return rxResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onContentReceived() {
|
|
||||||
if (eventPublisher.publishingEnabled()) {
|
|
||||||
eventsListener.onResponseContentReceived();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onContentReceiveComplete(long receiveStartTimeNanos) {
|
|
||||||
connectionInputSubscriber.onCompleted(); /*Unsubscribe from the input and hence close/release connection*/
|
|
||||||
if (eventPublisher.publishingEnabled()) {
|
|
||||||
long headerWriteStart = getHeaderWriteStartTimeNanos();
|
|
||||||
eventsListener.onResponseReceiveComplete(Clock.onEndNanos(receiveStartTimeNanos), NANOSECONDS);
|
|
||||||
eventsListener.onRequestProcessingComplete(Clock.onEndNanos(headerWriteStart), NANOSECONDS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onPooledConnectionRelease(ConnectionInputSubscriber connectionInputSubscriber) {
|
|
||||||
onChannelClose(connectionInputSubscriber);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,244 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.http.client.internal;
|
|
||||||
|
|
||||||
import io.netty.handler.codec.http.DefaultHttpRequest;
|
|
||||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
|
||||||
import io.netty.handler.codec.http.HttpMethod;
|
|
||||||
import io.netty.handler.codec.http.HttpRequest;
|
|
||||||
import io.netty.handler.codec.http.HttpUtil;
|
|
||||||
import io.netty.handler.codec.http.HttpVersion;
|
|
||||||
import io.netty.handler.codec.http.LastHttpContent;
|
|
||||||
import io.netty.handler.codec.http.cookie.ClientCookieEncoder;
|
|
||||||
import io.netty.handler.codec.http.cookie.Cookie;
|
|
||||||
import io.reactivex.netty.channel.Connection;
|
|
||||||
import io.reactivex.netty.channel.FlushSelectorOperator;
|
|
||||||
import rx.Observable;
|
|
||||||
import rx.functions.Func1;
|
|
||||||
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Map.Entry;
|
|
||||||
|
|
||||||
public final class RawRequest<I, O> {
|
|
||||||
|
|
||||||
private final Redirector<I, O> redirector;
|
|
||||||
private final HttpRequest headers;
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
private final Observable content;
|
|
||||||
private final Func1<?, Boolean> flushSelector;
|
|
||||||
private final boolean hasTrailers;
|
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
private RawRequest(HttpRequest headers, Observable content, Func1<?, Boolean> flushSelector, boolean hasTrailers,
|
|
||||||
Redirector<I, O> redirector) {
|
|
||||||
this.headers = headers;
|
|
||||||
this.content = content;
|
|
||||||
this.flushSelector = flushSelector;
|
|
||||||
this.hasTrailers = hasTrailers;
|
|
||||||
this.redirector = redirector;
|
|
||||||
}
|
|
||||||
|
|
||||||
public RawRequest<I, O> addHeader(CharSequence name, Object value) {
|
|
||||||
HttpRequest headersCopy = _copyHeaders();
|
|
||||||
headersCopy.headers().add(name, value);
|
|
||||||
return new RawRequest<>(headersCopy, content, flushSelector, hasTrailers, redirector);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RawRequest<I, O> addHeaders(Map<? extends CharSequence, ? extends Iterable<Object>> headers) {
|
|
||||||
HttpRequest headersCopy = _copyHeaders();
|
|
||||||
for (Entry<? extends CharSequence, ? extends Iterable<Object>> header : headers.entrySet()) {
|
|
||||||
headersCopy.headers().add(header.getKey(), header.getValue());
|
|
||||||
}
|
|
||||||
return new RawRequest<>(headersCopy, content, flushSelector, hasTrailers, redirector);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RawRequest<I, O> addHeaderValues(CharSequence name, Iterable<Object> values) {
|
|
||||||
HttpRequest headersCopy = _copyHeaders();
|
|
||||||
headersCopy.headers().add(name, values);
|
|
||||||
return new RawRequest<>(headersCopy, content, flushSelector, hasTrailers, redirector);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RawRequest<I, O> addCookie(Cookie cookie) {
|
|
||||||
String cookieHeader = ClientCookieEncoder.STRICT.encode(cookie);
|
|
||||||
return addHeader(HttpHeaderNames.COOKIE, cookieHeader);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RawRequest<I, O> addDateHeader(CharSequence name, Date value) {
|
|
||||||
HttpRequest headersCopy = _copyHeaders();
|
|
||||||
headersCopy.headers().add(name, value);
|
|
||||||
return new RawRequest<>(headersCopy, content, flushSelector, hasTrailers, redirector);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RawRequest<I, O> addDateHeader(CharSequence name, Iterable<Date> values) {
|
|
||||||
HttpRequest headersCopy = _copyHeaders();
|
|
||||||
for (Date value : values) {
|
|
||||||
headersCopy.headers().add(name, value);
|
|
||||||
}
|
|
||||||
return new RawRequest<>(headersCopy, content, flushSelector, hasTrailers, redirector);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RawRequest<I, O> setDateHeader(CharSequence name, Date value) {
|
|
||||||
HttpRequest headersCopy = _copyHeaders();
|
|
||||||
headersCopy.headers().set(name, value);
|
|
||||||
return new RawRequest<>(headersCopy, content, flushSelector, hasTrailers, redirector);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RawRequest<I, O> setHeader(CharSequence name, Object value) {
|
|
||||||
HttpRequest headersCopy = _copyHeaders();
|
|
||||||
headersCopy.headers().set(name, value);
|
|
||||||
return new RawRequest<>(headersCopy, content, flushSelector, hasTrailers, redirector);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RawRequest<I, O> setHeaders(Map<? extends CharSequence, ? extends Iterable<Object>> headers) {
|
|
||||||
HttpRequest headersCopy = _copyHeaders();
|
|
||||||
for (Entry<? extends CharSequence, ? extends Iterable<Object>> header : headers.entrySet()) {
|
|
||||||
headersCopy.headers().set(header.getKey(), header.getValue());
|
|
||||||
}
|
|
||||||
return new RawRequest<>(headersCopy, content, flushSelector, hasTrailers, redirector);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RawRequest<I, O> setHeaderValues(CharSequence name, Iterable<Object> values) {
|
|
||||||
HttpRequest headersCopy = _copyHeaders();
|
|
||||||
headersCopy.headers().set(name, values);
|
|
||||||
return new RawRequest<>(headersCopy, content, flushSelector, hasTrailers, redirector);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RawRequest<I, O> setDateHeader(CharSequence name, Iterable<Date> values) {
|
|
||||||
HttpRequest headersCopy = _copyHeaders();
|
|
||||||
boolean addNow = false;
|
|
||||||
for (Date value : values) {
|
|
||||||
if (addNow) {
|
|
||||||
headersCopy.headers().add(name, value);
|
|
||||||
} else {
|
|
||||||
headersCopy.headers().set(name, value);
|
|
||||||
addNow = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new RawRequest<>(headersCopy, content, flushSelector, hasTrailers, redirector);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RawRequest<I, O> setKeepAlive(boolean keepAlive) {
|
|
||||||
HttpRequest headersCopy = _copyHeaders();
|
|
||||||
HttpUtil.setKeepAlive(headersCopy, keepAlive);
|
|
||||||
return new RawRequest<>(headersCopy, content, flushSelector, hasTrailers, redirector);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RawRequest<I, O> setTransferEncodingChunked() {
|
|
||||||
HttpRequest headersCopy = _copyHeaders();
|
|
||||||
HttpUtil.setTransferEncodingChunked(headersCopy, true);
|
|
||||||
return new RawRequest<>(headersCopy, content, flushSelector, hasTrailers, redirector);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RawRequest<I, O> removeHeader(CharSequence name) {
|
|
||||||
HttpRequest headersCopy = _copyHeaders();
|
|
||||||
headersCopy.headers().remove(name);
|
|
||||||
return new RawRequest<>(headersCopy, content, flushSelector, hasTrailers, redirector);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RawRequest<I, O> setMethod(HttpMethod method) {
|
|
||||||
HttpRequest headersCopy = _copyHeaders(headers.uri(), method);
|
|
||||||
return new RawRequest<>(headersCopy, content, flushSelector, hasTrailers, redirector);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RawRequest<I, O> setUri(String uri) {
|
|
||||||
HttpRequest headersCopy = _copyHeaders(uri, headers.method());
|
|
||||||
return new RawRequest<>(headersCopy, content, flushSelector, hasTrailers, redirector);
|
|
||||||
}
|
|
||||||
|
|
||||||
public RawRequest<I, O> followRedirect(Redirector<I, O> redirectHandler) {
|
|
||||||
return new RawRequest<>(headers, content, flushSelector, hasTrailers, redirectHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
|
||||||
public Observable asObservable(Connection<?, ?> connection) {
|
|
||||||
HttpRequest headers = this.headers;
|
|
||||||
if (null == content) {
|
|
||||||
headers = _copyHeaders();
|
|
||||||
headers.headers().set(HttpHeaderNames.CONTENT_LENGTH, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
Observable toReturn = Observable.just(headers);
|
|
||||||
|
|
||||||
if (null != content) {
|
|
||||||
if (null == flushSelector) {
|
|
||||||
toReturn = toReturn.concatWith(content);
|
|
||||||
} else {
|
|
||||||
toReturn = toReturn.concatWith(content.lift(new FlushSelectorOperator(flushSelector, connection)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasTrailers) {
|
|
||||||
toReturn = toReturn.concatWith(Observable.just(LastHttpContent.EMPTY_LAST_CONTENT));
|
|
||||||
}
|
|
||||||
|
|
||||||
return toReturn;
|
|
||||||
}
|
|
||||||
|
|
||||||
private HttpRequest _copyHeaders() {
|
|
||||||
return _copyHeaders(headers.uri(), headers.method());
|
|
||||||
}
|
|
||||||
|
|
||||||
private HttpRequest _copyHeaders(String uri, HttpMethod method) {
|
|
||||||
final HttpRequest newHeaders = new DefaultHttpRequest(headers.protocolVersion(), method, uri);
|
|
||||||
// TODO: May be we can optimize this by not copying
|
|
||||||
for (Entry<String, String> header : headers.headers()) {
|
|
||||||
newHeaders.headers().set(header.getKey(), header.getValue());
|
|
||||||
}
|
|
||||||
return newHeaders;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <I, O> RawRequest<I, O> create(HttpVersion version, HttpMethod httpMethod, String uri,
|
|
||||||
Redirector<I, O> redirectHandler) {
|
|
||||||
final HttpRequest headers = new DefaultHttpRequest(version, httpMethod, uri);
|
|
||||||
return create(headers, null, null, false, redirectHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
public static <I, O> RawRequest<I, O> create(HttpRequest headers, Observable content, boolean hasTrailers,
|
|
||||||
Redirector<I, O> redirectHandler) {
|
|
||||||
return create(headers, content, null, hasTrailers, redirectHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
public static <I, O> RawRequest<I, O> create(HttpRequest headers, Observable content,
|
|
||||||
Func1<?, Boolean> flushSelector, boolean hasTrailers,
|
|
||||||
Redirector<I, O> redirectHandler) {
|
|
||||||
return new RawRequest<>(headers, content, flushSelector, hasTrailers, redirectHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
public HttpRequest getHeaders() {
|
|
||||||
return headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("rawtypes")
|
|
||||||
public Observable getContent() {
|
|
||||||
return content;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Func1<?, Boolean> getFlushSelector() {
|
|
||||||
return flushSelector;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasTrailers() {
|
|
||||||
return hasTrailers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Redirector<I, O> getRedirector() {
|
|
||||||
return redirector;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,201 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.http.client.internal;
|
|
||||||
|
|
||||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
|
||||||
import io.netty.handler.codec.http.HttpMethod;
|
|
||||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
|
||||||
import io.reactivex.netty.internal.VoidToAnythingCast;
|
|
||||||
import io.reactivex.netty.protocol.http.client.HttpClientRequest;
|
|
||||||
import io.reactivex.netty.protocol.http.client.HttpClientResponse;
|
|
||||||
import io.reactivex.netty.protocol.http.client.HttpRedirectException;
|
|
||||||
import io.reactivex.netty.protocol.tcp.client.TcpClient;
|
|
||||||
import rx.Observable;
|
|
||||||
import rx.functions.Func1;
|
|
||||||
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import java.util.logging.Level;
|
|
||||||
import java.util.logging.Logger;
|
|
||||||
|
|
||||||
import static io.reactivex.netty.protocol.http.client.HttpRedirectException.Reason.*;
|
|
||||||
|
|
||||||
public class Redirector<I, O> implements Func1<HttpClientResponse<O>, Observable<HttpClientResponse<O>>> {
|
|
||||||
|
|
||||||
public static final int DEFAULT_MAX_REDIRECTS = 5;
|
|
||||||
|
|
||||||
private static final Logger logger = Logger.getLogger(Redirector.class.getName());
|
|
||||||
|
|
||||||
private static final int[] REDIRECTABLE_STATUS_CODES = {301, 302, 303, 307, 308};
|
|
||||||
|
|
||||||
static {
|
|
||||||
Arrays.sort(REDIRECTABLE_STATUS_CODES); // Required as we do binary search. This is a safety net in case the
|
|
||||||
// array is modified (code change) & is not sorted.
|
|
||||||
}
|
|
||||||
|
|
||||||
private final List<String> visitedLocations; // Is never updated concurrently as redirects are sequential.
|
|
||||||
private final int maxHops;
|
|
||||||
private final AtomicInteger redirectCount; // Can be shared across multiple event loops, so needs to be thread-safe.
|
|
||||||
private volatile HttpResponseStatus lastRedirectStatus;
|
|
||||||
private final TcpClient<?, HttpClientResponse<O>> client;
|
|
||||||
|
|
||||||
private RawRequest<I, O> originalRequest;
|
|
||||||
|
|
||||||
public Redirector(int maxHops, TcpClient<?, HttpClientResponse<O>> client) {
|
|
||||||
this.maxHops = maxHops;
|
|
||||||
this.client = client;
|
|
||||||
visitedLocations = new ArrayList<>();
|
|
||||||
redirectCount = new AtomicInteger();
|
|
||||||
}
|
|
||||||
|
|
||||||
public Redirector(TcpClient<?, HttpClientResponse<O>> client) {
|
|
||||||
this(DEFAULT_MAX_REDIRECTS, client);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setOriginalRequest(RawRequest<I, O> originalRequest) {
|
|
||||||
if (null != this.originalRequest) {
|
|
||||||
throw new IllegalStateException("Original request is already set.");
|
|
||||||
}
|
|
||||||
this.originalRequest = originalRequest;
|
|
||||||
visitedLocations.add(originalRequest.getHeaders().uri());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<HttpClientResponse<O>> call(HttpClientResponse<O> response) {
|
|
||||||
|
|
||||||
Observable<HttpClientResponse<O>> toReturn;
|
|
||||||
|
|
||||||
if (null == originalRequest) {
|
|
||||||
toReturn = Observable.error(new IllegalStateException("Raw request not available to the redirector."));
|
|
||||||
} else if (requiresRedirect(response)) {
|
|
||||||
String location = extractRedirectLocation(response);
|
|
||||||
|
|
||||||
if (location == null) {
|
|
||||||
toReturn = Observable.error(new HttpRedirectException(InvalidRedirect, "No redirect location found."));
|
|
||||||
} else if (visitedLocations.contains(location)) {
|
|
||||||
// this forms a loop
|
|
||||||
toReturn = Observable.error(new HttpRedirectException(RedirectLoop,
|
|
||||||
"Redirection contains a loop. Last requested location: "
|
|
||||||
+ location));
|
|
||||||
} else if (redirectCount.get() >= maxHops) {
|
|
||||||
toReturn = Observable.error(new HttpRedirectException(TooManyRedirects,
|
|
||||||
"Too many redirects. Max redirects: " + maxHops));
|
|
||||||
} else {
|
|
||||||
URI redirectUri;
|
|
||||||
|
|
||||||
try {
|
|
||||||
redirectUri = new URI(location);
|
|
||||||
|
|
||||||
lastRedirectStatus = response.getStatus();
|
|
||||||
|
|
||||||
redirectCount.incrementAndGet();
|
|
||||||
|
|
||||||
toReturn = createRedirectRequest(originalRequest, redirectUri, lastRedirectStatus.code());
|
|
||||||
} catch (Exception e) {
|
|
||||||
toReturn = Observable.error(new HttpRedirectException(InvalidRedirect,
|
|
||||||
"Location is not a valid URI. Provided location: "
|
|
||||||
+ location, e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
return Observable.just(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
return response.discardContent()
|
|
||||||
.map(new VoidToAnythingCast<HttpClientResponse<O>>())
|
|
||||||
.ignoreElements()
|
|
||||||
.concatWith(toReturn);
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean requiresRedirect(HttpClientResponse<O> response) {
|
|
||||||
int statusCode = response.getStatus().code();
|
|
||||||
boolean requiresRedirect = false;
|
|
||||||
// This class only supports relative redirects as an HttpClient is always tied to a host:port combo and hence
|
|
||||||
// can not do an absolute redirect.
|
|
||||||
if (Arrays.binarySearch(REDIRECTABLE_STATUS_CODES, statusCode) >= 0) {
|
|
||||||
String location = extractRedirectLocation(response);
|
|
||||||
// Only process relative URIs: Issue https://github.com/ReactiveX/RxNetty/issues/270
|
|
||||||
requiresRedirect = null == location || !location.startsWith("http");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (requiresRedirect && statusCode != HttpResponseStatus.SEE_OTHER.code()) {
|
|
||||||
HttpMethod originalMethod = originalRequest.getHeaders().method();
|
|
||||||
// If the Method is not HEAD/GET do not auto redirect
|
|
||||||
requiresRedirect = originalMethod == HttpMethod.GET || originalMethod == HttpMethod.HEAD;
|
|
||||||
}
|
|
||||||
|
|
||||||
return requiresRedirect;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String extractRedirectLocation(HttpClientResponse<O> redirectedResponse) {
|
|
||||||
return redirectedResponse.getHeader(HttpHeaderNames.LOCATION);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected HttpClientRequest<I, O> createRedirectRequest(RawRequest<I, O> original, URI redirectLocation,
|
|
||||||
int redirectStatus) {
|
|
||||||
|
|
||||||
String redirectUri = getNettyRequestUri(redirectLocation, original.getHeaders().uri(), redirectStatus);
|
|
||||||
|
|
||||||
RawRequest<I, O> redirectRequest = original.setUri(redirectUri);
|
|
||||||
|
|
||||||
if (redirectStatus == 303) {
|
|
||||||
// according to HTTP spec, 303 mandates the change of request type to GET
|
|
||||||
// If it is a get, then the content is not to be sent.
|
|
||||||
redirectRequest = RawRequest.create(redirectRequest.getHeaders().protocolVersion(), HttpMethod.GET,
|
|
||||||
redirectUri, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
return HttpClientRequestImpl.create(redirectRequest, client);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static String getNettyRequestUri(URI uri, String originalUriString, int redirectStatus) {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
if (uri.getRawPath() != null) {
|
|
||||||
sb.append(uri.getRawPath());
|
|
||||||
}
|
|
||||||
if (uri.getRawQuery() != null) {
|
|
||||||
sb.append('?').append(uri.getRawQuery());
|
|
||||||
}
|
|
||||||
if (uri.getRawFragment() != null) {
|
|
||||||
sb.append('#').append(uri.getRawFragment());
|
|
||||||
} else if(redirectStatus >= 300) {
|
|
||||||
// http://tools.ietf.org/html/rfc7231#section-7.1.2 suggests that the URI fragment should be carried over to
|
|
||||||
// the redirect location if not exists in the redirect location.
|
|
||||||
// Issue: https://github.com/ReactiveX/RxNetty/issues/271
|
|
||||||
try {
|
|
||||||
URI originalUri = new URI(originalUriString);
|
|
||||||
if (originalUri.getRawFragment() != null) {
|
|
||||||
sb.append('#').append(originalUri.getRawFragment());
|
|
||||||
}
|
|
||||||
} catch (URISyntaxException e) {
|
|
||||||
logger.log(Level.WARNING, "Error parsing original request URI during redirect. " +
|
|
||||||
"This means that the path fragment if any in the original request will not be inherited " +
|
|
||||||
"by the redirect.", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,194 +0,0 @@
|
||||||
/*
|
|
||||||
* 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.http.client.internal;
|
|
||||||
|
|
||||||
import io.netty.channel.Channel;
|
|
||||||
import io.netty.channel.ChannelHandler;
|
|
||||||
import io.netty.channel.ChannelPipeline;
|
|
||||||
import io.netty.channel.FileRegion;
|
|
||||||
import io.netty.channel.embedded.EmbeddedChannel;
|
|
||||||
import io.netty.util.concurrent.EventExecutorGroup;
|
|
||||||
import io.reactivex.netty.channel.AllocatingTransformer;
|
|
||||||
import io.reactivex.netty.channel.Connection;
|
|
||||||
import io.reactivex.netty.channel.events.ConnectionEventListener;
|
|
||||||
import io.reactivex.netty.events.EventPublisher;
|
|
||||||
import rx.Observable;
|
|
||||||
import rx.Observable.Transformer;
|
|
||||||
import rx.functions.Action1;
|
|
||||||
import rx.functions.Func1;
|
|
||||||
|
|
||||||
final class UnusableConnection<R, W> extends Connection<R, W> {
|
|
||||||
|
|
||||||
protected UnusableConnection(Channel nettyChannel,
|
|
||||||
ConnectionEventListener eventListener,
|
|
||||||
EventPublisher eventPublisher) {
|
|
||||||
super(nettyChannel);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> write(Observable<W> msgs) {
|
|
||||||
return Observable.error(new IllegalStateException("Connection is not usable."));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> write(Observable<W> msgs, Func1<W, Boolean> flushSelector) {
|
|
||||||
return Observable.error(new IllegalStateException("Connection is not usable."));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> writeAndFlushOnEach(Observable<W> msgs) {
|
|
||||||
return Observable.error(new IllegalStateException("Connection is not usable."));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> writeString(Observable<String> msgs) {
|
|
||||||
return Observable.error(new IllegalStateException("Connection is not usable."));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> writeString(Observable<String> msgs, Func1<String, Boolean> flushSelector) {
|
|
||||||
return Observable.error(new IllegalStateException("Connection is not usable."));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> writeStringAndFlushOnEach(Observable<String> msgs) {
|
|
||||||
return Observable.error(new IllegalStateException("Connection is not usable."));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> writeBytes(Observable<byte[]> msgs) {
|
|
||||||
return Observable.error(new IllegalStateException("Connection is not usable."));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> writeBytes(Observable<byte[]> msgs, Func1<byte[], Boolean> flushSelector) {
|
|
||||||
return Observable.error(new IllegalStateException("Connection is not usable."));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> writeBytesAndFlushOnEach(Observable<byte[]> msgs) {
|
|
||||||
return Observable.error(new IllegalStateException("Connection is not usable."));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> writeFileRegion(Observable<FileRegion> msgs) {
|
|
||||||
return Observable.error(new IllegalStateException("Connection is not usable."));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> writeFileRegion(Observable<FileRegion> msgs, Func1<FileRegion, Boolean> flushSelector) {
|
|
||||||
return Observable.error(new IllegalStateException("Connection is not usable."));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> writeFileRegionAndFlushOnEach(Observable<FileRegion> msgs) {
|
|
||||||
return Observable.error(new IllegalStateException("Connection is not usable."));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void flush() {
|
|
||||||
throw new IllegalStateException("Connection is not usable.");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> close() {
|
|
||||||
return Observable.error(new IllegalStateException("Connection is not usable."));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> close(boolean flush) {
|
|
||||||
return Observable.error(new IllegalStateException("Connection is not usable."));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void closeNow() {
|
|
||||||
throw new IllegalStateException("Connection is not usable.");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Observable<Void> closeListener() {
|
|
||||||
throw new IllegalStateException("Connection is not usable.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Connection<?, ?> create() {
|
|
||||||
return new UnusableConnection<>(new EmbeddedChannel(), null, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <RR, WW> Connection<RR, WW> addChannelHandlerFirst(String name, ChannelHandler handler) {
|
|
||||||
return cast();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <RR, WW> Connection<RR, WW> addChannelHandlerFirst(EventExecutorGroup group, String name,
|
|
||||||
ChannelHandler handler) {
|
|
||||||
return cast();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <RR, WW> Connection<RR, WW> addChannelHandlerLast(String name, ChannelHandler handler) {
|
|
||||||
return cast();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <RR, WW> Connection<RR, WW> addChannelHandlerLast(EventExecutorGroup group, String name,
|
|
||||||
ChannelHandler handler) {
|
|
||||||
return cast();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <RR, WW> Connection<RR, WW> addChannelHandlerBefore(String baseName, String name, ChannelHandler handler) {
|
|
||||||
return cast();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <RR, WW> Connection<RR, WW> addChannelHandlerBefore(EventExecutorGroup group, String baseName, String name,
|
|
||||||
ChannelHandler handler) {
|
|
||||||
return cast();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <RR, WW> Connection<RR, WW> addChannelHandlerAfter(String baseName, String name, ChannelHandler handler) {
|
|
||||||
return cast();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <RR, WW> Connection<RR, WW> addChannelHandlerAfter(EventExecutorGroup group, String baseName, String name,
|
|
||||||
ChannelHandler handler) {
|
|
||||||
return cast();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <RR, WW> Connection<RR, WW> pipelineConfigurator(Action1<ChannelPipeline> pipelineConfigurator) {
|
|
||||||
return cast();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <RR> Connection<RR, W> transformRead(Transformer<R, RR> transformer) {
|
|
||||||
throw new IllegalStateException("Connection is not usable.");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public <WW> Connection<R, WW> transformWrite(AllocatingTransformer<WW, W> transformer) {
|
|
||||||
throw new IllegalStateException("Connection is not usable.");
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private <RR, WW> Connection<RR, WW> cast() {
|
|
||||||
return (Connection<RR, WW>) this;
|
|
||||||
}
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue