I'm struggling how to find out the session when receiving the onWebSocketClose event.
Official docs: https://www.eclipse.org/jetty/javadoc/current/org/eclipse/jetty/websocket/api/WebSocketAdapter.html#onWebSocketClose(int,java.lang.String)
It says there are two parameters: onWebSocketClose(int statusCode, java.lang.String reason)
Another site says, that there are 3 parameters:
https://www.eclipse.org/jetty/documentation/current/jetty-websocket-api-annotations.html
#OnWebSocketClose
An optional method level annotation.
Flags one method in the class as receiving the On Close event.
Method signature must be public, not abstract, and return void.
The method parameters:
Session (optional)
int closeCode (required)
String closeReason (required)
Currently on WebSocketOpen I add the session to a collection like this:
private static Set<Session> connections = Collections.synchronizedSet(new HashSet<Session>());
private static final Logger logger = LogManager.getLogger(WebSocketEvent.class);
#Override
public void onWebSocketConnect(Session session)
{
super.onWebSocketConnect(session);
connections.add(session);
logger.debug("Socket Connected: " + session);
}
And on WebSocketClose I want to delete this session again like this:
#Override
public void onWebSocketClose(Session session, int statusCode, String reason)
{
super.onWebSocketClose(session,statusCode,reason);
logger.debug("Connection closed. IP: " + session.getRemoteAddress().getAddress().toString()+ "; rc: "+statusCode+ "; reason: "+ reason);
connections.remove(session);
logger.debug("Session with following IP removed from list: " + session.getRemoteAddress().getAddress().toString());
}
But it seems that the optional session parameter is not available like described in official docs so it marks it as an error.
Any idea how to solve it? Thanks in advance!
Ok I found it out. On WebSocketClose Event in this object there is a method getSession() available.
#Override
public void onWebSocketClose(int statusCode, String reason)
{
connections.remove(this.getSession());
super.onWebSocketClose(statusCode,reason);
logger.debug("Connection closed. rc: "+statusCode+ "; reason: "+ reason);
}
Related
I have a Netty TCP Server with Spring Boot 2.3.1 with the following handler :
#Slf4j
#Component
#RequiredArgsConstructor
#ChannelHandler.Sharable
public class QrReaderProcessingHandler extends ChannelInboundHandlerAdapter {
private final CarParkPermissionService permissionService;
private final Gson gson = new Gson();
private String remoteAddress;
#Override
public void channelActive(ChannelHandlerContext ctx) {
ctx.fireChannelActive();
remoteAddress = ctx.channel().remoteAddress().toString();
if (log.isDebugEnabled()) {
log.debug(remoteAddress);
}
ctx.writeAndFlush("Your remote address is " + remoteAddress + ".\r\n");
}
#Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
log.info("CLIENT_IP: {}", remoteAddress);
String stringMsg = (String) msg;
log.info("CLIENT_REQUEST: {}", stringMsg);
String lowerCaseMsg = stringMsg.toLowerCase();
if (RequestType.HEARTBEAT.containsName(lowerCaseMsg)) {
HeartbeatRequest heartbeatRequest = gson.fromJson(stringMsg, HeartbeatRequest.class);
log.debug("heartbeat request: {}", heartbeatRequest);
HeartbeatResponse response = HeartbeatResponse.builder()
.responseCode("ok")
.build();
ctx.writeAndFlush(response + "\n\r");
}
}
Request DTO:
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class HeartbeatRequest {
private String messageID;
}
Response DTO:
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
public class HeartbeatResponse {
private String responseCode;
}
Logic is quite simple. Only I have to know the IP address of the client.
I need to test it as well.
I have been looking for many resources for testing handlers for Netty, like
Testing Netty with EmbeddedChannel
How to unit test netty handler
However, it didn't work for me.
For EmbeddedChannel I have following error - Your remote address is embedded.
Here is code:
#ActiveProfiles("test")
#RunWith(MockitoJUnitRunner.class)
public class ProcessingHandlerTest_Embedded {
#Mock
private PermissionService permissionService;
private EmbeddedChannel embeddedChannel;
private final Gson gson = new Gson();
private ProcessingHandler processingHandler;
#Before
public void setUp() {
processingHandler = new ProcessingHandler(permissionService);
embeddedChannel = new EmbeddedChannel(processingHandler);
}
#Test
public void testHeartbeatMessage() {
// given
HeartbeatRequest heartbeatMessage = HeartbeatRequest.builder()
.messageID("heartbeat")
.build();
HeartbeatResponse response = HeartbeatResponse.builder()
.responseCode("ok")
.build();
String request = gson.toJson(heartbeatMessage).concat("\r\n");
String expected = gson.toJson(response).concat("\r\n");
// when
embeddedChannel.writeInbound(request);
// then
Queue<Object> outboundMessages = embeddedChannel.outboundMessages();
assertEquals(expected, outboundMessages.poll());
}
}
Output:
22:21:29.062 [main] INFO handler.ProcessingHandler - CLIENT_IP: embedded
22:21:29.062 [main] INFO handler.ProcessingHandler - CLIENT_REQUEST: {"messageID":"heartbeat"}
22:21:29.067 [main] DEBUG handler.ProcessingHandler - heartbeat request: HeartbeatRequest(messageID=heartbeat)
org.junit.ComparisonFailure:
<Click to see difference>
However, I don't know how to do exact testing for such a case.
Here is a snippet from configuration:
#Bean
#SneakyThrows
public InetSocketAddress tcpSocketAddress() {
// for now, hostname is: localhost/127.0.0.1:9090
return new InetSocketAddress("localhost", nettyProperties.getTcpPort());
// for real client devices: A05264/172.28.1.162:9090
// return new InetSocketAddress(InetAddress.getLocalHost(), nettyProperties.getTcpPort());
}
#Component
#RequiredArgsConstructor
public class QrReaderChannelInitializer extends ChannelInitializer<SocketChannel> {
private final StringEncoder stringEncoder = new StringEncoder();
private final StringDecoder stringDecoder = new StringDecoder();
private final QrReaderProcessingHandler readerServerHandler;
private final NettyProperties nettyProperties;
#Override
protected void initChannel(SocketChannel socketChannel) {
ChannelPipeline pipeline = socketChannel.pipeline();
// Add the text line codec combination first
pipeline.addLast(new DelimiterBasedFrameDecoder(1024 * 1024, Delimiters.lineDelimiter()));
pipeline.addLast(new ReadTimeoutHandler(nettyProperties.getClientTimeout()));
pipeline.addLast(stringDecoder);
pipeline.addLast(stringEncoder);
pipeline.addLast(readerServerHandler);
}
}
How to test handler with IP address of a client?
Two things that could help:
Do not annotate with #ChannelHandler.Sharable if your handler is NOT sharable. This can be misleading. Remove unnecessary state from handlers. In your case you should remove the remoteAddress member variable and ensure that Gson and CarParkPermissionService can be reused and are thread-safe.
"Your remote address is embedded" is NOT an error. It actually is the message written by your handler onto the outbound channel (cf. your channelActive() method)
So it looks like it could work.
EDIT
Following your comments here are some clarifications regarding the second point. I mean that:
your code making use of EmbeddedChannel is almost correct. There is just a misunderstanding on the expected results (assert).
To make the unit test successful, you just have either:
to comment this line in channelActive(): ctx.writeAndFlush("Your remote ...")
or to poll the second message from Queue<Object> outboundMessages in testHeartbeatMessage()
Indeed, when you do this:
// when
embeddedChannel.writeInbound(request);
(1) You actually open the channel once, which fires a channelActive() event. You don't have a log in it but we see that the variable remoteAddress is not null afterwards, meaning that it was assigned in the channelActive() method.
(2) At the end of the channelActive() method, you eventually already send back a message by writing on the channel pipeline, as seen at this line:
ctx.writeAndFlush("Your remote address is " + remoteAddress + ".\r\n");
// In fact, this is the message you see in your failed assertion.
(3) Then the message written by embeddedChannel.writeInbound(request) is received and can be read, which fires a channelRead() event. This time, we see this in your log output:
22:21:29.062 [main] INFO handler.ProcessingHandler - CLIENT_IP: embedded
22:21:29.062 [main] INFO handler.ProcessingHandler - CLIENT_REQUEST: {"messageID":"heartbeat"}
22:21:29.067 [main] DEBUG handler.ProcessingHandler - heartbeat request: HeartbeatRequest(messageID=heartbeat)
(4) At the end of channelRead(ChannelHandlerContext ctx, Object msg), you will then send a second message (the expected one):
HeartbeatResponse response = HeartbeatResponse.builder()
.responseCode("ok")
.build();
ctx.writeAndFlush(response + "\n\r");
Therefore, with the following code of your unit test...
Queue<Object> outboundMessages = embeddedChannel.outboundMessages();
assertEquals(expected, outboundMessages.poll());
... you should be able to poll() two messages:
"Your remote address is embedded"
"{ResponseCode":"ok"}
Does it make sense for you?
My spring websocket code runs in Liberty server. The code works fine in local. When I move to my server, when I try from 'Simple Websocket Client', I get an error like
WebSocket connection to 'wss://url' failed: One or more reserved bits
are on: reserved1 = 0, reserved2 = 1, reserved3 = 1
On the server side logs, I can see that afterConnectionEstablished method gets triggered, and immediately afterConnectionClosed gets triggered and when I print close status, it gives me
Code 1002 Reason:: Invalid reserved bit.
Am not clear on what this means and what are reasons this could come from.
public class NotificationHandler extends TextWebSocketHandler {
Logger logger = LogManager.getLogger(NotificationHandler.class);
#Override
public void afterConnectionEstablished(WebSocketSession session)
throws IOException {
logger.info("In NotificationHandler, afterConnectionEstablished.. ");
session.sendMessage(new TextMessage("Hello !"));
}
#Override
public void handleTextMessage(WebSocketSession session, TextMessage message) throws IOException {
logger.info("In NotificationHandler, handleTextMessage.. ");
session.sendMessage(new TextMessage("Hello Text Message!"));
}
#Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
logger.info("In NotificationHandler, afterConnectionClosed, Code:: "+ status.getCode() + ".. Reason:: " + status.getReason());
}
}
Please let me know if you need more details.
Given the exact wording, that is not a reason code coming from the Liberty Websocket code, so I am guessing it is coming from the Spring code. If you are running on Liberty I would think you would want the system configured to use the Liberty Websocket code and not another provider.
I'm playing w/ EE and want to persist a user session state. I have the session bean here:
#Stateful(mappedName = "UserSessionState")
#Named("UserSessionState")
#SessionScoped
#StatefulTimeout(value = 5, unit = TimeUnit.MINUTES)
public class UserSessionState implements Serializable
{
private boolean hasPlayerId = false;
private String playerId = "";
public void setRandomPlayerId()
{
playerId = UUID.uuid();
hasPlayerId = true;
}
public boolean hasPlayerId()
{
return hasPlayerId;
}
public String getPlayerId()
{
return playerId;
}
}
And a servlet here (GameState is an Application Scoped bean that is working as expected, CustomExtendedHttpServlet is just a simple extension of HttpServlet)
public class NewUserJoined extends CustomExtendedHttpServlet
{
#Inject
protected GameState gameState;
#Inject
protected UserSessionState user;
#Override
protected String doGetImpl(HttpServletRequest request, HttpServletResponse response, UserContext userLoginContext)
{
if (!user.hasPlayerId())
{
user.setRandomPlayerId();
}
String userId = user.getPlayerId();
if (!gameState.hasUser(userId))
{
gameState.addUser(userId, user);
return "Hi, your ID is: " + user.getPlayerId() + ", there are " + gameState.getUserCount() + " other players here";
}
else
{
return user.getPlayerId() + " you're already in the game, there are: " + gameState.getUserCount() + " other players here";
}
}
}
I'm not sure what's going on, but whenever I call the New User Joined servlet from the same HTTP session, I get this response on the first call (as expected):
"Hi, your ID is: , there are 1 other players here"
Repeating the same servlet call in the same session gives me the same message:
"Hi, your ID is: , there are 2 other players here"
...
It looks like a new instance of User Session State is getting created over and over. Am I doing this correctly?
EDIT 1: Here the code I use to send a request. It appears I'm getting a new session ID with each request, what could cause that?
RequestCallback callback = new RequestCallback()
{
#Override
public void onResponseReceived(Request request, Response response)
{
log(response.getText());
}
#Override
public void onError(Request request, Throwable exception)
{
log(
"Response Error | "
+ "Exception: " + exception);
}
};
RequestBuilder rb = new RequestBuilder(RequestBuilder.GET, SERVLET_URL);
rb.setCallback(callback);
try
{
rb.send();
}
catch (RequestException e)
{
log("Response Error | "
+ "Exception: " + e);
}
Figured out the issue,
Turns out I had an old workaround in the GWT client that was changing the host to get around a CORS issue. Because the response didn't match up to the origin, the cookie wasn't getting sent with future servlet GET calls.
Have you tried a call to request.getSession(true) to make sure an EE HTTPSession is established here?
Below is my code:
rabbitTemplate.setMandatory(true);
rabbitTemplate.setReturnCallback(new ReturnCallback() {
#Override
public void returnedMessage(Message message, int replyCode,
String replyText, String exchange, String routingKey) {
System.out.println("Received returnedMessage with result {}"
+ routingKey);
}
});
rabbitTemplate.setConfirmCallback(new ConfirmCallback() {
#Override
public void confirm(CorrelationData correlationData, boolean ack,
String cause) {
System.out
.println("*************************************************************************************"
+ ack);
//log.info("Received confirm with result {}", ack);
System.out.println("Message received by broker");
}
});
It not printing the message "Message received by broker".
Help me any one for same. Thanks in Advance!
You also need to configure the connection factory to support these features see the documentation.
For returned messages, the template’s mandatory property must be set to true, or the mandatory-expression must evaluate to true for a particular message. This feature requires a CachingConnectionFactory that has its publisherReturns property set to true
...
For Publisher Confirms (aka Publisher Acknowledgements), the template requires a CachingConnectionFactory that has its publisherConfirms property set to true.
...
Is there a way to get the reason a HystrixCommand failed when using the #HystrixCommand annotation within a Spring Boot application? It looks like if you implement your own HystrixCommand, you have access to the getFailedExecutionException but how can you get access to this when using the annotation? I would like to be able to do different things in the fallback method based on the type of exception that occurred. Is this possible?
I saw a note about HystrixRequestContext.initializeContext() but the HystrixRequestContext doesn't give you access to anything, is there a different way to use that context to get access to the exceptions?
Simply add a Throwable parameter to the fallback method and it will receive the exception which the original command produced.
From https://github.com/Netflix/Hystrix/tree/master/hystrix-contrib/hystrix-javanica
#HystrixCommand(fallbackMethod = "fallback1")
User getUserById(String id) {
throw new RuntimeException("getUserById command failed");
}
#HystrixCommand(fallbackMethod = "fallback2")
User fallback1(String id, Throwable e) {
assert "getUserById command failed".equals(e.getMessage());
throw new RuntimeException("fallback1 failed");
}
I haven't found a way to get the exception with Annotations either, but creating my own Command worked for me like so:
public static class DemoCommand extends HystrixCommand<String> {
protected DemoCommand() {
super(HystrixCommandGroupKey.Factory.asKey("Demo"));
}
#Override
protected String run() throws Exception {
throw new RuntimeException("failed!");
}
#Override
protected String getFallback() {
System.out.println("Events (so far) in Fallback: " + getExecutionEvents());
return getFailedExecutionException().getMessage();
}
}
Hopefully this helps someone else as well.
As said in the documentation Hystrix-documentation getFallback() method will be thrown when:
Whenever a command execution fails: when an exception is thrown by construct() or run()
When the command is short-circuited because the circuit is open
When the command’s thread pool and queue or semaphore are at capacity
When the command has exceeded its timeout length.
So you can easily get what raised your fallback method called by assigning the the execution exception to a Throwable object.
Assuming your HystrixCommand returns a String
public class ExampleTask extends HystrixCommand<String> {
//Your class body
}
do as follows:
#Override
protected ErrorCodes getFallback() {
Throwable t = getExecutionException();
if (circuitBreaker.isOpen()) {
// Log or something
} else if (t instanceof RejectedExecutionException) {
// Log and get the threadpool name, could be useful
} else {
// Maybe something else happened
}
return "A default String"; // Avoid using any HTTP request or ypu will need to wrap it also in HystrixCommand
}
More info here
I couldn't find a way to obtain the exception with the annotations, but i found HystrixPlugins , with that you can register a HystrixCommandExecutionHook and you can get the exact exception in that like this :
HystrixPlugins.getInstance().registerCommandExecutionHook(new HystrixCommandExecutionHook() {
#Override
public <T> void onFallbackStart(final HystrixInvokable<T> commandInstance) {
}
});
The command instance is a GenericCommand.
Most of the time just using getFailedExecutionException().getMessage() gave me null values.
Exception errorFromThrowable = getExceptionFromThrowable(getExecutionException());
String errMessage = (errorFromThrowable != null) ? errorFromThrowable.getMessage()
this gives me better results all the time.