2

I am building a RESTful Web Service with Java Jersey 2.17. The Client for it. I am developing with ExtJS 5.

My classes on the service

Main.java

 public class Main
 {
    public static final String BASE_URI = "http://localhost:8080/app/rest/";
    public static HttpServer startServer() {
        final ResourceConfig rc = new ResourceConfig();

        rc.packages(true, "app");
        rc.register(ResponseCorsFilter.class);

        return GrizzlyHttpServerFactory.createHttpServer(URI.create(Main.BASE_URI), rc);
    }

    public static void main(final String[] args) throws IOException {
        final HttpServer server = Main.startServer();
        System.out.println(String.format("Jersey app started with WADL available at " + "%sapplication.wadl\nHit enter to stop it...",
                Main.BASE_URI));
        System.in.read();
        server.stop();
    }
}

UserRi.java

@Path("/user")
public class UsersRi {
    @DELETE
    @Path("/{id}")
    @Produces(MediaType.APPLICATION_JSON)
    public String deleteUser(@PathParam("id") final String id) {
        final String res = "{\"success\":true,\"msg\":\"User " + id + " successfully deleted.\"}";
        return res;
    }
}

ResponseCorsFilter.java

public class ResponseCorsFilter implements ContainerResponseFilter {

    @Override
    public void filter(final ContainerRequestContext req, final ContainerResponseContext contResp) {

        contResp.getHeaders().add("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
        contResp.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        contResp.getHeaders().add("Access-Control-Allow-Origin", "*");

        final String reqHead = req.getHeaderString("Access-Control-Request-Headers");
        if ((null != reqHead) && StringUtils.isNotBlank(reqHead)) {
            contResp.getHeaders().add("Access-Control-Request-Headers", reqHead);
        }

    }

}

At the moment I am stuck with deleting content. On the client I get the record from a form panel and calling the erase function. The Request/Response is looking like this:

General:
    Remote Address:127.0.0.1:8080
    Request URL:http://localhost:8080/app/rest/user/user4
    Request Method:DELETE
    Status Code:400 Bad Request

Response Headers
    Connection:close
    Content-Length:0
    Date:Tue, 14 Apr 2015 19:26:05 GMT

Request Headers
    Accept:*/*
    Accept-Encoding:gzip, deflate, sdch
    Accept-Language:de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4
    Connection:keep-alive
    Content-Length:14
    Content-Type:application/json
    Host:localhost:8080
    Origin:http://localhost
    Referer:http://localhost/app/
    User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36
    X-Requested-With:XMLHttpRequest

Request Payload
    {"id":"user4"}

On the console I see XMLHttpRequest cannot load http://localhost:8080/app/rest/user/user4. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost' is therefore not allowed access. The response had HTTP status code 400.

It is also reproduceable with following jQuery.ajax() call: $.ajax({method:'DELETE',url:"http://localhost:8080/app/rest/user/user4",data:{"id":"user4"}})

Sending the request without the form data or the payload is working.

Is there another way to solve this without overriding stuff in the ExtJS Framework?

Greetings
Sören

Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
Sören
  • 177
  • 4
  • 16
  • you need to set the access control allow origin to *, then it would work. – vinayakj Apr 14 '15 at 21:20
  • I am setting it in the ResponseCorsFilter.java or do you mean something else? For requests without payload or form data it is already working. – Sören Apr 14 '15 at 21:23

2 Answers2

7

DELETE, as well as GET requests should not have a payload (entity body). I don't know if this is specified anywhere, but you can see discussion here.

That's what's causing the 400 Bad Request(will occur for GET also). Get rid of it, and it will work. But anyway there is not even a need to send that body, as you are, only information is the id, which is already included in the in the URL path.

If that's just an example, and you need to send some other arbitrary information along with the request, then use query params, e.g.

 /app/rest/user/user4?some=value1&other=value2&data=value3

 public String deleteUser(@PathParam("id") final String id,
                          @QueryParam("some") String some,
                          @QueryParam("other") String other,
                          @QueryParam("data") String data) {}

With jQuery, you could do

var params = {
    some:"valeu1",
    other:"value2",
    data:"value3"
}

var encoded = $.param(params);
var url = baseUrl + "?" + encoded;
$.ajax({
    url: url,
    ...
})

UPDATE

So after some investigation, this seems to be a Grizzly problem. I've tested with Jetty, and it works fine.

See similar issue

  • Here - last comment says it's fixed, but I can't produce a working example
  • Here

UPDATE 2

So as @alexey stated in a comment below

You're right, Grizzly by default doesn't allow payload for HTTP methods, for which HTTP spec doesn't explicitly state that. Like HTTP GET, DELETE, HEAD. But you can switch the support on by calling: httpServer.getServerConfiguration().setAllowPayloadForUndefinedHttpMethods(true)‌​; Please not the method should be called before starting HttpServer

So the fix would be to do something like

public static HttpServer createServer() {
    final ResourceConfig rc = new ResourceConfig();
    rc.packages(true, "app");
    rc.register(ResponseCorsFilter.class);
    
    HttpServer server = GrizzlyHttpServerFactory.createHttpServer(
                               URI.create(BASE_URI), rc, false);
    server.getServerConfiguration().setAllowPayloadForUndefinedHttpMethods(true);
    return server;
}

public static void main(String[] args) throws IOException {
    final HttpServer server = createServer();
    server.start();
    System.out.println(String.format("Jersey app started with WADL available at "
            + "%sapplication.wadl\nHit enter to stop it...", BASE_URI));
    System.in.read();
    server.stop();
}

Tested and it works as expected.

Community
  • 1
  • 1
Paul Samsotha
  • 205,037
  • 37
  • 486
  • 720
  • 2
    You're right, Grizzly by default doesn't allow payload for HTTP methods, for which HTTP spec doesn't explicitly state that, like HTTP GET, DELETE, HEAD. But you can switch the support on by calling: httpServer.getServerConfiguration().setAllowPayloadForUndefinedHttpMethods(true); Please note the method should be called before starting HttpServer. – alexey Apr 15 '15 at 18:36
  • 1
    The example I provided is how ExtJS sends the DELETE request to server. Thanks to your solution I was able to get it running without adapting the ExtJS framework. Thanks a lot for that :) – Sören Apr 18 '15 at 13:12
  • For anyone else facing this issue with Payara, see https://github.com/payara/Payara/issues/1545#issuecomment-296399223. – Laird Nelson Dec 18 '17 at 22:06
  • The statement that DELETE requests should not have a payload is not correct. The http RFC https://tools.ietf.org/html/rfc7231#section-4.3.5 simply states: A payload within a DELETE request message has no defined semantics; sending a payload body on a DELETE request might cause some existing implementations to reject the request. – Simon Oct 26 '20 at 10:03
0

By using @Path on method level, you are overriding the defined @Path at class level.

If you are using Apache Tomcat, use their CORS lib: Tomcat CORS filter. This way it is cleaner and covers all the exceptions as well.

Community
  • 1
  • 1
Hannes
  • 2,018
  • 25
  • 32