CS639 Class 23
REST Services: more advanced features
paper handout of pg. 180 of RESTful Web
Services, by Richardson and Ruby, showing URI templates for their bookmark
service
Richardson and Ruby’s Bookmark Service: see paper
handout on URIs
Their book on the actual delicious site: http://delicious.com/leonardr/restbook
Look at R&R’s bookmark service,
based on del.icio.us bookmark service.
R&R’s book has lots of
good guidance on designing REST APIs.
See handed out URI templates
to describe the service. Well, not all
of it, but the core part.
See users resource, familiar
setup, like orders in orderService.
See bookmark management
running at second level in URIs: a new feature to us.
Template:
/users/{userId}/bookmarks/{bookmarkId}.
Each user has a set of
bookmarks, each id’d by MD5 hashes of the actual bookmark.
(it’s a problem to use the
URL itself, see the book, and del.icio.us does it this way.)
Java can compute MD5 hashes
using MessageDigest in JDK.
POST to collection URI to add
one, GET, PUT, DELETE on individual bookmarks.
Also here, as in firstRest2:
GET to a collection URI: GET to bookmarks for a user to get all or some of them
using query string. This returns the XML representations of the bookmarks, not
just a list of URIs back into the service, so this is not a hypermedia service.
(You can ask for atom feeds.)
Use of query string: a new
feature to us, for REST services.
--to specify a date or range
of dates on a query
.../?timestamp >= 1280296860145 (Unix
time, seconds since Jan 1 1970)
--Another query: ?limit=100
Could have ?start=50, but not
implemented here.
Another idea: use of
conditional HTTP GET
From HTTP 1.1 spec, now
linked from class web page under HTTP:
The semantics of the GET method change to a "conditional
GET" if the request message includes an If-Modified-Since,
If-Unmodified-Since, If-Match, If-None-Match, or If-Range header field. A
conditional GET method requests that the entity be transferred only under the
circumstances described by the conditional header field(s). The conditional GET
method is intended to reduce unnecessary network usage by allowing cached
entities to be refreshed without requiring multiple requests or transferring
data already held by the client.
The
If-Modified-Since is a request header, and the corresponding server response
usually has the Last-Modified header showing the timestamp related to the
response representation. See Webber et
al, pg. 174 for actual header listings, there discussed in terms of caching. If
there are no new-enough ones, the client gets back HTTP 304 “Not Modified”. Of course this means the server needs to
timestamp the data used for the representations. Webber et al, pg. 83 notes that the time used
here is good to one second.
Looking at the
implementation (R&R, pg. 192), we see that this server uses the
If-Modified-Since header. The client can
use this header to filter out old bookmarks the client doesn’t want to
see. If there are bookmarks recent enough, they
are represented in XML and returned to the user. If not, HTTP 304 is returned. Note
this is implemented in Ruby in this book.
Handling query params in
JAX-RS: close to HTML form params, since both are request parameter values.
Recall @FormParam annotation for service method param to provide incoming form
param to Java. Similarly, could have,
for GET /users/{username}/bookmarks?timestamp > 12345678—
@GET
@Path(“/{username}/bookmarks)”)
public
BookmarkList getBookmarks (@PathParm(“username”) String user, @QueryParam(“timestamp”)
long timestamp) …
For the incoming header, use @HeaderParam(“If-Modified-Since”) annotation on a method param.
Handling Hierarchical URIs in Jersey: Using Subresources
Hierarchical URIs like
/users/{userId}/bookmarks/{bookmarkId}.
Jersey implementation of
services for these multilevel URI patterns—isn’t it a mess?
There’s a concept of
subresource that keeps things from blowing up in complexity.
Basic Idea: processing the
URI from left to right--
/users/{userId}/bookmarks/{bookmarkId}
<-main
resource class-> <--subresource class------------->
|
|handoff from main resource to
subresource object
Back to Vogel’s JAX-RS
tutorial, Sec. 5. nicely set up:
Packages
resources: ToDoResource,
ToDosResource
dao: TodoDao: uses
HashMap<String, Todo> to store Todos
model: Todo, with minimal
JAXB markup, just @XmlRootElement on the class.
Note: no namespace in use
here, simplifying the JAXB needs. If we want to add a namespace, we can
annotate the child elements as in OrderRepresentation, or set up package
annotations.
No “activities” package,
though, simple activities get absorbed into resources.
His Todo service looks like
this:
GET
/todos
GET
/todos/count
POST
/todos
GET
/todos/{id]
PUT
/todos/{id}
DELETE
/todos/{id}
The implementation could be
all in TodosResource, but instead it’s split up into two source files to show
the power of subresources to handle hierarchy in URIs.
/todos/{id}
<---->
<--->
TodosResource: main resource
for handling collection of Todos
TodoResource: a subresource
handling a single Todo
TodosResource has
@Path(“/todos”) on the class:
the root resource, starts with / (not allowed in subresources)
sets first part of URI for
the class
@GET on a method, to handle
GET /todos
@GET
@Path(“count”) on a method, to handle GET /todos /count
@POST on a method, to handlle
POST /todos
Finally, we see the handoff
to the subresource: when JAX-RS sees /todos/something, but not /todos/count,
the more specific match, it looks here and sees a method returning an object
that (hopefully) qualifies as a resource object, i.e., has JAX-RS annotations
itself:
@Path(“{todo}”)
public
TodoResource getTodo(@PathParam(“todo”) String id {
return new TodoResource(uriInfo, request,
id);
}
Note we could drop the
uriInfo and request params: they are never used, and the uriInfo can be (and
is) obtained another way in the subresource.
More on this below.
This is understood by Jersey:
it will process the GET to such a URI to @GET in the newly created subresource.
We are not required to create a new resource object here, we could just look
one up somehow, or create different subclasses based on something. Whatever object gets returned will be
inspected for @GET, etc and used to finish doing the request, using the “rest”
of the URI.
The subresource class
TodoResource has no root resource @Path annotation (starting with/), since it
is working off the main resource’s root path @Path(“/todos”) processing. In this case it has @GET, @PUT, and @DELETE
for the three /todos/{id} templates.
A subresouce can have
relative @Path annotations. For example,
we could put the @Path(“status”) in the TodoResource on a method to handle
/todos/12/status requests.
Or use another subresource to
handle status—this can go on for multiple levels.
Note on uriInfo and request and dependency injection: The uriInfo
is the path in use wrapped up in a UriInfo object, and request is a
HTTPServletRequest wrapped up in a Request object. These are both fields in ToDosResource,
ToDoResource, and uriInfo is also in OrderResource. In all these cases, the field is annotated
with @Context. This
annotation directs JAX-RS to fill in these fields at request time before
calling the service method. This action of filling in a field of an object from
infrastructure code is called dependency injection.
The Bookmarks Project: Sketch of Jersey Implementation
using a Subresource
It’s common to handle two related
URI segments in one class, so for example for bookmarks
/users/{username}/bookmarks/{id}
<--users
resource-><--bookmarks-->
UsersResource:
@Path(/users) on the class
@GET on a method to handle
GET /users
@Path(“{username}”)
@GET on a method to handle
GET /users/username
@Path(“bookmarks”)
return new Bookmarks
subresource object
Bookmarks SubResource: no
@Path on class
@GET on a method to handle
GET /users/username/bookmarks
@Path(“{bookmarkId}”)
@GET on a method to handle GET
/users/username/bookmarks/id
Handling two or more PathParams at once: examples from Burke’s RESTful Java with JAX-RS
@Path (“/customers”)
public class CustomerResource
{
...
@Path(“{first}-{last}”)
@GET
@Produces (“application/xml”)
public Customer
getCustomer(@PathParam(“first”) String firstName,
@PathParam(“last”) String lastName) {
...
}
handles GET
/customers/bill-burke. Assumes no –‘s in
names.
Example returning an image (pg. 66, Burke)
@Path(“/cars/{make}”)
public class CarResource {
@GET
@Path(“/{model}/{year}”)
@Produces(“image/jpeg”)
public Jpeg
getPicture(@PathParam(“make”) String make,
@PathParm(“model”) String model,
@PathParam(“year”) String year)
{
...
}
handles GET /cars/ford/taurus/2003, sends jpeg for
it.
Handling queries: more examples from Burke’s RESTful Java with JAX-RS
@Path
(“/customers”)
public
class CustomerResource {
...
@GET
@Produces
(“application/xml”)
public
Customer getCustomers(@QueryParam(“start”) int start,
@QueryParam(“size”) int size) {
...
}
Handles GET
/customers?start=10&size=20
We can specify default values
too:
@Path
(“/customers”)
public
class CustomerResource {
...
@GET
@Produces
(“application/xml”)
public
Customer getCustomers(@DefaultValue(“0”)
@QueryParam(“start”) int start,
@DefaultValue(“10”) @QueryParam(“size”) int size) {
...
}