I've developed REST services, but now I realized that I'm doing something wrong.
For example, I have a service which retrieves information about a specific device. Each device has an address: sector.room.group.id.
The URI I did for this GET method was: (...)/services_devices/{sector}/{room}/{group}/{id} But now I realized that I should not have used the '/' to separate the device address, right?
How should I pass the address to this method? Using ';' ?
My GET method is:
#GET
#Path("{sector}/{room}/{group}/{id}")
#Produces("application/json")
public String getDeviceName(#PathParam("sector") int sector, #PathParam("room") int room, #PathParam("group") int group, #PathParam("id") int id) throws Exception
{
String name = null;
try {
name = new DevicesManager().getDeviceName(sector, room, group, id);
} catch (Exception e) {
e.printStackTrace();
}
return name;
}
There is a simple way of change this, to have a correct URI? I have this "error" in many methods.
If there is a hierarchy in your resources path variables are appropriate.
It seems in your case there is a hierarchy between devices and address, but first comes the address and after the deviceName. "deviceName" can be considered a one more hierarchy step.
The best way to reflect the above relations would be the following url:
(...)/sector/room/group/id/deviceName
You can then have another attribute of the device mapped like this:
(...)/sector/room/group/id/deviceOwner
The JAX-RS mapping would be:
#GET
#Path("{sector}/{room}/{group}/{id}/deviceName")
#Produces("application/json")
public String getDeviceName(#PathParam ...) {
//impl.
}
And yes, if the deviceName is the only relevant attribute of the resource, then you can leave out "deviceName" and your orignal mapping is correct.
If the resource at /sector/room/group/id has many attributes you should consider returning a composed object for the path:
#GET
#Path("{sector}/{room}/{group}/{id}")
#Produces("application/json")
public Device getDeviceName(#PathParam...) {
}
REST architectural style introduces HATEOAS, which means that client and server are loosely coupled. Simply the client is not aware of how the URLs look like and gets them from previous responses. (it's similar like surfing thru HTML pages). Of course there will be at least one URL, an entry point, that is known to the client. From this point of view, your need to have correct URIs is irrelevant. What's correct URI? The URI is correct when its form is aligned with RFC.
You are probably introducing URL patterns, that are not RESTful, because it implicates tight coupling between client and server (the client must be aware of URL patterns and have ability to construct URLs from them; fill up sector/room/ etc. in your case)
See also this post:
http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven
My advice is; don't waste your time on URL patterns, make URLs simple as is possible, flat hierarchy has also many benefits, and follow HATEOAS principle.
Related
I am developing a Rest API in spring boot. Which of the following is the best way to handle when an instance of resource not found ?
#GetMapping(value="/book/{id}")
public ResponseEntity<Book> getBook(#PathVariable String id){
Book book = bookService.getBook();
// Which is best Approach for resource instance not found ?
if(book == null) {
// This one
return new ResponseEntity<>(book, HttpStatus.NO_CONTENT);
//OR
return new ResponseEntity<>(book, HttpStatus.NOT_FOUND);
//OR
throw new DataNotFoundException("Book with id " + id + " Does not exist");
}
return new ResponseEntity<>(book , HttpStatus.OK);
}
I am clear about that when a collection of resource not found in Db then to pass an empty collection instead of null but I am not clear what to do with an instance of resource.
I have also read on StackOverflow that HttpStatus.NOT_FOUND should be used when a Resource under the criteria cannot exist instead of do not exist in the Db.
What is best approach to handle this ?
When working with Spring MVC, you usually have two choices when returning your result, either you work with plain objects, or you work with the ResponseEntity class. Neither of those is better than the other. Additionally, you can decide whether or not you separate your error handling using exceptions or not.
Given that, your third scenario by throwing an exception is essentially the same as one of your first two options. By default, throwing an exception will result into a 500 Internal Server Error, but it can be changed by using the #ResponseStatus annotation, for example:
#ResponseStatus(HttpStatus.NOT_FOUND) // Or #ResponseStatus(HttpStatus.NO_CONTENT)
public class DataNotFoundException extends RuntimeException {
}
Alternatively, you can also define an exception handler. Again, this can be done by either working with plain objects or ResponseEntity, for example:
#ResponseStatus(HttpStatus.NOT_FOUND) // Or #ResponseStatus(HttpStatus.NO_CONTENT)
#ExceptionHandler(DataNotFoundException.class)
public Book handleNotFound(DataNotFoundException ex) {
return null;
}
Or:
#ExceptionHandler(DataNotFoundException.class)
public ResponseEntity<Book> handleNotFound(DataNotFoundException ex) {
return new ResponseEntity<>(null, HttpStatus.NOT_FOUND); // Or HttpStatus.NO_CONTENT
}
Again, neither is better than the other and what you choose is mostly based upon personal preference. However, you should probably use one consistently.
Now, that means that there are still two choices left, either by choosing HttpStatus.NOT_FOUND (404) or HttpStatus.NO_CONTENT (204). While you can technically use either status, they have a different meaning:
204 = The request was succesful, but there's nothing.
404 = The request was not succesful, the resource does not exist
Now, if you request /book/123 and there's no book with ID 123, it could be seen as a resource that doesn't exist, and thus, HttpStatus.NOT_FOUND makes most sense.
First of all I think that you mean #PathVariable and not #RequestParam for your method parameter (see difference between PathVariable and RequestParam here ).
Secondly, it will be ambiguous for the client that receives the 404 not found response as this means that :
The server has not found anything matching the requested address (URI)
( not found ). This means the URL you have typed is wrong or obsolete
and does not match any document existing on the server (you may try to
gradualy remove the URL components from the right to the left to
eventualy retrieve an existing path).
Knowing that your return type is a ResponsEntity, it will be more appropriate to have this :
#GetMapping(value="/book/{id}")
public ResponseEntity getBook(#PathVariable String id){
Optional<Book> book = bookService.getBook();
if(book.isPresent()) {
return ResponseEntity.status(HttpStatus.OK).body(book.get());
}
return ResponseEntity.status(HttpStatus.NO_CONTENT).build();
}
If your endpoint gets book by id and the book does not exists, return 400. Do not return 404. 404 is protocol error: it should be reserved for wrong URL. Now URL is correct but id is wrong. Id almost often is not guessed, but returned by previous query. It cannot disappear suddenly: if id is wrong, the request is wrong.
If your endpoint gets book by title and the book does not exists, return 204. That is absolutely normal that book does not exists in such case and client should be prepared to handle 204.
Someone could argue that difference between 400 and 204 is fuzzy and better always return 204. Indeed, difference may be fuzzy, but from monitoring perspective I would like to know when everything is ok (no book found by title) and when something smells (no book found by id).
I know that my answer does not comply REST directives (or maybe does not comply). I don't care it too much. I simply think that 404 should be reserved for application server and should not be used by application. Reason is already explained in other answer here.
Summary:
404: wrong URL
400: wrong id
204: not found and that is OK
just return 404 HttpStatus to client ,do not waste time on it.No one will request id that not exist in db normally. usually client request like model/{id} come from
against your Collection [model1,model2,.....]
Whenever a resource cannot be found, you should indicate that to the client, most commonly using the HTTP Status Code 404 Not Found, as you already mentioned.
For collections, simply return an empty array in the response body (alongside with response code 200 OK, this is my opinion tough), do not return 404 Not Found since the resource actually exists.
Please note that 202 No Content is a bad choice here, since the server has not successfully fulfilled the request. Instead, use this return code, for example, for a successful PUT request (you have changed internal data but return no content in the response body).
In most APIs you will encounter additional information in the response body:
{"messages":{"error":[{"code":404,"message":"Resource not found."}]}}
You will find list of all errors and their response codes with informative descriptions. One thing is important tough: Stick to one format, otherwise it will be a pain for clients. Most APIs also only use about 6-8 HTTP response codes.
Also, Spring has a number of utilities to help you out:
#ResponseStatus(value=HttpStatus.NOT_FOUND, reason="No such Order")
public class OrderNotFoundException extends RuntimeException {
// ...
}
Or, the following annotation to create a custom response format:
#ExceptionHandler({ YourException.class })
first our scenario:
we have an OSGI environment, where several bundles publish their own rest endpoint, e.g.:
http://localhost:8080/api/cars
http://localhost:8080/api/food
http://localhost:8080/api/toys
This was done using JAXRSServerFactoryBean.create() method, with address being the ones listed above.
Now we need to add a tenant id to the users requests (not user auth, which is different, as users may be part of several tenants). URLs should look like this:
http://localhost:8080/api/tenant/{tenantid}/cars
http://localhost:8080/api/tenant/{tenantid}/food
http://localhost:8080/api/tenant/{tenantid}/toys
I tried two approaches to achieve this now:
Add tenant-id to address of service (http://localhost:8080/api/tenant/{tenantid}) - Result: I could access my service under the given URL, but I couldn't fill any data for tenantid but had to type {tenantid} in the URL, which is not how I need to use it.
Publish all three services under the same URL (http://localhost:8080/api) moving the tenant-part to the #Path annotation of each api class - Result: Exception, that address was already taken by other endpoint
Does anyone have an idea, how this can be done properly? I know that the ServiceBean can take an array of implementors as an argument instead of a single class, but this is not an option, as the bundles load separately and I had some dependency issues, when I tried make this "all in one".
As a sidenote: I know, we could put tenant id in a header, but typically tenant info is somewhere in a URL (host or path) and we wanna go with this "common" style instead of adding a custom header, though implementation of header style would be much easier (already got it to work).
Any ideas would help.
Thanks,
Kay
Try something like:
#Path("/tenants")
public class TenantResource{
#Path("/{tenantId}/cars")
#Get
public List<Car> getTenantCars(#PathParam("tenantId") long tenantId){...}
#Path("/{tenantId}/food")
#Get
public Food getTenantFood(#PathParam("tenantId") long tenantId){...}
#Path("/{tenantId}/toys")
#Get
public List<Toy> getTenantToys(#PathParam("tenantId") long tenantId){...}
}
If you have URLs such as tenants/{tenantid}/cars then this usually means "the cars of the tenant with id = tenantid".
"cars" is a property of the "tenant" resource and thus should be in the same resource.
I think it might be hard to modularize properties of a resource/ object.
But you could consider a "car" resource and query the resource like: /cars?tenantid={tenantid}
#Path("/cars")
public class CarResource{
#Get
public List<Car> getCarsByTenantId(#QueryParam("tenantId") long tenantId){...}
}
or similar.
I have to following endpoint structure in Jersey:
/objects/
/objects/:id
/objects/:id/:field
/objects/:id/:field/:subfield
The IDs I'm using have a very specific format, so I first check if the format is valid before making a request to the database.
Right now I have to put this code in each of the POST, PUT, GET, DELETE functions for each of the functions that has :id as a parameter. So this just means an early return.
if (!isIdValid(id)){
return Response.status(Response.StatusType.BAD_REQUEST)
.entity("The ID you've provided is invalid")
.build();
}
(In reality the error entity is an object containing more information about the error)
And then for each function using the :field or :subfield parameters the code is similar. This checking and error-handling behavior has to be copied every time. And when I start copy-pasting stuff, I start thinking: there should be a better way?
I would like to place the :id checking code at the the /objects/:id level, and then all further nested levels are assumed have a valid ID. The same for the other parameters further nesting down.
I've been looking into using subresource locators, but then you create a function returning a new instance of the subresource. I can't put a conditional return of a Response-object at that level for if the validation fails.
#Path("{id}")
function Class<ObjectFieldResource> getObjectById(#PathParam("id") String id){
return ObjectFieldResource.class;
}
I could start throwing exceptions, but I would rather avoid that, since I don't really consider invalid input to be an exception.
How would such a structure best be implemented? I've looked at bean validation but that doesn't seem to allow me to define validation for my specific format + custom error responses.
Am I missing something in the way subresources should be implemented?
Solution 1
If you can use regexp checks instead of your isIdValid method it's possible to define your resources like this
#POST
#Path("objects/{id:\\d+}")
public Response doSmth(#PathParam("id") String id) {
...
}
In a case of invalid id format caller will have 'Not Found' response status without even reaching your doSmth method.
Obviously, you can use String constants for all equal path values.
final static String ID_RES = "objects/{id:\\d+}";
#POST
#Path(ID_RES)
public Response postSmth(#PathParam("id") String id) {
...
}
...
#GET
#Path(ID_RES)
public Object getSmth(#PathParam("id") String id) {
...
}
The can also read full description of Path#value parameter
Solution 2
Create and register at your REST server javax.ws.rs.container.ContainerRequestFilter implementation with filter method having needed URI checks.
The single filter parameter has ContainerRequestContext type from witch you can call getUriInfo for getting URI and method abortWith(Response response) which can be used for aborting caller request if your resource ids validation was failed.
See Chapter 10. Filters and Interceptors chapter of Jersey Manual.
I am writing a web app with Spring 4.0.
I have written my controllers in what I believe to be the normal way using the #RequestMapping annotation to define the url pattern which the controller handles.
The snippet below illustrates this for a controller which displays a testimonial ...
#Controller
#RequestMapping("/testimonialView")
public class TestimonialRequestController {
#RequestMapping(value="/{testimonialName}", method=RequestMethod.GET)
public ModelAndView testimonialRequest(#PathVariable String testimonialName, ModelAndView modelAndView) throws FileNotFoundException {
Testimonial testimonial;
. . .
}
}
Elsewhere in my application I want to generate a link bar which includes all the testimonials to include in my left hand nav.
At the moment, when I construct the href for the anchor element to go into the link bar, I am hardcoding the url, like this:
String href="/testimonialView/" + testimonialName;
This does not seem right. If later on I want to change the url structure I have to change it in at least two places - possibly more. Once where the incoming URL is matched to the controller, and once to construct the anchor element which a user will click to invoke that URL.
Is there a best practice way of dealing with this problem? It must be a common one. Is it as simple as using Constants to represent the URLs and accessing these from different places? I know my example is simple but I am assuming the problem must exist for much larger web apps with complex URL structure so I want to understand what best practice is.
I hope this isn't a stupid question. I am keen to ensure that I implement best practice right from the beginning. I have looked through Stackoverflow and Google but nothing quite answers this specific question.
Any help gratefully received.
The short answer is that you can't do this dynamically because #RequestMapping puts data into the code at compile time.
However, there are a couple of options that work.
You can define the string constants in a separate class - this will make it easier for you to change the names of URLs
You can explore the request mappings at runtime within Spring, so could have some code that found URLs you'd coded elsewhere - I've done this for identifying when a URL is dynamic content, vs coded content.
My recommendation is
public class URLs {
public static final String TESTIMONIAL_VIEW = "/testimonialView";
}
with
#RequestMapping(URLs.TESTIMONIAL_VIEW)
and
String href= URLs.TESTIMONIAL_VIEW + "/" + testimonialName;
There isn't any better practice for this afaik. Most you can do is, as Ashley said, is to use constants. But as with any other client-server situation such as the Web, if you change the contract (the url in this case) you'll have to do so for both the client (i.e. the links) and the server (the controller mappings).
I would also mention that your controller can be more general, for example have a "Testimonials" controller and "view/{name}" as an action within that controller.
Hope this helps
I want to develop rest api.such as:
http://localhost:8080/TestSomeWay/resources/test/create?meg=sadasd&name=sadasd
and get params from urlparams exp."meg"&"name"
I am using jersey to develop a Restful post method
it dose not make it out
code:
#POST
#Path("/create")
#Consumes(MediaType.APPLICATION_FORM_URLENCODED)
#Produces(MediaType.TEXT_PLAIN)
#Override
public String create( #FormParam("meg")String megString, #FormParam("name")String nameString) {
TestUser testUser=new TestUser();
testUser.setMeg(megString);
testUser.setName(nameString);
em.persist(testUser);
em.flush();
return testUser.getId().toString();
}
You seem to be confused as to what you are trying to achieve, and that's showing up as an incoherent API. Once you've gone wrong that way, it's small wonder that things are going wrong!
First off, you've got to figure out whether you're using GET, PUT or POST, and in the latter two cases, what is the content type (or types) that you are consuming, as both PUT and POST are typically dealing with a document incoming. Moreover, if you're doing anything that isn't idempotent (i.e., so that it would be “the same” if you did it twice in a row as if once) then you should definitely be using POST; the classic example is paying for some goods, which you definitely don't want to do twice, whereas setting your preferences can be idempotent. The final complication is that it is usually bad style to mix query parameters with a body; either the parameters are in the query part or they are in the body (or they are in the path, but in that case you're dealing with different resources conceptually).
If you're just dealing with HTML forms, the two styles of method you'll want will be like this:
#GET
#Path("/create")
#Produces(MediaType.TEXT_PLAIN)
public String createFromGet(
#QueryParam("meg") String meg,
#QueryParam("name") String name) {
...
return theString;
}
#POST
#Path("/create")
#Consumes(MediaType.APPLICATION_FORM_URLENCODED)
#Produces(MediaType.TEXT_PLAIN)
public Response createFromPost(
#FormParam("meg") String meg,
#FormParam("name") String name) {
...
return Response.created(theNewUrl).entity(theString).build();
}
The first deals with a GET on a URL like /create?meg=foo&name=bar and the second deals with a POST to a URL like /create. However, given the name “create” I'd be tempted to go with just using the POST version and to not try to support encoding the parameters in the query part; creation is one of those things that tends to not be idempotent.
Note that I have assumed that your creation is making a resource (that's good RESTful programming!) so I adjusted to return the right kind of response; it's a bit more involved than the usual, but is exactly the right thing.
You should QueryParam instead of FormParam to obtain the functionality you want