can't send object via websocket - java

So i'm trying to send an object from client->server & server->client with WebSocket. Sending object from client->server works fine, meanwhile server->client throw an exception
org.springframework.messaging.converter.MessageConversionException: Could not read JSON: Can not deserialize instance of java.lang.String out of START_OBJECT token
Here is the class i'm trying to send
#Data
#AllArgsConstructor
#NoArgsConstructor
public class TextMessage {
private String sender;
private String room;
private String message;
}
and this is the code on the client-side
public class TelepatiClient {
public static void main(String[] args) {
WebSocketClient client = new StandardWebSocketClient();
WebSocketStompClient stompClient = new WebSocketStompClient(client);
stompClient.setMessageConverter(new MappingJackson2MessageConverter());
stompClient.setTaskScheduler(new ConcurrentTaskScheduler());
String url = "ws://localhost:8000/connect";
StompSessionHandler handler = new TelepatiSessionHandler();
stompClient.connect(url, handler);
new Scanner(System.in).nextLine();
}
}
public class TelepatiSessionHandler extends StompSessionHandlerAdapter {
#Override
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
session.subscribe("/room/global", this);
session.send("/test", new TextMessage("test", "test", "test"));
}
#Override
public void handleFrame(StompHeaders headers, Object payload) {
System.out.println(payload.toString());
}
#Override
public void handleException(StompSession session, StompCommand command, StompHeaders headers, byte[] payload, Throwable exception) {
exception.printStackTrace();
super.handleException(session, command, headers, payload, exception);
}
}
and this is message controller on the server-side
#Controller
public class TelepatiController {
#MessageMapping("/test")
#SendTo("/room/global")
public TextMessage getMessage(TextMessage message) {
System.out.println("get message :" + message.toString());
return new TextMessage("test2", "test2", "test2");
}
}
i was able to run System.out.println("get message :" + message.toString());, but get message convertion exception on the client-side when returning new TextMessage("test2", "test2", "test2");. From my test before, returning a String object works fine, why returning TextMessage object not working? How can i send any object (in this case TextMessage) from server->client? Thanks!

Well the problem is the content. In this line:
stompClient.setMessageConverter(new MappingJackson2MessageConverter());
You indicate that the client uses a json converter. So, your client is always expecting a JSON object.
But in your test, in this line:
return new TextMessage("test2", "test2", "test2");
You are sending plain text. Due the StompClient is thrown an exception
org.springframework.messaging.converter.MessageConversionException
Because the message in text plain is not JSON object.

I hope someone helps you, I had the same problem, what I did was tell the topler handler, to which I subscribe, the type of payload that will return.
This is the handler of my stompClient:
public class TelepatiSessionHandler extends StompSessionHandlerAdapter {
#Override
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
session.send("/test", new TextMessage("test", "test", "test"));
}
#Override
public void handleException(StompSession session, StompCommand command, StompHeaders headers, byte[] payload, Throwable exception) {
exception.printStackTrace();
super.handleException(session, command, headers, payload, exception);
}
}
And this is the handler for the topic to which I subscribe
WebSocketClient client = new StandardWebSocketClient();
WebSocketStompClient stompClient = new WebSocketStompClient(client);
stompClient.setMessageConverter(new MappingJackson2MessageConverter());
stompClient.setTaskScheduler(new ConcurrentTaskScheduler());
String url = "ws://localhost:8000/connect";
StompSessionHandler handler = new TelepatiSessionHandler();
StompSession session = stompClient.connect(url, handler).get();
session.subscribe("/room/global", new StompFrameHandler() {
#Override
public Type getPayloadType(StompHeaders headers) {
return TextMessage.class;
}
#Override
public void handleFrame(StompHeaders headers, Object payload) {
TextMessage textMessage = (TextMessage) payload;
System.out.println(textMessage.toString());
}
});
Here is a complete example:
https://github.com/jaysridhar/spring-websocket-client/blob/master/src/main/java/sample/Application.java

Related

Springboot Websocket Client to read messages from a Websocket stream with different port

I am new to both springboot and websockets so please be gentle and this is my last question chance on this account. I have a websocket jar that sends messages from two urls ws:localhost/operations and ws:localhost/prices from 8080 port. My task is to read those messages. The Jar file streams messages like:
For operation:
"data":{ "description":"lorem ipsum", "id":"OJ1136453723" },
"type":"DELETE"
For price:
"data":{ "price":1384.1685, "id":"WN6427148286" }, "type":"PRICE"
I have spring-boot-starter-websocket, spring-boot-starter-security and lombok as dependencies.
I seem to be connecting to the jar file, because powershell says "Socket connected
Frame TEXT (fin=true, buffer len = 48)" when I run my code but it only hits the Application breakpoints whenever I debug, it doesnt even go to controller or config classes. It may be an obvious mistake but I am a bit lost so any help would be appriciated. Also, my port for this client is 8443 which is specified in application.yml and it starts there.
My Client:
public class WSClient{
private static String URL = "ws://localhost:8080";
public static void main(String[] args) throws Exception {
WebSocketClient client = new StandardWebSocketClient();
WebSocketStompClient stompClient = new WebSocketStompClient(client);
stompClient.setMessageConverter(new MappingJackson2MessageConverter());
StompSessionHandler sessionHandler = new MyStompSessionHandler();
stompClient.connect(URL, sessionHandler);
new Scanner(System.in).nextLine();
}
Controller:
#Controller
public class MsgController {
#Autowired
private SimpMessagingTemplate simpMessagingTemplate;
#MessageMapping("/operations")
public Operations operations(Operations operations)throws Exception{
System.out.println(operations);
simpMessagingTemplate.convertAndSend("/operations", operations);
return operations;
}
StompSessionHandler:
public class MyStompSessionHandler extends StompSessionHandlerAdapter {
private Logger logger = LogManager.getLogger(MyStompSessionHandler.class);
#Override
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
logger.info("New session established : " + session.getSessionId());
session.subscribe("/operations", this);
System.out.println("Subscribed to /operations");
logger.info("Subscribed to /operations");
}
#Override
public void handleException(StompSession session, StompCommand command, StompHeaders headers, byte[] payload, Throwable exception) {
logger.error("Got an exception", exception);
}
#Override
public Type getPayloadType(StompHeaders headers) {
return Operations.class;
}
#Override
public void handleFrame(StompHeaders headers, Object payload) {
Operations ops = (Operations) payload;
System.out.println(ops);
}
}
Operations:
#lombok.Data
public class Operations{
public String description;
public String id;
public String type;
}
EDIT:
Thanks to help of #OkanKonur the problem is that StompHandler's afterConnected is not called even though it seems like they are connected. We tried it with the github link he's given in the comments and it seems to work. The jar file is most probably not a spring project so is there a way to solve this? It still can't read anything.

NoSuchMethodError ResponseEntity$BodyBuilder

I'm trying to implement web sockets to listen to a server for messages. I've made a sample project which works successfully and I'm now trying to port it over to our existing code base. When I run the application I get the following error:
2019-08-29 09:34:33 - org.springframework.web.socket.sockjs.client.SockJsClient: [AWT-EventQueue-0] ERROR - Initial SockJS "Info" request to server failed, url=ws://localhost:8080/igip-gateway/messaging
java.lang.NoSuchMethodError: org.springframework.http.ResponseEntity.status(I)Lorg/springframework/http/ResponseEntity$BodyBuilder;
at org.springframework.web.socket.sockjs.client.RestTemplateXhrTransport.lambda$static$1(RestTemplateXhrTransport.java:158) ~[spring-websocket-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:496) ~[org.springframework.web-3.2.3.RELEASE.jar:3.2.3.RELEASE]
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:465) ~[org.springframework.web-3.2.3.RELEASE.jar:3.2.3.RELEASE]
at org.springframework.web.socket.sockjs.client.RestTemplateXhrTransport.executeInfoRequestInternal(RestTemplateXhrTransport.java:137) ~[spring-websocket-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.web.socket.sockjs.client.AbstractXhrTransport.executeInfoRequest(AbstractXhrTransport.java:129) ~[spring-websocket-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.web.socket.sockjs.client.SockJsClient.getServerInfo(SockJsClient.java:294) ~[spring-websocket-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.web.socket.sockjs.client.SockJsClient.doHandshake(SockJsClient.java:260) ~[spring-websocket-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.web.socket.messaging.WebSocketStompClient.connect(WebSocketStompClient.java:268) [spring-websocket-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.web.socket.messaging.WebSocketStompClient.connect(WebSocketStompClient.java:249) [spring-websocket-5.1.9.RELEASE.jar:5.1.9.RELEASE]
I can see the method exists as a class in an external library as part of spring-web-5.1.9.RELEASE.jar and when I run the application with verbose:class the only references I can see to that jar are:
[Loaded org.springframework.http.converter.json.Jackson2ObjectMapperBuilder from file:/C:/DEV/m2/org/springframework/spring-web/5.1.9.RELEASE/spring-web-5.1.9.RELEASE.jar]
[Loaded org.springframework.http.converter.json.SpringHandlerInstantiator from file:/C:/DEV/m2/org/springframework/spring-web/5.1.9.RELEASE/spring-web-5.1.9.RELEASE.jar]
[Loaded org.springframework.http.HttpLogging from file:/C:/DEV/m2/org/springframework/spring-web/5.1.9.RELEASE/spring-web-5.1.9.RELEASE.jar]
Here is my code as well in case it's relevant:
private static String URL = "ws://localhost:8080/gateway/messaging";
public static void start() {
StandardWebSocketClient webSocketClient = new StandardWebSocketClient();
List<Transport> transports = new ArrayList<>();
transports.add(new WebSocketTransport(webSocketClient));
WebSocketClient client = new SockJsClient(transports);
WebSocketStompClient stompClient = new WebSocketStompClient(client);
ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
threadPoolTaskScheduler.setThreadNamePrefix("Heart-Beat-Executor-");
threadPoolTaskScheduler.setPoolSize(1);
threadPoolTaskScheduler.initialize();
stompClient.setTaskScheduler(threadPoolTaskScheduler);
stompClient.setMessageConverter(new StringMessageConverter());
StompHeaders connectHeaders = new StompHeaders();
connectHeaders.setHeartbeat(new long[]{20000, 20000});
connectHeaders.add("SECURITY-TOKEN", "token");
StompSessionHandler sessionHandler = new CallListeningStompSessionHandler();
ListenableFuture<StompSession> connect = stompClient.connect(URL, new WebSocketHttpHeaders(), connectHeaders, sessionHandler);
System.out.println(connect);
}
public class CallListeningStompSessionHandler extends StompSessionHandlerAdapter {
#Override
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
StompHeaders connectHeaders = new StompHeaders();
connectHeaders.add("SECURITY-TOKEN", "token");
connectHeaders.add("destination", "/topic/services/user");
session.subscribe(connectHeaders, this);
}
#Override
public void handleException(StompSession session, StompCommand command, StompHeaders headers, byte[] payload, Throwable exception) {
}
#Override
public Type getPayloadType(StompHeaders headers) {
return String.class;
}
#Override
public void handleFrame(StompHeaders headers, Object payload) {
System.out.println(payload);
}
}
What is the reason I am getting this error?

Feign Client Error Handling - Suppress the Error/Exception and convert to 200 success response

I am using feign client to connect to downstream service.
I got a requirement that when one of the downstream service endpoint returns 400 ( it's partial success scenario ) our service need this to be converted to 200 success with the response value.
I am looking for a best way of doing this.
We are using error decoder to handle the errors and the above conversion is applicable for only one endpoint not for all the downstream endpoints and noticed that decode() method should returns exception back.
You will need to create a customized Client to intercept the Response early enough to change the response status and not invoke the ErrorDecoder. The simplest approach is to create a wrapper on an existing client and create a new Response with a 200 status. Here is an example when using Feign's ApacheHttpClient:
public class ClientWrapper extends ApacheHttpClient {
private ApacheHttpClient delegate;
public ClientWrapper(ApacheHttpClient client) {
this.client = client;
}
#Override
public Response execute(Request request, Request.Options options) throws IOException {
/* execute the request on the delegate */
Response response = this.client.execute(request, options);
/* check the response code and change */
if (response.status() == 400) {
response = Response.builder(response).status(200).build();
}
return response;
}
}
This customized client can be used on any Feign client you need.
Another way of doing is by throwing custom exception at error decoder and convert this custom exception to success at spring global exception handler (using #RestControllerAdvice )
public class CustomErrorDecoder implements ErrorDecoder {
#Override
public Exception decode(String methodKey, Response response) {
if (response.status() == 400 && response.request().url().contains("/wanttocovert400to200/clientendpoints") {
ResponseData responseData;
ObjectMapper mapper = new ObjectMapper();
try {
responseData = mapper.readValue(response.body().asInputStream(), ResponseData.class);
} catch (Exception e) {
responseData = new ResponseData();
}
return new PartialSuccessException(responseData);
}
return FeignException.errorStatus(methodKey, response);
}}
And the Exception handler as below
#RestControllerAdvice
public class GlobalControllerExceptionHandler {
#ResponseStatus(HttpStatus.OK)
#ExceptionHandler(PartialSuccessException.class)
public ResponseData handlePartialSuccessException(
PartialSuccessException ex) {
return ex.getResponseData();
}
}
Change the microservice response:
public class CustomFeignClient extends Client.Default {
public CustomFeignClient(
final SSLSocketFactory sslContextFactory, final HostnameVerifier
hostnameVerifier) {
super(sslContextFactory, hostnameVerifier);
}
#Override
public Response execute(final Request request, final Request.Options
options) throws IOException {
Response response = super.execute(request, options);
if (HttpStatus.SC_OK != response.status()) {
response =
Response.builder()
.status(HttpStatus.SC_OK)
.body(InputStream.nullInputStream(), 0)
.headers(response.headers())
.request(response.request())
.build();
}
return response;
}
}
Add a Feign Client Config:
#Configuration
public class FeignClientConfig {
#Bean
public Client client() {
return new CustomFeignClient(null, null);
}
}

Using Post in Retrofit

Can you anyone help me giving good example on how to use retrofit for posting large data from my local DB to mysql server.
Currently I am using async-http API and strangely there is always memory error coming up. I am looking for better API that wont give memory error while uploading huge text of data.
This is my setup:
List<TextDetails> unTextList = dbvalue.getTextData();
for (TextDetails td : unTextList)
{
String textID = td.getSerialNumber();
String textSMS = td.getText();
String textAddress = td.getFulladdress();
String textDate = td.getFulldate();
String textException = td.getExceptiontext();
textDetailsBackUpDataOnline(textID , textSMS, textAddress, textDate, textException);
}
private void textDetailsBackUpDataOnline(final String textID ,
String textSMS, String textAddress, String textDate, String textException)
{
final String uploadWebsite = url_backup_text_details;
RequestParams requestParams = new RequestParams();
requestParams.put("textSMS", textSMS);
requestParams.put("textAddress", textAddress);
requestParams.put("textDate", textDate);
requestParams.put("textException", textException);
Text_HttpClient.post(uploadWebsite, requestParams, new AsyncHttpResponseHandler()
{
#Override
public void onSuccess(int statusCode, org.apache.http.Header[] headers, byte[] responseBody)
{
Log.e("textID", "= how many times");
}
#Override
public void onFailure(int statusCode, org.apache.http.Header[] headers, byte[] errorResponse, Throwable e)
{
e.printStackTrace(System.out);
}
});
}
Text_HttpClient class has the following:
public class Text_HttpClient
{
private static AsyncHttpClient client = new AsyncHttpClient();
public static void get(String url, RequestParams params, AsyncHttpResponseHandler responseHandler)
{
client.get(url, params, responseHandler);
}
public static void post(String url, RequestParams requestParams, AsyncHttpResponseHandler responseHandler)
{
client.post(url, requestParams, responseHandler);
}
}
1) Write service interface:
public interface ArticleGetListService {
#FormUrlEncoded // Request will have "application/x-www-form-urlencoded" MIME type
#POST("/api/Article/ArticleGetList")
public void getArticleList(#Field("LanguageCode") String languageCode,
#Field("CategoryId") String categoryId,
#Field("Token") String token,
Callback<ArticleViewPojo> response); //POJO: The json retrieved from the server is added to this class.
}
Here my Rest service requires 3 Parameters, change it as your need.
2) Write POJO for converting JSON returned from Rest Api into java class object so you can use data.
Just copy your JSON into this site, choose JSON source type, annotation as Gson. It will generate POJO for your JSON automatically.
3)On your Main Activity
RestAdapter restAdapter = new RestAdapter.Builder()
.setLogLevel(RestAdapter.LogLevel.FULL)
.setEndpoint(baseUrl)
.build();
ArticleGetListService articleGetListService = restAdapter.create(ArticleGetListService.class);
Callback<ArticleViewPojo> callback = new Callback<ArticleViewPojo>() {
#Override
public void success(ArticleViewPojo model, Response response) {
//use model which is data returned to you
}
#Override
public void failure(RetrofitError error) {
//handle error
}
};
//START REST CALL
articleGetListService.getArticleList(languageCode, categoryId, token, callback);
//above parameters are those written in service interface at 1
//Whole Url is baseUrl+ArticleGetListService in above example

Requests and response from netty server hangs

I have the following code to create a netty web server based on http server created in the netty's example. My buisness logic is the following.
public class HttpServerHandler extends SimpleChannelInboundHandler<Object> {
private final static Logger LOG = LogManager
.getLogger(HttpServerHandler.class);
private WorkflowService workflowService;
private HttpRequest request;
private final StringBuffer buff = new StringBuffer();
private API avalancheApi;
public HttpServerHandler(WorkflowService workflowService) {
this.workflowService = workflowService;
this.avalancheApi = new API(this.workflowService);
}
#Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
LOG.debug("channelActive");
LOG.debug(ctx.toString());
};
#Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.flush();
}
#Override
public void channelRead0(ChannelHandlerContext ctx, Object msg)
throws IOException {
avalancheApi.setContext(ctx);
if (msg instanceof HttpRequest) {
HttpRequest request = this.request = (HttpRequest) msg;
if (HttpHeaders.is100ContinueExpected(request)) {
send100Continue(ctx);
}
String command = getCommand(request);
LOG.debug(command);
Map<String, List<String>> parameters = getParameters(request);
LOG.debug(parameters);
switch (command) {
case "/login":
ctx = avalancheApi.login(parameters);
break;
case "/test":
ctx = avalancheApi.test();
break;
default:
break;
}
}
if (msg instanceof LastHttpContent) {
LOG.debug("msg is of LastHttpContent");
}
if (!HttpHeaders.isKeepAlive(request)) {
// If keep-alive is off, close the connection once the content is
// fully written.
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(
ChannelFutureListener.CLOSE);
}
}
public class API {
private static final Logger LOG = LogManager.getLogger(API.class);
private ChannelHandlerContext ctx;
private HttpResponse response;
private WorkflowService workflowService;
public API(WorkflowService workflowService) {
this.workflowService = workflowService;
this.ctx = null;
}
public void setContext(ChannelHandlerContext ctx) {
this.ctx = ctx;
}
public ChannelHandlerContext login(Map<String, List<String>> parameters) {
boolean success;
String username = getUsername(parameters);
String password = getPassword(parameters);
User user = null;
user = workflowService.login(username, password);
success = validateLogin(user);
this.response = writeLoginResponse(success);
this.ctx.write(this.response);
writeLoginContext(success, response);
return this.ctx;
}
private void writeLoginContext(boolean success, HttpResponse response) {
JsonObject jsonResponseMessage = new JsonObject();
jsonResponseMessage.addProperty("result", success);
LOG.debug(jsonResponseMessage.toString());
this.ctx.write(Unpooled.copiedBuffer(jsonResponseMessage.toString(),
CharsetUtil.UTF_8));
this.response.headers().set(HttpHeaders.Names.CONTENT_LENGTH,
jsonResponseMessage.toString().length());
}
private HttpResponse writeLoginResponse(boolean success) {
if (success)
return createSuccessfullLoginResponse();
else
return createLoginFailureResponse();
}
private HttpResponse createLoginFailureResponse() {
return Response.loginFailureResponse();
}
private HttpResponse createSuccessfullLoginResponse() {
return Response.loginSuccessResponse();
}
}
Response class is only creating the response and the content_type which is of application/json. Content Length is set in the API class. Using python client with requests, results in the request made in http://localhost/login?username=name&password=pass works only once. The second time everything works, but it doesn't finish processing the request and send the response object. Api calls get executed normally, and I also get the message of LastHttpContext message getting print. The problem sometimes happens with browser too. Am I missing something? Maybe the content data and the content length doesn't match? Could it be that when making requests from python client, the content of the previous context isn't flushed and the content_length value of the header and content length of the context doesn't match?
Just wild guess
this.response.headers().set(HttpHeaders.Names.CONTENT_LENGTH,
jsonResponseMessage.toString().length());
Instead, shouldn't you be doing jsonResponseMessage.toString().getBytes().length ?? Sometimes, one character is not just one byte.
My guess is that you have overwritten the context in your API class, and as a result, are writing the response to the wrong context. Is your HttpServerHandler marked with #Shareable?

Categories