
info.bitrich.xchangestream.binance.BinanceUserDataChannel Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of xchange-stream-binance Show documentation
Show all versions of xchange-stream-binance Show documentation
Development fork. Not for general use.
The newest version!
package info.bitrich.xchangestream.binance;
import io.reactivex.Observable;
import io.reactivex.disposables.Disposable;
import org.knowm.xchange.binance.BinanceAuthenticated;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import static java.util.concurrent.TimeUnit.SECONDS;
/**
* Binance user data streams must be established by first requesting a unique
* "listen key" via authenticated REST API, which is then used to create an
* obscured WS URI (rather than authenticating the web socket). This class
* handles the initial request for a listen key, but also the 30-minute
* keepalive REST calls necessary to keep the socket open. It also allows for
* the possibility that extended downtime might cause the listen key to expire
* without being able to renew it. In this case, a new listen key is requested
* and a caller can be alerted via asynchronous callback to re-establish the
* socket with the new listen key.
*
* @author Graham Crockford
*/
class BinanceUserDataChannel implements AutoCloseable {
private static final Logger LOG = LoggerFactory.getLogger(BinanceUserDataChannel.class);
private final BinanceAuthenticated binance;
private final String apiKey;
private final Disposable keepAlive;
private String listenKey;
private Consumer onChangeListenKey;
/**
* Creates the channel, establishing a listen key
* (immediately available from {@link #getListenKey()}) and
* starting timers to ensure the channel is kept alive.
*
* @param binance
* @param apiKey
*/
BinanceUserDataChannel(BinanceAuthenticated binance, String apiKey) {
this.binance = binance;
this.apiKey = apiKey;
openChannel();
// Send a keepalive every 30 minutes as recommended by Binance
this.keepAlive = Observable.interval(30, TimeUnit.MINUTES)
.subscribe(x -> keepAlive());
}
/**
* Set this callback to get notified if the current listen key is revoked.
*
* @param onChangeListenKey The callback.
*/
void onChangeListenKey(Consumer onChangeListenKey) {
this.onChangeListenKey = onChangeListenKey;
}
private void keepAlive() {
if (listenKey == null)
return;
try {
LOG.debug("Keeping user data channel alive");
binance.keepAliveUserDataStream(apiKey, listenKey);
LOG.debug("User data channel keepalive sent successfully");
} catch (Exception e) {
LOG.error("User data channel keepalive failed.", e);
this.listenKey = null;
reconnect();
}
}
private void reconnect() {
try {
openChannel();
if (onChangeListenKey != null) {
onChangeListenKey.accept(this.listenKey);
}
} catch (Exception e) {
LOG.error("Failed to reconnect. Will retry in 15 seconds.", e);
Observable.timer(15, SECONDS).subscribe(x -> reconnect());
}
}
private void openChannel() {
try {
LOG.debug("Opening new user data channel");
this.listenKey = binance.startUserDataStream(apiKey).getListenKey();
LOG.debug("Opened new user data channel");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* @return The current listen key.
* @throws NoActiveChannelException If no listen key is currently available.
*/
String getListenKey() throws NoActiveChannelException {
if (listenKey == null)
throw new NoActiveChannelException();
return listenKey;
}
@Override
public void close() {
keepAlive.dispose();
}
/**
* Thrown on calls to {@link BinanceUserDataChannel#getListenKey()} if no
* channel is currently open.
*
* @author Graham Crockford
*/
static final class NoActiveChannelException extends Exception {
private static final long serialVersionUID = -8161003286845820286L;
NoActiveChannelException() {
super();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy