Swagger 2 accept xml instead of json - java

I have a project with spring boot and I want to use swagger2 to document my json web services.
I have this configuration :
#Configuration
#EnableSwagger2
public class Swagger2Config {
#Bean
public Docket welcomeMessageApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("My API")
.description("Lorem Ipsum is simply dummy text of ...")
.termsOfServiceUrl("an url")
.contact("contact")
.license("")
.licenseUrl("")
.version("2.0")
.build();
}
To read the documentation, I use this link : http://localhost:9081/v2/api-docs
In the swagger UI, it works fine. But when I try this link directly in my browser, I have this error :
With Firebug, I see that it accept XML content instead of JSON content.
How can I modify swagger configuration to accept JSON content ?

You meet the problem because of the Spring MVC default get the server to render XML instead of JSON in a browser.
The official document say:
To get the server to render XML instead of JSON you might have to send an Accept: text/xml header (or use a browser).
So all you need to do is make the server render JSON in browser.
When you deep into the request in browser you'll see the Request Header:
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
And if you debug into the spring boot, you will see the spring mvc will default delegate HttpMessageConverters include MappingJackson2XmlHttpMessageConverter and MappingJackson2HttpMessageConverter.
The MappingJackson2HttpMessageConverter is to render json and MappingJackson2XmlHttpMessageConverter is to render xml.
They both have a field supportedMediaTypes which means what mediatypes are supported.
The value of supportedMediaTypes in MappingJackson2HttpMessageConverter is:
The value of supportedMediaTypes in MappingJackson2XmlHttpMessageConverter is:
There is a 'text/xml;charset=UTF-8' in MappingJackson2XmlHttpMessageConverter.This is why browser render xml instend of json.
So you need add a custom MappingJackson2XmlHttpMessageConverter which support 'text/xml', for example :
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
List<MediaType> list = new ArrayList<>();
list.add(MediaType.APPLICATION_JSON_UTF8);
list.add(new MediaType("text", "html", Charset.forName("UTF-8")));
list.add(new MediaType("application", "*+json", Charset.forName("UTF-8")));
converter.setSupportedMediaTypes(list);
converters.add(converter);
}
}
Try this and browser will render JSON instead of XML in browser, and all things right!

What worked for me was updating the Maven dependencies for springfox-swagger2 and springfox-swagger-ui.
The newest working combination for me was:
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.8.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.9.2</version>
</dependency>

I had a similar issue that the Swagger API returns XML when I tried to read it with a web-browser.
I resolved it with a different approach.
If you try calling the endpoint with some other tools, such as PostMan or Curl command tool, it would be return JSON correctly.
It looks like it happened in a web-browser only.
If you have Jackson XML extension in pom.xml, you may try this approach.
But if you need to return XML data as a response, please find out another solution.
This is my approach, add below Configuration which extends the WebMvcConfigurationSupport class to the project source code.
#Configuration
public class MessageConverterConfiguration extends WebMvcConfigurationSupport {
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
HttpMessageConverter<?> target = null;
for (HttpMessageConverter<?> converter : converters) {
if (converter.getClass() == MappingJackson2XmlHttpMessageConverter.class) {
target = converter;
}
}
if (target != null) {
converters.remove(target);
}
}
}
For regular GET type request, web-browser sends a request data to the endpoint, including the 'Accept' key in request header, having 'xml' value in it.
Besides, on the server-side, even if the response data type is JSON, and even if there's JSON message converter specified, Spring picks up the XML message converter first, if it is compatible with the JSON data.
On top of the above, the selection process is based on the order of 'Accept' value from request.
You may debug the AbstractMessageConverterMethodProcessor, especially the writeWithMessageConverters method during the response
process.
In the selecting process, Spring picks up a proper converter based on the registered message converter list which is initialized on the process of the system initialization by the WebMvcConfigurationSupport.
The WebMvcConfigurationSupport provides extendMessageConverters to extend or modify the converter list.
So I tried to remove that special XML message converter from the list and it works.
Although above solution worked for me, you may not use this approach.
Please find out another solution if you need to return XML data as a response.

Related

Object Mapper giving different response in similar implementation

I have two different Java projects. In both the project, we wanted keys of response object to be configurable based on request header. So, we created a JsonUtils class that can be used in the both projects using below method.
public String getResponseJson(String casing, Object response) {
if (casing.equals(SNAKE_CASE)) {
return JsonUtils.toJson(response);
} else {
return JsonUtils.toCamelCaseJson(response);
}
}
In 1st project we want request Json keys to be in snake case, So added this config
#Configuration
#EnableWebMvc
#RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {
#Autowired
private ObjectMapper objectMapper;
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new MappingJackson2HttpMessageConverter(objectMapper));
WebMvcConfigurer.super.configureMessageConverters(converters);
}
}
After adding this config in 1st project, JsonUtils class
getResponseJson method started giving this kind of API response.
"[{\"columnName\":\"transporterName\",\"dataType\":\"String\"},{\"columnName\":\"consignorName\",\"dataType\":\"String\"},{\"columnName\":\"consigneeName\",\"dataType\":\"String\"},{\"columnName\":\"locationName\",\"dataType\":\"String\"},{\"columnName\":\"locationCode\",\"dataType\":\"String\"},{\"columnName\":\"locationCity\",\"dataType\":\"String\"},{\"columnName\":\"packageKey1\",\"dataType\":\"String\"},{\"columnName\":\"packageKey2\",\"dataType\":\"String\"},{\"columnName\":\"packageKey3\",\"dataType\":\"String\"},{\"columnName\":\"vehicleType\",\"dataType\":\"String\"},{\"columnName\":\"tripKey1\",\"dataType\":\"String\"},{\"columnName\":\"tripKey2\",\"dataType\":\"String\"},{\"columnName\":\"tripKey3\",\"dataType\":\"String\"},{\"columnName\":\"state\",\"dataType\":\"String\"},{\"columnName\":\"pincode\",\"dataType\":\"String\"},{\"columnName\":\"country\",\"dataType\":\"String\"},{\"columnName\":\"material\",\"dataType\":\"String\"}]"
But, my requirement is proper Json response.
Note: We are also using hibernate dependency in our first project. I guess hiberante's objectMapper is being used and creating issue. We can't remove hibernate from the project as it is required.
But in 2nd project we wanted request Json keys to be in camel case.So we did not add this config in second project. And it is working fine as it is giving proper json response (not as stringified json)

How to properly configure jackson converters for JSON and plain text

For a Spring MVC (not Spring Boot) I've had to change the configuration class that extended WebMvcConfigurationSupport to implement WebMvcConfigurer and add the #EnableWebMvc annotation. This causes problems with the conversion of the responses for several endpoints. The project defaults to application/json and it is used for most of the responses however, there are several endpoints which return application/xml and even a few that return text/plain. JSON responses are modified to remove fields containing null using the following Java config:
#Bean
public MappingJackson2HttpMessageConverter jsonConverter() {
List<MediaType> supportedMediaTypes = new ArrayList<>();
supportedMediaTypes.add(MediaType.APPLICATION_JSON);
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.serializationInclusion(JsonInclude.Include.NON_NULL);
builder.timeZone(TimeZone.getTimeZone(timeZone));
MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter(
builder.build()
);
jsonConverter.setSupportedMediaTypes(supportedMediaTypes);
return jsonConverter;
}
This causes JSON responses to be returned correctly but results in an exception for the text/plain endpoints. They then produce an error:
No converter for [class java.lang.String] with preset Content-Type 'null'
The error can be resolved by adding the default string converter before the JSON converter:
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new StringHttpMessageConverter());
converters.add(jsonConverter());
}
However, this causes a problem specifically for endpoints that return JSON but in Java only have String as the return type. A string in between double quotes should be returned: "response", but they only return the string without quotes: response. This makes most clients to not recognise the response as valid JSON. Curiously POJOs are still converted to valid JSON.
How can I configure my Spring MVC (not Spring Boot) project using a configuration class that implements WebMvcConfigurer and is annotated with #EnableWebMvc to return JSON without null fields and single strings as valid JSON (e.g. with double quotes: "response") but also plain text?
A suitable solution has been found to have the REST API return valid JSON responses even for methods which return a string (so with double quotes around the returned string) while also being able to return XML and plain text responses.
In the configuration class that implements WebMvcConfigurer and is annotated with #EnableWebMvc we register the default converters by overriding the extendMessageConverters. The default MappingJackson2HttpMessageConverter is removed from the list and our custom JSON converter is added instead as the first converter in the list. By adding the JSON converter before the StringHttpMessageConverter methods that return a string have their responses converted to valid JSON (with double quotes) while application/xml and text/plain responses also work properly.
What I can suggest is add one more bean for handling string responses as you added for json and add the convertor to converters.add(stringConverter());

How should I configure httpMessageConverters in Spring boot to convert messages into required format?

I have written an api service using Spring Boot, which serves the requests in Google ProtoBuf format. My requirement is whenever a user hits my service with accept header as application-json format I want to convert the rendered proto objects into JSON format using proto-buf-java Format (library for converting JSON strings to and from proto Objects). Whats happening right now is whenever user hits for application/x-protobuf format, spring uses protobufHTTPMessageConverter to convert into required format, but when the user hits for Json format spring boot uses default JacksonMapper and it couldn't be able to convert into JsonString. I am new to Spring Boot and I need solution to configure message converters to serve JSON format too.
Try to add the class below. SpringBoot should scan for it assuming you left AutoScan on. On the request for your API it will return the object serialized to JSON by default or just add &mediaType=json to request.
#EnableWebMvc
#Configuration
public class CustomConfig extends WebMvcConfigurerAdapter
{
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters)
{
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
mappingJackson2HttpMessageConverter.setPrettyPrint(true);
mappingJackson2HttpMessageConverter.getObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
converters.add(mappingJackson2HttpMessageConverter);
}
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer)
{
configurer.defaultContentType(MediaType.APPLICATION_JSON);
configurer.favorPathExtension(false);
configurer.favorParameter(true);
configurer.ignoreAcceptHeader(true);
configurer.useJaf(false);
configurer.parameterName("mediaType");
configurer.mediaType("json", MediaType.APPLICATION_JSON);
}
}

Unable to test Jax-rs with JSON entity

I am trying to test a Jax-rs resource by following this https://jersey.java.net/documentation/latest/test-framework.html,
and I am using container jersey-test-framework-provider-jdk-http
I can assert status code. However, when I try to readEntity, I get exception:
javax.ws.rs.ProcessingException: Unable to find a MessageBodyReader of content-type application/json and type class java.lang.String
at org.jboss.resteasy.core.interception.ClientReaderInterceptorContext.throwReaderNotFound(ClientReaderInterceptorContext.java:39)
at org.jboss.resteasy.core.interception.AbstractReaderInterceptorContext.getReader(AbstractReaderInterceptorContext.java:73)
at org.jboss.resteasy.core.interception.AbstractReaderInterceptorContext.proceed(AbstractReaderInterceptorContext.java:50)
at org.jboss.resteasy.client.jaxrs.internal.ClientResponse.readFrom(ClientResponse.java:248)
at org.jboss.resteasy.client.jaxrs.internal.ClientResponse.readEntity(ClientResponse.java:181)
at org.jboss.resteasy.specimpl.BuiltResponse.readEntity(BuiltResponse.java:217)
My Resource Class:
#Path("/")
public class SampleResource {
#GET
#Path("/health")
#Produces(MediaType.APPLICATION_JSON)
public String getServiceStatus() {
return "{\"Status\": \"OK\"}";
}
}
My Test Class:
public class TestSampleResource extends JerseyTest {
#Override
protected Application configure() {
return new ResourceConfig(SampleResource.class);
}
#Test
public void testHealthEndpoint() {
Response healthResponse = target("health").request(MediaType.APPLICATION_JSON).get();
Assert.assertEquals(200, healthResponse.getstatus()); // works
String body = healthResponse.readEntity(String.class);
Assert.assertEquals("{\"Status\": \"OK\"}", body);
}
}
Can anyone please help?
The problem comes from having both Jersey and RestEasy client on the classpath. When you call target() on the JerseyTest, the WebTarget is obtained from a Client that is built by calling ClientBuilder.newClient().
The ClientBuilder is a standard JAX-RS API, and it is implemented first to search for an implementation of ClientBuilder through the META-INF/services files, looking for a file named javax.ws.rs.client.ClientBuilder, whose content is the name of an implementation of the ClientBuilder. If no such file is found, it defaults to looking for JerseyClientBuilder.
jersey-client has no such file META-INF/services/javax.ws.rs.core.ClientBuilder because it's ClientBuilder is the default for JAX-RS client. If you look in your resteasy-client jar, you will see the it does have that file. And if you look in the contents of that file, you will see the ResteasyClientBuilder as the implementation.
So even though you are using Jersey's test framework, the Client being used, is RESTeasy's implementation. And I guess all the standard configurations with entity providers never gets configured. Conversion between String and application/json is one of those standard providers you need in your case.
I would say just explicitly use Jersey client implementation. You will no longer be able to call target on the JerseyTest. You will need to explicitly create the client
#Test
public void dotest() {
final Client client = new JerseyClientBuilder().build();
WebTarget target = client.target("http://localhost:9998");
final Response response = target.path("health").request().get();
final String json = response.readEntity(String.class);
}
The default base path for JerseyTest is http://localhost:9998, so I explicitly create the WebTarget with that.
Note that I said the String to/from application/json is supported by standard providers. So if you will only be serializing Strings, then you don't need anything else. If you want POJO serialization support for the Jersey client (and server side), you should add the following
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>${jersey2.version}</version>
<scope>test</scope>
</dependency>
I suspect the json parser in your test is being misguided by the presence of curly braces. Basically it thinks you are returning a json object, not a json string. Try returning "Status:OK"
As the exception says you are missing a MessageBodyReader for content-type application/json. Do you have JacksonJsonProvider on your classpath? It can be added as a dependency to jackson-jaxrs-json-provider:
<dependency>
<groupId>com.fasterxml.jackson.jaxrs</groupId>
<artifactId>jackson-jaxrs-json-provider</artifactId>
<version>2.7.3</version>
</dependency>
Then register the JacksonJsonProvider in your test application:
#Override
protected Application configure() {
return new ResourceConfig(SampleResource.class, JacksonJsonProvider.class);
}

Avoid file extension detection in Spring MVC request mapping with floating point number in URI

I used Spring Boot to implement a REST application. I have one resource that is mapped like this
#RequestMapping(value = "/{fromLat}/{fromLon}/{toLat}/{toLon:.+}", method = {RequestMethod.GET},
produces = {"application/json"})
Thus the path contains coordinates and a request looks like this
$ curl -I -X GET http://localhost:8085/foobar/53.481297/9.900539/53.491691/9.946046
Unfortunatly the last end is interpreted as a file extension which leads to a response header that offers a file download instead of just the plain data.
Content-Disposition: attachment;filename=f.txt
I thought I could handle the situation with a custom WebMvcConfigurerAdapter Bean (and without #EnableWebMvc) annotation like explained here.
public class CustomWebMvcConfigurerAdapter extends WebMvcConfigurerAdapter {
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false);
}
}
But that does not do the trick. Unfortunatly the detected file extension is not fix - thus I can not use a rule for a fix extension.
How can I configure the system to just respond with the content and without the Content-Disposition header (which leads to an f.txt download)? I would not like to use a slash ("/") at the end.
I already looked at the following ressource
Spring MVC controller browser downloads "f.txt"
Add property support for configureContentNegotiation
In Spring Framework 4.1.9 and 4.2.3 the Content-Disposition header was fixed to use the "inline" type which only suggests a file download name, should the content end up being downloaded. It won't force a Save As dialog any more.
Note also that the reason for the Content-Disposition header in the first place is to protect applications against RFD attacks. This is a very complex issue but you can see a summary in the CVE-2015-5211 report.
I had similar issues with Spring Boot as described here.
What worked for me is the following configuration:
#Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
#Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.setUseSuffixPatternMatch(false);
}
}
Did you try to set:
1) Content-Disposition: inline; -> you can use:
return new ResponseEntity(body, headers, statusCode); and set Content-Disposition in headers.
Look here:
How to set 'Content-Disposition' and 'Filename' when using FileSystemResource to force a file download file?
and
Return a stream with Spring MVC's ResponseEntity
2) text/x-json - Experimental MIME type for JSON before application/json got officially registered.
#RequestMapping(value = "/{fromLat}/{fromLon}/{toLat}/{toLon:.+}", method = {RequestMethod.GET},
produces = {"text/x-json"})
It will try to display the content instead of downloading it.
Hope it will help.

Categories

Resources