spring requestmapping http error 406 on file extension - java

I have created this REST mapping so that it can accept filenames at the end of the URI ...
#RequestMapping(value="/effectrequest/{name}/{imagename:[a-zA-Z0-9%\\.]*}",
headers="Accept=*/*", method=RequestMethod.GET,
produces = "application/json")
public #ResponseBody EffectRequest effectRequest(
#PathVariable("name") String name,
#PathVariable("imagename") String imageName)
{
return new EffectRequest(2, "result");
}
Which returns JSON content using MappingJackson2HttpMessageConverter. I make a test jQuery AJAX call to this mapping with ...
var effectName = 'Blur';
var imageName = 'Blah.jpg';
var requestUri = '/effectrequest/' + effectName + '/' + imageName;
alert(requestUri);
$(document).ready(function() {
$.ajax({
url: /*[+ [[${hostname}]] + requestUri +]*/
}).then(function(data) {
$('.effect').append(data.id);
$('.image').append(data.content);
});
});
This generates a URI of http://localhost/effectrequest/Blur/Blah.jpg and in a debugging session the filename is received correctly in the effectRequest() method above. However, the client or jQuery AJAX call receives a HTTP 406 error (Not Acceptable) from the server even with the produces = "application/json" in the RequestMapping.
After much debugging later, I have this narrowed down - when I modify the test javascript code to generate a URI of http://localhost/effectrequest/Blur/Blah.json it works. So either Tomcat or MappingJackson2HttpMessageConverter is causing the HTTP 406 error by looking at the filename extension at the end of the URI and deciding that the JSON content I'm sending back is not a good match.
Is there anyway to override this behaviour without having to encode the . (dot) in the filename?

By default, Spring MVC prefers to use the request's path when it's trying to figure out the media type for a response to a request. This is described in the javadoc for ContentNegotiationConfigurer.favorPathExtension():
Indicate whether the extension of the request path should be used to determine the requested media type with the highest priority.
By default this value is set to true in which case a request for /hotels.pdf will be interpreted as a request for "application/pdf" regardless of the Accept header.
In your case this means that the request for /effectrequest/Blur/Blah.jpg is being interpreted as a request for image/jpeg which leaves MappingJackson2HttpMessageConveter trying to write an image/jpeg response which it is unable to do.
You can easily change this configuration using ContentNegotiationConfigurer accessed by extending WebMvcConfigurerAdapter. For example:
#SpringBootApplication
public class Application extends WebMvcConfigurerAdapter {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
public void configureContentNegotiation(
ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false);
}
}

Related

Combine file upload and request body on a single endpoint in rest controller

The UI for my webapp has the ability to either upload a file(csv), or send the data as json in request body. However either a file upload, or a json request would be present in the request and not both. I am creating a spring rest controller which combine file upload and also accepts the request json values as well.
With the below endpoint tested from postman, I am not getting exception:
org.apache.tomcat.util.http.fileupload.FileUploadException: the request was rejected because no multipart boundary was found
#RestController
public class MovieController {
private static final Logger LOGGER = LoggerFactory.getLogger(MovieController.class);
#PostMapping(value="/movies", consumes = {"multipart/form-data", "application/json"})
public void postMovies( #RequestPart String movieJson, #RequestPart(value = "moviesFile") MultipartFile movieFile ) {
// One of the below value should be present and other be null
LOGGER.info("Movies Json Body {}", movieJson);
LOGGER.info("Movies File Upload {}", movieFile);
}
}
Appreciate any help in getting this issue solved?
Note: I was able to build two separate endpoint for file upload and json request, but that won't suffice my requirement. Hence I'm looking for a solution to combine both
Try something like:
#RequestMapping(value = "/movies", method = RequestMethod.POST, consumes = { "multipart/form-data", "application/json" })
public void postMovies(
#RequestParam(value = "moviesFile", required = false) MultipartFile file,
UploadRequestBody request) {
In RequestBody you can add the parameters you want to send.
This will not send the data as JSON.
Edit:- I forgot to add the variable for the Multipart file and I mistakenly used the RequestBody which is reserved keyword in spring.
Hope it helps.
I would suggest to create two separate endpoints. This splits and isolates the different functionality and reduces the complexity of your code. In addition testing would be easier and provides better readability.
Your client actually has to know which variable to use. So just choose different endpoints for your request instead of using different variables for the same endpoint.
#PostMapping(value="/movies-file-upload", consumes = {"multipart/form-data"})
public void postMoviesFile(#RequestPart(value = "moviesFile") MultipartFile movieFile ) {
LOGGER.info("Movies File Upload {}", movieFile);
}
#PostMapping(value="/movies-upload", consumes = {"application/json"})
public void postMoviesJson( #RequestPart String movieJson) {
LOGGER.info("Movies Json Body {}", movieJson);
}

API call with Java + STS returning "Content type 'application/octet-stream' not supported"

I am working on part of an API, which requires making a call to another external API to retrieve data for one of its functions. The call was returning an HTTP 500 error, with description "Content type 'application/octet-stream' not supported." The call is expected to return a type of 'application/json."
I found that this is because the response received doesn't explicitly specify a content type in its header, even though its content is formatted as JSON, so my API defaulted to assuming it was an octet stream.
The problem is, I'm not sure how to adjust for this. How would I get my API to treat the data it receives from the other API as an application/json even if the other API doesn't specify a content type? Changing the other API to include a contenttype attribute in its response is infeasible.
Code:
The API class:
#RestController
#RequestMapping(path={Constants.API_DISPATCH_PROFILE_CONTEXT_PATH},produces = {MediaType.APPLICATION_JSON_VALUE})
public class GetProfileApi {
#Autowired
private GetProfile GetProfile;
#GetMapping(path = {"/{id}"})
public Mono<GetProfileResponse> getProfile(#Valid #PathVariable String id){
return GetProfile.getDispatchProfile(id);
}
The service calling the external API:
#Autowired
private RestClient restClient;
#Value("${dispatch.api.get_profile}")
private String getDispatchProfileUrl;
#Override
public Mono<GetProfileResponse> getDispatchProfile(String id) {
return Mono.just(id)
.flatMap(aLong -> {
MultiValueMap<String, String> headers = new HttpHeaders();
headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
return restClient.get(getDispatchProfileUrl, headers);
}).flatMap(clientResponse -> {
HttpStatus status = clientResponse.statusCode();
log.info("HTTP Status : {}", status.value());
return clientResponse.bodyToMono(GetProfileClientResponse.class);
// the code does not get past the above line before returning the error
}).map(GetProfileClientResponse -> {
log.debug("Response : {}",GetProfileClientResponse);
String id = GetProfileClientResponse.getId();
log.info("SubscriberResponse Code : {}",id);
return GetProfileResponse.builder()
// builder call to be completed later
.build();
});
}
The GET method for the RestClient:
public <T> Mono<ClientResponse> get(String baseURL, MultiValueMap<String,String> headers){
log.info("Executing REST GET method for URL : {}",baseURL);
WebClient client = WebClient.builder()
.baseUrl(baseURL)
.defaultHeaders(httpHeaders -> httpHeaders.addAll(headers))
.build();
return client.get()
.exchange();
}
One solution I had attempted was setting produces= {MediaType.APPLICATION_JSON_VALUE} in the #RequestMapping of the API to produces= {MediaType.APPLICATION_OCTET_STREAM_VALUE}, but this caused a different error, HTTP 406 Not Acceptable. I found that the server could not give the client the data in a representation that was requested, but I could not figure out how to correct it.
How would I be able to treat the response as JSON successfully even though it does not come with a content type?
Hopefully I have framed my question well enough, I've kinda been thrust into this and I'm still trying to figure out what's going on.
Are u using jackson library or jaxb library for marshalling/unmarshalling?
Try annotating Mono entity class with #XmlRootElement and see what happens.

How to config spring to ignore invalid Accept header?

I'm using spring to build my web app.
In my custom WebMvcConfigurationSupport class, I setup basic ContentNegotiationConfigurer like following:
#Override
public void configureContentNegotiation(final ContentNegotiationConfigurer configurer) {
configurer
.favorPathExtension(false)
.favorParameter(true)
.parameterName("mediaType")
.ignoreAcceptHeader(false)
.useJaf(false)
.defaultContentType(MediaType.APPLICATION_XML)
.mediaType("json", MediaType.APPLICATION_JSON)
.mediaType("xml", MediaType.APPLICATION_XML);
}
I cannot set ignoreAcceptHeader to true, since some of my customers rely on this header for response.
But when I try to access my API with an invalid Accept header like Accept: :*/* (note that extra colon), spring redirects to the error page /error, with the following log:
12:18:14.498 468443 [6061] [qtp1184831653-73] DEBUG o.s.w.s.m.m.a.ExceptionHandlerExceptionResolver
Resolving exception from handler [public MyController.myAction() throws java.io.IOException]: org.springframework.web.HttpMediaTypeNotAcceptableException:
Could not parse accept header [: application/json,*/*]: Invalid mime type ": application/json": Invalid token character ':' in token ": application"
Can I change this behavior? I want to ignore Accept header completely instead of jump to error page. Is that possible?
Use a filter to intercept the requests with wrong header and wrap them replacing (or removing) the wrong header.
Adding an HTTP Header to the request in a servlet filter
In the example change the getHeader() method to
public String getHeader(String name) {
if ("accept".equals(name)) {
return null; //or any valid value
}
String header = super.getHeader(name);
return (header != null) ? header : super.getParameter(name);
}

how to encode and decode url in path string in java

I have requirement such that i need to send a URL in Ajax and the URL is /somehost/users/{userid}/feed/{feedurl} where userid and feedurl are path params which will be
accepted in a servlet written Using Rest Frame work.
My Ajax call is
$.ajax({
url : "/somehost/users/1/feeds/"+encodeURIComponent("**Please Think
that i passing a valid URL here**")),
type : "DELETE",
/*contentType: 'Content-type: text/plain; charset=iso-8859-1',*/
data : {feed_url : tr.attr("value")},
....
....
....
)};
My servlet is
#Path("/users")
public class UserServlet {
#DELETE
#Path("{user_id}/feeds/{feed_url}")
#Produces(MediaType.APPLICATION_JSON)
public String deletefee(
#PathParam("feed_url") String feedId,
#PathParam("user_id") #DefaultValue("1") String userId) {
System.out.println("I am in UserServlet delete");
}
}
Now i am not able hit my servlet.
I want to know how to send url as a #pathparam to my servlet.
If you add the extra slash to "/somehost/users/1/feeds" so it becomes "/somehost/users/1/feeds/" does that work?
Currently you are accessing a URI like "/somehost/users/1/feeds1" instead of "/somehost/users/1/feeds/1"

Ignore Accept header in spring mvc

I have a controller that serves files (images, pdfs, etc,.):
#Controller
public class FileController {
#ResponseBody
#RequestMapping("/{filename}")
public Object download(#PathVariable String filename) throws Exception {
returns MyFile.findFile(filename);
}
}
If I request a file with the following Accept header I get a 406:
Request
URL: http://localhost:8080/files/thmb_AA039258_204255d0.png
Request Method:GET
Status Code:406 Not Acceptable
Request Headers
Accept:*/*
If I request the same file with the following Accept header I get a 200:
URL: http://localhost:8080/files/thmb_AA039258_204255d0.png
Request Method: GET
Status Code:200 OK
Request Headers
Accept: application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
This is the only view resolver in my spring-mvc context:
<bean class="org.springframework.web.servlet.view.UrlBasedViewResolver" id="tilesViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.tiles2.TilesView"/>
</bean>
Is there anyway to configure spring mvc to ignore the Accept header? I've seen example of doing this with ContentNegotiatingViewResolver, but only for handling xml and json.
So this is the code I ended up with to get it working:
#Controller
public class FileController {
#ResponseBody
#RequestMapping("/{filename}")
public void download(#PathVariable String filename, ServletResponse response) throws Exception {
MyFile file = MyFile.find(filename);
response.setContentType(file.getContentType());
response.getOutputStream().write(file.getBytes());
}
}
I used this to lock to the JSON response type:
#Configuration
#EnableWebMvc
public class ApplicationConfiguration extends WebMvcConfigurerAdapter {
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false);
configurer.ignoreAcceptHeader(true);
configurer.defaultContentType(MediaType.APPLICATION_JSON);
}
}
favorPathExtension(false) is needed because Spring by default (at least in 4.1.5) favors path-based content negotiation (i.e. if the URL ends with ".xml", it will try to return XML, etc.).
When you use ResponseBody annotation, I think that is part of the deal that it looks at the Accept header and tries to do some mapping or whatever. There are plenty of other ways to send a response though if you can't figure out how to do it with that annotation.

Categories