How to get message body from RoutingContext? - java

I created a vert.x HTTP server and I want to receive messages from clients in json format.
This is my code:
public class HttpServerVerticle extends AbstractVerticle {
#Override
public void start(Future<Void> future) {
Router router = Router.router(vertx)
.exceptionHandler(ex -> log.error("Error: " + ex));
router.route().handler(BodyHandler.create());
router.route("/getData").handler(this::getRequestHandler);
HttpServerOptions serverOptions = new HttpServerOptions();
vertx.createHttpServer(serverOptions)
.requestHandler(router::accept)
.listen(9539, result -> {
if (result.succeeded()) {
future.complete();
} else {
future.fail(result.cause());
}
});
}
private void getRequestHandler(RoutingContext request) {
JsonObject data = request.getBodyAsJson(); // In this place I had exception
...
request.response().end("{\"reply\":\"ok\"}");
}
}
When my handler was fire and I get some message from the client I get this exception:
io.vertx.core.json.DecodeException: Failed to decode:Unexpected end-of-input: expected close marker for Object (start marker at [Source: (String)"{"ClientRequest":["ok"],"Type":"Test"[truncated 678 chars]; line: 1, column: 1])
How can I avoid this exception and all time get the full message?

You should specify the HTTP method
router.route("/getData").handler(this::getRequestHandler);
is expecting an HTTP GET, but what you need is an HTTP POST:
router.post("/getData")
.consumes("application/json")
.handler(this::getRequestHandler);
will accept json in the body. The consumes is optional and enforces the client to only send json.
I would also suggest to name the route "/data" instead of "/getData" to conform with the concept of RESTful APIs.

Its possible the client is not sending a valid json.
This is a valid exception to handle in that case.
For logging the request, you can simply use request.getBodyAsString() before doing getBodyAsJson()

Related

How to throw exception based on feign.Response?

I have a Feign client with a method returning the feign.Response class. When another service throws an exception, feign puts an exception message on response body and puts status, but my service does not throw an exception. Can I throw an exception based on what I received in response like when I use ResponseEntity.
Feign client
#FeignClient(name = "ms-filestorage")
#RequestMapping(value = "/files", produces = "application/json")
public interface FileStorageApi {
#GetMapping(value = "/{id}")
Response getFileById(#PathVariable String id);
}
Usage of client
#Override
public Response getFileFromStorage(String fileId) {
Response fileStorageResponse = fileStorageApi.getFileById(fileId);
// NOW I USE THIS WAY FOR CHECKING RESPONSE BUT IT DOESN'T LOOK GOOD
//if (fileStorageResponse.status() != HttpStatus.OK.value()) {
// throw new OsagoServiceException();
//}
return fileStorageResponse;
}
Usually, if a Feign client call receives an error response from the API it is calling, it throws a FeignException.
This can be caught in a try / catch block (or a Feign ErrorDecoder if you want to be more sophisticated, but that's another post).
However, this is not the case if you map the error response into a Feign.Response return type - see this Github issue.
Instead of returning Feign.Response from getFileFromStorage(), you should create a custom Java object to hold the response, and you will then have access to the FeignException which you can handle as you wish.
Note that if you don't need access to the data that is returned from the API you are calling, changing the return type to void will also resolve this issue.

Spring - Retry request if service returns 409 HTTP Code

I have an Spring + CXF application which consumes a Transmission API: Transmission RPC running in another server.
According to Transmission docs, you need to send a token which is generated on the first request. The server then responds with a 409 http code along with a header containing the token. This token should be sent on all subsequent calls:
2.3.1. CSRF Protection Most Transmission RPC servers require a X-Transmission-Session-Id header to be sent with requests, to prevent
CSRF attacks. When your request has the wrong id -- such as when you
send your first request, or when the server expires the CSRF token --
the Transmission RPC server will return an HTTP 409 error with the
right X-Transmission-Session-Id in its own headers. So, the correct
way to handle a 409 response is to update your
X-Transmission-Session-Id and to resend the previous request.
I was looking for solution either using a CXF filter or interceptor, that basically will handle the 409 response and retry the initial request adding the token header. I'm thinking that clients can persist this token and send it in future calls.
I'm not very familiar with cxf so I was wondering if this can be accomplish and how. Any hint would be helpful.
Thanks!
Here spring-retry can be utilized which is now an independent project and no longer part of spring-batch.
As explained here retry callback will help make another call updated with the token header.
Pseudo code / logic in this case would look something like below
RetryTemplate template = new RetryTemplate();
Foo foo = template.execute(new RetryCallback<Foo>() {
public Foo doWithRetry(RetryContext context) {
/*
* 1. Check if RetryContext contains the token via hasAttribute. If available set the header else proceed
* 2. Call the transmission API
* 3.a. If API responds with 409, read the token
* 3.a.1. Store the token in RetryContext via setAttribute method
* 3.a.2. Throw a custom exception so that retry kicks in
* 3.b. If API response is non 409 handle according to business logic
* 4. Return result
*/
}
});
Make sure to configure the RetryTemplate with reasonable retry & backoff policies so as to avoid any resource contention / surprises.
Let know in comments in case of any queries / roadblock.
N.B.: RetryContext's implementation RetryContextSupport has the hasAttribute & setAttribute method inherited from Spring core AttributeAccessor
Assuming you are using Apache CXF JAX RS Client it is easy to do by just creating a custom Runtime Exception and ResponseExceptionMapper for it. So the idea is to manually convert 409 outcomes to some exception and then handle them correctly (in your case retry the service call).
See following code snipped for fully working example.
#SpringBootApplication
#EnableJaxRsProxyClient
public class SpringBootClientApplication {
// This can e stored somewhere in db or elsewhere
private static String lastToken = "";
public static void main(String[] args) {
SpringApplication.run(SpringBootClientApplication.class, args);
}
#Bean
CommandLineRunner initWebClientRunner(final TransmissionService service) {
return new CommandLineRunner() {
#Override
public void run(String... runArgs) throws Exception {
try {
System.out.println(service.sayHello(1, lastToken));
// catch the TokenExpiredException get the new token and retry
} catch (TokenExpiredException ex) {
lastToken = ex.getNewToken();
System.out.println(service.sayHello(1, lastToken));
}
}
};
}
public static class TokenExpiredException extends RuntimeException {
private String newToken;
public TokenExpiredException(String token) {
newToken = token;
}
public String getNewToken() {
return newToken;
}
}
/**
* This is where the magic is done !!!!
*/
#Provider
public static class TokenExpiredExceptionMapper implements ResponseExceptionMapper<TokenExpiredException> {
#Override
public TokenExpiredException fromResponse(Response r) {
if (r.getStatus() == 409) {
return new TokenExpiredException(r.getHeaderString("X-Transmission-Session-Id"));
}
return null;
}
}
#Path("/post")
public interface TransmissionService {
#GET
#Path("/{a}")
#Produces(MediaType.APPLICATION_JSON_VALUE)
String sayHello(#PathParam("a") Integer a, #HeaderParam("X-Transmission-Session-Id") String sessionId)
throws TokenExpiredException;
}
}

Retrofit global error handle

Well, as we know, if the server's reponse is formatted like this:
{data: T,status: 200,error:""}
it's easy to have your base response like this:
public class HttpResult<T> {
public int satus;
public String error;
public T data;
}
But what if the server return a JSON object when succeeding, and return another
JSON object when failed, for example:
{
"error": "Consumer key missing.",
"status": 401
}
Now, how can I write a base class to encapsulate the response , for both succeed and error result?
Or should I let GSON parse different JSON objects in these two situations?
PS: I use GSON and retrofit + rxjava in this project...
Can anyone give me some advice?
Thanks in advance
In GSON library, you no need to handle it because if your parameter is missing it will automatically return null.
public class HttpResult<T>
{
public int satus;
public String error;
public T data;
}
In above class you will get T as a null.
When you use RxJava with Retrofit and define your request return type as Observable<Result> where Result is the object type you want to deserialize the response to, RxJava automatically emits only responses with HTTP status code between <200,300) which are considered as a success.. Every other response that gets back with a different status code is considered as a failure and will throw HttpException, thus you can do your error handling in onError callback as follows:
create(ApiService.class).someRequest()
.subscribe((result) -> {
// process your successful result
}, (throwable) -> {
if(throwable instanceof HttpException){
switch (((HttpException) throwable).code()) {
case 401: // handle this type of error
default: // do whatever you want when you don't expect this type of error to happen
}
} else {
// error returned is not related to a failed response
// and is probably a result of an exception along the
// way in the stream ( wrong (de)serialization, no network
// connection or whatever you might mess up in the stream )
}
})
However, by the returned syntax you provided, I guess that your server ignores the HTTP standards and sends you the status code within the json body itself. And although this is not a good way to go and you might wanna discuss this with whoever is responsible for your backend, there is of course a way around. Retrofit itself cannot handle this without your help as it doesn't know which field corresponds to the status code. But you can modify the stream so that it behaves like you want. If you define the request with the returned HttpResult as you described, then you can do something like this:
create(ApiService.class).someRequest()
.map((result) -> {
if(result.status >= 200 && result.status < 300) return result.data;
else throw new MyHttpException(result.error, result.status);
})
.subscribe((result) -> {
// process your successful result
}, (throwable) -> {
if(throwable instanceof MyHttpException){
switch (((MyHttpException) throwable).code) {
case 401: // handle this type of error
default: // do whatever you want when you don't expect this type of error to happen
}
} else {
// error returned is not related to a failed response
// and is probably a result of an exception along the
// way in the stream ( wrong (de)serialization, no network
// connection or whatever you might mess up in the stream )
}
})
public class MyHttpException extends RuntimeException {
public String message;
public int code;
public MyHttpException(String message, int code) {
super(message);
this.message = message;
this.code = code;
}
}
You can sugarcoat this a little bit by encapsulating the map operator into a Transformer so you can just apply it through .compose(). Unfortunately, there is no good way how to do this globally until the guys at Retrofit accept the pull request ( or implement it by their own ) for defining a global transformer for all requests.

Send STOMP ERROR from Spring Websocket program

I have a Spring Websocket Stomp application that accepts SUBSCRIBE requests.
In application I have a handler for SUBSCRIBE, that is,
#Component
public class SubscribeStompEventHandler implements ApplicationListener<SessionSubscribeEvent> {
#Override
public void onApplicationEvent(SessionSubscribeEvent event) {}
}
that I use to validate subscription.
I would check something in the onApplicationEvent and send STOMP ERROR message back to client from this function.
I found this recipe How to send ERROR message to STOMP clients with Spring WebSocket? but I need to understand how to get outboundChannel.
I tried also the following code:
public void sendStompError(SimpMessagingTemplate simpMessagingTemplate, String sessionId, String topic, String errorMessage) {
StompHeaderAccessor headerAccessor = StompHeaderAccessor.create(StompCommand.ERROR);
headerAccessor.setMessage(errorMessage);
headerAccessor.setSessionId(sessionId);
headerAccessor.setLeaveMutable(true);
simpMessagingTemplate.convertAndSendToUser(sessionId, topic, new byte[0], headerAccessor.getMessageHeaders());
}
and I tried topic to be some subsciption topic and /queue/error topic. However I did not see messages propagating to client.
In Client, I use:
stompClient.connect(headers
, function (frame) {
console.log("Conn OK " + url);
}, function (error) {
console.log("Conn NOT OK " + url + ": " + JSON.stringify(error));
});
}
and my goal is to have function(error) called when I send STOMP ERROR.
Please advice me how exactly I can send proper STOMP ERROR, e.g. by getting Outboundchannel.
You can send ERROR Message like this:
StompHeaderAccessor headerAccessor = StompHeaderAccessor.create(StompCommand.ERROR);
headerAccessor.setMessage(error.getMessage());
headerAccessor.setSessionId(sessionId);
this.clientOutboundChannel.send(MessageBuilder.createMessage(new byte[0], headerAccessor.getMessageHeaders()));
The following is just enough to inject that clientOutboundChannel:
#Autowired
#Qualifier("clientOutboundChannel")
private MessageChannel clientOutboundChannel;
Just because clientOutboundChannel bean is declared in the AbstractMessageBrokerConfiguration.
UPDATE
STOMP ERROR always closes connection? I am getting this effect. Code 1002.
Yes, it is. See StompSubProtocolHandler.sendToClient():
if (StompCommand.ERROR.equals(command)) {
try {
session.close(CloseStatus.PROTOCOL_ERROR);
}
catch (IOException ex) {
// Ignore
}
}

Error message in REST webservices

I am calling REST webservices from JSP using AJAX . Can you tell me the best way to send custom error message from REST webservice to JSP ?
Consider using HTTP response codes with (possibly) json response bodies to supply any required information so the client application can react accordingly.
Consider using the WebapplicationException. You can give it the Errorcode (also custom ones) and a body for the response. You could use the JSON Format if you have a complex structure to display your errors but i would suggest just using the an errormessage (for example in case of a bad request, what part of the request was bad).
If you are using JAX-RS REST webservice, you can configure Spring #Controller. Your method should produce application/json and return Response object, like in this example:
#GET
#Path("/get/{id}")
#Produces(MediaType.APPLICATION_JSON)
public Response getUserById(#PathParam("id") String userId) {
// Here your logic
Foo foo = new Foo();
foo.setMsg("Bad Request");
foo.setData("User " + userId + " not found")
return Response.status(400).entity(foo).build();
}
And from AJAX, you can catch error message
// Get user details
$.getJSON(encodeURI("./rest/user/get/" + userId), function(data) {
// Some logic on success
// Fail
}).fail( function(jqxhr) {
console.log(jqxhr.responseJSON.msg);
});
There are a couple of ways.
1. You can look at the response status you receive from the web service. The statuses starting with 2** are a success response (Eg: 200, 201), the ones starting with 4** or 5** are errors.
But the optimal way to handle and track exceptions is to use ExceptionMapper. You can write your own class that implements ExceptionMapper like below:
#Provider
public class GenericExceptionMapper implements ExceptionMapper<Throwable> {
#Override
public Response toResponse(Throwable arg0) {
return Response.status(Status.INTERNAL_SERVER_ERROR)
.entity("Custom Exception: Error retrieving data")
.build();
}
}
You can write your own custom exceptions like below or can throw blanket exception like below. The above approach is the preferred one though.
#Provider
public class GenericExceptionMapper implements ExceptionMapper<Throwable> {
#Override
public Response toResponse(Throwable arg0) {
return Response.status(Status.INTERNAL_SERVER_ERROR)
.entity("Custom Exception: Error retrieving data")
.build();
}
}

Categories

Resources