I've read quite a few documentations and other stackoverflow questions regarding this matter but I can't seem to get my code working.
So essentially I have a WebClient making a POST request.
IF the response status is 200, then I make another call to another endpoint using a different WebClient. After second webclient call, return a string.
ELSE I just return a String from the method e.g. "failed to create order.".
Simple enough. (this is all done in a seperate thread fyi, not the main thread.)
But I've noticed that if i do get back a 500 error code, WebClient throws an exception. What I want to do is capture the exception and handle that gracefully and return a String like "Error calling first endpoint etc."
This is what I have so far:
private String generateOrder(ImportedOrderDetails importedOrderDetails)
{
Order requestBody = generateRequestBody(importedOrderDetails);
OrderResponse responseForCreatingOrder = orderWebClient()
.post()
.body(Mono.just(requestBody), Order.class)
.retrieve()
.bodyToMono(OrderResponse.class)
.block();
if (responseForCreatingOrder.getResponseStatus().equals(SUCCESS))
{...other call using different webclient}
else{ return "Error creating order."}
This works fine when the response status is 200 but when its 500 it blows up.
OrderResponse is a custom object. orderWebClient() is just a method that returns a prebuilt WebClient containing the baseUrl and headers etc.
I came across this:
Spring WebClient - How to handle error scenarios I did try implementing it but couldn't figure out where to put the block method since I kept on getting the following:
reactor.core.Exceptions$ReactiveException: java.lang.Exception
at reactor.core.Exceptions.propagate(Exceptions.java:393)
at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:97)
at reactor.core.publisher.Mono.block(Mono.java:1680)
I had to edit my code a bit to try and implement the answer to that question:
private Mono<? extends Throwable> handleError(String message) {
log.error("====---"+message);
return Mono.error(Exception::new);
}
private String generateOrder(ImportedOrderDetails importedOrderDetails)
{
Order requestBody = generateRequestBody(importedOrderDetails);
Mono<OrderResponse> responseForCreatingDemo = orderWebClient()
.post()
.body(Mono.just(requestBody), Order.class)
.retrieve()
.onStatus(
(HttpStatus::is5xxServerError),
(it -> handleError(it.statusCode().getReasonPhrase()))
)
.bodyToMono(OrderResponse.class);
System.out.println("-=-"+responseForCreatingDemo);
if (responseForCreatingOrder != null && responseForCreatingOrder.block().getHeader().getResponseStatus().equals(SUCCESS)){...}
The error was coming from the .block part in the if condition. I believe this is something pretty trivial and missing the big picture.
Any suggestions?
It seems you have two kinds of statuses:
Http status, defined by the protocol itself (see HTTP response status codes)
Something specific to the application you're working on, encapsulated into the OrderResponse class.
So you have to handle two "errors" instead of one, one of the possible solutions might look like
.retrieve()
.bodyToMono(OrderResponse.class)
// 4xx, 5xx errors and return "Unable to create order" String instead
.onErrorContinue(WebClientResponseException.class, (ex, v) ->
Mono.just("Unable to create order"))
// if application specific status is not "ok" return "Unable to create order"
.map(it -> it.ok ? "Ok response" : "Unable to create order")
.block();
Please note that this code sample ignores exception and does not even log it
Related
I have recently upgraded my project to the latest version of spring-boot 2.5.0 and got going with refactoring a ton of deprecated code. I noticed that awaitExchange() has now been deprecated and should be replaced with awaitExchange{it}
However, as soon as I replaced one with the other it appears I can no longer extract the body from the ClientResponse object by response.awaitBody() in a different class and keep getting No value received via onNext for awaitSingle. Is such behaviour by design?
Is there any other way to actually get hold of the body without having to use `
awaitExchange{ it.awaitBody() } in the class that makes the webservice call?
Since you did not show your code its hard to say what is the issue. But you can use WebClient in following ways
val client = WebClient.create()
val data: MultiValueMap<String, String> = LinkedMultiValueMap()
data["username"] = "johndoe"
data["target_site"] = "aloha"
client.create()
.post()
.uri("some uri")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(BodyInserters.fromFormData(data))
.retrieve()
.awaitBodyOrNull<String>() ?: throw Exception("Received null response")
Another way to do is
val response = client.get()
.uri("some uri")
.contentType(MediaType.APPLICATION_JSON)
.retrieve()
.toEntity(String::class.java)
.awaitSingle()
if (!response.statusCode.is2xxSuccessful) {
throw Exception("Received ${response.statusCodeValue} response.")
}
I have the following method to modify a user in my Postgres database:
public void modifyUser(User usr){
try (Connection conn = sql2o.beginTransaction()){
conn.createQuery("update public.\"user\" set name=:name, lastname=:lastname, email=:email where id=:id")
.addParameter("id", usr.getId())
.addParameter("name", usr.getName())
.addParameter("lastname", usr.getLastname())
.addParameter("email", usr.getEmail())
.executeUpdate();
conn.commit();
}
}
that is called by this method:
//update
put("/users", (request, response) -> {
response.type("application/json");
User user = new Gson().fromJson(request.body(), User.class);
model.modifyUser(user);
return new Gson().toJson(response);
});
I use Postman to specify the body, this way:
{ "id": 3,
"name": "Mary",
"lastname": "Changed",
"email": "email"
}
But, even if the post method is working fine, this "put" operation throws the following error:
spark.http.matching.MatcherFilter - The requested route [/users/] has not been mapped in Spark for Accept: [*/*]
I don't get what the error is. I couldn't find ny solution.
I know this is an old thread but I was hoping to provide some extra info on the topic.
The path's "/users" and "/users/" are 2 different paths, thus optionally allowing 2 different spark routes (spark's actually really well made like that, in my opinion).
Below is an example of how I typically address this [pun intended, not sorry] in my spark servers when I do NOT want 2 different routes for these:
path("/user", () ->
{
get("", GenericRoutes.redirectToSlashIndex);
get("/index.html", GenericRoutes.pathGet_index);
get("/*", GenericRoutes.redirectToIndex);
});
The idea here is that the first ("") sends to the second ("/index.html") and the third ("/*") sends any non-existing paths to the second ("/index.html") as well.
Then in Generic Routes class you'd have something like:
public static final Route redirectToIndex = new Route()
{
#Override
public Object handle(Request request, Response response) throws Exception
{
String path = request.pathInfo();
path = path.substring(0, path.lastIndexOf('/')+1)+"index.html";
response.redirect(path, 301);
return "<html>Redirecting...</html>";
}
};
public static final Route redirectToSlashIndex = new Route()
{
#Override
public Object handle(Request request, Response response) throws Exception
{
response.redirect(request.pathInfo() + "/index.html", 301);
return "<html>Redirecting...</html>";
}
};
I've simplified some of what I usually do here (you may want some more error handling in those routes and auth checks via 'before' outside of those routes). However here is a small sample of how this works:
Nav: "/users" -> calls get("",...) -> redirects to: "/users/index.html"
Nav: "/users/" -> calls get("/*",...) -> redirects to: "/users/index.html"
Nav: "/users/index.html" -> calls get("/index.html",...) -> no redirects.
So you can see how using something like this will allow you to send the traffic to where you'd expect. There's some things to keep in mind, however:
Using this can be problematic when you have URL params/tokens (you may lose them if you're not careful)
the order is important because if "/*" was before "/index.html" then "/users/index.html" would always trigger "/*" (and infinitely redirect) instead of triggering the route for "/index.html"
In my example I'm using get, thus this approach is typically what is intended (save #1), however with others (such as put/post for example) changing the path like this can result in unexpected outcomes, so be careful when using this approach that you don't redirect traffic to unintended endpoints.
If SEO is important to you, you may consider using this method I've described above as a 301 to hint to those that are crawling your site that your output URL is a permanent redirect. This may be preferable to just putting the same route for all endpoints (which is also a valid alternative [see below]).
Mine is intentionally a bit more complicated than yours, here's how you could handle yours:
path("/user", () ->
{
/*Looks like you mentioned you had a post with the 'first version', so I think like this?*/
post("", UserRoutes.postUsers); //post: /users
put("", UserRoutes.putUsers); //put: /users
put("/*", UserRoutes.putUsers); //put: /users/
});
Note that in the last put example above you could do "/*" or just "/". With * you'll catch any path starting "/users/" whereas without * you only allow "/users/" through. So with * you could do "/users/e13a319e-16d9-3ff5-a83c-96564007998e" and it would trigger the endpoint but that same path would fail without *, this directly relates to #1 above.
So which method you go with really depends on the intent of your site and what you do or do not want those hitting your endpoints to do & see. Happy coding!
Don't know the exact reason why, but the problem was in
put("/users", (request, response) -> {
Spark wants it, in this case, to be written as
put("/users/", (request, response) -> {
Differently from "post" method that accept the first version.
I'm new to camel and writing a small POC to implement in an existing application. Application takes a xml request as input which contains the requested services and relevant data. It then calls those services one by one.
When a service is called successfully then I retrieve the http response code in a processor like below and do further logic:
Object code = exchange.getIn().getHeader(Exchange.HTTP_RESPONSE_CODE);
if(null!=code && code instanceof Integer)
{
responseCode = (Integer) code;
}
In success case, responseCode received = 201
Based on the responseCode, I know if the service call is successful and then proceed with the next one.
However, I tried to produce the negative scenario by making the service url incorrect and can't see the http response code anymore:
Original service url - http://xxx:0000/.../.../.../.../...
Modified service url - http://xxx:0000/.../.../.../.../abc/...
In failure case, responseCode received = null
In postman, I get the below error:
org.apache.camel.http.common.HttpOperationFailedException: HTTP
operation failed invoking http://xxx:0000/.../.../.../.../abc/...
with statusCode: 404 at
org.apache.camel.component.http.HttpProducer.populateHttpOperationFailedException(HttpProducer.java:274)
at
org.apache.camel.component.http.HttpProducer.process(HttpProducer.java:183)
I don't know why exchange doesn't contain the http response code when it's present in the error message in the postman.
I'm using onException to handle any exceptions and then calling a processor to process the flow further:
<camel:onException>
<camel:exception>java.lang.Exception</camel:exception>
<camel:process ref="xxxProcessor" />
</camel:onException>
I think I can consider responseCode=null as failure and proceed with my logic but want to understand why response code is being returned as null.
Thanks in advance!
I figured it out. It seems that in case of service exception, an instance of org.apache.camel.http.common.HttpOperationFailedException is thrown and the http status code is present in it. It can be retrieved in the processor like below:
Exception e = exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class);
if(null!=e && e instanceof HttpOperationFailedException)
{
HttpOperationFailedException httpOperationFailedException = (HttpOperationFailedException)e;
responseCode=httpOperationFailedException.getStatusCode();
}
The accepted answer helped me and it might have been valid! In the camel version I'm usin (2.20.1), getting the exception via the property does not seem to work. The following does
HttpOperationFailedException httpOperationFailedException = exchange.getException(HttpOperationFailedException.class);
if(null!=e) {
responseCode = httpOperationFailedException.getStatusCode());
}
I want to do something to sign up users with spark+java+hibernate+postgres
This is my code:
post("/registrar", (request, response) -> {
EntityManagerFactory emf = Persistence.
createEntityManagerFactory("compradorcitoPU");
EntityManager em = emf.createEntityManager();em.getTransaction().begin();
em.persist(u);
em.getTransaction().commit();
em.close(); return null; });
but this error shows up:
INFO spark.webserver.MatcherFilter - The requested route
[/registrarnull] has not been mapped in Spark
I had a similar problem. The items I'm returning are large and I wanted to write them out over stream. So, my software looked like this:
post("/apiserver", "application/json", (request, response) -> {
log.info("Received request from " + request.raw().getRemoteAddr());
ServerHandler handler = new ServerHandler();
return handler.handleRequest(request, response);
});
In my handler, I got the raw HttpResponse object, opened its OutputStream and wrote over it like so:
ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(response.raw().getOutputStream(), records);
Since I knew I had written over the OutputStream what the caller had asked for at that point (or an error), I figured I could just return null. My program worked fine. Spark would route the request to my handler as expected. And, since I was writing over the raw OutputStream, I was getting back what was expected on the client side. But, I kept seeing the message '/apiserver route not defined' in my server logs.
In looking at the Spark documentation, it says:
The main building block of a Spark application is a set of routes. A route is made up of three simple pieces:
A verb (get, post, put, delete, head, trace, connect, options)
A path (/hello, /users/:name)
A callback (request, response) -> { }
Obviously Spark does not know what you wrote over the raw HttpResponse and as a web-server, you should be providing some response to callers. So, if your response is null, you haven't fulfilled the requirements of providing a callback and you get the error that there's no map found even if Spark behaved as expected otherwise. Just return a response (null is not a response, "200 OK" is) and the error will go away.
[Edit] Spelling and grammar.
do not "return null" instead return the empty string or something
As explained in the comments of this issue, SparkJava considers that returning null means the route has not been mapped and therefore it logs the error message and replies a response with 404 status.
To avoid such behaviour you have to return a String (possibly empty).
The error message will disappear and a response with the String as body and 200 status will be replied.
In my case, I had to implement the options request to please the preflight CORS check:
options("/*", (request,response)->{
String accessControlRequestHeaders = request.headers("Access-Control-Request-Headers");
if (accessControlRequestHeaders != null) {
response.header("Access-Control-Allow-Headers", accessControlRequestHeaders);
}
String accessControlRequestMethod = request.headers("Access-Control-Request-Method");
if(accessControlRequestMethod != null){
response.header("Access-Control-Allow-Methods", accessControlRequestMethod);
}
return "OK";
});
As described here https://cloud.google.com/appengine/docs/java/endpoints/exceptions Google Cloud Endpoints only returns a very limited range of http status codes, namely:
HTTP 400 BadRequestException
HTTP 401 UnauthorizedException
HTTP 403 ForbiddenException
HTTP 404 NotFoundException (also: Timeout)
HTTP 405
HTTP 408
HTTP 409 ConflictException
HTTP 410
HTTP 412
HTTP 413
Google suggests to use the existing status codes to return custom errors:
"In many situations, you may want to use common HTTP status codes to indicate the success or failure of a user's API request. For example, if a user is attempting to retrieve an entity which does not exist, you may want to send an HTTP 404 status code saying No entity exists with ID: entityId.
You can send such common HTTP status codes by throwing an exception provided by the endpoints library as follows:
String message = "No entity exists with ID: " + entityId;
throw new NotFoundException(message);
"
Further down in the same document, Google states:
"Any other HTTP 4xx codes will be returned as error 404"
What's the problem with that? I throw 404 if my entity cannot be found, but Google also throws 404 for almost anything else that goes wrong.
With the exception of 401, 403, and 409, which I can use to tell my client what the exact error was (authorization, forbidden or conflict), I need to fall back to 400 and 404 for all my other status codes, with the result that my client never knows exactly what the problem was.
Sure I can include a human readable error message, but that is meant for RuntimeException(s) that occured in the server code, not to tell my client there was a problem with the data it sent.
Sure, I can also use the first few digits of the error description to send an application specific error code and send the generic 400 Bad Request, but I guess that's not how this should be done.
Any input appreciated. How do you return application specific error codes which your client can use to resolve an application-specific problem?
Having read the following and other posts
http://archive.oreilly.com/pub/post/restful_error_handling.html
Standard JSON API response format?
I would almost say what Google suggests is wrong, because there is no clear differentiation between http status codes and application codes. Both happen on different layers, and the client has no way to tell if it made a bad request, such as violating a contract (e.g. calling a non-existing endpoint, essentially a runtime error), or passing a wrong id (an application layer error).
Articles suggest the following solutions:
use http error codes: not always possible as discussed above
add the application error as custom response header: I would not choose this because it won't appear in the log, which will make debugging tough.
always return 200 and wrap the result in a JSON (as sockets.io does): not viable with endpoints
I came up with another solution which I admit is a compromise (a violation of the error message, in fact), but which I believe is the best suitable integration of individual application error codes into Cloud Endpoints:
I extended 400 BadRequestException, so that any error message is returned as JSON. The client still receives receives http status code 400, but instead of a String error message, it receives a JSON string like this:
{
"code": 400,
"message": "This is a human readable error message."
}
And here I have two options: Either I return code 400, which means this is a BadRequestException where the client actually violated a contract, or I return any other application specific code, which the client can easily parse and process.
My ApplicationException looks like this (it uses a custom JSONizer so it won't work for you like this but you could use JSONObject, GSON, Jackson, whatever):
import com.google.api.server.spi.response.BadRequestException;
public class ApplicationException extends BadRequestException {
private static final int DEFAULT_APPLICATION_CODE = 400; // use this code for all requests without explicit code
public ApplicationException(int code, String message) {
super(JsonResponse.build()
.add("code", code)
.add("message", message)
.toString());
}
public ApplicationException(String message) {
super(JsonResponse.build()
.add("code", DEFAULT_APPLICATION_CODE)
.add("message", message)
.toString());
}
public ApplicationException(String message, Throwable cause) {
super(JsonResponse.build()
.add("code", DEFAULT_APPLICATION_CODE)
.add("message", message)
.toString());
}
}
I haven't marked my answer as correct as I want you to keep posting further suggestions and comments if you believe there are better ways to do this.