Can I change Netty pipeline by events? - websocket

My app should do the next:
Send a POST request to server to get the token.
Connect to the websocket using this token in the headers while handshake.
Short question: To activate WebSocketClientProtocolHandler I have to fire event ctx.fireChannelActive() but from channelRead method because in this method I receive token from server . Is it correct place?
I implemented custom ChannelInboundHandlerAdapter and override:
#Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
authenticator.authenticate(ctx.channel()).addListener(new ChannelFutureListener() {
#Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
if (!channelFuture.isSuccess()) {
authPromise.tryFailure(channelFuture.cause());
ctx.fireExceptionCaught(new RuntimeException("Auth is failed."));
} else {
ctx.fireUserEventTriggered("Auth is successful");
}
}
});
}
#Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (!(msg instanceof FullHttpResponse)) {
ctx.fireChannelRead(msg);
}
FullHttpResponse response = (FullHttpResponse) msg;
try {
authenticator.finishAuthentication(ctx.channel(), response);
authPromise.trySuccess();
ctx.pipeline().remove(this);
ctx.fireChannelActive();
} finally {
response.release();
}
}
Authenticator class adds needed handlers, sends POST request and then it should parse response from server and change the pipeline.
public class Authenticator {
private final Logger LOGGER = LoggerFactory.getLogger(this.getClass());
private final ObjectMapper mapper = new ObjectMapper();
private final MessengerConfig messengerConfig;
public Authenticator(MessengerConfig messengerConfig) {
this.messengerConfig = messengerConfig;
}
public ChannelFuture authenticate(Channel channel) {
this.preCheck(channel);
return this.authenticate(channel, channel.newPromise());
}
private void preCheck(Channel channel) {
ChannelPipeline pipeline = channel.pipeline();
HttpClientCodec httpClientCodec = pipeline.get(HttpClientCodec.class);
if (httpClientCodec == null) {
LOGGER.warn("Pipeline does not contain HttpClientCodec.");
pipeline.addFirst(HttpClientCodec.class.getName(), new HttpClientCodec());
LOGGER.info("HttpClientCodec was added to pipeline.");
}
HttpObjectAggregator httpObjectAggregator = pipeline.get(HttpObjectAggregator.class);
if (httpObjectAggregator == null) {
LOGGER.warn("Pipeline does not contain HttpObjectAggregator.");
pipeline.addAfter(
HttpClientCodec.class.getName(),
HttpObjectAggregator.class.getName(),
new HttpObjectAggregator(8192)
);
LOGGER.info("HttpObjectAggregator was added to pipeline.");
}
}
private ChannelFuture authenticate(Channel channel, ChannelPromise promise) {
HttpRequest request = createAuthRequest();
try {
channel.writeAndFlush(request).addListener(new ChannelFutureListener() {
#Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
if (channelFuture.isSuccess()) {
promise.setSuccess();
} else {
promise.setFailure(new RuntimeException(""));
}
}
});
} catch (Exception e) {
LOGGER.error("Error", e);
}
return promise;
}
public void finishAuthentication(Channel channel, FullHttpResponse response) {
ByteBuf content = response.content();
AuthenticationData authenticationData = null;
try {
authenticationData = this.mapper.readValue(content.toString(CharsetUtil.UTF_8), AuthenticationData.class);
} catch (JsonProcessingException e) {
LOGGER.error("Can't parse authentication data.", e);
throw new RuntimeException((e));
}
LOGGER.info(Objects.toString(authenticationData));
DefaultWebSocketClientProtocolHandlerFactory factory = new DefaultWebSocketClientProtocolHandlerFactory();
WebSocketClientProtocolHandler handler = factory.getHandler(this.messengerConfig, authenticationData);
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(WebSocketClientProtocolHandler.class.getName(), handler);
LOGGER.info("WebSocketClientProtocolHandler was added.");
pipeline.addLast(MessageHandler.class.getName(), new MessageHandler());
LOGGER.info("MessageHandler was added.");
}
So here I have two stages:
Auth stage with a pipeline:
io.netty.handler.codec.http.HttpClientCodec
io.netty.handler.codec.http.HttpObjectAggregator
AuthenticationHandler
2 Web-socket stage with a pipeline:
io.netty.handler.codec.http.HttpClientCodec
io.netty.handler.codec.http.HttpObjectAggregator
io.netty.handler.codec.http.websocketx.WebSocketClientProtocolHandshakeHandler
io.netty.handler.codec.http.websocketx.Utf8FrameValidator
io.netty.handler.codec.http.websocketx.WebSocketClientProtocolHandler
com.github.apsyvenko.client.messaging.MessageHandler
To activate second stage I have to fire event - ctx.fireChannelActive() but from channelRead.
As a result I got an exception:
18:19:37.055 [nioEventLoopGroup-2-1] WARN i.n.channel.DefaultChannelPipeline - An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception.
java.net.SocketException: Connection reset
after hand-shake had started.

Related

AWS Active MQ STOMP Issue

I am having issues with the Amazon MQ implementation of topics. When I use the in memory SimpleMessageBroker clients receive only messages that they have subscribed to. However with the externalized message broker, all clients receive all messages broadcast through the SimpMessagingTemplate.
relevant code:
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
if ("true".equalsIgnoreCase(environment.getProperty("aws.activemq.enabled")))
config.setApplicationDestinationPrefixes("/app")
.enableStompBrokerRelay("/topic")
.setUserDestinationBroadcast("/user")
.setRelayHost(this.activeMQCreds.getHost())
.setRelayPort(this.activeMQCreds.getPort())
.setClientLogin(this.activeMQCreds.getUsername())
.setClientPasscode(this.activeMQCreds.getPassword())
.setAutoStartup(true)
.setSystemLogin(this.activeMQCreds.getUsername())
.setSystemPasscode(this.activeMQCreds.getPassword())
.setTcpClient(this.createClient());
else config.setApplicationDestinationPrefixes("/app")
.enableSimpleBroker("/topic");
#Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(webSocketSessionChannelInterceptor);
}
private TcpOperations<byte[]> createClient() {
return new ReactorNettyTcpClient<>(
(client) -> client.remoteAddress(this::getAddress).secure(), new StompReactorNettyCodec());
}
private SocketAddress getAddress() {
try {
InetAddress address =
InetAddress.getByName(this.activeMQCreds.getHost().replace("stomp+ssl://", ""));
return new InetSocketAddress(address, this.activeMQCreds.getPort());
} catch (UnknownHostException e) {
log.error("Exception", e);
return null;
}
}
}
How we are handling subscriptions:
public class WebSocketSessionChannelInterceptor implements ChannelInterceptor {
#Override
public Message<?> preSend(final Message<?> message, final MessageChannel channel) throws AuthenticationException {
log.info("Message: {}", message);
final StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
try {
final String destination = accessor.getDestination();
if (StompCommand.CONNECT == accessor.getCommand()) {
final String token = accessor.getFirstNativeHeader("Authorization");
final UsernamePasswordAuthenticationToken user =
webSocketAuthenticatorService.getAuthenticatedOrFail(token);
if (user != null) SecurityContextHolder.getContext().setAuthentication(user);
accessor.setUser(user);
} else if (StompCommand.SUBSCRIBE == accessor.getCommand()
&& StringUtils.isNotEmpty(destination)
&& destination.contains("conversations?orgId=")) {
String orgId = destination.substring(destination.lastIndexOf('=') + 1);
if (WebSocketAuthenticatorService.hasPermissionToOrgId(orgId, (Authentication) accessor.getUser()))
return message;
else
throw new BadCredentialsException(
String.format("you do not have privilege to this organization: %s", orgId));
}
return message;
} catch (Exception e) {
log.error("Exception: ", e);
return null;
}
}
#Override
public void afterSendCompletion(Message<?> message, MessageChannel channel, boolean sent, #Nullable Exception ex) {
final StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
final String destination = accessor.getDestination();
if (StompCommand.SUBSCRIBE == accessor.getCommand()
&& StringUtils.isNotEmpty(destination)
&& destination.contains("conversations?orgId=")) {
String orgId = destination.substring(destination.lastIndexOf('=') + 1);
if (WebSocketAuthenticatorService.hasPermissionToOrgId(orgId, (Authentication) accessor.getUser()))
webSocketUtils.publishConversationsForOrgId(orgId);
}
}
}
How we are sending messages to clients:
this.simpMessagingTemplate.convertAndSend(
ApplicationConstants.ORG_WEBSOCKET_TOPIC_PATH + orgId, conversationBO);

How to use Netty's channel pool map as a ConnectorProvider for a Jax RS client

I have wasted several hours trying to solve a issue with the use of netty's channel pool map and a jax rs client.
I have used jersey's own netty connector as an inspiration but exchanged netty's channel with netty's channel pool map.
https://jersey.github.io/apidocs/2.27/jersey/org/glassfish/jersey/netty/connector/NettyConnectorProvider.html
My problem is that I have references that I need inside my custom SimpleChannelInboundHandler. However by the design of netty's way to create a channel pool map, I can not pass the references through my custom ChannelPoolHandler, because as soon as the pool map has created a pool the constructor of the channel pool handler never runs again.
This is the method where it makes acquires a pool and check out a channel to make a HTTP request.
#Override
public Future<?> apply(ClientRequest request, AsyncConnectorCallback callback) {
final CompletableFuture<Object> completableFuture = new CompletableFuture<>();
try{
HttpRequest httpRequest = buildHttpRequest(request);
// guard against prematurely closed channel
final GenericFutureListener<io.netty.util.concurrent.Future<? super Void>> closeListener =
future -> {
if (!completableFuture.isDone()) {
completableFuture.completeExceptionally(new IOException("Channel closed."));
}
};
try {
ClientRequestDTO clientRequestDTO = new ClientRequestDTO(NettyChannelPoolConnector.this, request, completableFuture, callback);
dtoMap.putIfAbsent(request.getUri(), clientRequestDTO);
// Retrieves a channel pool for the given host
FixedChannelPool pool = this.poolMap.get(clientRequestDTO);
// Acquire a new channel from the pool
io.netty.util.concurrent.Future<Channel> f = pool.acquire();
f.addListener((FutureListener<Channel>) futureWrite -> {
//Succeeded with acquiring a channel
if (futureWrite.isSuccess()) {
Channel channel = futureWrite.getNow();
channel.closeFuture().addListener(closeListener);
try {
if(request.hasEntity()) {
channel.writeAndFlush(httpRequest);
final JerseyChunkedInput jerseyChunkedInput = new JerseyChunkedInput(channel);
request.setStreamProvider(contentLength -> jerseyChunkedInput);
if(HttpUtil.isTransferEncodingChunked(httpRequest)) {
channel.write(jerseyChunkedInput);
} else {
channel.write(jerseyChunkedInput);
}
executorService.execute(() -> {
channel.closeFuture().removeListener(closeListener);
try {
request.writeEntity();
} catch (IOException ex) {
callback.failure(ex);
completableFuture.completeExceptionally(ex);
}
});
channel.flush();
} else {
channel.closeFuture().removeListener(closeListener);
channel.writeAndFlush(httpRequest);
}
} catch (Exception ex) {
System.err.println("Failed to sync and flush http request" + ex.getLocalizedMessage());
}
pool.release(channel);
}
});
} catch (NullPointerException ex) {
System.err.println("Failed to acquire socket from pool " + ex.getLocalizedMessage());
}
} catch (Exception ex) {
completableFuture.completeExceptionally(ex);
return completableFuture;
}
return completableFuture;
}
This is my ChannelPoolHandler
public class SimpleChannelPoolHandler implements ChannelPoolHandler {
private ClientRequestDTO clientRequestDTO;
private boolean ssl;
private URI uri;
private int port;
SimpleChannelPoolHandler(URI uri) {
this.uri = uri;
if(uri != null) {
this.port = uri.getPort() != -1 ? uri.getPort() : "https".equals(uri.getScheme()) ? 443 : 80;
ssl = "https".equalsIgnoreCase(uri.getScheme());
}
}
#Override
public void channelReleased(Channel ch) throws Exception {
System.out.println("Channel released: " + ch.toString());
}
#Override
public void channelAcquired(Channel ch) throws Exception {
System.out.println("Channel acquired: " + ch.toString());
}
#Override
public void channelCreated(Channel ch) throws Exception {
System.out.println("Channel created: " + ch.toString());
int readTimeout = Integer.parseInt(ApplicationEnvironment.getInstance().get("READ_TIMEOUT"));
SocketChannelConfig channelConfig = (SocketChannelConfig) ch.config();
channelConfig.setConnectTimeoutMillis(2000);
ChannelPipeline channelPipeline = ch.pipeline();
if(ssl) {
SslContext sslContext = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build();
channelPipeline.addLast("ssl", sslContext.newHandler(ch.alloc(), uri.getHost(), this.port));
}
channelPipeline.addLast("client codec", new HttpClientCodec());
channelPipeline.addLast("chunked content writer",new ChunkedWriteHandler());
channelPipeline.addLast("content decompressor", new HttpContentDecompressor());
channelPipeline.addLast("read timeout", new ReadTimeoutHandler(readTimeout, TimeUnit.MILLISECONDS));
channelPipeline.addLast("business logic", new JerseyNettyClientHandler(this.uri));
}
}
And this is my SimpleInboundHandler
public class JerseyNettyClientHandler extends SimpleChannelInboundHandler<HttpObject> {
private final NettyChannelPoolConnector nettyChannelPoolConnector;
private final LinkedBlockingDeque<InputStream> isList = new LinkedBlockingDeque<>();
private final AsyncConnectorCallback asyncConnectorCallback;
private final ClientRequest jerseyRequest;
private final CompletableFuture future;
public JerseyNettyClientHandler(ClientRequestDto clientRequestDTO) {
this.nettyChannelPoolConnector = clientRequestDTO.getNettyChannelPoolConnector();
ClientRequestDTO cdto = clientRequestDTO.getNettyChannelPoolConnector().getDtoMap().get(clientRequestDTO.getClientRequest());
this.asyncConnectorCallback = cdto.getCallback();
this.jerseyRequest = cdto.getClientRequest();
this.future = cdto.getFuture();
}
#Override
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
if(msg instanceof HttpResponse) {
final HttpResponse httpResponse = (HttpResponse) msg;
final ClientResponse response = new ClientResponse(new Response.StatusType() {
#Override
public int getStatusCode() {
return httpResponse.status().code();
}
#Override
public Response.Status.Family getFamily() {
return Response.Status.Family.familyOf(httpResponse.status().code());
}
#Override
public String getReasonPhrase() {
return httpResponse.status().reasonPhrase();
}
}, jerseyRequest);
for (Map.Entry<String, String> entry : httpResponse.headers().entries()) {
response.getHeaders().add(entry.getKey(), entry.getValue());
}
if((httpResponse.headers().contains(HttpHeaderNames.CONTENT_LENGTH) && HttpUtil.getContentLength(httpResponse) > 0) || HttpUtil.isTransferEncodingChunked(httpResponse)) {
ctx.channel().closeFuture().addListener(future -> isList.add(NettyInputStream.END_OF_INPUT_ERROR));
response.setEntityStream(new NettyInputStream(isList));
} else {
response.setEntityStream(new InputStream() {
#Override
public int read() {
return -1;
}
});
}
if(asyncConnectorCallback != null) {
nettyChannelPoolConnector.executorService.execute(() -> {
asyncConnectorCallback.response(response);
future.complete(response);
});
}
}
if(msg instanceof HttpContent) {
HttpContent content = (HttpContent) msg;
ByteBuf byteContent = content.content();
if(byteContent.isReadable()) {
byte[] bytes = new byte[byteContent.readableBytes()];
byteContent.getBytes(byteContent.readerIndex(), bytes);
isList.add(new ByteArrayInputStream(bytes));
}
}
if(msg instanceof LastHttpContent) {
isList.add(NettyInputStream.END_OF_INPUT);
}
}
#Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if(asyncConnectorCallback != null) {
nettyChannelPoolConnector.executorService.execute(() -> asyncConnectorCallback.failure(cause));
}
future.completeExceptionally(cause);
isList.add(NettyInputStream.END_OF_INPUT_ERROR);
}
The references needed to be passed to the SimpleChannelInboundHandler is what is packed into the ClientRequestDTO as seen in the first code block.
I am not sure as it is not a tested code. But it could be achieved by the following code.
SimpleChannelPool sPool = poolMap.get(Req.getAddress());
Future<Channel> f = sPool.acquire();
f.get().pipeline().addLast("inbound", new NettyClientInBoundHandler(Req, jbContext, ReportData));
f.addListener(new NettyClientFutureListener(this.Req, sPool));
where Req, jbContext, ReportData could be input data for InboundHandler().

Building a Future API on top of Netty

I want to build an API based on Futures (from java.util.concurrent) that is powered by a custom protocol on top of Netty (version 4). Basic idea is to write a simple library that would abstract the underlying Netty implementation and make it easier to make requests.
Using this library, one should be able to write something like this:
Request req = new Request(...);
Future<Response> responseFuture = new ServerIFace(host, port).call(req);
// For example, let's block until this future is resolved
Reponse res = responseFuture.get().getResult();
Underneath this code, a Netty client is connected
public class ServerIFace {
private Bootstrap bootstrap;
private EventLoopGroup workerGroup;
private String host;
private int port;
public ServerIFace(String host, int port) {
this.host = host;
this.port = port;
this.workerGroup = new NioEventLoopGroup();
bootstrap();
}
private void bootstrap() {
bootstrap = new Bootstrap();
bootstrap.group(workerGroup);
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
#Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ObjectEncoder());
ch.pipeline().addLast(new ObjectDecoder(ClassResolvers.cacheDisabled(Response.class.getClassLoader())));
ch.pipeline().addLast("response", new ResponseReceiverChannelHandler());
}
});
}
public Future<Response> call(final Request request) throws InterruptedException {
CompletableFuture<Response> responseFuture = new CompletableFuture<>();
Channel ch = bootstrap.connect(host, port).sync().channel();
ch.writeAndFlush(request).addListener((f) -> {
if (f.isSuccess()) {
System.out.println("Wrote successfully");
} else {
f.cause().printStackTrace();
}
});
ChannelFuture closeFuture = ch.closeFuture();
// Have to 'convert' ChannelFuture to java.util.concurrent.Future
closeFuture.addListener((f) -> {
if (f.isSuccess()) {
// How to get this response?
Response response = ((ResponseReceiverChannelHandler) ch.pipeline().get("response")).getResponse();
responseFuture.complete(response);
} else {
f.cause().printStackTrace();
responseFuture.cancel(true);
}
ch.close();
}).sync();
return responseFuture;
}
}
Now, as you can see, in order to abstract Netty's inner ChannelFuture, I have to 'convert' it to Java's Future (I'm aware that ChannelFuture is derived from Future, but that information doesn't seem useful at this point).
Right now, I'm capturing this Response object in the last handler of my inbound part of the client pipeline, the ResponseReceiverChannelHandler.
public class ResponseReceiverChannelHandler extends ChannelInboundHandlerAdapter {
private Response response = null;
#Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
this.response = (Response)msg;
ctx.close();
}
public Response getResponse() {
return response;
}
}
Since I'm new to Netty and these things in general, I'm looking for a cleaner, thread-safe way of delivering this object to the API user.
Correct me if I'm wrong, but none of the Netty examples show how to achieve this, and most of the Client examples just print out whatever they get from Server.
Please note that my main goal here is to learn more about Netty, and that this code has no production purposes.
For the reference (although I don't think it's that relevant) here's the Server code.
public class Server {
public static class RequestProcessorHandler extends ChannelInboundHandlerAdapter {
#Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ChannelFuture future;
if (msg instanceof Request) {
Request req = (Request)msg;
Response res = some function of req
future = ctx.writeAndFlush(res);
} else {
future = ctx.writeAndFlush("Error, not a request!");
}
future.addListener((f) -> {
if (f.isSuccess()) {
System.out.println("Response sent!");
} else {
System.out.println("Response not sent!");
f.cause().printStackTrace();
}
});
}
}
public int port;
public Server(int port) {
this.port = port;
}
public void run() throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
#Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ObjectDecoder(ClassResolvers.cacheDisabled(Request.class.getClassLoader())));
ch.pipeline().addLast(new ObjectEncoder());
// Not really shutting down this threadpool but it's ok for now
ch.pipeline().addLast(new DefaultEventExecutorGroup(2), new RequestProcessorHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture f = b.bind(port).sync();
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port;
if (args.length > 0) {
port = Integer.parseInt(args[0]);
} else {
port = 8080;
}
new Server(port).run();
}
}

Http proxy in Netty websocket client to connect to internet

My application is running behind a corporate firewall and I need to use http proxy(http://theclientproxy.net:8080) to connect to internet
I have used the Netty client as below,
https://github.com/netty/netty/tree/4.1/example/src/main/java/io/netty/example/http/websocketx/client
Code:
public final class WebSocketClient {
static final String URL = System.getProperty("url", "wss://127.0.0.1:8080/websocket");
public static void main(String[] args) throws Exception {
URI uri = new URI(URL);
String scheme = uri.getScheme() == null? "ws" : uri.getScheme();
final String host = uri.getHost() == null? "127.0.0.1" : uri.getHost();
final int port;
final boolean ssl = "wss".equalsIgnoreCase(scheme);
final SslContext sslCtx;
if (ssl) {
sslCtx = SslContextBuilder.forClient()
.trustManager(InsecureTrustManagerFactory.INSTANCE).build();
} else {
sslCtx = null;
}
EventLoopGroup group = new NioEventLoopGroup();
try {
final WebSocketClientHandler handler =
new WebSocketClientHandler(
WebSocketClientHandshakerFactory.newHandshaker(
uri, WebSocketVersion.V13, null, true, new DefaultHttpHeaders()));
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
#Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline p = ch.pipeline();
if (sslCtx != null) {
p.addFirst(new HttpProxyHandler(new InetSocketAddress("theclientproxy.net", 8080) ) );
p.addLast(sslCtx.newHandler(ch.alloc(), host, port));
}
p.addLast(
new HttpClientCodec(),
new HttpObjectAggregator(8192),
WebSocketClientCompressionHandler.INSTANCE,
handler);
}
});
Channel ch = b.connect(uri.getHost(), port).sync().channel();
handler.handshakeFuture().sync();
BufferedReader console = new BufferedReader(new InputStreamReader(System.in));
while (true) {
String msg = console.readLine(); //THIS IS NULL IN DATA CENTER LOGS
if (msg == null) {
break;
} else if ("bye".equals(msg.toLowerCase())) {
ch.writeAndFlush(new CloseWebSocketFrame());
ch.closeFuture().sync();
break;
} else if ("ping".equals(msg.toLowerCase())) {
WebSocketFrame frame = new PingWebSocketFrame(Unpooled.wrappedBuffer(new byte[] { 8, 1, 8, 1 }));
ch.writeAndFlush(frame);
} else {
WebSocketFrame frame = new TextWebSocketFrame(msg);
ch.writeAndFlush(frame);
}
}
} finally {
group.shutdownGracefully();
}
Handler:
public class WebSocketClientHandler extends SimpleChannelInboundHandler<Object> {
private final WebSocketClientHandshaker handshaker;
private ChannelPromise handshakeFuture;
public WebSocketClientHandler(WebSocketClientHandshaker handshaker) {
this.handshaker = handshaker;
}
public ChannelFuture handshakeFuture() {
return handshakeFuture;
}
#Override
public void handlerAdded(ChannelHandlerContext ctx) {
handshakeFuture = ctx.newPromise();
}
#Override
public void channelActive(ChannelHandlerContext ctx) {
handshaker.handshake(ctx.channel());
}
#Override
public void channelInactive(ChannelHandlerContext ctx) {
System.out.println("WebSocket Client disconnected!");
}
#Override
public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
Channel ch = ctx.channel();
if (!handshaker.isHandshakeComplete()) {
try {
handshaker.finishHandshake(ch, (FullHttpResponse) msg);
System.out.println("WebSocket Client connected!");
handshakeFuture.setSuccess();
} catch (WebSocketHandshakeException e) {
System.out.println("WebSocket Client failed to connect");
handshakeFuture.setFailure(e);
}
return;
}
The application is able to connect to the websocket server endpoint from my local machine successfully.
But in the company datacenter where my application is deployed, I see the msg value is null and the websocket client is disconnected
Does that mean my connection is blocked at firewall? If that is the case then why did the statement "WebSocket Client connected!" is printed at all?
Thanks
The httpproxyhandler you used is correct
Just remove the BufferredReader code as mentioned below when deploying in linux, docker, etc:
Netty WebSocket Client Channel always gets inactive on Linux Server

Catch Elasticsearch bulk errors when using bulkProcessor

I use bulkProcessor to insert/update bulks in ElasticSearch.
I would like to catch
EsRejectedExecutionException
VersionConflictEngineException
DocumentAlreadyExistsException
but it doesn't throw anything.
It only set a message on the response item.
How can I handle it properly? e.g. applicative retry if rejected...
public BulkResponse response bulkUpdate(.....) {
BulkResponse bulkWriteResult = null;
long startTime = System.currentTimeMillis();
AtomicInteger amountOfRequests = new AtomicInteger();
long esTime;
ElasticBulkProcessorListener listener = new ElasticBulkProcessorListener(updateOperations);
BulkProcessor bulkProcessor = BulkProcessor.builder(client, listener)
.setBulkActions(MAX_BULK_ACTIONS)
.setBulkSize(new ByteSizeValue(maxBulkSize, ByteSizeUnit.MB))
.setConcurrentRequests(5)
.build();
updateOperations.forEach(updateRequest -> {
bulkProcessor.add(updateRequest);
amountOfRequests.getAndIncrement();
});
try {
boolean isFinished = bulkProcessor.awaitClose(bulkTimeout, TimeUnit.SECONDS);
if (isFinished) {
if (listener.getBulkWriteResult() != null) {
bulkWriteResult = listener.getBulkWriteResult();
} else {
throw new Exception("Bulk updating failed, results are empty");
}
} else {
throw new Exception("Bulk updating failed, received timeout");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return bulkWriteResult;
}
public class ElasticBulkProcessorListener implements BulkProcessor.Listener {
private long esTime = 0;
private List<Throwable> errors;
private BulkResponse response;
public long getEsTime() {
return esTime;
}
#Override
public void beforeBulk(long executionId, BulkRequest request) {
String description = "";
if (!request.requests().isEmpty()) {
ActionRequest request1 = request.requests().get(0);
description = ((UpdateRequest) request1).type();
}
log.info("Bulk executionID: {}, estimated size is: {}MB, number of actions: {}, request type: {}",
executionId, (request.estimatedSizeInBytes() / 1000000), request.numberOfActions(), description);
}
#Override
public void afterBulk(long executionId, BulkRequest request, BulkResponse response) {
log.info("Bulk executionID: {}, took : {} Millis, bulk size: {}", executionId, response.getTookInMillis(), response.getItems().length);
esTime = response.getTookInMillis();
response = createBulkUpdateResult(response);
}
#Override
public void afterBulk(long executionId, BulkRequest request, Throwable failure) {
log.error("Bulk , failed! error: ", executionId, failure);
throw new DataFWCoreException(String.format("Bulk executionID: %d, update operation failed", executionId), failure);
}
}
The failure handler will be called only when network failure occurred,
Any other case will get success handler.
The only way to handle exception as I mention above is by parse each response item and figure out what happened.

Resources