update to 4.1.69, remove rx/reactive subprojects

This commit is contained in:
Jörg Prante 2021-10-13 18:20:36 +02:00
parent 7f69090bcb
commit b2135d4c80
282 changed files with 3 additions and 44789 deletions

View file

@ -1,14 +1,12 @@
group = org.xbib
name = netty-http
version = 4.1.68.0
version = 4.1.69.0
gradle.wrapper.version = 6.6.1
netty.version = 4.1.68.Final
tcnative.version = 2.0.43.Final
netty.version = 4.1.69.Final
tcnative.version = 2.0.44.Final
bouncycastle.version = 1.69
reactivestreams.version = 1.0.3
reactivex.version = 1.3.8
conscrypt.version = 2.5.2
javassist.version = 3.28.0-GA
jackson.version = 2.11.4
@ -16,5 +14,3 @@ mockito.version = 3.10.0
xbib.net.version = 2.1.1
xbib-guice.version = 4.4.2
junit.version = 5.7.1
# uuhh, too many tests to update to jupiter in rx...
junit4.version = 4.13.1

View file

@ -1,5 +0,0 @@
This work is based on
https://github.com/ReactiveX/RxNetty
(branch 0.5.x as of 22-Sep-2019)

View file

@ -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')}"
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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 + '}';
}
}
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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);
}
}
};
}
}

View file

@ -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*/
}
}
}

View file

@ -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());
}
}
}

View file

@ -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();
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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.
}
}));
}
}

View file

@ -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;
}
}

View file

@ -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 {
}

View file

@ -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;
}
}

View file

@ -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));
}
}

View file

@ -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());
}
}
}
}
}

View file

@ -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 + '}';
}
}
}

View file

@ -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();
}
}
}
}

View file

@ -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() {
}
}

View file

@ -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();
}
}
};
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}));
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}

View file

@ -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() { }
}

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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 =&gt; ConnectionInputSubscriberEvent =&gt; ConnectionReuseEvent =&gt;
ConnectionInputSubscriberEvent =&gt; ConnectionReuseEvent =&gt; 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() {
}
}
}

View file

@ -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());
}
}
}
}

View file

@ -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();
}

View file

@ -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);
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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) {}
}

View file

@ -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."));
}
}

View file

@ -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
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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);
}

View file

@ -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);
}
};
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}
}

View file

@ -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);
}
}

View file

@ -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);
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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();
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}
}
}
}

View file

@ -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<>();
}
}
}

View file

@ -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);
}
}

View file

@ -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.
}
}

View file

@ -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;
}
}

View file

@ -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");
}

View file

@ -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);
}

View file

@ -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();
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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();
}
}
}

View file

@ -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 {
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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);
}
}

View file

@ -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);
}

View file

@ -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);
}
}

View file

@ -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();
}

View file

@ -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);
}
}

View file

@ -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();
}

View file

@ -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());
}
}

View file

@ -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;
}
}

View file

@ -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();
}

View file

@ -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);
}
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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) {}
}

View file

@ -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;
}
}

View file

@ -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;
}
});
}
}

View file

@ -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);
}
}

View file

@ -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);
}
};
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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();
}
}

View file

@ -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