WebSocketStompClient stompSession future timingout - spring-boot

I'm trying to connect to a Secure WebSocket service using WebSocketStompClient in a Spring Boot application, but it never get past StompSession Future get call (timing out).
I've verified this service works by using 'Smart Websocket Client' Chrome browser extension, but haven't been able to get it working with the Spring Boot client.
I'm unable to figure out what else I can do to further debug this. Not sure if this is a certificate issue or something else.
Any suggestion ?
Update: I even got this working with Java/Kotlin using 'org.java-websocket:Java-WebSocket:1.5.1'. It was just a one liner: connect with the same URI. I think this rules out any potential issue with the certificate.
val URL = "wss://service.foo/bar"
val countDownLatch = CountDownLatch(1)
val client = StandardWebSocketClient()
val stompClient = WebSocketStompClient(client)
stompClient.messageConverter = MappingJackson2MessageConverter()
val sessionHandler = MyEventSocketHandler()
val stompSessionFuture = stompClient.connect(URL, sessionHandler)
// MyEventSocketHandler::afterConnected() never got called. Doing this to figure out what is going on.
val stompSession = stompSessionFuture.get(10, TimeUnit.SECONDS)
countDownLatch.await()

Related

How to disconnect a Stomp client Session from Spring

I know how to disconnect Sessions from Client Side, but I couldn't find a way to disconnect my session from my Spring Boot Backend. I've already seen the following post:
Disconnect client session from Spring websocket stomp server
This would kinda adress my problem, but I thought maybe there is a much more elegant or easier way to solve my problem, since the above mentioned post is already 8 years old. And still i couldn't get it to work as expected.
Trying to sketch my exact problem:
JS-Client-Side looks like this(pseudo code):
![creates a simple request and sends it to my Spring Backend]
function subscribeToUser(){
request = {};
request.type = "USER";
var user = {};
user.userId = userId;
user.email = email;
request.user = user;
send(request);
}
Server-Side:
Here I detect a Subscribe Event, extract the destination, and check if it is valid. If there is some problem
with the destination I want my server to disconnect from that client.(This should happen in line 122)
#EventListener
private void handleSessionSubscribe(SessionSubscribeEvent event){
String destination =
event.getMessage().getHeaders().get("simpDestination").toString();
Principal p = canSubscribeToThatEndpoint(destination,
event.getUser());
}
private Principal canSubscribeToThatEndpoint(String dest, Principal
user){
if(dest.containt("some invalid thing")){
//close the session from server-side
}else return user;
}
I already tried to follow the mentioned StackOverflow Post but I couldn't get it to run. Also another method would be to send a message from the backend and trigger a disconnect Event in JS. But I think it would be convient(if there is a way) to access current client sessions in Backend and disconnect from them if needed.

okhttp3.internal.framed.StreamResetException: stream was reset: CANCEL

I'm getting secret from Azure KeyVault through rest api. At backend, I'm using the azure-keyvault-client which is using retrofit and okhttp3 at behind. My app has been running well for a long time. Now it shows up the exceptions suddenly. Each time the exceptions happens, I restarts the app. Then the exceptions are gone. Everything looks good. What will be the reason that "stream was cancelled"?
Below is the full stack trace.
I have tried to remote debug the app. I found that the exception is thrown when call FramedStream.closeInternal(). The HttpEngine tries to close but source.finished = false and sink.finished = true.
It could either be a local cancel of the stream, or from the server. You can use HTTP/2 Frame logging so see if it's coming from the server.
https://square.github.io/okhttp/debug_logging/#http2-frame-logging
Does the problem persist until restart? If so it's possible that the server is expecting you or OkHttp to close the connection after the cancel, but that isn't expected in that case. So best to debug the frames and then discuss with the server team.
https://github.com/square/okhttp/blob/master/okhttp-testing-support/src/main/kotlin/okhttp3/OkHttpDebugLogging.kt
fun enableHttp2() = enable(Http2::class)
fun enableTaskRunner() = enable(TaskRunner::class)
fun enable(loggerClass: String) {
val logger = Logger.getLogger(loggerClass)
if (configuredLoggers.add(logger)) {
logger.addHandler(ConsoleHandler().apply {
level = Level.FINE
formatter = object : SimpleFormatter() {
override fun format(record: LogRecord) =
String.format("[%1\$tF %1\$tT] %2\$s %n", record.millis, record.message)
}
})
logger.level = Level.FINEST
}
}

Examples of use ReactorNettyWebSocketClient

New Spring has some WebSocketClient example on Spring documentation.
WebSocketClient client = new ReactorNettyWebSocketClient();
client.execute("ws://localhost:8080/echo"), session -> {... }).blockMillis(5000);
But it is very short and not clear:
How to send a message to the server (subscribe to a channel)?
Then handle incoming stream and emit Flux messages?
Reconnect to the server when the connection is interrupted?
Could some one provide more complex example?
UPD.
I tried to do something like:
public Flux<String> getStreaming() {
WebSocketClient client = new ReactorNettyWebSocketClient();
EmitterProcessor<String> output = EmitterProcessor.create();
Flux<String> input = Flux.just("{ event: 'subscribe', channel: 'examplpe' }");
Mono<Void> sessionMono = client.execute(URI.create("ws://api.example.com/"),
session -> session
.send(input.map(session::textMessage))
.thenMany(session.receive().map(WebSocketMessage::getPayloadAsText).subscribeWith(output).then())
.then());
return output.doOnSubscribe(s -> sessionMono.subscribe());
}
But that returns only one message. Like I didnt get subscription.
I assume you are using an "echo" service. In order to get some messages from the service, you have to push them into the websocket and the service will "echo" them back to you.
In your example code you are writing only a single element to the websocket. As soon as you push more messages into the socket you will get more back.
I adapted the code to connect to ws://echo.websocket.org instead of a local service. When you browse to /stream you see every second a new message appear.
#GetMapping(path = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> getStreaming() throws URISyntaxException {
Flux<String> input = Flux.<String>generate(sink -> sink.next(String.format("{ message: 'got message', date: '%s' }", new Date())))
.delayElements(Duration.ofSeconds(1));
WebSocketClient client = new ReactorNettyWebSocketClient();
EmitterProcessor<String> output = EmitterProcessor.create();
Mono<Void> sessionMono = client.execute(URI.create("ws://echo.websocket.org"), session -> session.send(input.map(session::textMessage))
.thenMany(session.receive().map(WebSocketMessage::getPayloadAsText).subscribeWith(output).then()).then());
return output.doOnSubscribe(s -> sessionMono.subscribe());
}
Hope this helps...
The documentation link above is to the temporary docs from before Spring Framework 5 was released. Currently the reference provides more information about implementing a WebSocketHandler.

Spring 4 WebSockect over STOMP Authentication

I'm developing a multiplayer game based on Spring 4 WebSocket.
my server is stateless so in order to identify players i use tokens.
after struggling for sometime with how to identify players over WebSockets i came up with this solution: on the client player registers like this:
var sockjs = new SockJS("http://mygame/games/", null, {server : token});
this adds the token to the url, I have set up a filter using spring security:
String requestURI = request.getRequestURI();
String[] parts = StringUtils.split(requestURI, "/");
if (parts.length == 4) {
String token = parts[1];
List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority(Role.ROLE_MULTIPLAYER)));
SecurityContextHolder.getContext().setAuthentication(new PreAuthenticatedAuthenticationToken(token, "MULTIPLAYER", authorities));
}
and it works! in all WebSockets requests i have a Principal set.
However some browsers seems to not support this, in Safari for example the Principal is not set, when debugging the request i see that the URL is correct and the filter works but the Principal is not set. same goes for IE, Chrome and FF works.
I'm using STOMP (https://github.com/jmesnil/stomp-websocket) as a messege protocol.
why is there a different behaviour between the browsers?
is it a Spring or Client issue?

WebClient NotFound error but working with HttpWebRequest/Response

In my WinPhone app I'm accessing a REST service.
At the beginnings I was using this code:
WebClient wc = new WebClient();
wc.Credentials = credentials;
wc.Headers["App-Key"] = appKey;
wc.DownloadStringCompleted +=
(o, args) => MessageBox.Show(args.Error == null ? "OK" : "Error");
wc.DownloadStringAsync(uri);
but it suddenly stopped working returning me a "The remote server returned an error: NotFound" error. After a google session and some clicks in the control panel, I didn't get it to work.
I decided to try this other way:
HttpWebRequest request = HttpWebRequest.CreateHttp(uri);
request.Credentials = credentials;
request.Headers["App-Key"] = appKey;
request.BeginGetResponse(asResult =>
{
var response = request.EndGetResponse(asResult) as HttpWebResponse;
StreamReader reader = new StreamReader(response.GetResponseStream());
string responseString = reader.ReadToEnd();
Dispatcher.BeginInvoke(
() => MessageBox.Show(response.StatusCode.ToString()));
}, null);
and it works.
I also tried to run the first snipped pointing the URI to google's home page and it works (I had to remove the credentials, of course).
Can anyone explain what's going on?
UPDATE
I managed to get it working by replacing the
wc.Credentials = new NetworkCredentials(username, password);
with
wc.Headers["Authorization"] = "Basic someBase64encodedString";
but i still wonder what happened and which are the differences between the first and the second line.
PS: the test URI is: https://api.pingdom.com/api/2.0/checks but you will need an app-key from them.
When using the Credentials property, the HttpWebRequest implementation will wait the challenge response from server before to send the 'Authorization' header value.
But this can be an issue in some cases, so you have to force Basic authentication by providing directly the Authorization header.
Example when using a REST Client library like Spring.Rest :
RestTemplate template = new RestTemplate("http://example.com");
template.RequestInterceptors.Add(new BasicSigningRequestInterceptor("login", "password"));
string result = template.GetForObject<string>(uri);

Resources