In JavaEE documentation for JAX-RS webservices, I came across below statement:
http://docs.oracle.com/javaee/6/tutorial/doc/gilik.html
If the URI path template variable cannot be cast to the specified
type, the JAX-RS runtime returns an HTTP 400 (“Bad Request”) error to
the client. If the #PathParam annotation cannot be cast to the
specified type, the JAX-RS runtime returns an HTTP 404 (“Not Found”)
error to the client.
#Path("/{username}")
public class MyResourceBean {
...
#GET
public String printUsername(#PathParam("username") String userId) {
...
}
}
So if request has parameter "username" and if it cannot be type-casted to String then we get 400 error, then when we will get 404 error? I am new to web-services, please help me in understanding this.
Let's say you had something like this instead:
#Path("/{userId}")
public class MyResourceBean {
...
#GET
public String printUsername(#PathParam("userId") int userId) {
...
}
}
if the URI for your request is something like /abc, then you'd get a 400 because abc cannot be cast into an int. Now, if your request URI is '/', you'll get a 404 because there's no resource method associated with this URI.
URIs are used as resource locators in this case. These rules match the requirements of the HTTP specification.
Say JAX-RS exposes a single end-point matching on the URL http://host/app/{someInt} and {someInt} must be converted to an integer.
http://host/app does not match this so it returns 404 - not found.
http://host/app/foo matches the pattern but foo cannot be parsed to an integer so it returns 400 - bad request.
Related
I am getting following for two completely different URLs and I cannot explain why:
RESTEASY002142:
Multiple resource methods match request "GET /devices/distinctValues/3".
Selecting one.
Matching methods:
[public javax.ws.rs.core.Response
mypackage.DevService.getDistinctValues(int) throws java.lang.Exception,
public javax.ws.rs.core.Response
mypackage.DevRESTService.getDevice(int,java.lang.String)
throws java.lang.Exception]
This warning should not come up, since the URLS are completely different. If anybody knows why this is happening:
URLs for both methods:
getDevice:
#GET
#Path("devices/{customerId}/{deviceIds}")
#Produces({ "application/json" })
getDistinctValues:
#GET
#Path("devices/distinctValues/{customerId}")
#Consumes("application/json")
#Produces("application/json")
The warning happens because your request string can match both path templates. The request "devices/distinctValues/3"
matches devices/distinctValues/{customerId} in that customerId = "3"
matches devices/{customerId}/{deviceIds} in that customerId = "distinctValues" and deviceIds = "3".
There is no type resolution and since your request is a String there is no way to tell customerId that it cannot accept "distinctValues".
As a workaround, you can either specify a regex as shown in the linked question, or use the RESTEasy proxy framework which is basically a shared interface that both server (your JAX-RS resource) and client use, and then you have a common language with type resolution. Note that there is a typo in the example of the docs.
I'm working with Dropwizard, which uses Jersey internally. I have two methods on a controller:
PUT /garbage/[id1,id2,...idN] is intended to take a path parameter that's a list of numeric IDs representing resources to be updated. I'm using a regex-based PathParam here. I've fudged the regex in this example because I don't think it matters, but the point is that a single numeric ID should match the regex.
GET /garbage/[id] fetches data about a single piece of garbage.
Jersey seems to get confused, despite the difference in method. When I query with something like
curl localhost:8080/garbage/1
Jersey gives me a 405 error. If I take the PUT out of the picture (for example, sabotage the path param regex, or remove it entirely), the GET endpoint works fine.
I assume there is some detail in JAX-RS 3.7.2 I'm missing that explains why this should be the case, but I can't figure out what it is.
Here's the code:
#Path("/garbage")
#Produces(MediaType.APPLICATION_JSON)
public class GarbageController {
private static final Logger LOG = LoggerFactory.getLogger(GarbageController.class);
#PUT
#Path("/{params: [\\d,]+}")
#Consumes(MediaType.APPLICATION_JSON)
#Timed
public Response updateGarbage(#PathParam("params") List<PathSegment> params) {
LOG.warn("updateGarbage");
return Response.status(Response.Status.OK).build();
}
#GET
#Path("/{garbageId}")
public Response getGarbageById(#PathParam("garbageId") long garbageId) {
LOG.warn("getGarbage");
return Response.status(Response.Status.OK).build();
}
}
The main purpose of #PathSegment is to handle fragments of the URI which is useful to retrieve Matrix Parameters. For example the method below:
#GET
#Path("/book/{id}")
public String getBook(#PathParam("id") PathSegment id) {...}
Should be able to handle this request:
GET /book;name=EJB 3.0;author=Bill Burke
Because the #PathSegment intercepts the entire URL fragment the GET method seems to be ignored. You can handle the comma-separated IDs on the PUT request with a simple String split:
#PUT
#Path("/{params}")
#Consumes(MediaType.APPLICATION_JSON)
#Timed
public Response updateGarbage(#PathParam("params") String params) {
LOG.warn("updateGarbage ", params.split(","));
return Response.status(Response.Status.OK).build();
}
You can also change the request format to query parameters or implement a Converter/Provider to handle a custom object. All of them should solve the GET not implemented issue.
I believe this is not a case of route priorities between GET and PUT but instead this is related to the #Consumes annotation which cannot be used on a GET request. Either DW is ignoring this endpoint or is converting it into the default POST method, which would explain the 405 response for the GET request.
I figured this out, although I have not traced far enough into Jersey to know why it works. The solution is to rewrite the #GET method to use the same regex syntax as the #PUT. Jersey will handle the type conversion in the method signature, with the note that it will return a 404 if the type conversion fails (ie, GET /garbage/xyz).
#PUT
#Path("/{params: .+}")
#Consumes(MediaType.APPLICATION_JSON)
public Response updateGarbage(#PathParam("params") List<PathSegment> params) {
LOG.warn("updateGarbage");
return Response.status(Response.Status.OK).build();
}
#GET
#Path("/{params: .+}")
public Response getGarbageById(#PathParam("params") long garbageId) {
LOG.warn("getGarbage {}", garbageId);
return Response.status(Response.Status.OK).build();
}
I'm trying to create a Rest Api using Jax-rs Jersey from a base code generated by swagger.
The specifications are for exemple for a specific Request :
Code : 200
Description : User token for login
Schema : String
My problem is that the generated code use the class :javax.ws.rs.core.Response that should not be extended according to the documentation.
I'm using this kind of code to build the response :
return Response.ok().entity(new ApiResponseMessage(ApiResponseMessage.OK,apiToken)).build();
The response generated looks like that :
{"code":4,"type":"ok","message":"uHN2cE7REfZz1pD17ITa"}
When i only want to have :"uHN2cE7REfZz1pD17ITa" in the body. Is that possible using Jersey ? Or is this format part of the jax-rs specifications ?
Thank you.
ApiResponseMessage from Swagger does not extend Response from JAX-RS. Check the code and you will see that ApiResponseMessage is just a POJO. That is, the piece of code you posted in your question is just fine.
If you only need the token, you can use:
return Response.ok(apiToken).build();
The following gives you the same result:
return Response.ok().entity(apiToken).build();
Since your resource method will produce just a piece of text (not a valid JSON unless the piece of text is wrapped into quotes), the most suitable media type for the response would be text/plain. It could be achieved by either annotating the resource method with #Produces(MediaType.TEXT_PLAIN) or setting the media type in the response, as following:
#GET
#Produces(MediaType.TEXT_PLAIN)
public Response getToken() {
String apiToken = ...
return Response.ok(apiToken).build();
}
#GET
public Response getToken() {
String apiToken = ...
return Response.ok(apiToken, MediaType.TEXT_PLAIN).build();
}
Alternatively you also could make your method return just a String:
#GET
#Produces(MediaType.TEXT_PLAIN)
public String getToken() {
String apiToken = ...
return apiToken;
}
JAX-RS does not require Request or Response in specific format for text,json, xml, or html fallowing a schema . But They all have to be well formated in according to their specifications.
You can send text response like this in jersey
like this
return Response.ok().entity("uHN2cE7REfZz1pD17ITa").build();
I am new to swagger myself So i don't know if the Response in question can be changed or not . But There is no restriction from jersey side
#GET
#Produces(MediaType.APPLICATION_JSON)
public String getRscSubTypes(){
return AddResourceMysql.getRscSubType();
}
#GET
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public String getDbTypes() {
return AddResourceMysql.getDbType();
}
This is returning the following exception:
org.glassfish.jersey.server.model.ModelValidationException:
Validation of the application resource model has failed during application initialization.
Can you please help me?
How request matching works
Definitely, you can have more than one method annotated with #GET in the same class. However, your current definition is ambiguous.
For more clarification, have a look at the JAX-RS 2.0 specification:
3.7.2 Request Matching
A request is matched to the corresponding resource method or sub-resource method by comparing the normalized request URI, the media type of any request entity, and the requested response entity format to the metadata annotations on the resource classes and their methods. [...]
How to fix it
You need change your method annotations to ensure you have no ambiguity. To do it, you can play with the following annotations:
HTTP method: #GET, #POST, #PUT, #DELETE, #HEAD and #OPTIONS
Request URI: #Path
Media type of any request entity: #Consumes
Requested response entity format: #Produces
To fix it, for example, you can just add a #Path annotation with different values to each method.
If you want to define multiple resource methods, which handle GET requests for the same MIME type, within the same class, you have to specify a different subpath for the methods:
#Path("rcsubtypes")
#GET
#Produces(MediaType.APPLICATION_JSON)
#Produces(MediaType.APPLICATION_JSON)
public String getRscSubTypes()
{
return AddResourceMysql.getRscSubType();
}
#Path("dbtypes")
#GET
#Produces(MediaType.APPLICATION_JSON)
#Consumes(MediaType.APPLICATION_JSON)
public String getDbTypes()
{
return AddResourceMysql.getDbType();
}
The path, specified in the #Path annotation of this method, is a subpath of the path specified in the #Path annotation of the class, which is a subpath of the path you defined for your application.
To explain your behaviour, that always the second method is called, if there is no #Consumes annotation present on the first method: #Consumes defines which media type (set in the Content-Type header of the request) can be accepted by the method. Without a #Consumes annotation all requests are accepted, but i think, if a method specifies an accepted media-type, it will be preferred.
The matching section in the jersey documentation: 3.1. Root Resource Classes
I'm trying to figure out how #Consumes works here.
I have a simplified resource that looks like the below, and I only want this resource to consume "application/vnd.myApp+xml".
#Path("/app")
#Consumes("application/vnd.myApp+xml")
#Produces("application/vnd.myApp+xml")
public class AppResource {
#POST
public Response postStuff() {
...
}
}
I have the following testcases:-
public class AppResourceTest extends JerseyTest {
#Test
public void testApp() {
// #1: Works fine
ClientResponse response = resource().path("app")
.accept("application/vnd.myApp+xml")
.post(ClientResponse.class);
...
// #2: Throws a 415 Unsupported Media Type
ClientResponse response = resource().path("app")
.accept("application/vnd.myApp+xml")
.type("text/plain")
.post(ClientResponse.class);
...
// #3: Works fine
ClientResponse response = resource().path("app")
.accept("application/vnd.myApp+xml")
.type("application/vnd.myApp+xml")
.post(ClientResponse.class);
...
}
}
From the 3 tests above, #2 and #3 work as expected.
As for #1, if I don't set the content-type, why doesn't it throw a 415 too?
Based on #Consumes api (http://jsr311.java.net/nonav/releases/1.0/javax/ws/rs/Consumes.html) and the HTTP type spec (http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7.2.1) coupled with the behavior you are seeing I think it is safe to conclude the following Jersey implementation:
If the Content-Type is NOT set by the client Jersey does NOT default but allows it to pass through any/all #Consumes annotions.
When multiple #Consumes {different types} are set for the URI and the client has NOT set the Content-Type then Jersey will default to the first #Consumes annotation or first type in a list of acceptable types.
When the Accepts header value is set Jersey will find the best fitting method to execute. If multiple methods are a best fit it will default to the first defined.
In conclusion the #Consumes only acts as a filter if and ONLY if the client sets the Content-Type otherwise Jersey will attempt to find the best fitting match. This does match the HTTP spec:
Any HTTP/1.1 message containing an entity-body SHOULD include a Content-Type header field defining the media type of that body. If and only if the media type is not given by a Content-Type field, the recipient MAY attempt to guess the media type via inspection of its content and/or the name extension(s) of the URI used to identify the resource. If the media type remains unknown, the recipient SHOULD treat it as type "application/octet-stream".
If the goal is to have the #Consumes to act as a white list then a servlet filter could be used to default the Content-Type on requests where none is set.
You should specify the type- for example:
ClientResponse res =
service.path("accounts")
.type("application/vnd.dsu.account+json")
.post(ClientResponse.class,ent);
Based on the docs it seems using #Consumes at the class level does not explicitly override method level definitions (which default to */*), so it stands to reason it may be working in an additive manner...
Have you tried applying the same #Consumes on the method definition?