Get better logging on Jackson parsing error - java

I am working on a REST application built with Jackson-2.2.3.
Here is the Dependency:
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>2.2.3</version>
</dependency>
I have a simple endpoint to create a User as below:
#POST
#Path(value = "/addUser")
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public Response createUser(User user) {
...
}
As this endpoint consumes JSON, when api users send JSON Jackson will desearilize to User object.
If user invoke the endpoint with faulty JSON like, missing a property or bad structure. I want to log the fault JSON as a string and ERROR.
How can I achieve that?
I tried using Filters but didn't work.

Consider handling the JsonParseException:
Exception type for parsing problems, used when non-well-formed content (content that does not conform to JSON syntax as per specification) is encountered.
To achieve it, you could use an ExceptionMapper:
#Slf4j
#Provider
public class JsonParseExceptionMapper implements ExceptionMapper<JsonParseException> {
#Override
public Response toResponse(JsonParseException exception) {
log.error("Cannot parse JSON", exception);
return Response.status(Response.Status.BAD_REQUEST)
.entity("Cannot parse JSON")
.type(MediaType.TEXT_PLAIN)
.build();
}
}
For logging purposes, you may also be interested in getting some details from JsonLocation using exception.getLocation().
To register the exception mapper in Jersey, refer to this answer.

Related

Cannot send http post request to springboot

When I send an HTTP post request to spring boot rest API from my angular application, request is failing with below error
Browser Error
HTTP Status 415 – Unsupported Media Type
Type Status Report
Description: The origin server is refusing to service the request because the payload is in a format
not supported by this method on the target resource
Spring boot console error
java.lang.IllegalArgumentException: No converter found for return value of type: class java.util.LinkedHashMap
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:187) ~[spring-webmvc-4.3.8.RELEASE.jar:4.3.8.RELEASE]
at org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor.handleReturnValue(HttpEntityMethodProcessor.java:203) ~[spring-webmvc-4.3.8.RELEASE.jar:4.3.8.RELEASE]
at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:81) ~[spring-web-4.3.8.RELEASE.jar:4.3.8.RELEASE]
.......
What I have tried so far
As this solution mentioned, i have added the necessary headers to the request from angular end
this.http.post(ipUrl, args, { headers: new HttpHeaders({'Content-Type': 'application/json', 'Accept': 'application/json', 'Access-Control-Allow-Headers': 'Content-Type'})});
As this answer, I have added getters/setters to the Model objects
I want to know where i went wrong and how to resolve this issue?
UPDATE
Springboot Rest Controller method
#PostMapping("/login")
public #ResponseBody ResponseWrapper<WebUser> login(#RequestBody LoginData loginData){
try {
return loginService.loginProcess(loginData);
}
catch (Exception ex){
ProgrammerAlert.printStackTrace(ex);
return new ResponseWrapper<>(ResponseWrapper.ERROR, ex.getMessage());
}
}
Could you write your controller in this way. and see if it responds.
#ResponseBody
#RequestMapping(value = "/login",
method = RequestMethod.POST)
public ResponseWrapper<WebUser> login(...){
.
.
.
}
Is there a reason that you do not want to use RequestMapping ?
Is there any reasons not to add produces and consumes properties?
#PostMapping("/login", produces = MediaType.APPLICATION_JSON_UTF8, consumes = MediaType.APPLICATION_JSON_UTF8)
public #ResponseBody ResponseWrapper<WebUser> login(#RequestBody LoginData loginData){
try {
return loginService.loginProcess(loginData);
}
catch (Exception ex){
ProgrammerAlert.printStackTrace(ex);
return new ResponseWrapper<>(ResponseWrapper.ERROR, ex.getMessage());
}
}
or,
Did you add any JSON message converter, like jackson? Check your application has below dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
your return object should be converted to JSON properly. Add Jackson to you pom (separately from starter web) and retry.
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.10.2</version>
</dependency>
As per your recent edit, you're now getting 406 Not Acceptable, to fix this error, keep the media type of your Spring boot application's response same as the Accept header of your client. Try the following.
Change the value of produces to MediaType.APPLICATION_JSON as you have accept header in client as "application/json". Also please note:
APPLICATION_JSON_UTF8 is deprecated in favor of APPLICATION_JSON
Reference: https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/http/MediaType.html
For those who are facing a similar issue, you can also check if there is any typo in Accept header as I often face this problem.
I also came across the same issue, I guess the answer of this question is very clear
HTTP Status 415 – Unsupported Media Type
while sending the request make the content type json instead of text

How to log the incoming json request in spring restful webservice with restcontroller?

I want to log all the incoming requests which will be in json format.I am using spring #RestController and #RequestBody annotations to bind the incoming json content to java objects.But i want to log these requests to logger files.I have searched around for objectmapper and jacksonbinding.
#RestController
public class restClassName{
#RequestMapping(value={"/uri"})
public ObjectResponse functionRestName(#RequestBody ObjectRequest or){
String jsonInString = mapper.writeValueAsString(staff);//Redundant stuff as the request json is already read by MappingJackson2HttpMessageConverter
logger.info("request::"+jsonInString)
return instance;
}
}
But this seems to be a rendundant way of doing.Since MappingJackson2HttpMessageConverter already reads the httprequest to convert json request to java object.I just need to log the json before MappingJackson2HttpMessageConverter converts the request json to java object.
The simplest way to achieve it is to use CommonsRequestLoggingFilter as described in below pseudo code.
#Bean
public CommonsRequestLoggingFilter requestLoggingFilter() {
CommonsRequestLoggingFilter crlf = new CommonsRequestLoggingFilter();
crlf.setIncludeClientInfo(true);
crlf.setIncludeQueryString(true);
crlf.setIncludePayload(true);
return crlf;
}
Then in application.properties file add the follwing line.
logging.level.org.springframework.web.filter.CommonsRequestLoggingFilter=DEBUG
This will log all the requests, please follow the link to CommonsRequestLoggingFilter api doc for more customization.

Can't retrieve JSONObject from my rest api

So I recently moved from Jersey 1.x to 2.x and after a long list of problems finaly got it working. But whenever I try to reach a resource which returns a JSONObject I get problems. First of, here is my example method:
#GET
#Path("/foobar")
#Produces(MediaType.APPLICATION_JSON)
public JSONObject print2() throws JSONException {
JSONObject jsonObject = new JSONObject();
jsonObject.put("hi", 22);
return jsonObject;
}
Now if I use Jettison 1.3.8 for my JSONObject, I get the following if I try to reach this resource:
{"escapeForwardSlashAlways":true}
Not sure whats going on there. Then I tried some older versions of Jettison and also the org.json but these gives me this issue instead:
No serializer found for class org.json.JSONObject and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) )
Not sure why I get these problems when this kind of method worked fine for me on Jersey 1.x.
Assuming you are using Servlet 3.0 and above, the following example might help you to setup your environment to work with JSON data:
Dependency: if you are using Maven you need the following dependencies:
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-servlet</artifactId>
<version>2.23.2</version>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.23.2</version>
</dependency>
If you are not using Maven, you need to add the correponding jars into your classpath.
Define POJOs to contain the data you want to serialize to JSON, for example,
public class User {
private String username;
private String email;
// getters + setters
}
Modify your resource method accordingly:
#GET
#Path("/foobar")
#Produces(MediaType.APPLICATION_JSON)
public User print2() {
User jsonObject = new User();
jsonObject.setUsername("Me");
jsonObject.setEmail("my#email.com");
return jsonObject;
}
Package and deploy, and the output should be:
{
"username": "Me",
"email": "my#email.com"
}
Note: This example is deployed and works on Tomcat 8.5.5.
I was struggling with the same issue, and eventually Jersey's bookmark exmple helped.
The problem is that your Jersey has no serializer for JSONObject and it tries to use BeanSerializer instead. Jettison JSONObject has only one public getter (isEscapeForwardSlashAlways) and org.json.JSONObject has no getters at all so BeanSerializer cannot be applied.
The solution is for (jettison json object):
Add dependency jersey-media-json-jettison:
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jettison</artifactId>
<version>2.26</version>
</dependency>
Register the jettison feature declaratively in your web.xml
<init-param>
<param-name>jersey.config.server.provider.classnames</param-name>
<param-value>org.glassfish.jersey.jettison.JettisonFeature</param-value>
<init-param>
Or programmatically in your application:
#ApplicationPath("/")
public class MyApplication extends ResourceConfig {
public MyApplication() {
registerClasses(UsersResource.class);
register(new JettisonFeature());
}
}
web.xml:
<init-param>
<param-name>javax.ws.rs.Application</param-name>
<param-value>org.glassfish.jersey.examples.bookmark.MyApplication</param-value>
</init-param>
Perhaps org.json.JSONObject has such serializer feature for Jersey too, I don't know...
Another option is to allow the Response to convert your object to JSON. This gives the added benefit of adding the HTTP code as well. So you can return a 400, 404, 500 etc. and still send back a JSON response that can be acted upon by your JS. You should be able to drop your JSONObject in there since it's basically just extended Map - or any object for that matter.
#GET
#Path("/foobar")
#Produces(MediaType.APPLICATION_JSON)
public Response print2() {
User jsonObject = new User();
jsonObject.setUsername("Me");
jsonObject.setEmail("my#email.com");
return Response.status(Response.Status.OK).entity(jsonObject).build());
}

Data Binding Error Handling in Spring MVC

I have a question about data binding in Spring MVC.
I have a Controller which accepts a JSON request in the form of #RequestBody. I have all the JSR 303 validations in place and it works like a charm.
JSON Request
public class TestJSONRequest {
#Size(min=10,message="{invalid.demo.size}")
String demo;
int code;
}
Controller
#Controller
#RequestMapping("/test")
public class TestController {
public void testEntry(#RequestBody TestJSONRequest jsonRequest,ModelMap map)
Set<ConstraintViolation<TestJSONRequest>> violationList = validator.val(jsonRequest);
....
....
TestJSONResponse response = // Do complex Logic.
modelMap.addattribute("TestJSONResponse",response);
}
}
But JSR 303 validations kick in once the incoming JSON data is bound to the Request object.
If I send ab in the code field of the input JSON request, binding would itself fail.
How do I handle that?
I want to catch those data binding errors and do some kind of generalized error handling in my controller.
Could you please help me out on this?
P.S - I am using Spring 3.0.3
According to the current Spring documentation (V3.1) :
Unlike #ModelAttribute parameters, for which a BindingResult can be used to examine the errors, #RequestBody validation errors always result in a MethodArgumentNotValidException being raised. The exception is handled in the DefaultHandlerExceptionResolver, which sends a 400 error back to the client.
Now you can to tell Spring that you'd like to handle this, by creating a new method, as follows:
#ExceptionHandler(MethodArgumentNotValidException.class)
public String handleValidation(MethodArgumentNotValidException e, ModelMap map) {
List<ObjectError> errors = e.getBindingResult() .getAllErrors();
// your code here...
return "path/to/your/view";
}
Finally, have a read of the Spring docs wrt #ExceptionHandler. There's most likely some useful information there.

Custom HTTP status response with JAX-RS (Jersey) and #RolesAllowed

With my very simple JAX-RS service I'm using Tomcat with JDBC realm for authentication, therefore I'm working the the JSR 250 annotations.
The thing is that I want to return a custom message body in the HTTP status response. The status code (403) should stay the same. For example, my service looks like the following:
#RolesAllowed({ "ADMIN" })
#Path("/users")
public class UsersService {
#GET
#Produces(MediaType.TEXT_PLAIN)
#Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public String getUsers() {
// get users ...
return ...;
}
}
If a user with a different role than "ADMIN" access the service, I want to change the response message to something like that (depending on the media type [xml/json]):
<error id="100">
<message>Not allowed.</message>
</error>
At the moment Jersey returns the following body:
HTTP Status 403 - Forbidden
type Status report
message Forbidden
description Access to the specified resource (Forbidden) has been forbidden.
Apache Tomcat/7.0.12
How can I change the default message body? Is there a way to handle the (maybe thrown) exception to build my own HTTP status response?
The easiest way to handle this sort of thing is to throw an exception and to register an exception mapper to convert into the kind of message you want to send in that case. So, suppose you throw an AccessDeniedException, you would then have a handler like this (with full class names in places for clarity):
#javax.ws.rs.ext.Provider
public class AccessDeniedHandler
implements javax.ws.rs.ext.ExceptionMapper<AccessDeniedException> {
public javax.ws.rs.core.Response toResponse(AccessDeniedException exn) {
// Construct+return the response here...
return Response.status(403).type("text/plain")
.entity("get lost, loser!").build();
}
}
The way in which you register the exception mapper varies according to the framework you're using, but for Jersey you should be fine with just using #Provider. I'll let you figure out for yourself how you want to generate the kind of error documents that you want, but I do recommend handling failures as HTTP error codes of some kind (that's more RESTful...)
With creating an ExceptionMapper (mapping exceptions of WebApplicationException) it is possible to "catch" certain exceptions thrown by the application:
#Provider
public class MyExceptionMapper implements ExceptionMapper<WebApplicationException> {
#Override
public Response toResponse(WebApplicationException weException) {
// get initial response
Response response = weException.getResponse();
// create custom error
MyError error = ...;
// return the custom error
return Response.status(response.getStatus()).entity(error).build();
}
}
You also need to add the package to your application web.xml for registering the provider:
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>
com.myapp.userservice; // semi-colon seperated
com.myapp.mappedexception
</param-value>
</init-param>
REST is build upon HTTP so you don't have to change the default behavior of an authentication failure. Having a 403 error when accessing a resource is enough for the client to clearly understand what appends.
The more your resources are HTTP compliant, the more others can understand it.

Categories