I’m testing out Jersey and I can’t seem to figure out why I get a 405 Method Not Allowed when I call PUT:
@Singleton @Path("images")
public class ImageResource {
private static final String IMAGE_ID_PATH_PARAM = "{imageId : [A-Za-z0-9_\\-]+}";
private static final String EXTENSION_PATH_PARAM = "{extension : (\\.[A-Za-z]+)?}";
@GET @Path(IMAGE_ID_PATH_PARAM + EXTENSION_PATH_PARAM)
@Produces("image/*")
public Response getImage(@PathParam("imageId") String imageId,
@PathParam("extension") String extension, @QueryParam("mods") String mods) {
...
}
@PUT @Path(IMAGE_ID_PATH_PARAM)
@Consumes("image/*")
public Response putImage(@PathParam("imageId") String imageId, File image) {
...
}
}
PUT only works if I set the @GET path to @Path(IMAGE_ID_PATH_PARAM). When I add the extension part, I am getting a 405 status code. GET seems to work in both cases. Here’s the output from a failed PUT:
$ curl -v --header "Content-Type: image/jpeg" --upload-file /Users/andy/Desktop/test.jpg http://localhost:9090/images/abcde
* About to connect() to localhost port 9090 (#0)
* Trying 127.0.0.1... connected
* Connected to localhost (127.0.0.1) port 9090 (#0)
> PUT /images/abcde HTTP/1.1
> User-Agent: curl/7.21.4 (universal-apple-darwin11.0) libcurl/7.21.4 OpenSSL/0.9.8r zlib/1.2.5
> Host: localhost:9090
> Accept: */*
> Content-Type: image/jpeg
> Content-Length: 48198
> Expect: 100-continue
>
< HTTP/1.1 100 Continue
< HTTP/1.1 405 Method Not Allowed
< Content-Type: text/html; charset=iso-8859-1
< Date: Mon, 20 Aug 2012 18:35:59 GMT
< Allow: GET,OPTIONS,HEAD
< Transfer-Encoding: chunked
I’ve also tried testing without the @Produces and @Consumes annotations and it did not work either.
Let’s take a look at what happens when you send the request.
Situation 1: no extension
Your methods look like this:
When you send the following request:
PUT http://localhost:9090/images/abcdefirst of all, Jersey looks for resources with the URI in question:
http://localhost:9090/images/abcdeAfter the resource is found, it checks what methods can be used to access it.
In this case, you have a single resource with the path defined by
IMAGE_ID_PATH_PARAM. This resource can be accessed by eitherGETorPUTrequests. Just like you specified with the annotations.Situation 2: extension added to
getImageYou methods now look like this:
Again, you send the same request:
PUT http://localhost:9090/images/abcdeAnd yet again, Jersey finds the first resource matching the URL. The resource is represented by your
getImagemethod, just like the first time. This time again, the@GETannotation does not match your request. And just like before, Jersey tries to find another method available for the resource, in order to match your request.This time, however, no such method is found so it returns a 405.
The reason why this happens is that the methods
getImageandputImagenow represent different resources. If you look carefully, the paths can be read like this (I’ll omit the regex for clarity):@Path({imageId}{extension})forgetImageand
@Path({imageId})forputImageWhile these two paths, considering the regex, can become the same thing, Jersey still sees them as identifiers of separate resources.
If you take a look at the WADL (feel free to look here if you’re not familiar with the standard) file generated by Jersey (it should be available at
http://localhost:9090/application.wadl), you’ll notice that this is exactly what’s happening.Notice how
imageshas two, separate sub-resources, each one with a single method.Solution
Adding the
EXTENSION_PATH_PARAMsegment toputImage‘s@Pathannotation causes these two methods to be mapped to a single resource again, so the problem disappears. Since the regex makes this part optional, you are free to omit it and pretend it doesn’t exist.The difference can be clearly seen in the generated WADL.
In this case,
imageshas exactly one sub-resource, which in turn has two available methods.Personally, I find the automatic generation of WADL a fantastic feature of Jersey. It’s a great way to see what’s happening with your resource methods without spending too much time with curl or other REST client.