
info.bitrich.xchangestream.bitmex.BitmexStreamingService Maven / Gradle / Ivy
package info.bitrich.xchangestream.bitmex;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import info.bitrich.xchangestream.bitmex.dto.BitmexWebSocketSubscriptionMessage;
import info.bitrich.xchangestream.bitmex.dto.BitmexWebSocketTransaction;
import info.bitrich.xchangestream.service.netty.JsonNettyStreamingService;
import info.bitrich.xchangestream.service.netty.WebSocketClientHandler;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.HttpClientCodec;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory;
import io.netty.handler.codec.http.websocketx.WebSocketVersion;
import io.netty.handler.codec.http.websocketx.extensions.WebSocketClientExtensionHandler;
import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketClientCompressionHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.reactivex.Completable;
import io.reactivex.Observable;
/**
* Created by Lukas Zaoralek on 13.11.17.
*/
public class BitmexStreamingService extends JsonNettyStreamingService {
private static final Logger LOG = LoggerFactory.getLogger(BitmexStreamingService.class);
private final ObjectMapper mapper = new ObjectMapper();
public BitmexStreamingService(String apiUrl) {
super(apiUrl, Integer.MAX_VALUE);
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
@Override
public Completable connect() {
return Completable.create(completable -> {
try {
LOG.info("Connecting to {}://{}:{}{}", uri.getScheme(), uri.getHost(), uri.getPort(), uri.getPath());
String scheme = uri.getScheme() == null ? "ws" : uri.getScheme();
String host = uri.getHost();
if (host == null) {
throw new IllegalArgumentException("Host cannot be null.");
}
final int port;
if (uri.getPort() == -1) {
if ("ws".equalsIgnoreCase(scheme)) {
port = 80;
} else if ("wss".equalsIgnoreCase(scheme)) {
port = 443;
} else {
port = -1;
}
} else {
port = uri.getPort();
}
if (!"ws".equalsIgnoreCase(scheme) && !"wss".equalsIgnoreCase(scheme)) {
throw new IllegalArgumentException("Only WS(S) is supported.");
}
final boolean ssl = "wss".equalsIgnoreCase(scheme);
final SslContext sslCtx;
if (ssl) {
sslCtx = SslContextBuilder.forClient().build();
} else {
sslCtx = null;
}
final WebSocketClientHandler handler = getWebSocketClientHandler(WebSocketClientHandshakerFactory.newHandshaker(
uri, WebSocketVersion.V13, null, true, new DefaultHttpHeaders(), maxFramePayloadLength),
this::messageHandler);
Bootstrap b = new Bootstrap();
b.group(eventLoopGroup)
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, java.lang.Math.toIntExact(connectionTimeout.toMillis()))
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline p = ch.pipeline();
if (sslCtx != null) {
p.addLast(sslCtx.newHandler(ch.alloc(), host, port));
}
WebSocketClientExtensionHandler clientExtensionHandler = getWebSocketClientExtensionHandler();
List handlers = new ArrayList<>(4);
handlers.add(new HttpClientCodec());
handlers.add(WebSocketClientCompressionHandler.INSTANCE);
handlers.add(new HttpObjectAggregator(8192));
handlers.add(handler);
if (clientExtensionHandler != null) handlers.add(clientExtensionHandler);
p.addLast(handlers.toArray(new ChannelHandler[handlers.size()]));
}
});
b.connect(uri.getHost(), port).addListener((ChannelFuture future) -> {
webSocketChannel = future.channel();
if (future.isSuccess()) {
handler.handshakeFuture().addListener(f -> completable.onComplete());
} else {
completable.onError(future.cause());
}
});
} catch (Exception throwable) {
completable.onError(throwable);
}
});
}
@Override
protected void handleMessage(JsonNode message) {
if (message.has("info") || message.has("success")) {
return;
}
if (message.has("error")) {
String error = message.get("error").asText();
LOG.error("Error with message: " + error);
return;
}
super.handleMessage(message);
}
public Observable subscribeBitmexChannel(String channelName) {
return subscribeChannel(channelName).map(s -> {
BitmexWebSocketTransaction transaction = mapper.readValue(s.toString(), BitmexWebSocketTransaction.class);
return transaction;
})
.share();
}
@Override
protected String getChannelNameFromMessage(JsonNode message) throws IOException {
String instrument = message.get("data").get(0).get("symbol").asText();
String table = message.get("table").asText();
return String.format("%s:%s", table, instrument);
}
@Override
public String getSubscribeMessage(String channelName, Object... args) throws IOException {
BitmexWebSocketSubscriptionMessage subscribeMessage = new BitmexWebSocketSubscriptionMessage("subscribe", new String[]{channelName});
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.writeValueAsString(subscribeMessage);
}
@Override
public String getUnsubscribeMessage(String channelName) throws IOException {
BitmexWebSocketSubscriptionMessage subscribeMessage = new BitmexWebSocketSubscriptionMessage("unsubscribe", new String[]{});
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.writeValueAsString(subscribeMessage);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy