Spring MVC 3: Is consumes supposed to disambiguate request mappings? - spring

I have two request mappings in a Spring MVC 3 application, one which takes json and xml, and another that takes application/x-www-form-urlencoded data. Example:
#RequestMapping(value={"/v1/foos"}, method = RequestMethod.POST, consumes={"application/json", "application/xml"})
public FooDTO createFoo(#RequestBody FooDTO requestDTO) throws Exception {
...
}
#RequestMapping(value={"/v1/foos"}, method = RequestMethod.POST, consumes="application/x-www-form-urlencoded")
public FooDTO createFooWithForm(#ModelAttribute FooDTO requestDTO) throws Exception {
...
}
I expected that the different consumes parameter makes each request unique, though I get an java.lang.IllegalStateException: Ambiguous handler methods mapped....
Should consumes and produces makes requests unique? Any ideas?
Edit 1: To add weight to this, if you set the content-type in the header rather than using consumes, this actually works and makes them unique: headers="content-type=application/x-www-form-urlencoded. Perhaps there is a bug with consumes?
Edit 2: We're using Spring 3.1.1.RELEASE.

This has been resolved by Marten Deinum on the Spring Forum (here):
You should change both the HandlerMapping as well as the
HandlerAdapter (use the RequestMappingHandlerAdapter).
In theory it should work if it doesn't feel free to register an issue.
The solution to this problem was to use the correct HandlerMapping and HandlerAdapter in my servlet configuration:
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>
Thanks Marten.

Related

How to set header variables in GraphQL-SPQR

I'm running a GraphQL API using GraphQL-SPQR and Spring Boot.
At the moment, I am throwing RuntimeExceptions to return GraphQL errors. I have a customExceptionHandler that implements DataFetcherExceptionHandler that returns errors in the correct format, as shown below:
class CustomExceptionHandler : DataFetcherExceptionHandler {
override fun onException(handlerParameters: DataFetcherExceptionHandlerParameters?): DataFetcherExceptionHandlerResult {
// get exception
var exception = handlerParameters?.exception
val locations = listOf(handlerParameters?.sourceLocation)
val path = listOf(handlerParameters?.path?.segmentName)
// create a GraphQLError from your exception
if (exception !is GraphQLError) {
exception = CustomGraphQLError(exception?.localizedMessage, locations, path)
}
// cast to GraphQLError
exception as CustomGraphQLError
exception.locations = locations
exception.path = path
val errors = listOf<GraphQLError>(exception)
return DataFetcherExceptionHandlerResult.Builder().errors(errors).build()
}
}
I use the CustomExceptionHandler as follows (in my main application class):
#Bean
fun graphQL(schema: GraphQLSchema): GraphQL {
return GraphQL.newGraphQL(schema)
.queryExecutionStrategy(AsyncExecutionStrategy(CustomExceptionHandler()))
.mutationExecutionStrategy(AsyncSerialExecutionStrategy(CustomExceptionHandler()))
.build()
}
I'd like to set a header variable for a UUID that corresponds to the exception, for logging purposes. How would I do that?
Even better, is it possible to create a Spring Bean that puts the UUID in the header for all queries and mutations?
Thanks!
when you're using spring boot, there's two options:
you're using the spring boot graphql spqr starter (which brings it's own controller to handle all graphQL requests)
you're using plain graphql-spqr and have your own controller to handle GraphQL requests
In any case, you've got a few options:
Making your CustomExceptionHandler a Spring Bean and Autowiring HttpServletResponse
That would probably be the easiest way to go - and it would probably work in any case: You could simply make your CustomExceptionHandler a Spring bean and have it autowire the HttpServletRequest - in the handler method, you could then set it to whatever you would like it to be. Here's some dummy code in Java (sorry, I am not proficient enough in Kotlin):
#Component
class CustomExceptionHandler implements DataFetcherExceptionHandler {
private final HttpServletResponse response;
public CustomExceptionHandler(HttpServletResponse response) {
this.response = response;
}
#Override
public DataFetcherExceptionHandlerResult onException(DataFetcherExceptionHandlerParameters handlerParameters) {
response.setHeader("X-Request-ID", UUID.randomUUID().toString());
// ... your actual error handling code
}
}
This is going to work because spring will realise that HttpServletRequest differs for each request. It will therefore inject a dynamic proxy into your error handler that will point to the actual HttpServletResponse instance for every request.
I would argue, that it's not the most elegant way, but it will certainly solve your problem.
for the graphql-spqr spring boot starter
There's a default controller implementation that is used in projects using this starter. That controller will handle every graphql request that you receive. You can customise it, by implementing your own GraphQLExecutor and making it a spring bean. That executor is responsible to call the GraphQL engine, pass the parameters in and output the response. Here's the default implementation, that you might want to base your work on.
Similarly to the previous solution, you could autowire the HttpServletResponse in that class and set a HTTP Response header.
That solution would allow you to decide, if you want to set a request id in all cases, or just in specific error cases. (graphql.execute returns an object from which you can get the information if and what errors existed)
when using graphql-spqr without the spring boot starter
Locate your GraphQL controller, add an argument to that method of type HttpServletRequest - and then add headers to that as you prefer (see previous section on some more specific suggestions)

Spring Autowiring error: java.lang.IllegalStateException: Method [name] can only contain 1 method field. Found: [PUT, POST]

I have a method in one of my webservices that accepts both PUT and POST. This is because we started using PUT but later we needed to support POST too (for a new service).
#RequestMapping(
value = "/endpointURL",
method = {RequestMethod.PUT, RequestMethod.POST})
I am trying to create a test application that calls this method, but Spring throws an Autowiring error during startup with the following error:
java.lang.IllegalStateException: Method [name] can only contain 1
method field. Found: [PUT, POST]
Both the Spring and Feign versions are the same in both applications (webservice with this endpoint, and testing application).
Any ideas on how to fix it please?
Thank you!
Method supports various HTTP method as below. Could you post your class source code. I think you should have another problem. Maybe duplicated path or else.
#RequestMapping("/v1/echo")
#RestController
public class EchoApi {
#RequestMapping(value = "/", method = { RequestMethod.PUT, RequestMethod.POST })
public ResponseEntity<String> echo(#RequestBody String body){
System.err.println(body);
return new ResponseEntity<String>(body, HttpStatus.OK);
}
}
In the end it was due to the Feign version we were using. It's fixed after version 10. Will close this topic. Thanks!

5 levels of media type Spring REST

I am trying to apply CQRS principles on my REST API with domain-driven-design principles, using the 5 levels of Media Types, as explained in these articles:
https://www.infoq.com/articles/rest-api-on-cqrs
http://byterot.blogspot.ch/2012/12/5-levels-of-media-type-rest-csds.html
My technical context is Spring REST framework version 3.2.
Basically, i need to be able to map my commands using different "domain-model" media types.
Therefore, i would expect the following mapping to work:
#Controller
#RequestMapping("resources")
public class MyController {
#RequestMapping(value = "{id}", method = RequestMethod.PUT, consumes = "application/json;domain-model=CommandOne")
#ResponseBody
public void commandOne(#PathVariable Long id, #RequestBody CommandOne commandOne) {
LOG.info("Using command {}", commandOne);
}
#RequestMapping(value = "{id}", method = RequestMethod.PUT, consumes = "application/json;domain-model=CommandTwo")
#ResponseBody
public void commandTwo(#PathVariable Long id, #RequestBody CommandTwo commandTwo) {
LOG.info("Using command {}", commandTwo);
}
}
Problem is, I am getting mapping errors when requesting for a PUT:
PUT /resources/123
Content-Type: application/json;domain-model=CommandOne
Error is:
java.lang.IllegalStateException: Ambiguous handler methods mapped for HTTP path ...
Spring doesn't allow me to map the same uri the different domain-model Media Types. Any idea how could I achieve that? Am I missing something?
Many thanks
:o)
That's because the content-type is still the same application/json. Please look at Content-Type syntax
What you are passing as domain-model=CommandOne is just a parameter and Spring doesn't recognize as a difference to call the different methods.
This is described in more detail on answer
Does HTTP content negotiation respect media type parameters
This was submitted as a BUG to the Spring team but they closed with "Work as designed".
Unfortunately Spring can't treat this case currently.

How to configure which controllers Spring #ControllerAdvice will be applied to?

I have two types of controllers in my spring application.
View controllers that forward to views to generate HTML
API controllers that return JSON directly from the controllers
Both the API and View controllers are part of the same spring dispatcher servlet. Spring 3.2 introduced the #ControllerAdvice annotation to allow for a global location to handle exception.
The documentation implies that #ControllerAdvice will be applied to every controller associated with a Dispatcher Servlet.
Is there a way to configure which controllers #ControllerAdvice will apply to?
For example in my scenario I want a #ControllerAdvice for my View Controllers and separate #ControllerAdvice for my API controllers.
For people that will still find this question:
As from Spring 4 ControlerAdvice's can be limited to Controler's with the specified annotations. Take a look at:
http://blog.codeleak.pl/2013/11/controlleradvice-improvements-in-spring.html
(second half of this article) for more details.
UPDATE
I am using spring 4. You can do one of 2 below options.
(1) You can add the packages you want. (Inside those packages you have controllers that you want to follow #ControllerAdvice).
Ex:
#ControllerAdvice(basePackages={"my.pkg.a", "my.pkg.b"})
(2) You can directly add the controller classes you want.
Ex:
#ControllerAdvice(basePackageClasses={MyControllerA.class, MyControllerB.class})
I do not think this is possible now. If you can make the API and View controllers throw different Exception types, then you could define two different #ExceptionHandlers and achieve what you want.
// For handling API Exceptions
#ExceptionHandler(APIException.class) // Single API Exception
#ExceptionHandler({APIException.class, ..., ,,,}) // Multiple API Exceptions
// For handling View Exceptions
#ExceptionHandler(ViewException.class) // Single View Exception
#ExceptionHandler({ViewException.class, ..., ...}) // Multiple View Exceptions
You could use aop to translate the Exceptions coming out of APIs to a standard APIException. See this thread on spring forums.
Hope it helps.
Your exceptions should not dictate the content-type of your response. Instead check the request's Accept header for what the browser expects.
#ExceptionHandler(Throwable.class)
public #ResponseBody String handleThrowable(HttpServletRequest request, HttpServletResponse response, Throwable ex) throws IOException {
...
String header = request.getHeader("Accept");
if(supportsJsonResponse(header)) {
//return response as JSON
response.setContentType(JSON_MEDIA_TYPE.toString());
return Json.stringify(responseMap);
} else {
//return as HTML
response.setContentType("text/html");
}
#ExceptionHandler(value=Exception.class)
public ModelAndView error(Exception ex) {
return new ModelAndView("redirect:/error/m");
}
...//ErrorController
#RequestMapping(value = "/m", produces="text/html")
public ModelAndView error()...
#RequestMapping(value = "/m", produces="application/json")
#ResponseBody
public Map errorJson()...

Spring #RequestMapping consumes charset?

I'm trying to use #RequestMapping with the consumes-element. Reading the API-document it works on the Content-Type-header of the request. However, using
#RequestMapping(consumes = "application/x-www-form-urlencoded;charset=UTF-8", value = "/test")
public void test() {
:
}
or
#RequestMapping(consumes = "application/x-www-form-urlencoded;charset=ISO-8859-1", value = "/test")
public void test() {
:
}
doesn't make a difference. The header in the request can look like
Content-Type: application/x-www-form-urlencoded;charset=UTF-8
or
Content-Type: application/x-www-form-urlencoded
test() will be called in all four possible constellations.
However, and this is proof to me that Spring sees and tries to use the charset-part, if I specify
#RequestMapping(consumes = "application/x-www-form-urlencoded;charset=UTF-x8", value = "/test")
public void test() {
:
}
I get an exception during startup (!) of the web-app:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#0': Initialization of bean failed;
nested exception is java.nio.charset.UnsupportedCharsetException: UTF-x8
Note that the documentation on the produces-element also doesn't mention the use of charset, but according to Google some use it.
Any clues to what's happening here or what I'm doing wrong?
BTW, this is Spring 3.1.1.RELEASE.
I think you have already answered your question, so this is more of a confirmation, from a code point of view, as to why the charset is not taken into account when resolving mappings.
When digging into Spring code, the culprit seems to be the MediaType#includes(). method
More digging reveals that a RequestMappingInfo is created in association to the RequestMapping annotation of the method. This RequestMappingInfo stores a series of AbstractRequestCondition objects, one of them being the ConsumesRequestCondition which holds the MediaType defined in the consumes part of the annotation (i.e. application/x-www-form-urlencoded;charset=UTF-8).
Later when a request is made, this ConsumesRequestCondition has an inner ConsumeMediaTypeExpression class with a matchMediaType() method that extracts the MediaType of the HttpServletRequest and checks it against it's own MediaType to see if it's included.
If you look at the MediaType#includes() implementation (Lines 426 to 428), it returns true when type (i.e. application) and subtype (i.e. x-www-form-urlencoded) are equal, completely disregarding the parameters Map which in this case
holds the remnant "charset","UTF-8" combination.
Digging into the produces track seems to show similar results, but in this case it's the MediaType#isCompatibleWith() method involved, and again, it reaches only to type and subtype if they are equal.
If you found evidence on Google of the produces working for charset request mapping, I would doubt it (unless they changed core Spring stuff)
As to why it was designed this way, well that's another question :)

Resources