Multiple default content type in spring boot - spring

We are migrating monolithic netty web application into multiple spring boot microservices. Currently the consumers are not passing Content-Type header in the request.
To support that i have added below code in my application
#Override
public void configureContentNegotiation(final ContentNegotiationConfigurer configurer) {
configurer
.defaultContentType(APPLICATION_XML)
But the problem is i have endpoints with application/json as content type.
Is it possible to have default content type based on url's or is it possible to add headers in the request using some filter or interceptor.

If you add mediaType to your configuration it will enable content negotiation for whichever media types you want.
Spring checks three things, in this order
Add an extension to your request, e.g. /endpoint becomes /endpoint.json if you want JSON
Add the format parameter, e.g. /endpoint becomes /endpoint?format=json
Add the requisite Accept header, e.g. Accept=application/json (unless you configure the ContentNegotiationConfigurer to ignore the Accept header.
#Override
public void configureContentNegotiation(final ContentNegotiationConfigurer configurer) {
configurer
.defaultContentType(APPLICATION_XML)
.mediaType("json", MediaType.APPLICATION_JSON)
.mediaType("xml", MediaType.APPLICATION_XML);
}
Configured in this manner, your endpoints will marshal XML unless overridden by one of the means described above.
If you cannot make changes to your clients to ask that they request the format they expect, you can override the behavior of a specific endpoint by adding produces to the mapping, like this
#GetMapping("/endpoint", produces = MediaType.APPLICATION_JSON_VALUE)
or
#RequestMapping("/endpoint", produces = MediaType.APPLICATION_JSON_VALUE)
This answer assumes that you've imported the correct dependencies into your project, you'll need the jackson XML mapper. Also, assuming you leave the content negotiation configuration in place, I would highly recommend that on your methods that you specify produces, that you include produces = {"application/xml", "application/json"} just in case your clients start following the proper specs and send an Accept header. Also, note that you'll need to make sure your objects are annotated with at a minimum, #XmlRootElement
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>

Related

Spring: Which internal class inserts the #RequestBody parameter in controllers?

#RequestMapping(value = "/something", method = RequestMethod.POST, produces = "application/json")
public void triggerInvitations(#RequestBody String postBody)
Which Spring class does the actual task of reading the input stream and setting the request body in the parameter "postBody"?
Actually, I'm just getting the response status as 400 Bad Request and unable to see the actual cause. As such, I wanted to debug the Spring code and know exactly the reason why its a bad request. (If I don't have a #RequestBody parameter in the controller signature, control is able to reach the method)
I'm using Spring Boot 1.5.8
There are different HTTP Message converters used by Spring. This article will give you an overview of this and also list different message converters.
Turn on your springframework debugging in your application.properties:
logging.level.org.springframework = DEBUG
It should start to show you why it's not matching the request to your RequestMapping.

Why do we need jackson databind?

I am new in Spring MVC. My question is, why do we need jackson databind? Because We can receive the Request Params by #ModelAttribute and requests through http PUT or POST by #RequestBody. I can't find a reason why we need jackson databind to convert json/xml to POJO or vice versa.
Thanks.
Why do we need jackson databind?
Because representing structured data is much easier using XML (or JSON) than using simple name-value pairs.
Because it is more convenient to send and receive JSON from the client side when you are doing AJAX.
Because once you have to deal with sending and receiving JSON or XML in the server side Java app, it is more convenient to deal with structured data as POJOs.
None of the above points mean you have to use a binding. There are other ways of dealing with each of the above. But many Java developers think that data bindings the better way to go: more efficient in terms of developer time, and more reliable. Especially if you are implementing services with a complex APIs. That's why they are popular.
And as other answers/comments point out, if you are using #RequestBody, then that is using a binding library under the hood to give you the POJOs. In the case of Spring, it is Jackson that is being used.
By default, when an endpoint expects a JSON document as input and a given controller method argument is annotated with #RequestBody, Spring will use Jackson databind features to map the incoming JSON document to a Java object. You don't need to use the Jackson's ObjectMapper directly, as Spring does it for you.
For example purposes, consider the following HTTP request to create a comment:
POST /comments HTTP/1.1
Host: example.org
Content-Type: application/json
{
"content": "Lorem ipsum"
}
And the following class which represents a comment:
#Data
public class Comment {
private String content;
}
A #RestController to handle such request would be like:
#RestController
#RequestMapping("/comments")
public class CommentController {
#PostMapping(consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Foo> createComment(#RequestBody Comment comment) {
// By default, Spring will rely on Jackson databind to map the incoming
// JSON document to the comment argument annotated with #RequestBody
...
}
}
If you are interested in the Spring component that maps the incoming JSON document to a Java object, have a look at the MappingJackson2HttpMessageConverter class:
Implementation of HttpMessageConverter that can read and write JSON using Jackson 2.x's ObjectMapper.
This converter can be used to bind to typed beans, or untyped HashMap instances.
By default, this converter supports application/json and application/*+json with UTF-8 character set. [...]
If you are creating a HTTP API and exposing resources that can be manipulated with JSON representations, it's unlikely you'll use #ModelAtribute. Such annotation is particularly useful when you are dealing with web views.
When you get some request in some data types like json/xml, the Spring MVC platform will try to deserialize this request attributes in some model object of your project.
But the platform itself don't provide a des-serialize implementation out of the box. So it will try to use some des-serializer provider in the classpath like jackson, jersey, gson, etc.
As you said - is possible to use #ModelAttribute - but this annotation is a better option to a request from a form view in the front-end. In cases rest json/xml requests, the #ModelAttribute won't be able to convert correctly the received data to a business class of your program.

How to pass MultipartFile in requestBody for a spring boot rest controller

So I have seen examples where a MultiPartFile type is passed in #RequestParam and not in #RequestBody. That seems to be a very usual way people suggest to consume a file content in a #RestController something like this
public ResponseEntity<String> submitFile(#RequestParam(value="file") MultipartFile file)
I am wondering how is it a good practice as the file data gets passed in the url. Why not pass it in #RequestBody instead?
So I changed the above code to something like this
public ResponseEntity<String> submitFile(#RequestBody MyCustomObj myObj)
myCustomObj is a pojo with just one field named file of type MultipartFile
The problem is that I only have swagger and postman to test it and when I use the #RequestBody approach, none of these would give me an option to upload a file as they would in case of passing MultipartFile in RequestParam.
Can someone please throw some more light on this and tell me the right way to do this?
As an alternative and based on your comments I would recommend you take a look at the community project called Spring Content. This provides a resource abstraction over storage giving flexibility to where your content is stored and it injects the service and controller implementations for you so that you don't need to implement either yourself. Also, as you mentioned it might become important, Spring Content allows you to associate uploaded content with Spring Data entities too.
Adding it to your project would look something like this:
pom.xml (assuming maven. Spring boot starters also available)
<!-- Java API -->
<!-- just change this depdendency if you want to store somewhere else -->
<dependency>
<groupId>com.github.paulcwarren</groupId>
<artifactId>spring-content-fs</artifactId>
<version>0.8.0</version>
</dependency>
<!-- REST API -->
<dependency>
<groupId>com.github.paulcwarren</groupId>
<artifactId>spring-content-rest</artifactId>
<version>0.8.0</version>
</dependency>
StoreConfig.java
#Configuration
#EnableFilesystemStores
#Import(RestConfiguration.class)
public class StoreConfig {
#Bean
FileSystemResourceLoader fileSystemResourceLoader() throws IOException {
return new FileSystemResourceLoader(new File("/path/to/uploaded/files").getAbsolutePath());
}
}
FileStore.java
#StoreRestResource(path="files")
public interface FileStore extends Store<String> {
}
And that's it. The FileStore is essentially a generic Spring ResourceLoader. The spring-content-fs dependency will cause Spring Content to inject a filesystem-based implementation. The spring-content-rest dependency will cause Spring Content to also inject an implementation if an #Controller that forwards HTTP requests onto the methods of the FileStore service.
So you will now have a fully functional (POST, PUT, GET, DELETE) REST-based file service at /files that will use your FileStore to retrieve (and store) files in /path/to/uploaded/files on your server.
So:
curl --upload-file some-image.jpg /files/some-image.jpg
will upload some-image.jpg and store it in /path/to/uploaded/files on your server.
And:
curl /files/some-image.jpg
will retrieve it again.
HTH
The injected controller also supports video streaming too, in case that is useful.
Later on down the line if/when you want to associate content with a Spring Data entity all you would need to do is make your FileStore extend ContentStore instead of Store, type it to the Spring Data entity that you are associating with and add the Spring Content annotations to your entity, as follows:
//#StoreRestResource(path="files") <-- no longer required
public interface FileStore extends ContentStore<YourEntity, String> {
}
#Entity
public class YourEntity {
#Id
...
#ContentId
private String contentId;
#ContentLength
private String contentLen;
#MimeType
private String contentType;
}
And that's it. As you might expect your REST endpoints change so that you now address content using the same URI space as your Spring Data entity. So:
curl --upload-file some-image.jpg /yourEntities/{yourEntityId}
will upload some-image.jpg, store it in /path/to/uploaded/files on your server and associate it with the entity yourEntityId.
And:
curl /yourEntities/{yourEntityId}
will retrieve it again.
Multiple pieces of content can be associated by using conventional #OneToOne and #OneToMany associations and are reflected accordingly in the URI in a (hopefully) intuitive way.
HTH
#RequestParam maps to query parameters, form data, and parts in multipart requests and not only query parameters as mentioned the offical docs.
https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/bind/annotation/RequestParam.html
Files are not supposed to be sent as the request body serialized in JSON.
What you should do instead is to use the content type "multipart/form-data" for your file uploads (as mentioned in the HTML 4 spec below) and in that case #RequestParam will be the appropriate annotation to use
https://www.w3.org/TR/html401/interact/forms.html#h-17.13.4

How to implement request timeout management in an AOP way in Spring Boot

currently I'm exploring approaches to implement 'request timeout management in an AOP way in Spring Boot' with several restrictions. The requirements/restrictions are stated as below:
The original purpose is that if the processing time of an api request exceeds 5 seconds, then directly return timeout result instead of continue processing
The rest api to be monitored is implemented by standard spring mvc rest controller. All apis inside are returning json strings like this:
#RestController
public class xxxxxx {
#RequestMapping(value = "xxxxxxx")
public String xxxxxx(#RequestParam(value = "xxxx", required = true) String xxxx) {
....
return json.toString();
}
}
The timeout logic is required to be implemented by AOP
(The real mean part)
No changes should be made to the controllers, which means: Request generation approach should not be changed; Return type should not be changed(No 'Callable<...>' allowed)
I have already found 1 answer(Async approach) which can perfectly resolve the problem itself with spring async, and the timeout return result is very pretty, but it's changing the return type, and also touching the code in controller. I also found one solution(AOP approach) which is using AOP, but the scenario is quite different from mine. It's already moving some business logic into AOP class, but I'm not allowed to touch the controller code. I would be grateful if anyone can provide a solution. Solutions that can't meet all the restrictions but are minimizing the differences are also admitted.
Since there is still no response to this question, I will put my own temporary solution here.
I'm using Hystrix dependency.
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-hystrix</artifactId>
<version>1.4.0.RELEASE</version>
</dependency>
It's well integrated with springboot, so the configuration is easy. Once properly configured, need to append an annotation on the request method that requires timeout handling. e.g.
#HystrixCommand(fallbackMethod="fallback")
#RequestMapping(value = "xxxxxxx")
public String xxxxxx(#RequestParam(value = "xxxx", required = true) String xxxx) {
....
return json.toString();
}
And need to add a fallback method with the name mapped to the value of 'fallbackMethod' inside annotation:
public String fallback() {
...
}
The timeout time value can be globally configured inside application.properties
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=3000
hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds=3000
This is still not concise in these points:
Need to copy/paste this annotation for every method
Need to copy/paste the fallback method in every place hystrix is used
For hystrix fallback method itself, the parameter type and number need to be exactly same with the hystrix marked method. Currently I'm using several overloading method called 'fallback' for this, in each controller
But at least it's not changing method return types and code inside methods anymore and is the best solution I can think of currently. Will perform update once I find better solutions.

Spring data rest how to serve hal+json serialization for content-type json

We are using spring-data-rest to expose our repositories.
Choosing as default type application/hal+json produce the desired presentation of the resources and links, with content exposed as _embedded and links as _links.
Unfortunately, Firefox is not able to render application/hal+json, suggesting user to download the hal+json document if the user is navigating the api using the browser.
As a possible solution to this, we would like to support application/json as defaultMediaType and also hal+json if the user is explicitly requiring that.
Using application/json as defaultMediaType with spring-data-rest tough bypass the hal+json serialization provided by spring-hateoas, so the resources are displayed with "content" and "links".
I've tried to extends the RepositoryRestConfigurerAdapter, overriding the configureHttpMessageConverter this way
#Override
public void configureHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new Jackson2HalModule());
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
MappingJackson2HttpMessageConverter halConverter = new TypeConstrainedMappingJackson2HttpMessageConverter(ResourceSupport.class);
halConverter.setObjectMapper(mapper);
halConverter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_JSON, MediaTypes.HAL_JSON));
//make sure this is inserted first
messageConverters.add(0, halConverter);
super.configureHttpMessageConverters(messageConverters);
}
but the converter is not used to render the resources.
Any one has any suggestion on how to tackle this?

Resources