Accepting multiple http methods? - java

Is there any way to accept multiple http mothods in Micronaut controller? For example, I would like to process GET, POST, and OPTIONS requests in a method. I tried annotated with three annotations #Get, #Post, and #Options, however, only first annotation works and others are ignored.
#Controller("/echo")
public class EchoController {
private ObjectMapper mapper = new ObjectMapper();
#Get
#Post
#Options
public HttpResponse<String> process(HttpRequest<?> request) throws JsonProcessingException {
Headers headers = request.getHeaders();
return ok(mapper.writeValueAsString(headers.asMap()));
}
}
Is adding separete methods for each HTTP method and annotate them with corresponding annotation the only way to do this?

There is an open issue to allow the HTTP annotations to be repeatable.
It looks like it is planned vor 1.2.

Related

Distinguish Spring Boot PostMapping based on key in RequestBody

I have a REST endpoint with a PostMapping that should be able to accept different objects in the body and map them based on existence of keys.
When I use the same PostMapping for both functions, it gives me an "Ambiguous mapping" error.
When I use params similar to https://www.baeldung.com/spring-requestmapping, the default mapping is called even if the specialKey exists in the request body.
Is there any workaround to achieve this?
#PostMapping(value = "/classes", params = {"specialKey"})
public ResponseEntity<Class> createClass(#Valid #RequestBody SpecialClass class) throws URISyntaxException {
// do something special
}
#PostMapping("/classes")
public ResponseEntity<Class> createClass(#Valid #RequestBody Class class) throws URISyntaxException {
// do something
}
Based on Mapping the same url to different methods based on request body in spring it's not possible (or at least wasn't at the time). The params needs a separate request parameter, it can't be used to look for things inside the request body like that.
You could include the parameter in the URI, the special endpoint would be /classes?specialKey, and the normal endpoint just /classes. But I would just use different paths.

Spring 406 with a custom Configurer

I recently added jackson-dataformat-xml to the classpath, and magically spring started sending XML instead of JSON if the Accept header wasn't precised in the request. Since I'm dealing with spaghetti legacy code adding the header to all requests is not an option, I added a custom content negociation filter :
#Override
public void configureContentNegotiation (ContentNegotiationConfigurer configurer) {
super.configureContentNegotiation(configurer);
configurer
.defaultContentType(MediaType.APPLICATION_JSON_UTF8);
}
However, this backfired for a request like
#RequestMapping(value = "edit", method = RequestMethod.GET)
public ResponseEntity<?> edit(...) throws ApiException {
.... // returns an inputstream
}
Where I get a 406 error. If I remove the default content type, it works fine but then I get xml for other requests. How can I configure both?

Managing any HTTP request in a generic way

In my organisation, when I want to expose an API, I have to declare it with a swagger contract, same for any update, and it can take multiple weeks before the creation or change is taken into account.
That's why we've come with the idea to declare only one contract for all the APIs we need to expose, and manage the routing in an applicative reverse proxy (the request would include the necessary metadata to allow to route to the appropriate endpoint) :
{
"genericHttpRequest" : base64encodedByteArrayOfAnyHttpRequest
}
Now the question is :
how to manage this request without reimplementing HTTP ? Is it possible to put back the array of byte into a structured HttpServletRequest ?
/**
* Manage a generic request
*/
#RequestMapping(value = "/genericRequest", method = RequestMethod.POST)
public #ResponseBody void manageGenericRequest(#RequestBody GenericHttpRequestDto body) {
byte[] genericHttpRequest = body.getGenericHttpRequest();
//(...)
}
Spring will inject a HttpServletRequest if it is set as a method parameter. Furthermore, wildcard path mappings will enable the methods to be matched to every request:
#RestController
#RequestMapping("/generic-endpoint/**")
public class DemoController {
#RequestMapping
public ResponseEntity<Object> genericGetRequest(HttpServletRequest httpServletRequest) {
return ResponseEntity.ok().body(httpServletRequest.getMethod());
}
}
Optionally, you could return a ResponseEntity to gain more control over your HTTP response.

#FeignClient forces #GetMapping with #RequestBody to POST

I have following REST controller with GET method that have BODY, that works fine with tests and postman
#RestController
#RequestMapping(value = "/xxx")
public class Controller {
#GetMapping({"/find"})
public LocalDateTime findMax(#RequestBody List<ObjectId> ids) {
//return sth
}
}
but when FeignClient is used to call service, instead GET request a POST request is generated (#GetMapping annotation is ignored)
#FeignClient
public interface CoveragesServiceResource extends CoveragesService {
#GetMapping({"/find"})
LocalDateTime findMax(#RequestBody List<ObjectId> ids);
}
that gives an error:
Request method 'POST' not supported
GET request technically can have body but the body should have no meaning as explained in this answer. You might be able to declare a GET endpoint with a body but some network libraries and tools will simply not support it e.g. Jersey can be configured to allow it but RESTEasy can't as per this answer.
It would be advisable to either declare /find as POST or don't use #RequestBody.

Annotating resource to produce JSON, but return "text/plain" in response header

I am currently implementing a web API
Spring
Jersey
com.thetransactioncompany.cors http://software.dzhuvinov.com/cors-filter.html
The output (if any) will be JSON, so all my classes are annotated with the expected media type.
#Produces(MediaType.APPLICATION_JSON)
public class CustomerResource {
...
}
that way my classes are automatically transformed to json.
BUT...
Due to microsoft, their IE only support CORS, if the request/response type is text/plain http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx
4. Only text/plain is supported for the request's Content-Type header
so I need to force my application to respond with text/plain in the header but still projecting my classes to json output. I know that the CORS classes I added is setting that header, but somehow that is overwritten again by my annotation, even if I add another filter by my own.
Hum, the link you are pointing says that it is true for REQUESTS only.
So you can accept only text plain but are free to produce what ever you want.
EDIT Try registering a custom responsefilter with code similar to that (maybe you already did it?):
#Provider
public class HeaderRewriteFilter implements ContainerResponseFilter {
#Override
public ContainerResponse filter(ContainerRequest request, ContainerResponse response) {
response.setResponse(Response
.fromResponse(response.getResponse()).header(HttpHeaders.CONTENT_TYPE, "text/plain").build());
return response;
}
}
However check the result to ensure it is ok if the response already contains this header.
Otherwise you can try to modify the current response, but I am not sure you can as it might be an immutable object. And by the way it looks less clean to me :)
List<Object> contentTypes = response.getHttpHeaders().get(HttpHeaders.CONTENT_TYPE);
contentTypes.clear();
contentTypes.add("text/plain");
Also for doing json<>java databiding you can check Genson library http://code.google.com/p/genson/, it integrates well with Jersey. Just drop the jar in the classpath and run!
EDIT 2 OK then you must do it in the other way, use produces "text/plain" and define a json bodywriter for for that type. Downside is that you will be able to produce only json. With Genson you could do it that way:
#Provider
#Produces({ MediaType.TEXT_PLAIN })
public class PlainTextJsonConverter extends GensonJsonConverter {
public GensonJsonConverter() {
super();
}
public GensonJsonConverter(#javax.ws.rs.core.Context Providers providers) {
super(providers);
}
}

Categories

Resources