So I'm trying to implement a publisher/subscriber pattern in JAX-RS however it seems that after the subscriber subscribes the publisher cannot find the subscription.
Server Code:
#GET
#Path("{id}/subscribe")
public void subscribe(#Suspended AsyncResponse asyncResponse, #PathParam("id") Long id) {
if (responses.containsKey(id)) {
responses.get(id).add(asyncResponse);
} else {
List<AsyncResponse> newList = new ArrayList<>();
newList.add(asyncResponse);
responses.put(id, newList);
}
System.out.println(responses.size());
}
#POST
#Path("{id}/publish")
#Consumes(MediaType.TEXT_PLAIN)
public void publish(String message, #PathParam("id") Long id) {
System.out.println(responses.size());
List<AsyncResponse> responseList = responses.get(id);
if (responseList == null) {
return;
}
for (AsyncResponse response : responseList) {
response.resume(message);
}
responseList.clear();
}
Client Code:
public void subscribeToConcertNews(ConcertDTO concertDTO) {
Response response = null;
String url = CONCERT_URL + "/" + concertDTO.getId() + "/subscribe";
ClientBuilder.newClient().target(url)
.request()
.async()
.get(new InvocationCallback<String>() {
#Override
public void completed(String s) {
System.out.println(s);
_client.target(CONCERT_URL + "/" + concertDTO.getId() + "/subscribe")
.request()
.async()
.get(this);
}
#Override
public void failed(Throwable throwable) {
throw new ServiceException("");
}
});
}
public void publishToConcertNews(ConcertDTO concertDTO, String message) {
Response response = _client.target(CONCERT_URL + "/" + concertDTO.getId() + "/publish")
.request()
.post(Entity.entity("News!", MediaType.TEXT_PLAIN));
}
Testing Code:
ConcertDTO concertDTO = new ConcertDTO(1L, "123", new HashSet<>(), new HashMap<>(), new HashSet<>());
_service.subscribeToConcertNews(concertDTO);
_service.publishToConcertNews(concertDTO, "213123");
After the subscription, the size of the map is 1, however news is attempted to be published it reads that the size of the map to hold the responses is 0. So the AsyncResponse stored in the map is disappearing. Any help would be appreciated!
Related
What I want to achieve
I want to get my string variable I am using as #DestinationVariable, called lobbyName, when socket disconnects using #EventListener on server side:
#Component
public class WebSocketEventListener {
private SimpMessageSendingOperations messagingTemplate;
public WebSocketEventListener(SimpMessageSendingOperations messagingTemplate) {
this.messagingTemplate = messagingTemplate;
}
#EventListener
public void handleWebSocketDisconnectListener(SessionDisconnectEvent event) {
//here I want to get my data
}
}
My problem
I have been trying to get lobbyName using SessionDisonnectEvent but I don't know how, when and where to put this lobbyName in order to have it in SessionDisconnectEvent.
What I have been trying
On Server Side:
#Controller
public class WebSocketController {
private final SimpMessagingTemplate template;
WebSocketController(SimpMessagingTemplate template) {
this.template = template;
}
public void pokeLobby(#DestinationVariable String lobbyName, SocketMessage message) {
// This didn't work
// Map<String, Object> headers = new HashMap<>();
// headers.put("lobbyName", lobbyName);
// this.template.convertAndSend("/lobby/"+lobbyName.toLowerCase(), message, headers);
this.template.convertAndSend("/lobby/"+lobbyName.toLowerCase(), message);
}
}
Is it possible to do on client side? :
connectToLobbyWebSocket(lobbyName: string): void {
const ws = new SockJS(this.addressStorage.apiAddress + '/socket');
this.stompClient = Stomp.over(ws);
// this.stompClient.debug = null;
const that = this;
this.stompClient.connect({}, function () {
that.stompClient.subscribe('/lobby/' + lobbyName, (message) => {
if (message.body) {
that.socketMessage.next(message.body); //client logic
}
});
});
}
EDIT (progress)
Since I can easily get sessionId on SessionDisconnectEvent I have decided to change sessionId (upon handshake) to something like playerId:lobbyName:uuid
I don't feel very comfortable with this solution so if you have any suggestions I am all ears.
const ws = new SockJS(this.addressStorage.apiAddress + '/socket', null, {
sessionId: function (): string {
return that.authManager.playerId + ':' + lobbyName + ':' + uuid();
}
});
You can send lobbyName in the body of the message as attribute and get it in the listner like this :
#EventListener
public void handleWebSocketDisconnectListener(SessionDisconnectEvent event) {
StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(event.getMessage());
String lobbyName = (String) headerAccessor.getSessionAttributes().get("lobbyName");
if(lobbyName != null) {
SocketMessage message = new SocketMessage();
messagingTemplate.convertAndSend("/topic/public/"+lobbyName, message);
}
}
I have simple Vertx-based websocket chatting app. It consists of two parts MsgServerVerticle and MsgClientVerticle (source code below). So, if I am instantiating one server and only one client it looks like working normally. After second client connects, server starts trying to announce it to other clients. And things gonna weird. Log says that netty backed are encoding-decoding websocket frames continuously in loop. There is no difference what type of frames I am using, binary or text, issues are the same.
log screenshot here
What's wrong?
MsgClientVerticle Source code:
private Logger L;
private String eBusTag;
private String backwardTag;
private String targetHost;
private int port;
private String id;
private String path;
private EventBus eBus;
private HttpClient client;
public MsgClientVerticle(String eBusTag, String targetHost, int port, String path, String id, String backwardTag) {
this.eBusTag = eBusTag;
this.targetHost = targetHost;
this.path = path;
this.port = port;
this.id = id;
this.backwardTag = backwardTag;
L = LoggerFactory.getLogger(eBusTag);
}
#Override
public void start(Future<Void> startFuture) throws Exception {
L.info("Initializing client connection to " + targetHost + ":" + port + path);
eBus = vertx.eventBus();
try {
client = vertx.createHttpClient();
client.websocket(port, targetHost, path, webSock -> {
L.info("Connected to " + targetHost + ":" + port + "/" + path);
eBus.publish(backwardTag, Utils.msg("Connected"));
webSock.binaryMessageHandler(buf -> {
eBus.publish(backwardTag, Utils.bufToJson(buf));
});
eBus.consumer(eBusTag).handler(msg -> {
JsonObject message = (JsonObject) msg.body();
webSock.writeBinaryMessage(Utils.jsonToBuf(message));
});
});
} catch (NullPointerException e) {
L.error("Null Pointer: " + e.getLocalizedMessage());
e.printStackTrace();
}
startFuture.complete();
}
#Override
public void stop(Future<Void> stopFuture) throws Exception {
L.info("Connection to " + targetHost + ":" + port + "/" + path + " closed");
client.close();
stopFuture.complete();
}
And MsgServerVerticle source:
private Logger L;
private String path;
private int port;
private String eBusTag;
private String backwardTag;
private HttpServer server;
private EventBus eBus;
private Set<ServerWebSocket> conns;
public MsgServerVerticle(int port, String eBusTag, String backwardTag) {
this.port = port;
this.eBusTag = eBusTag;
this.backwardTag = backwardTag;
conns = new ConcurrentSet<>();
path = eBusTag;
L = LoggerFactory.getLogger(eBusTag);
}
#Override
public void start(Future<Void> startFuture) throws Exception {
eBus = vertx.eventBus();
L.info("Initializing server instance at port " + port);
server = vertx.createHttpServer();
server.websocketHandler(webSock -> {
if (!webSock.path().equals(path)) {
webSock.reject();
} else {
conns.add(webSock);
conns.forEach(sock -> {
if (sock != webSock) {
sock.writeBinaryMessage(Utils.jsonToBuf(Utils.msg("SERVER: new client " + webSock.remoteAddress().toString())));
}
});
eBus.publish(backwardTag, Utils.msg("SERVER: new client " + webSock.remoteAddress().toString()));
webSock.binaryMessageHandler(buf -> {
JsonObject msg = Utils.bufToJson(buf);
conns.forEach(sock -> {
if (sock != webSock) {
sock.writeBinaryMessage(buf);
}
});
eBus.publish(backwardTag, msg);
});
}
});
server.listen(port);
startFuture.complete();
}
#Override
public void stop(Future<Void> stopFuture) throws Exception {
conns.forEach(sock -> {
sock.writeFinalTextFrame("Server is shutting down...");
});
server.close();
stopFuture.complete();
}
I wasn't able to reproduce your original problem. But I had to make a few changes in the first place to test it.
One change is in initialization of your server:
this.path = "/" + eBusTag;
Otherwise this check will always fail:
if (!webSock.path().equals(this.path)) {
Since websock.path() will always start with /, hence /anything=/=anything
Second, please take a look how I initialized the clients, and check if you do the same:
final Vertx vertx = Vertx.vertx();
vertx.deployVerticle(new MsgServerVerticle(8080,
"ebus",
"back"), new DeploymentOptions().setWorker(true), (r) -> {
vertx.deployVerticle(new MsgClientVerticle("ebus",
"127.0.0.1",
8080,
"/ebus",
"a",
"back"), new DeploymentOptions().setWorker(true), (r2) -> {
vertx.deployVerticle(new MsgClientVerticle("ebus",
"127.0.0.1",
8080,
"/ebus",
"b",
"back"), new DeploymentOptions().setWorker(true), (r3) -> {
System.out.println("Done");
});
});
});
And third, as far as I could understand, your Utils class is something you implemented. My implementation looks as follows:
public class Utils {
public static Buffer jsonToBuf(final JsonObject message) {
return message.toBuffer();
}
public static JsonObject bufToJson(final Buffer buf) {
return buf.toJsonObject();
}
public static JsonObject msg(final String msg) {
return new JsonObject("{\"value\":\"" + msg + "\"}");
}
}
Hope that helps pinpoint your problem.
In my app I make post request to the server with a special code inside the body. Then I should get some information in the response. However, I always get the name of the response class.
My request code:
#POST("/accounts/login/vk-oauth2/")
Call<RegistrationProcessCodeResponse> postCode(#Body CodePostRequest code);
My ResponseClass:
public class RegistrationProcessCodeResponse {
private String message;
private String partial_token;
private String phase;
public String getMessage() {
return message;
}
public String getPartial_token() {
return partial_token;
}
public String getPhase() {
return phase;
}
public void setMessage(String message) {
this.message = message;
}
public void setPartial_token(String partial_token) {
this.partial_token = partial_token;
}
public void setPhase(String phase) {
this.phase = phase;
}
}
My request code:
HseAlumniApi hseAlumniApi = HseAlumniApi.retrofit.create(HseAlumniApi.class);
Call<RegistrationProcessCodeResponse> postComment = hseAlumniApi.postCode(codePostRequest);
postComment.enqueue(new Callback<RegistrationProcessCodeResponse>() {
#Override
public void onResponse(Call<RegistrationProcessCodeResponse> call, Response<RegistrationProcessCodeResponse> response) {
Log.d("myLogs", "String.valueOf(response.code())\n" + String.valueOf(response.code()));
Log.d("myLogs", "response.body().toString()\n" + response.body().toString());
if (response.isSuccessful()) {
Log.d("myLogs", "Request succeeded");
}
}
#Override
public void onFailure(Call<RegistrationProcessCodeResponse> call, Throwable t) {
Log.d("myLogs", "Request failed");
}
});
My logs:
D/myLogs: String.valueOf(response.code())
200
D/myLogs: response.body().toString()
com.example.vitaly.hsealumni.RegistrationProcessCodeResponse#498e7e7
D/myLogs: Request succeeded
Response Json:
{
"message": "email needed",
"partial_token": "231445d4fc5a4ed99dccb681942d5d7e",
"phase": 1
}
I really have no idea what to do, help please
public class RegistrationProcessCodeResponse {
private String message;
private String partial_token;
private String phase;
public RegistrationProcessCodeResponse() {
message = "";
partial_token = "";
phase = "";
}
// getters and setters
#Override
public String toString() {
return "RegistrationProcessCodeResponse{" +
"message='" + message + '\'' +
", partial_token='" + partial_token + '\'' +
", phase='" + phase + '\'' +
'}';
}
}
I'm trying to tie Google's Firebase Messaging platform into my app, and I'm trying to use Spring's built in RestTemplate REST abstraction to simplify it.
I'm currently trying to:
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new GsonHttpMessageConverter());
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
headers.add("Authorization", "key=" + Constants.FIREBASE_SERVER_KEY);
headers.add("Content-Type", "application/json");
HttpEntity<FireBasePost> entity = new HttpEntity<>(fbp, headers);
URI uri;
uri = new URI(firebaseApi);
FireBaseResponse fbr = restTemplate.postForObject(uri, entity, FireBaseResponse.class);
The FireBasePost object just contains the required fields for the POST Message API: Firebase API - and I have verified the request Entity works by posting with String.class, so the response is unmarshalled JSON.
However, with trying to get the response to marshall directly into the FireBaseResponse object, the call to postForObject hangs and never returns.
#JsonIgnoreProperties(ignoreUnknown = true)
public class FireBaseResponse {
public Integer multicast_id;
public Integer success;
public Integer failure;
public Integer canonical_ids;
public FireBaseResponse() {}
}
I'm having trouble understanding why this call never completes. I would love to be able to have the response directly into an object.
try like this:
package yourpackage;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
#JsonIgnoreProperties(ignoreUnknown = true)
public class FirebaseResponse {
private long multicast_id;
private Integer success;
private Integer failure;
private Object canonical_ids;
public FirebaseResponse() {
}
//---- use this one ----
public boolean is_success() {
if (getSuccess() == 1) {
return true;
} else {
return false;
}
}
public long getMulticast_id() {
return multicast_id;
}
public void setMulticast_id(long multicast_id) {
this.multicast_id = multicast_id;
}
public Integer getSuccess() {
return success;
}
public void setSuccess(Integer success) {
this.success = success;
}
public Integer getFailure() {
return failure;
}
public void setFailure(Integer failure) {
this.failure = failure;
}
public Object getCanonical_ids() {
return canonical_ids;
}
public void setCanonical_ids(Object canonical_ids) {
this.canonical_ids = canonical_ids;
}
#Override
public String toString() {
return "FirebaseResponse{" +
"multicast_id=" + multicast_id +
", success=" + success +
", failure=" + failure +
", canonical_ids=" + canonical_ids +
'}';
}
}
//--------------- USAGE ------------------
ArrayList<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
interceptors.add(new HeaderRequestInterceptor("Authorization", "key=" + FIREBASE_SERVER_KEY));
interceptors.add(new HeaderRequestInterceptor("Content-Type", "application/json"));
restTemplate.setInterceptors(interceptors);
JSONObject body = new JSONObject();
// JsonArray registration_ids = new JsonArray();
// body.put("registration_ids", registration_ids);
body.put("to", "cfW930CZxxxxxxxxxxxxxxxxxxxxxxxxxxipdO-bjHLacHRqQzC0aSXlRFKdMHv_aNBxkRZLNxxxxxxxxxxx59sPW4Rw-5MtwKkZxxxxxxxgXlL-LliJuujPwZpLgLpji_");
body.put("priority", "high");
// body.put("dry_run", true);
JSONObject notification = new JSONObject();
notification.put("body", "body string here");
notification.put("title", "title string here");
// notification.put("icon", "myicon");
JSONObject data = new JSONObject();
data.put("key1", "value1");
data.put("key2", "value2");
body.put("notification", notification);
body.put("data", data);
HttpEntity<String> request = new HttpEntity<>(body.toString());
FirebaseResponse firebaseResponse = restTemplate.postForObject("https://fcm.googleapis.com/fcm/send", request, FirebaseResponse.class);
log.info("response is: " + firebaseResponse.toString());
return new ResponseEntity<>(firebaseResponse.toString(), HttpStatus.OK);
//--------------- HELPER CLASS ------------------
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.client.support.HttpRequestWrapper;
import java.io.IOException;
public class HeaderRequestInterceptor implements ClientHttpRequestInterceptor {
private final String headerName;
private final String headerValue;
public HeaderRequestInterceptor(String headerName, String headerValue) {
this.headerName = headerName;
this.headerValue = headerValue;
}
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
HttpRequest wrapper = new HttpRequestWrapper(request);
wrapper.getHeaders().set(headerName, headerValue);
return execution.execute(wrapper, body);
}
}
I'm trying to retrieve a JSON object of a request using RxJava!
In my example I have a restful service Java that works perfectly in browser.
//Object Data
#XmlRootElement
public class Person {
private String firstName;
private String lastName;
//getters and setters
}
Restful Java
#Path("/persons")
public class PersonResource {
#GET
#Produces(MediaType.APPLICATION_JSON)
public List<Person> getBandas() {
Person paulo = new Person();
paulo.setFirstName("Paulo Henrique");
paulo.setLastName("Pereira Santana");
//
List<Person> persons = new ArrayList<>();
persons.add(paulo);
return persons;
}
}
as a result (in browser : http://localhost:8080/learn-java-rs/persons) have a JSON object:
{"Person": {
"firstName": "Paulo Henrique",
"lastName": "Pereira Santana"
}
}
I tried to make the same request using RxJava not worked (or did not understand the implementation). Follow:
public class Example {
public static void main(String[] args) {
// TODO Auto-generated method stub
try(CloseableHttpAsyncClient client = HttpAsyncClients.createDefault()) {
client.start();
Observable<Map> requestJson = requestJson(client, "http://localhost:8080/learn-java-rs/persons");
Helpers.subscribePrint(requestJson.map(json -> json.get("firstName") + " " + json.get("lastName")), "person");
} catch (IOException e1) {
e1.printStackTrace();
}
}
private static Map<String, Set<Map<String, Object>>> cache = new ConcurrentHashMap<>();
#SuppressWarnings({"rawtypes","unchecked"})
private static Observable<Map> requestJson(HttpAsyncClient client,String url){
Observable<String> rawResponse=ObservableHttp.createGet(url,client).toObservable().flatMap(resp -> resp.getContent().map(bytes -> new String(bytes,java.nio.charset.StandardCharsets.UTF_8))).retry(5).cast(String.class).map(String::trim).doOnNext(resp -> getCache(url).clear());
Observable<String> objects=rawResponse.filter(data -> data.startsWith("{")).map(data -> "[" + data + "]");
Observable<String> arrays=rawResponse.filter(data -> data.startsWith("["));
Observable<Map> response=arrays.ambWith(objects).map(data -> {
return new Gson().fromJson(data,List.class);
}
).flatMapIterable(list -> list).cast(Map.class).doOnNext(json -> getCache(url).add((Map<String,Object>)json));
return Observable.amb(fromCache(url),response);
}
private static Observable<Map<String, Object>> fromCache(String url) {
return Observable.from(getCache(url)).defaultIfEmpty(null)
.flatMap(json -> (json == null) ? Observable.never() : Observable.just(json))
.doOnNext(json -> json.put("person", true));
}
private static Set<Map<String, Object>> getCache(String url) {
if (!cache.containsKey(url)) {
cache.put(url, new HashSet<Map<String,Object>>());
}
return cache.get(url);
}
Edit
public static <T> Subscription subscribePrint(Observable<T> observable,
String name) {
return observable.subscribe(
(v) -> System.out.println(Thread.currentThread().getName()
+ "|" + name + " : " + v), (e) -> {
System.err.println("Error from " + name + ":");
System.err.println(e);
System.err.println(Arrays
.stream(e.getStackTrace())
.limit(5L)
.map(stackEl -> " " + stackEl)
.collect(Collectors.joining("\n"))
);
}, () -> System.out.println(name + " ended!"));
}
running nothing happens.
Someone could tell me what I'm missing?
Note: I used Rxjava api 1.1.0 and rxapache-http-0.21.0