Use header in addition to method to route request to annotated method - java

I wonder if it's possible with JAX-RS to route a request using an header in addition to the HTTP method. In fact, I can't find out the way to do that.
I thought about something like that where x-header is an header:
#Path("/contacts/")
public class MyResource {
#POST
#Header("x-header:content1")
public void method1(MyContent1 content) {
(...)
}
#POST
#Header("x-header:content2")
public void method2(MyContent2 content) {
(...)
}
}
This question follows this answer: How to Update a REST Resource Collection.
Thanks very much for your help!
Thierry

If you need to affect the request matching/routing process, you have to use JAX-RS filters - PreMatching filters to be specific (#PreMatching) [this will work in JAX-RS 2.0 onwards] If the use header info in the resource methods, it wont make sense because JAX-RS would have already matched the method
Here is the overall flow in filter implementation
Use the ContainerRequestContext to fetch header info
Apply your business criteria depending on the header value
Now the trick is to be able to route to the desired resource method - one option you have is to use the setRequestUri method of ContainerRequestContext and have different resource methods set on different URIs (using #Path)
Jersey docs might help -- https://jersey.java.net/documentation/latest/filters-and-interceptors.html#d0e9538

Just throw in another option. You can also use Sub-resource locators, which give us some control over the chosen resource (and is part of the JAX-RS spec). For example
#Path("contacts")
public class ContactsResource {
#Path("/")
public AbstractHeaderResource doSomething(#HeaderParam("X-Header") String xHeader) {
if ("RequiredValue".equals(xHeader)) {
return new WithHeaderResource();
}
return new WithOutHeaderResource();
}
public static abstract class AbstractHeaderResource {
#POST
#Consumes(MediaType.APPLICATION_JSON)
public abstract Response doSometing(Contact contact);
}
public static class WithHeaderResource extends AbstractHeaderResource {
#Override
public Response doSometing(Contact contact) {
return Response.ok("*** With Header ***").build();
}
}
public static class WithOutHeaderResource extends AbstractHeaderResource {
#Override
public Response doSometing(Contact contact) {
return Response.ok("*** WithOut Header ***").build();
}
}
}
Test
C:\>curl -v -X POST
-d "{\"name\":\"Peeskillet\"}"
-H "X-Header:RequiredValue"
-H "Content-Type:application/json"
http://localhost:8080/contacts
*** With Header ****
C:\>curl -v -X POST
-d "{\"name\":\"Peeskillet\"}"
-H "Content-Type:application/json"
http://localhost:8080/contacts
*** WithOut Header ****
The resource methods don't have to accept the same parameter type. I did it just for brevity. You can have the sub resource classes extend an empty abstract class or interface (just for typing), and create the methods however you want
UPDATE
If you need some objects injected into the sub resource, you can also return the sub resource class from the locator method or you can inject ResourceContext into the main resource class and use that to create the sub resource instance and return that instance from the locator method. Both ways will support container injection as the container will create the instance instead of you just instantiating it.

Related

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.

Content-Type is missing with Spring GET/POST method

I am new to Spring and I am trying to do the basic GET and POST method.
This is how I am trying to do the methods:
#RestController
public class DeskController {
#Autowired
private DeskDao dao;
#GetMapping("desks")
public List<Desk> getDesks() {
System.out.println(dao.findById(1L));
return dao.findAll();
}
#PostMapping("desks")
public Desk save(#RequestBody #Valid Desk desk) {
Desk deskObj = dao.save(desk);
System.out.println(deskObj);
return deskObj;
}
When I am calling the POST method like this I get the pring with the actual object that I had called it with so it is working fine, but I also get this error:
javax.ws.rs.ProcessingException: Content-Type is missing
And when trying to call GET it tells me that:
org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'GET' not supported
I am aware that I have not included the whole code, but I will add what is needed to resolve this error since there are a lot of classes.
My questions are, what do I do against the first error and why is GET method not supported?
Two things you need to change :
Use a / to indicate that for this path you will perform an
operation. For eg : (/desks)
Use the annotation #Consumes to
indicate that this method accepts the payload in particular format. For eg : #Consumes(MediaType.APPLICATION_JSON) annotated over your save() method.

How to test only specific http request method available for controller method in Spring MVC?

I need to check if only specific http method is available for some url.
For example, if there is a controller like this
#Controller
public class FooController {
#RequestMapping(value = "bar", method = RequestMethod.GET)
public void bar() {/*do something*/};
...
}
For controller test I use junit(4.10), spring-test(3.2.10) and easymock(3.1).
If I write test like this
#Test
public void testBar() throws Exception {
mockMvc.perform(post("bar").session(session))
.andExpect(/*some application's default error response*/);
}
it will pass (although test calls post-method, not get-method).
So I'm looking for a proper way to make sure, that my rest resources are only avaiable by request methods specified in documentation. Two solutions came to my mind:
write tests with wrong request methods and somehow check resource is not available
add custom exception resolver to process org.springframework.web.HttpRequestMethodNotSupportedException: Request method '__' not supported and return same application default error response, but with https status 405 Method not allowed.
What would you suggest and how to check in controller test request method?
Thanks in advance.
You would need to check the status of all the request methods, you could do it using andExpect with status().isMethodNotAllowed() or status().isNotFound() depends on your needs:
Examples:
get: mockMvc.perform(get("bar").andExpect(status().isNotFound()) or mockMvc.perform(get("bar").andExpect(status().isMethodNotAllowed())
Do the same same for put, delete, ....

Apache-CXF JAX-RS implementation not respecting proper #Path ordering

I have a JAX-RS webservice that looks like this:
#Path("/service")
public interface Service {
#GET
#Path("/{serviceId}")
public Response getService(#PathParam("serviceId") String serviceId);
#GET
#Path("/{serviceId}/private")
public Response getPrivateService(#PathParam("serviceId") String serviceId);
#GET
#Path("/other-thing")
public Response getOtherThing(#CookieParam("cookieName") String cookieValue);
}
For some reason, GET /other-thing always invokes the first method with #Path("/{serviceId}"). Invoking GET /abc/private returns a 404 claiming there is no matching route. According to the spec, the path with the most matching literal characters should be selected, but it seems as if my routes are being completely ignored. How can I debug this?
Here is the log message from CXF:
No operation matching request path "/service/abc/private" is found, Relative Path: /abc/private, HTTP Method: GET, ContentType: */*, Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8,. Please enable FINE/TRACE log level for more details.
I discovered the problem.
I have recently switch from Eclipse to IntelliJ. Eclipse's default behavior is to ignore any annotations when auto-generating interface method implementations. IntelliJ, on the other hand, keeps the annotations. Here is the different:
// In Eclipse
public class ServiceImplementation implements Service {
public Response getService(String serviceId) {
return null;
}
}
// In IntelliJ
public class ServiceImplementation implements Service {
// Note the #PathParam
public Response getService(#PathParam("serviceId") String serviceId) {
return null;
}
}
The additional annotations in the implementation of the service causes path resolution to fail. Since I had implemented getService in Eclipse, it worked correctly, but the new methods implemented IntelliJ did not work until I removed the parameter annotation in the service implementation.

REST Subresource Locators

I am reading through the book RESTful Java with JAX-RS 2.0, 2nd Edition and am struggling to understand how Subresource Locators work, below is a cut-down version of the example provided.
CustomerDatabaseResource class
#Path("/customers")
public class CustomerDatabaseResource {
#Path("{database}-db")
public CustomerResource getDatabase(#PathParam("database") String db) {
// find the instance based on the db parameter
CustomerResource resource = locateCustomerResource(db);
return resource;
}
protected CustomerResource locateCustomerResource(String db) {
...
}
}
CustomerResource Class
public class CustomerResource {
private Map<Integer, Customer> customerDB =
new ConcurrentHashMap<Integer, Customer>();
private AtomicInteger idCounter = new AtomicInteger();
public CustomerResource(Map<Integer, Customer> customerDB)
{
this.customerDB = customerDB;
}
#GET
#Path("{id}")
#Produces("application/xml")
public StreamingOutput getCustomer(#PathParam("id") int id) {
...
}
So I understand that as a request such as GET /customers/northamerica-db/333 comes in, will first match the expression on the method CustomerDatabaseResource.getDatabase() which based upon the location, will create the correct instance of CustomerResource.
What I don't understand is what happens next...
The instance resource gets returned, but returned to where?
How does the web service know to then match and process the remaining part of the request with the method CustomerResource.getCustomer()? I guess this is because The CustomerDataBaseResource class doesn't have a #GET, but I don't really understand how the transition happens.
Is this specific to RESTEasy?
The instance resource gets returned, but returned to where?
It's get's returned to the request processing engine and continues to look for a matching method (inside the return resource object), just like any other request.
How does the web service know to then match and process the remaining part of the request with the method CustomerResource.getCustomer()? I guess this is because The CustomerDataBaseResource class doesn't have a #GET, but I don't really understand how the transition happens
Resource locators aren't supposed to be annotated with Http Methods. That's how they are known to be locators. Since it is not the resource method to be called, it should not be annotated. Imagine this
public class CustomerResource {
#PUT
#Path("{id}")
public Response updateCustomer(Customer customer) {}
#POST
#Path("{id}")
public Response createCustomer(Customer customer) {}
}
If CustomerDataBaseResource.getDatabase() were to be annotated with an Http method, then we couldn't hit the above methods. All the locator needs is the #Path, and the URI matching will continue starting from that path.
/customers/database-us
Once the CustomerResource is created, if the request uri is /customers/database-us/123, then now the next logical step is to find a matching resource method based on the URI, so will be looking for something annotated with #Path that will match 123. Then the Http method is checked.
Is this specific to RESTEasy?
Going through the jax-rs spec, I don't see anything about sub-resource locators, but Jersey also implements this exact behavior. I've read the book you are referring to, and from what I remember, the author doesn't really get much into anything that is implementation specific, but does mention common feautres that most implementers implemented, that is not part of the spec. Maybe this is one of those things.
UPDATE
So it is in the spec. Go to the link and download the spec. You will find everything under 3.4.1 Sub Resources and some algorithm info for request matching in 3.7.2 Request Matching

Categories

Resources