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);
}
Related
I have a Spring Boot application with an already working endpoint that produces an xlsx file.
Now, I want to implement content negotation on this endpoint but I always receive 406 Not Acceptable.
{
"timestamp": "2021-03-09T18:44:56.997+0000",
"status": 406,
"error": "Not Acceptable",
"message": "Could not find acceptable representation",
"path": "/students/excel"
}
And I am using URL parameters and I am calling it like that
localhost:8080/students/excel/excel?format=xlsx
The implementation
Endpoint
#PostMapping(path = "/excel", produces = {"application/vnd.ms-excel"})
public byte[] generateExcel(HttpServletResponse response, final #RequestBody #NonNull Criteria criteria) {
response.setContentType("application/vnd.ms-excel");
response.setHeader("Content-Disposition", "attachment; filename=Students.xlsx");
return studentService.generateExcelReport(response, criteria);
}
The method that is finalizing the excel file.
public static byte[] write(HttpServletResponse response, final #NonNull XSSFWorkbook workbook) {
try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
os.writeTo(response.getOutputStream());
workbook.write(os);
workbook.close();
return os.toByteArray();
} catch (final IOException ex) {
throw new RuntimeException("Error generating excel", ex);
}
}
And the relevant method on WebConfiguration that implements WebMvcConfigurer
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false).
favorParameter(true).
parameterName("format").
ignoreAcceptHeader(true).
useJaf(false).
defaultContentType(MediaType.APPLICATION_JSON).
mediaType("xlsx", MediaType.MULTIPART_FORM_DATA);
}
I tried a lot of combinations with MediaType and on WebConfiguration and the attribute of produces like
application/csv to check if there is a possibility that it could work due to formating of the excel file and others. But I couldn't overcome this status. When setting it to application/json and text/plain it works but it's not the wanted functionality or the correct one.
When I am not using content negotiation, the generation of the excel works as I mentioned.
Edit:
Based on suggestions, I changed the content type to application/vnd.openxmlformats-officedocument.spreadsheetml.sheet and changed the header of both Accept and Content-Type on Postman and still receive 406.
This is how the request on Postman looks
I also debugged the application and it doesn't enter the method of the endpoint, it seems to fail instantly because of the produces value.
Ι want to add that this is a POST request that accepts a JSON. So, using any other content-type on Postman will break it.
Update
It works by using the accept header instead of parameters and changing WebConfigurer method. But, I wanted to to use URL parameters and to understand why they don't work.
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(true).
favorParameter(false).
ignoreAcceptHeader(false).
useJaf(false);
}
I found a solution in order to make it work with URL parameters, as it was my first intention.
I added a new Media Type of vnd.ms-excel on WebConfiguration as following.
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false).
favorParameter(true).
parameterName("format").
ignoreAcceptHeader(true).
useJaf(false).
defaultContentType(MediaType.APPLICATION_JSON).
mediaType("xlsx", new MediaType("application","vnd.ms-excel"));
}
On Accept header of the request I added the value application/vnd.ms-excel.
Finally, by calling the excel endpoint now with the needed format it generates the excel file properly.
localhost:8080/students/excel/excel?format=xlsx
I have developed a webservices using spring+ XSD+ Payload. I have a requirement of authenticating the request header with username and password coming in SOAP request header which i achieved with SOAPUI
I m able to generate the below header in the request
<soapenv:Envelope xmlns:jaxb="http://jaxb.miws.sg.com/" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis- 200401-wss-wssecurity-secext-1.0.xsd"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<wsse:UsernameToken wsu:Id="UsernameToken-C3092BFBAE5B212E93144378035575013">
<wsse:Username>User</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">test</wsse:Password>
<wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">CT1Fyo/g2WMaadE52bsnkg== </wsse:Nonce>
<wsu:Created>2015-10-02T10:05:55.750Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
</soapenv:Header>
Now i want to validate the header elements for userName and Password.
Ex:
Case 1:
userName=User and Password=test //Authentication passed and give response Success
Case 2:
userName=User1 and Password=test1 //Authentication failed and give response Failure
Please help me to provide the suitable samples to achieve same.
Handlers in SOAP webservices (similar to Interceptors/Filters) can be used for the authentication purpose on the server side and then chaining the request further.
Please have a look at SOAPHandler to parse the header information from the payload and authenticating the username/password.
SOAP Handler at Server Side
Here are some steps to do that:
Implement a SOAPHandler class by writing a custom handleMessage method.
Within the handleMessage method, evaluate the context's MESSAGE_OUTBOUND_PROPERTY. If it is false (meaning it is an inbound message), then write code that introspects the context.getMessage(). There you can evaluate the MIME headers, the security headers & tokens and the body, to determine if you need to reject the authentication credential. If you do, return false at the end of the method.
Add the SoapHander you created to the service's Handler chain.
Example of a SOAPHandler:
public class MyCustomSoapHandler implements SOAPHandler<SOAPMessageContext>
{
public Set<QName> getHeaders()
{
return Collections.emptySet();
}
public boolean handleMessage(SOAPMessageContext messageContext)
{
Boolean outboundProperty = (Boolean)
messageContext.get (MessageContext.MESSAGE_OUTBOUND_PROPERTY);
if (outboundProperty.booleanValue()) {
//This is for handling messages going out of the conduit
} else {
//Here is where you want to authenticate
}
return true; //return false if do not want to proceed to the next handler in the chain
}
public boolean handleFault(SOAPMessageContext messageContext)
{
return true;
}
public void close(MessageContext messageContext)
{
}
Here a starter template for your SOAPHandler that you need to add to your Service's handlerChain:
#WebService(name = "Handler", targetNamespace = "http://example.org")
#HandlerChain(file="handler-chain.xml")
public class HandlerWS
{
#Resource
WebServiceContext ctx;
#WebMethod()
public String getProperty(String propertyName)
{
return (String) ctx.getMessageContext().get(propertyName);
}
}
You'll also need to add the handler-chain.xml to your classpath:
examples.webservices.handler.Handler1
examples.webservices.handler.Handler2
For a complete guide, see Oracle's guide to creating SOAPHandlers
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);
}
}
I am creating a RESTful service using Play. I want Play to reject any request where the media type is not specified as JSON in the request header.
There tutorial has a good example of this.
http://www.playframework.com/documentation/2.0/JavaJsonRequests
Read where it says ...
#BodyParser.Of(Json.class)
public static index sayHello() {
String name = json.findPath("name").getTextValue();
if(name == null) {
return badRequest("Missing parameter [name]");
} else {
return ok("Hello " + name);
}
}
Note: This way, a 400 HTTP response will be automatically returned for non JSON requests.
Why is it returning HTTP error 400, bad request, instead of HTTP error 415, unsupported media type?
Is there a way to change this behavior?
You can return a custom HTTP response with the status(int, String) method:
return status(415, "The only supported content type is application/json");
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.