All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.reactivex.netty.protocol.http.ws.client.OperatorCacheSingleWebsocketConnection Maven / Gradle / Ivy

There is a newer version: 0.5.3-rc.2
Show newest version
/*
 * 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.ws.client;

import io.reactivex.netty.protocol.http.ws.WebSocketConnection;
import rx.Observable;
import rx.Observable.Operator;
import rx.Subscriber;
import rx.annotations.Experimental;
import rx.functions.Action0;
import rx.functions.Actions;
import rx.functions.Func1;

/**
 * An operator to cache a {@link WebSocketConnection} until it closes, upon which the source that re-creates an HTTP
 * upgrade request to get a fresh {@link WebSocketConnection} is subscribed, to refresh the stale connection in the
 * cache.
 *
 * A typical usage example for this operator is:
 *
 
 {@code
     HttpClient.newClient(socketAddress)
               .createGet("/ws")
               .requestWebSocketUpgrade()
               .map(WebSocketResponse::getWebSocketConnection)
               .nest()
               .lift(new OperatorCacheSingleWebsocketConnection())
 }
 
* * Since multiple subscriptions to {@link WebSocketResponse#getWebSocketConnection()} do not re-run the original HTTP * upgrade request, this operator expects the source {@code Observable} to be passed to it, so that on close of the * cached {@link WebSocketConnection}, it can re-subscribe to the original HTTP request and create a fresh connection. * This is the reason the above code uses {@link Observable#nest()} to get a reference to the source {@code Observable}. * *

Cache liveness guarantees

* * Although, this operator will make sure that when the cached connection has terminated, the next refresh will * re-subscribe to the source, there is no guarantee that a dead connection is never emitted from this operator as it * completely depends on the timing of when the connection terminates and when a new subscription arrives. The two * events can be concurrent and hence unpredictable. */ @Experimental public class OperatorCacheSingleWebsocketConnection implements Operator>> { private boolean subscribedToSource; /*Guarded by this*/ private Observable cachedSource; /*Guarded by this*/ @Override public Subscriber>> call(final Subscriber subscriber) { return new Subscriber>>(subscriber) { private volatile boolean anItemEmitted; @Override public void onCompleted() { if (!anItemEmitted) { subscriber.onError(new IllegalStateException("No Observable emitted from source.")); } } @Override public void onError(Throwable e) { subscriber.onError(e); } @Override public void onNext(Observable> source) { anItemEmitted = true; /** * The idea below is for using a single cache {@code Observable} so that the cache operator can cache * the generated connection. However, when the cached connection is terminated, a new cached source * must be generated to be used for subsequent subscriptions. * As the only way to re-run the original HTTP upgrade request, to obtain a fresh connection, is to * subscribe to the {@code Observable>}, that is the reason the below * code uses a {@code flatmap} to transform {@code Observable>} to an * {@code Observable} and still keeping the ability to re-subscribe to the original * {@code Observable>}. */ final Observable _cachedSource; final Observable o = source.flatMap( new Func1, Observable>() { @Override public Observable call(Observable connSource) { /*This is for flatmap to subscribe to the nested {@code Observable}*/ return connSource; } }).map(new Func1() { @Override public WebSocketConnection call(WebSocketConnection connection) { Observable lifecycle = connection.closeListener(); lifecycle = lifecycle.onErrorResumeNext(Observable.empty()) .finallyDo(new Action0() { @Override public void call() { synchronized (OperatorCacheSingleWebsocketConnection.this) { // refresh the source on next subscribe subscribedToSource = false; } } }); subscriber.add(lifecycle.subscribe(Actions.empty())); return connection; } }).cache(); synchronized (OperatorCacheSingleWebsocketConnection.this) { if (!subscribedToSource) { subscribedToSource = true; /*From here on, all subscriptions will use the newly created cached source which on first subscription will re-run the original HTTP upgrade request and get a fresh WS connection*/ cachedSource = o; } _cachedSource = cachedSource; } _cachedSource.unsafeSubscribe(subscriber); } }; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy