Vertx event bus interceptor blocking the request call - java

I am trying to send data over event bus when got the call from client and response back to client.
Everything is working fine until i add the interceptor on event bus..
Here is the Code:-
public class TestVerticle extends AbstractVerticle {
#Override
public void start(Promise<Void> promise) {
Router router = Router.router(vertx);
vertx.exceptionHandler(globalExceptionHandler -> {
System.out.println("Exception not handled in application : " + globalExceptionHandler.getCause());
});
vertx.eventBus().consumer("test", handler -> {
System.out.println("Message receive form event bus : " + handler.body().toString());
handler.reply(handler.body().toString());
});
router.get().handler(this::rootHandler);
vertx.createHttpServer().requestHandler(router).listen(8080, resultHandler -> {
if (resultHandler.succeeded()) {
promise.complete();
} else {
promise.fail(resultHandler.cause());
}
});
vertx.eventBus().addOutboundInterceptor(handler -> {
System.out.println("Outbound data : "+handler.body().toString());
});
// vertx.eventBus().addInboundInterceptor(handler -> {
// System.out.println("Inbound data : " + handler.body().toString());
// });
}
private void rootHandler(RoutingContext routingContext) {
JsonObject msg = new JsonObject().put("path", routingContext.request().path());
vertx.eventBus().request("test","Hello from Application",reply -> this.replyHandler(routingContext,reply));
}
private void replyHandler(RoutingContext ctx, AsyncResult<Message<Object>> reply) {
HttpServerResponse response = ctx.response()
.putHeader("Content-Type", "application/json");
System.out.println("Reply from event bus : " +reply.result().body().toString());
if (reply.succeeded()) {
response.setStatusCode(200)
.setStatusMessage("OK")
.end(reply.result().body().toString());
} else {
response.setStatusCode(500)
.setStatusMessage("Server Error")
.end(new JsonObject().put("error", reply.cause().getLocalizedMessage()).encodePrettily());
}
}
public static void main(String[] args) {
Vertx.vertx().deployVerticle(new TestVerticle(), deploymenHandler -> {
if (deploymenHandler.succeeded()) {
System.out.println("verticle deplyed");
} else {
System.out.println("failed");
}
});
}
#Override
public void stop(Promise<Void> promise) {
System.out.println("Exiting verticle");
promise.complete()
}
}
One more doubt is when I stop the application from IDE the Stop method is not called, but If I undeploy this verticle from another verticle that is working fine.

When you are using eventbus interceptors you will have to call the next() method in the handler otherwise you will get stuck there.
vertx.eventBus().addOutboundInterceptor(handler -> {
System.out.println("Outbound data : "+handler.body().toString());
handler.next();
});
vertx.eventBus().addInboundInterceptor(handler -> {
System.out.println("Inbound data : "+handler.body().toString());
handler.next();
});

Related

How to stop and start a PubSub programatically through the MessageReceiver in Java

Is it possible to pause and start a GCP PubSub Subscriber(pull) programatically using Java?
I have the following code for the MessageReceiver:
MessageReceiver receiver = (message, consumer) -> {
System.out.println("Received message: " + message.getData().toStringUtf8() + " at " + LocalDateTime.now());
Mono<Void> mono = Mono.just(message.getData().toStringUtf8())
.flatMap(deserializePayload())
.doOnSuccess(creditNoteRequestDTO -> configureMDC(message.getAttributesMap(), creditNoteRequestDTO.getOrderNumber()))
.doOnError(error -> log.error("Problem while parsing CreditNoteRequest DTO {}", message.getData().toStringUtf8(), error))
.map(creditNoteRequestMapper::mapToCreditNoteRequest)
.flatMap(creditNoteRequestProcess::process)
.doOnError(throwable -> {
if (throwable instanceof CallNotPermittedException) ConstantUtils.ENABLED_SUBSCRIBER = false;
consumer.nack();
})
.doOnSuccess(unused -> {
log.info("Doing ACK on payload {}", message.getData().toStringUtf8());
consumer.ack();
});
if (Boolean.TRUE.equals(ConstantUtils.ENABLED_SUBSCRIBER)) {
mono.subscribe();
} else {
System.err.println("Subscription has been disabled.");
//mono.subscribe().dispose();
try {
System.err.println("PRE: " + LocalDateTime.now() + " " + ConstantUtils.ENABLED_SUBSCRIBER);
TimeUnit.SECONDS.sleep(15);
ConstantUtils.ENABLED_SUBSCRIBER = true;
System.err.println("POST: " + LocalDateTime.now() + " " + ConstantUtils.ENABLED_SUBSCRIBER);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
And the following for the subscriber:
Subscriber subscriber = null;
FlowControlSettings controlSettings = FlowControlSettings
.newBuilder()
.setMaxOutstandingElementCount(2000L)
.build();
subscriber = Subscriber
.newBuilder(subscription, receiver)
.setFlowControlSettings(controlSettings)
.build();
// Start the subscriber.
subscriber.startAsync().awaitRunning();
System.out.printf("Listening for messages on %s:\n", subscription);
System.out.printf("Max elements count: %s\n", subscriber.getFlowControlSettings().getMaxOutstandingElementCount());
// Allow the subscriber to run for 45s unless an unrecoverable error occurs.
Subscriber finalSubscriber = subscriber;
new java.util.Timer().schedule(
new java.util.TimerTask() {
#Override
public void run() {
System.err.println("Subscriber has been shut down.");
finalSubscriber.stopAsync();
}
},
120000
);
It is somehow possible to use finalSubscriber.stopAsync(); on the MessageReceiver or pause the messageReceiver?
UPDATE: Thanks, I managed to stop it, but how do I restart it? Now, when I try to restart, nothing happens.
private void createSubscriber() {
ProjectSubscriptionName subscription = ProjectSubscriptionName.of("txd-boss-dev", "circuit-breaker-test-sub");
this.subscriber = Subscriber.newBuilder(subscription, getMessageReceiver()).build();
}
private void runSubscriber(boolean start) {
if(start) {
try {
this.subscriber.startAsync().awaitRunning();
System.out.printf("Listening for messages on %s:\n", this.subscriber.getSubscriptionNameString());
subscriber.awaitTerminated(240, TimeUnit.SECONDS);
} catch (TimeoutException e) {
System.err.println("Shutting down subscription.");
runSubscriber(false);
}
} else {
subscriber.stopAsync();
}
}
#EventListener(ContextRefreshedEvent.class)
public void test() {
createSubscriber();
runSubscriber(true);
}
You need to return the same subscriber object to start and stop it:
check some google examples here.
here is a sketch (adapt for your class):
private Subscriber StartSubscriber() {
Subscriber subscriber = null;
try {
subscriber = Subscriber.newBuilder(subscriptionName, receiver).build();
subscriber.startAsync().awaitRunning();
System.out.printf("Listening for messages on %s:\n", subscriptionName.toString());
subscriber.awaitTerminated(30, TimeUnit.SECONDS);
return subscriber;
} catch (TimeoutException timeoutException) {
// Shut down the subscriber after 30s. Stop receiving messages.
System.out.printf("Error \n");
subscriber.stopAsync();
}
}
private void StopSubscriber(Subscriber subscriber) {
System.out.printf("Stoping subscriber\n");
subscriber.stopAsync();
}
public void test() {
Subscriber subscriber = StartSubscriber();
StopSubscriber(subscriber);
}

how to implement WebSocket client reconnect after some time in Vertx?

I'm connecting websocket server using vertx, but when connection closed/Server went down(temporarily) try to reconnect after some interval.
WebSocket code is :
public void start() throws Exception {
HttpClient client = vertx.createHttpClient();
MultiMap form = MultiMap.caseInsensitiveMultiMap();
if(MapUtils.isNotEmpty(header)) {
header.forEach((k,v)-> {
form.set(k, v);
});
}
RequestOptions options = new RequestOptions()
.setHost(host)
.setPort(port)
.setSsl(sslFlag)
.setURI(uri);
Handler<WebSocket> socket = new Handler<WebSocket>() {
#Override
public void handle(WebSocket event) {
event.handler(data -> {
currentCount = 0;
LOGGER.info("Received data " + data.toString("ISO-8859-1"));
});
}
};
Handler<Throwable> exception = new Handler<Throwable>() {
#Override
public void handle(Throwable event) {
LOGGER.error("Exception occured in Websocket connection, retrying count is {}", currentCount, event);
}
};
client.websocket(options, form, socket , exception);
}
Above code Port, Host, etc are configured. Please any one suggest how to reconnect?
You can close the client and restart it:
public void start() {
startClient(this.vertx);
}
private void startClient(Vertx vertx) {
HttpClient client = vertx.createHttpClient();
client.webSocket(8080, "localhost", "/", (ar) -> {
if (ar.succeeded()) {
WebSocket ctx = ar.result();
ctx.textMessageHandler((msg) -> {
System.out.println("Client " + msg);
ctx.writeTextMessage("pong");
}).exceptionHandler((e) -> {
System.out.println("Restarting in 10 seconds");
client.close();
vertx.setTimer(10 * 1000, (__) -> {
startClient(vertx);
});
});
} else {
System.out.println(ar.cause());
}
});
}
Edited: Any exception when connecting time also retry will happen in below code.
private void startClient(Vertx vertx) {
HttpClient client = vertx.createHttpClient();
MultiMap form = MultiMap.caseInsensitiveMultiMap();
if(MapUtils.isNotEmpty(header)) {
header.forEach((k,v)-> {
form.set(k, v);
});
}
RequestOptions options = new RequestOptions()
.setHost(host)
.setPort(port)
.setSsl(sslFlag)
.setURI(uri);
client.websocket(options, form, (ctx) ->{
ctx.textMessageHandler( msg -> {
System.out.println("Client " + msg);
}).exceptionHandler((e) -> {
client.close();
vertx.setTimer(waitTime * 10 * 1000, (__) -> {
startClient(vertx);
});
});
}, (exec) -> {
vertx.setTimer(waitTime * 10 * 1000, (__) -> {
startClient(vertx);
});
});
}

vertx failed future but treated as succeeded()

I have a method:
public void getVmsAdminToken(HttpClient httpClient, handler<AsyncResult<String>> handler) {
httpClient.postAbs(url, h -> h.bodyHandler(bh -> {
try {
switch (h.statusCode()) {
case 200:
JsonObject vmsResponse = bh.toJsonObject();
handler.handle(Future.succeededFuture(Json.encode(vmsResponse)));
break;
default:
LOG.error("VMS call failed {}", h.statusCode());
handler.handle(Future.failedFuture(500 + ""));
break;
}
} catch (Throwable t) {
LOG.error("Exception in getVmsAdminToken", t);
handler.handle(Future.failedFuture(500 + ""));
}
}))
.setTimeout(timeOutMs)
.putHeader("content-type", "application/json")
.putHeader("stub", apiKey)
.end(vehicleReqBody.encode());
}
I use this inside the following method call :
private void getAdminToken(RoutingContext ctx, RedisFleetStorage storage, HttpClient httpClient) {
getVmsAdminToken(fleetId, user, vehicle, httpClient, replyVms -> {
if (reply.succeeded()) {
// why succeeded?!!
}
});
}
And even if the getVmsToken fails, the execution falls into the if (reply.succeeded())
Why might that be?
You should check the same AsyncResult object being the result of your HTTP call:
private void getAdminToken(RoutingContext ctx, RedisFleetStorage storage, HttpClient httpClient) {
getVmsAdminToken(fleetId, user, vehicle, httpClient, replyVms -> {
if (replyVms.succeeded()) {
// do you thing
}
});
}

RxJava error handling in chain API calls

I have playing with Rxjava recently trying to implement a chain of events(Api callas/Database operations) and seem to have hit a roadblock when it comes to handling errors.
This is what I am trying to do. I am calling an Api that will check if user exists in the database. Based on the response I get, I am trying to chain a few sequences using rxjava. Following diagram might explain a little better.
checkUser()
/ \
No Yes
/ \
createUserRemote() FetchUserNotesRemote()
| |
End SaveUserNotesLocal()
|
End
I am able to chain together checkUser() -> FetchUserNotesRemote() -> SaveUserNotesLocal() sequence with the following code.
checkUser()
.flatMap(id -> {return fetchData(id);})
.flatMap(notesResponseObject -> {return saveFetchedData(notesResponseObject);})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<Integer>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onSuccess(Integer integer) {
//handle onsuccess here
}
#Override
public void onError(Throwable e) {
//handle errors here
}
});
The issue I am mainly trying to solve.
I can't figure out how to handle a case where checkUser() returns
a 404 http status. Because when that happens, subscriber's onError
method gets called which seems to me is what should happen. How can I
handle it so that when I get an error (404) response from API,
instead of executing FetchUserNotesRemote() and SaveUserNotesLocal(),
I execute a different chain of events?
Another thing I am not sure about is, if there is an error called on
any of the observables in a chain, how does the subscriber's onError method know
which observable called it?
1) To execute different chain of observables on error you can use method onErorrResumeNext(). More info here: github.com/ReactiveX/RxJava/wiki/Error-Handling-Operators
Example:
checkUser().flatMap(id -> {return fetchData(id);})
.flatMap(notesResponseObject -> {return saveFetchedData(notesResponseObject);})
.onErrorResumeNext(throwable -> { return doSomethingDifferent(); }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<Integer>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onSuccess(Integer integer) {
//handle onsuccess here
}
#Override
public void onError(Throwable e) {
//handle errors here
}
});
2) If the exception is thrown somewhere in your stream, it is passed down to subscriber onError(). If you want to know at which part of stream error was thrown, you can add multiple onErorrResumeNext() calls, that throw concrete exception after each api call.
checkUser()
.onErrorResumeNext(throwable -> { return Observable.error(new CheckUserException()); }
.flatMap(id -> {return fetchData(id);})
.onErrorResumeNext(throwable -> { return Observable.error(new FetchDataException()); }
.flatMap(notesResponseObject -> {return saveFetchedData(notesResponseObject);})
.onErrorResumeNext(throwable -> { return Observable.error(new SaveDataException()); }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<Integer>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onSuccess(Integer integer) {
//handle onsuccess here
}
#Override
public void onError(Throwable e) {
//handle errors here
}
});
I completely forgot about this. But #mol pushed me in the right direction. My solution was a bit different. This may not be the best solution but it worked for me at the time.
I first created my own custom exception classes like following.
public class CreateUserLocalException extends Exception {
public CreateUserLocalException(String message) {
super(message);
}
}
Then in my checkUser() function I throw exception of type I created above like following.
public Single<String> checkUser(String id) {
return Single.create(new SingleOnSubscribe<String>() {
#Override
public void subscribe(SingleEmitter<String> emitter) throws Exception {
try {
GetUserResponseObject getUserResponseObject = apiClient.usersIdGet(id);
Log.d("Test", "checkUserCall: Status: " + getUserResponseObject.getStatus());
emitter.onSuccess(getUserResponseObject.getBody().getUserId());
} catch (AmazonServiceException e) {
Log.d("Test", "AmazonServiceException : " + e.getErrorMessage());
e.printStackTrace();
if (e.getErrorMessage().equals("timeout")) {
throw new SocketTimeoutException();
} else {
throw new CheckUserException(Integer.toString(e.getStatusCode()));
}
} catch (Exception e) {
e.printStackTrace();
throw new CheckUserException(Integer.toString(AppConstants.ERROR));
}
}
});
}
Then in my chain of calls, in the event of an error, onError(throwable) gets invoked where I am checking the instanceof Exception to identify what kind of exception occurred. Below is the code for chain of functions.
cloudSyncHelper.checkUser(user.getUser_id())
.retry(3, new Predicate<Throwable>() {
#Override
public boolean test(Throwable throwable) throws Exception {
Log.d("Test", throwable.toString());
if (throwable instanceof SocketTimeoutException) {
Log.d("Test", "Time out.. Retrying..");
return true;
}
return false;
}
})
.flatMap(s -> {
return cloudSyncHelper.createUserLocal(user)
.onErrorResumeNext(throwable -> {
Log.d("Test", "onErrorResumeNext, throwable message: " + throwable.getMessage());
if (throwable instanceof CreateUserLocalException) {
if (Integer.parseInt(throwable.getMessage()) == AppConstants.LOCAL_DB_DUPLICATE) {
return Single.just(user.getUser_id());
}
}
return Single.error(new CreateUserLocalException(Integer.toString(AppConstants.LOCAL_DB_ERROR)));
});
})
.flatMap(id -> {
return cloudSyncHelper.fetchData(id)
.retry(3, new Predicate<Throwable>() {
#Override
public boolean test(Throwable throwable) throws Exception {
Log.d("Test", throwable.toString());
if (throwable instanceof SocketTimeoutException) {
Log.d("Test", "Time out.. Retrying..");
return true;
}
return false;
}
});
})
.flatMap(notesResponseObject -> {
return cloudSyncHelper.saveFetchedData(notesResponseObject);
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver<Integer>() {
#Override
public void onSubscribe(Disposable d) {
}
#Override
public void onSuccess(Integer integer) {
//handle onsuccess here
googleSignInButton.setEnabled(true);
progressBar.setVisibility(View.GONE);
Log.d("Test", "onSuccess Called");
getSharedPreferences(AppConstants.AppName, MODE_PRIVATE).edit().putBoolean("isFirstRun", false).apply();
startActivity(new Intent(LoginScreen.this, HomeScreen.class));
}
#Override
public void onError(Throwable e) {
if (e instanceof SocketTimeoutException) {
googleSignInButton.setEnabled(true);
progressBar.setVisibility(View.GONE);
Log.d("Test", "Socket Time Out");
Utils.createToast(LoginScreen.this, "Socket timed out");
return;
}
int code = Integer.parseInt(e.getMessage());
Log.d("Test", "onError Called");
if (e instanceof CheckUserException) {
Log.d("Test", "onError CheckUserException");
if (code == AppConstants.NOTFOUND) {
newUserSequence(user);
} else {
googleSignInButton.setEnabled(true);
progressBar.setVisibility(View.GONE);
Utils.createToast(LoginScreen.this, "Unable to user information from cloud. Try again.");
}
}
if (e instanceof CreateUserLocalException) {
Log.d("Test", "onError CreateUserLocalException");
googleSignInButton.setEnabled(true);
progressBar.setVisibility(View.GONE);
}
if (e instanceof FetchDataException) {
Log.d("Test", "onError FetchDataException");
if (code == AppConstants.NOTFOUND) {
googleSignInButton.setEnabled(true);
progressBar.setVisibility(View.GONE);
getSharedPreferences(AppConstants.AppName, MODE_PRIVATE).edit().putBoolean("isFirstRun", false).apply();
startActivity(new Intent(LoginScreen.this, HomeScreen.class));
} else {
googleSignInButton.setEnabled(true);
progressBar.setVisibility(View.GONE);
Log.d("Test", "Unable to fetch data from cloud");
Utils.createToast(LoginScreen.this, "Unable to fetch data from cloud. Try again.");
}
}
if (e instanceof SaveDataLocalException) {
googleSignInButton.setEnabled(true);
progressBar.setVisibility(View.GONE);
Log.d("Test", "onError SaveDataLocalException");
if (code == AppConstants.LOCAL_DB_ERROR) {
Log.d("Test", "Unable to save data fetched from cloud");
Utils.createToast(LoginScreen.this, "Unable to save data fetched from cloud");
} else {
Utils.createToast(LoginScreen.this, "Unable to save data fetched from cloud");
}
}
}
});
Hope this helps.

Where I may wrong while connected to StompClient?

I need help with connect on android to my WebSocket server based on Spring boot. Source code of this server I have taken https://spring.io/guides/gs/messaging-stomp-websocket/
Everything works fine on server and browser client on this sample,
but if I use StompClient (https://github.com/NaikSoftware/StompProtocolAndroid) to connect on my socket I am getting mStompClient.isConnected() == false and conand mStompClient.send(...) doesn't send anything (?).
After few minutes socketweb server closes the connection and I get in my log: '~~ Stomp connection closed'.
Web server locates on Heroku cloud system.
There is my connecting code from android activity:
private StompClient mStompClient;
private void connectStomp(){
mStompClient = Stomp.over(WebSocket.class, "wss://myserver/gs-guide-websocket");
mStompClient.topic("/topic/greetings").subscribe(new Action1<StompMessage>() {
#Override
public void call(StompMessage stompMessage) {
Log.w(TAG, "== "+stompMessage.getPayload());
}
});
mStompClient.connect();
mStompClient.lifecycle().subscribe(new Action1<LifecycleEvent>() {
#Override
public void call(LifecycleEvent lifecycleEvent) {
switch (lifecycleEvent.getType()) {
case OPENED:
Log.w(TAG, "~~ Stomp connection opened");
break;
case ERROR:
Log.e(TAG, "~~ Error", lifecycleEvent.getException());
break;
case CLOSED:
Log.w(TAG, "~~ Stomp connection closed "+lifecycleEvent.getMessage());
break;
}
}
});
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_maps);
connectStomp();
}
// Send test request to server
public void onSend(View view){
Log.w(TAG,"onSend: click");
mStompClient.send("/app/hello","Test").subscribe(new Observer<Void>() {
#Override
public void onCompleted() {
Log.w(TAG, "~~~~ onCompleted");
}
#Override
public void onError(Throwable e) {
Log.w(TAG, "~~~~ onCompleted "+e.getMessage());
}
#Override
public void onNext(Void aVoid) {
Log.w(TAG, "~~~~ onNext ");
}
});
if (mStompClient.isConnected()){
mStompClient.send("/app/hello","test msg").subscribe();
Log.w("aaaa : ","onCreate: connected");
}
}
It would be my mistake but if I connect to my server socket with spring boot WebSocketStompClient everithing works fine:
private SockJsClient sockJsClient;
private WebSocketStompClient stompClient;
private final WebSocketHttpHeaders headers = new WebSocketHttpHeaders();
#Before
public void setup() {
List<Transport> transports = new ArrayList<>();
transports.add(new WebSocketTransport(new StandardWebSocketClient()));
this.sockJsClient = new SockJsClient(transports);
this.stompClient = new WebSocketStompClient(sockJsClient);
this.stompClient.setMessageConverter(new MappingJackson2MessageConverter());
}
#Test
public void getGreeting() throws Exception {
final CountDownLatch latch = new CountDownLatch(1);
final AtomicReference<Throwable> failure = new AtomicReference<>();
StompSessionHandler handler = new TestSessionHandler(failure) {
#Override
public void afterConnected(final StompSession session, StompHeaders connectedHeaders) {
session.subscribe("/topic/greetings", new StompFrameHandler() {
#Override
public Type getPayloadType(StompHeaders headers) {
return Greeting.class;
}
#Override
public void handleFrame(StompHeaders headers, Object payload) {
Greeting greeting = (Greeting) payload;
try {
System.out.println(greeting.getContent());
assertEquals("Hello, Spring!", greeting.getContent());
} catch (Throwable t) {
System.out.println(t.getMessage());
failure.set(t);
} finally {
session.disconnect();
latch.countDown();
}
}
});
try {
session.send("/app/hello", "Test");
} catch (Throwable t) {
failure.set(t);
latch.countDown();
}
}
};
this.stompClient.connect("wss://myserver/gs-guide-websocket", this.headers, handler, 443);
if (latch.await(10, TimeUnit.SECONDS)) {
if (failure.get() != null) {
throw new AssertionError("", failure.get());
}
}
else {
fail("Greeting not received");
}
}
private class TestSessionHandler extends StompSessionHandlerAdapter {
private final AtomicReference<Throwable> failure;
public TestSessionHandler(AtomicReference<Throwable> failure) {
this.failure = failure;
}
#Override
public void handleFrame(StompHeaders headers, Object payload) {
this.failure.set(new Exception(headers.toString()));
}
#Override
public void handleException(StompSession s, StompCommand c, StompHeaders h, byte[] p, Throwable ex) {
this.failure.set(ex);
}
Any ideas? Thanks a lot!
I used the same Library in order to connect with Stomp based web socket. There were some configurations on the server side. On the android side, I was using URL as starting with "ws://" and ending with "websocket" like "ws://" + SERVER_URL + "/websocket".
See this answer for server side https://stackoverflow.com/a/41751897/5392825

Categories

Resources