at work we building an web application with java spring backend and vue frontend.
At the moment we uses 2 or 3 http response code to pass errors between frontend and backend.
If you call and endpoint with wrong parameters, you'll get an BAD_REQUEST. If some exception was thrown in the backend (which isn't related with the parameters) the backend returns an INTERNAL_SERVER_ERROR and if you pass some ids which aren't in the database the backend returns an NOT_FOUND.
This method has multiple problems:
we have no structure in error case which we can use to pass information to the user (frontend)
we want to indicate problems to the user, which can't be classified by HTTP response codes. For example if an external service isn't available, we want to pass the service name to the frontend. But I don't know if "SERVICE_UNAVAILABLE" fits here...
I found this already: https://www.baeldung.com/spring-response-status-exception
We could use the message field to pass detailed information about an error (specific error json in message field).
Is this a good idea?
Opinions?
T
You can certainly pass information back in this field but using ResponseStatusExceptions. Depending on how much information the frontend needs (e.g. if it's just surfacing a user friendly message to the user) this may be enough for your needs.
Another approach, if you want to use a custom object in the response (esp. per exception/response code), is using #ControllerAdvice and extending ResponseEntityExceptionHandler.
e.g. say you have a custom exception ExternalServiceUnavailableException which had some underlying ServiceInformation you could retrieve from it. Then you could do something like
public class ServiceInformation {
private final String name;
private final String status;
private final String statusMessage;
//snip
}
#ControllerAdvice
public class CustomResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler({ ExternalServiceUnavailableException.class })
public ResponseEntity<Object> handleExternalServiceUnavailable(ExternalServiceUnavailableException ex, WebRequest request) {
final ServiceInformation si = ex.getServiceInformation();
return ResponseEntity
.status(503) // or whatever code you want
.body(si); // or map to some other object/format
// or use the constructor to supply headers etc.
}
}
When you throw a ExternalServiceUnavailableException this would result in a response body like
{
"name": "my-external-service",
"status": "timeout",
"statusMessage": "Service timed out after 30 seconds"
}
A more complete example of this can be found in the below article where the same custom error object is used for each of the exceptions of consequence as well as a default handler.
https://www.baeldung.com/exception-handling-for-rest-with-spring
This makes it easier for the frontend to interpret (as does your proposed approach) since there is only a single format to expect and parse, but you are free to return different response shapes per exception.
Edit: it's worth remembering that there are also response codes 502 (bad gateway) and 504 (gateway timeout) which can be used to indicate an external service is either unavailable or timing out. If these are appropriate you could just use appropriate ResponseStatusExceptions with a message set to include the service name (or other info). As above, it depends on what you need/want the fronted to receive.
Related
I am setting up new microservice api to connect and grab information from 3rd party REST api. This is the api more like internal for our services so other api's can consume this one.
I have an internal microservice design like this:
Controller -> (calls) -> Service Layer -> (calls) -> Service Implementation -> (calls) -> rest 3rd party API
Error Handling as below, when there is any issue/exception i fill all ErrorDetailsObj in exception handler(#ControllerAdvice) and return the response entity wrapper to the consuming services.
public class ErrorObject {
private String code;
private String message; //(i choose message from error.prop based on the code)
}
public class ErrorDetailsObj {
private HttpStatus httpStatus;
private LocalDateTime timestamp;
private String message;
private List<ErrorObject> errors;
}
Am I doing the right design? Or is there any better way to simplify the error handling?
Do I need to send only error code, so that consuming services can choose the right error message and display? in my case this is my own internal service which connects to 3rd party service.
Any best example's for error handling?
I am not sure, but u should send correct http code and your message.
Also u can look at default spring exception handler ResponseEntityExceptionHandler.
I don't see any issue with your approach. and since you mentioned that it's working fine, you would have already defined all the required classes custom exceptions classes. and per your question: 'possibly expose only error code so that consuming services can make use of error messages in there services'. - you need to fill the error message where you are seeing the exception (source).
I can suggest one small change, you can extract httpStatus out of ErrorDetailsObj.
while returning the ResponseEntity object, you can HttpStatus to the cconstructor of ResponseEntity. ex: return ResponseEntity(errorDetailsObj, HttpStatus.<>);
Hope this helps.
I'm wondering if there is any elegant way to catch all exceptions (specifically custom runtime exceptions) and return an exception containing a list of the messages.
Instead of having a String message, the big exception would then contain String[] message for example.
Scenario:
A REST request is made to the back-end with a JSON object containing a bunch of fields. I want to validate these fields on the backend and return a list of errors if any exceptions occur.
If both the name and lastname field are not acceptable input, I don't want to throw an exception on the invalid name and have the user change the name and submit again only to get an error message that the lastname is invalid too.
Hence why I want to collect all invalid input and return a list of these in the form of an exception.
Spring collects JSR-303/JSR-349 bean validation failures into a BindException:
Thrown when binding errors are considered fatal. Implements the BindingResult interface (and its super-interface Errors) to allow for the direct analysis of binding errors.
Instead of developing your own mechanism for bean validation you might want to read 3. Validation, Data Binding, and Type Conversion and follow the standards.
With Spring Boot, you can use the following annotation to handle any kind of Exception for a class or a method :
#ExceptionHandler(YourExceptionHandler.class)
And you can create a class that let you regroup all your custom exception management like this (if you want to gather it) :
#ControllerAdvice
class GlobalControllerExceptionHandler {
#ResponseStatus(HttpStatus.CONFLICT) // 409
#ExceptionHandler(DataIntegrityViolationException.class)
public void handleConflict() {
// Nothing to do
}
}
You can also implement the interface HandlerExceptionResolver to manage all Exceptions that ARE NOT handled by the Controllers (all the others runtime Exceptions)
public interface HandlerExceptionResolver {
ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex);
}
Everything is explained in details here : https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
EDIT: I just read that you added up scenario. Actually, for your special case, you should just parse the object, and return one exception (like bad object format, along with a 400 HTTP status code error, with a custom message containing all the fields that are invalid. I guess.
I have a request that creates a payment. A payment has a form of payment that can be credit card or ticket.
When I return the HTTP response I would like to not have to verify what form of payment was used. The way I did this was by having my model class know how to create the form of payment response.
My question is: Is code smells considered the domain class know how to create the response class of an HTTP request? If yes, what's the best way to handle with this situation?
Example for a better understanding of the question:
class Payment {
private BigDecimal value;
private PaymentMethod method;
}
abstract class PaymentMethod {
public abstract String getResponse();
}
class Card extends PaymentMethod {
//attributes
public Response getResponse() {
return new CardResponse();
}
}
class Ticket extends PaymentMethod {
//attributes
public Response getResponse() {
return new TicketResponse();
}
}
Is code smells considered the domain class know how to create the response class of an HTTP request?
Probably yes. It will be harder to test as your unit tests will now need to be aware of HTTP. It also limits the usability of your domain class. If you end up needing to use another endpoint technology other than HTTP (SOAP, web sockets, etc.) you will have a problem. If you end up returning a redirect response then it spreads your URL mapping throughout your application making it harder to keep a handle on that.
Now, if I were to inherit your code base I wouldn't necessarily jump in and change it right away. I wouldn't say it is a fatal flaw but it isn't something I would do in a new project.
If yes, what's the best way to handle with this situation?
This question is probably overly broad and you haven't given a lot of details so here is a generic answer.
The simplest solution I've seen is to have a layer on the outermost of your application whose job is to convert from HTTP to domain logic and back. These types are often called controllers but that word is used so much I prefer the word endpoints. Forgive my pseudocode here, it's been a while since I've used the Java HTTP types.
// Crude example
public class PaymentEndpoint {
public Response handlePayment(HttpRequest request) {
if (request.getContentType() == "application/json") {
Ticket ticket = deserializeTicket(request);
Payment payment = this.paymentService.processTicket(ticket);
return serializeTicketPayment(Payment);
} else if (request.getContentType() == "application/x-www-form-urlencoded") {
CCInfo ccInfo = getCcInfoFromForm(request);
Payment payment = this.paymentService.processPayment(ccInfo);
return serializeCcPayment(payment);
}
}
}
However, that can lead to a lot of boilerplate code. Most HTTP libraries now have methods for doing the domain->HTTP conversion in filters. For example, JAX-RS has annotations you can use to specify which method to use if the content type is X and which to use if the content type is Y and annotations to populate method parameters from form fields or JSON content.
JAX-RS also allows you to return POJOs from your endpoints and it will handle the conversion to a response in its own logic. For example, if you return an object, it will use Jackson to serialize the object to JSON and set the response status to 200. If you return void then it will set the response status to 204.
// Crude example
public class PaymentEndpoint {
#Consumes("application/x-www-form-urlencoded")
public Payment handleCcPayment(#FormParam("name") String name) {
CCInfo ccInfo = new CCInfo(name);
return this.paymentService.processPayment(ccInfo);
}
#Consumes("application/json")
public Payment handleTicketPayment(Ticket ticket) {
return this.paymentService.processTicket(ticket);
}
}
Now, I'm not entirely sure what your asking, but it almost seems like you might need to serialize a ticket payment to HTTP very differently than the way you would serialize a credit card payment to HTTP. If they are simply different JSON objects you can use the example above. However, if you need to set certain HTTP headers in one response and not the other then you might have to do something more fancy.
You could just shove the logic in the endpoint:
if (payment.getPaymentMethod() instanceof Card) {
//Set special HTTP headers
}
However, if you have several endpoints that have to repeat this same logic then it can quickly become boilerplate code. Then you usually extend your filter layer to control the serialization. For example, in JAX-RS there is the concept of a MessageBodyWriter. You can give the JAX-RS framework a custom message body writer which tells it how to convert a card payment into an HTTP response. Although if you were just setting HTTP headers you could probably use a ContainerResponseFilter instead.
I have tried to find the answer to this, but I cannot seem to find what I am looking for. So I apologize if this question already exists.
PROBLEM:
I want to be able to access the request type of a request inside of a generic method within my Controller.
DESCRIPTION:
Using Spring ROO and Spring MVC, I have developed a small web service that will respond with certain tidbits from a database when queried. In one of my controller classes, I have some methods that handle some variety of GET, PUT, POST, etc., for the URIs that are mapped within the #RequestMapping parameter.
For example:
#RequestMapping(method = RequestMethod.Get, value = "/foo/bar")
#ResponseBody
public ResponseEntity<String> getFooBar() {
// stuff
}
If a request is made to the web service that it is not currently mapped, a 405 error is returned (which is correct), but I want to return more information along with a 405 response. Maybe respond with something like:
"I know you tried to execute a [some method], but this path only handles [list of proper methods]."
So I wrote a short method that only has the RequestMapping:
#RequestMapping(value = "/foo/bar")
I have found that the method with this mapping will catch all unhandled request types. But I am having trouble accessing the information of the request, specifically the type, from within the method.
QUESTION:
A. How can I access the request type from within the method? OR
B. Is this the right approach? What would be the right approach?
EDIT
ANSWER:
I added a HttpServletRequestobject to the method parameters. I was able to access the method type from that.
I tried using HttpRequest, but it didn't seem to like that much.
Thanks all!
You can add a method parameter of HttpServletRequest, but I think you'd be better off continuing to reply with 405. A client should then make an HTTP OPTIONS call (see How to handle HTTP OPTIONS with Spring MVC?) and you can return the list of allowed methods there.
A. you can access request if you mentioned it as parameter in controller method
public ... getFooBar(HttpRequest request) {
...
}
B. you do not need to add any other description as the 405 status is descriptive.
In answer to "A", just add "HttpRequest req" as an additional argument to your controller methods. Spring will automatically inject a reference to the request, and you can play with headers to your heart's content.
In answer to "B" - "What would be the right approach", how about this?
In order to return that 405, Spring has raised a MethodArgumentNotValidException. You can provide custom handling for this like so:
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public MyMethodArgumentMessage handleMathodArgumentNotValidException(
MethodArgumentNotValidException ex) {
BindingResult result = ex.getBindingResult();
MyMethodArgumentMessage myMessage =
new MyMethodArgumentMessage(result.getFieldErrors());
return myMessage;
}
You should take a look at the #ExceptionHandler annotation. This lets you add methods such as the following to your controller. You can define your own exceptions and appropriate custom handlers for them. I use it to return well-structured XML and JSON from REST services. Although for it to work, you need to throw specific exceptions from your controller methods.
A good walk-through of using this was provided by Petri Kainulkainen in his blog:
http://www.petrikainulainen.net/programming/spring-framework/spring-from-the-trenches-adding-validation-to-a-rest-api/
I've create a simple test case with Jackson 1.9 and Spring 3. My goal was to test how easy was to use both of them to generate JSON results. My problem is that I get an error 406
So far, it seems that you only need to configure Spring and the code is really straightforward . My Spring configuration file is just:
<context:component-scan base-package="com.goldengecko" />
<context:annotation-config/>
<mvc:annotation-driven />
And my controller:
#Controller
public class TestsController
{
#RequestMapping(value = "/tests", method=RequestMethod.GET)
public #ResponseBody Item getBooks() {
return new Item();
}
}
The Item class is just:
public class Item {
private String name;
public Item() {
name = "Test name";
}
private String getName() {
return name;
}
private void setName( String name ) {
this.name = name;
}
}
I made sure I added the jackson-core-asl.jar and jackson-mapper-asl.jar.
From everything I read, it's just that: you don't seem to need to worry about setting a content-accepted in the request, just open a normal Chrome browser and request that service.
That's where I get a 406.
I created a simple jQuery getJSON call, with the same result.
Do you know what can be wrong? So far there are few things to fail: the code seems to be the right approach and the Jackson files required by Spring are there.
Per section 10.4.7 at http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html:
The resource identified by the request is only capable of generating response entities which have content characteristics not acceptable according to the accept headers sent in the request.
Unless it was a HEAD request, the response SHOULD include an entity containing a list of available entity characteristics and location(s) from which the user or user agent can choose the one most appropriate. The entity format is specified by the media type given in the Content-Type header field. Depending upon the format and the capabilities of the user agent, selection of the most appropriate choice MAY be performed automatically. However, this specification does not define any standard for such automatic selection.
Note: HTTP/1.1 servers are allowed to return responses which are not acceptable according to the accept headers sent in the request. In some cases, this may even be preferable to sending a 406 response. User agents are encouraged to inspect the headers of an incoming response to determine if it is acceptable.
If the response could be unacceptable, a user agent SHOULD temporarily stop receipt of more data and query the user for a decision on further actions.
So it sounds like perhaps you do need to set the acceptable content types on the receiving side.