3

I'm doing a REST application. I have made the GET method without issues, however, when I implement the POST method, it says that I don't have the OPTIONS method implemented for it. I am doing the OPTIONS method for URI:

http://192.168.1.26:8080/sellAppWeb/api/object/

I have the POST and OPTIONS methods:

@OPTIONS
@Produces("application/json; charset=UTF-8")
public Response options() {
    return Response.ok().build();
}

@Override
@POST
public Response save(CervejaDTO cervejaDTO) {
    cervejaController.register(cervejaDTO);
    return Response.ok(cervejaDTO).build();
}

Then I am made the DELETE method and again it says that I don't have a OPTIONS method. Then I need to make another OPTIONS method, which has an ID in the URI end. For example to delete a object with id = 3:

http://192.168.1.26:8080/sellAppWeb/api/object/3

I need to have another OPTIONS with same structure of DELETE URI:

@OPTIONS
@Path("/{id}")
@Produces("application/json; charset=UTF-8")
public Response optionsDelete(@PathParam("id") Integer id) {
    return Response.ok().build();
}

@Override
@POST
public Response save(CervejaDTO cervejaDTO) {
    cervejaController.register(cervejaDTO);
    return Response.ok(cervejaDTO).build();
}

Does anyone have a way to do a generic OPTIONS for all REST requests?

the web.xml:

<display-name>Testes de serviços REST</display-name>
<description>Testes de serviços REST</description>

<welcome-file-list>
    <welcome-file>index.html</welcome-file>
</welcome-file-list>

<context-param>
    <param-name>resteasy.scan</param-name>
    <param-value>true</param-value>
</context-param>

<context-param>
    <param-name>resteasy.servlet.mapping.prefix</param-name>
    <param-value>/api</param-value>
</context-param>

<context-param>
    <param-name>resteasy.providers</param-name>
    <param-value>br.com.sell.app.exception.handler.DefaultExceptionHandler</param-value>
</context-param>

<listener>
    <listener-class>org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap</listener-class>
</listener>

<servlet>
    <servlet-name>resteasy-servlet</servlet-name>
    <servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name>resteasy-servlet</servlet-name>
    <url-pattern>/api/*</url-pattern>
</servlet-mapping>

6 Answers6

3

You don't need to implements the OPTIONS HTTP VERB in this case. Since you're using RESTEasy, which is the JAX-RS implementation used by Wildfly, the issue I encountered was due to the servlet-mapping on web.xml.

I have encountered this when I added the JAX-RS facet on Eclipse and tell it to update the web.xml. The default generated web.xml containing the Restful application mapping doesn't map your application properly to your RESTful resource path.

This is how the web.xml should look like, provided you have not created your own custom Application.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
    version="3.1">
    <display-name>My REST API</display-name>
    <description>My REST API</description>
    <servlet>
        <description>JAX-RS Tools Generated - Do not modify</description>
        <servlet-name>javax.ws.rs.core.Application</servlet-name>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>javax.ws.rs.core.Application</servlet-name>
        <url-pattern>/jaxrs/*</url-pattern>
    </servlet-mapping>
</web-app>

Make sure that your <servlet-name> and <servlet-mapping> are mapped as in the example above. If you extended the Application class, just specify it in your web.xml instead of the default Application as shown above.

Also, your @POST resource method, it's recommended to specify the resource type of your RESTful data (in your case, your DTO) using @Consumes annotation.

Eg.

@POST
@Path("/save")
@Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
public Response save(CervejaDTO cervejaDTO)

}
Buhake Sindi
  • 82,658
  • 26
  • 157
  • 220
  • 1
    I modify my web.xml like your examle, but the server don't starts. And the @Consumes annotation, dont solve the problem too. – Eduardo Vendruscolo Sep 02 '15 at 15:49
  • 1
    _org.jboss.resteasy.spi.DefaultOptionsMethodException: No resource method found for options, return OK with Allow header_ – Eduardo Vendruscolo Sep 02 '15 at 16:28
  • I see you're using RESTEasy specify configuration. Can you post the stacktrace as well so that we can see what happens? Also, what do you get when you access the link `http://192.168.1.26:8080/sellAppWeb/api/`? – Buhake Sindi Sep 03 '15 at 07:37
  • i get this error, but i this it's correct. because i dont have path mapped to this address ` Could not find resource for full path: http://192.168.1.103:8080/sellAppWeb/api/ ` – Eduardo Vendruscolo Sep 03 '15 at 17:57
3

"however, when I implement the POST method, it says that I don't have the OPTIONS method implemented for it."

"When i make a POST or DELTE request, the application make automatically a OPTIONS request before"

This definitely sound like a CORS (Cross Origin Resource Sharing) problem. You can read more about it at HTTP access control (CORS). Basically the OPTIONS request is preflight request before the actual request. This will happen for certain types of AJAX requests.

For this, RESTeasy has the CorsFilter you can register. You need to configure the filter to the settings you want to allow. Also see an example here for one way to configure it.

Community
  • 1
  • 1
Paul Samsotha
  • 188,774
  • 31
  • 430
  • 651
  • I have a CORS filter, it's implemented like bellow: `headers.add("Access-Control-Allow-Origin", "*"); headers.add("Access-Control-Allow-Methods", "GET,POST,DELETE,PUT,OPTIONS"); headers.add("Access-Control-Allow-Headers", "Content-Type");` – Eduardo Vendruscolo Sep 02 '15 at 16:34
  • Add a print statement in the filter's constructor to make sure it's created. Second make a simple GET request, and look at the headers to make sure they are there. If you add the headers to all request (as you are in your filter), the headers should even show for GET requests. The nice thing about RESTesy's filter is that it is implemented to only send the response headers for preflight (OPTIONS) request, so the headers aren't sent for all requests. It's not a problem if the headers are set for all responses, it just makes the response a little lighter. – Paul Samsotha Sep 02 '15 at 16:52
  • You may also want to just try and use RESTeasy's filter instead of your own. – Paul Samsotha Sep 02 '15 at 17:29
  • Yes, the constructor print's in the console when created, the response of a simple GET Request, is `Access-Control-Allow-Headers:Content-Type Access-Control-Allow-Methods:GET,POST,DELETE,PUT,OPTIONS Access-Control-Allow-Origin:* Connection:keep-alive Content-Length:994 Content-Type:application/json;charset=UTF-8 Date:Thu, 03 Sep 2015 18:01:11 GMT Server:WildFly/9 X-Powered-By:Undertow/1` – Eduardo Vendruscolo Sep 03 '15 at 18:02
  • Please try and use the RESTeasy filter – Paul Samsotha Sep 03 '15 at 19:55
  • which class i need to implement, to use the RESTEasy filter? the **ContainerResponseFilter** class it's default's java EE class from **javax.ws.rs.container** package. – Eduardo Vendruscolo Sep 03 '15 at 22:14
  • You don't need to implement anything. I meant register resteasys `CorsFilter`. Look at my answer – Paul Samsotha Sep 04 '15 at 00:47
3

I tried RestEasy's CorsFilter but calls made with the OPTIONS method were returning

RESTEASY003655: No resource method found for options, return OK with Allow header

I wrote a simple filter that:

  1. Makes sure the CORS header you need are applied to the response.
  2. Returns the HTTP status code 200 when calling an endpoint with the OPTIONS method. You just tell the client that its CORS preflight requests was accepted.

Here is the code. This is a simplified version, ditry - yet efficient. Feel free to refine the filter if you only want to send back a 200 when querying a "real" endpoint.

@Provider 
public class CorsFilter implements ContainerResponseFilter {  

  @Override
  public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
    MultivaluedMap<String, Object> headers = responseContext.getHeaders();
    headers.add("Access-Control-Allow-Origin", "*"); // If you want to be more restrictive it could be localhost:4200
    headers.add("Access-Control-Allow-Methods", "GET, PUT, POST, OPTIONS"); // You can add HEAD, DELETE, TRACE, PATCH
    headers.add("Access-Control-Allow-Headers", "Content-Type, Authorization, Accept, Accept-Language"); // etc

    if (requestContext.getMethod().equals("OPTIONS"))
        responseContext.setStatus(200);
}}

From this post and my preferred CORS explanation.

otonglet
  • 2,925
  • 20
  • 34
2

You can use @Path("{path:.*}").

@OPTIONS
@Path("{path:.*}")
public Response handleCORSRequest() throws Exception {
    Response.ResponseBuilder builder = Response.ok();
    return builder.build();
}
lianjm
  • 21
  • 4
1

I recommend you to use Spring Controllers and RequestMapping annotations, they are really easy to use:

@RequestMapping(value="/method0", method="POST")
@ResponseBody
public String method0(){
    return "method0";
}

You dont need to implement OPTIONS methods, just declare your method and use the annotation to define it as a POST/GET/PUT/DELETE request method. Here are lots of examples.

melli-182
  • 1,170
  • 3
  • 15
  • 28
  • 4
    Why do you *recommend* using Spring when Java EE has JAX-RS in the specification? JBoss Wildlfy has JAX-RS support in the box and doesn't use any Spring REST or Spring Data under the covers. In fact, one can write an entire RESTful application without using Spring and purely JAX-RS. – Buhake Sindi Sep 02 '15 at 14:48
1

For me that was the only way to do work.

Create the class in your java restclient project.

    import javax.ws.rs.core.Context;
    import javax.ws.rs.core.HttpHeaders;
    import javax.ws.rs.core.Response;
    import javax.ws.rs.core.Response.ResponseBuilder;
    import javax.ws.rs.ext.ExceptionMapper;
    import javax.ws.rs.ext.Provider;

    import org.jboss.resteasy.spi.DefaultOptionsMethodException;

    @Provider
    public class OptionsHander implements
         ExceptionMapper<DefaultOptionsMethodException> {

    private static final String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers";
    private static final String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method";

    private static final String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers";
    private static final String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods";
    private static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin";

    private static final String ACCESS_CONTROL_ALLOW_ORIGIN_ANYONE = "*";

    @Context HttpHeaders httpHeaders;

    @Override
    public Response toResponse(DefaultOptionsMethodException exception) {

        final ResponseBuilder response = Response.ok();

        String requestHeaders = httpHeaders.getHeaderString(ACCESS_CONTROL_REQUEST_HEADERS);
        String requestMethods = httpHeaders.getHeaderString(ACCESS_CONTROL_REQUEST_METHOD);

        if (requestHeaders != null)
            response.header(ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders);

        if (requestMethods != null)
            response.header(ACCESS_CONTROL_ALLOW_METHODS, requestMethods);

        // TODO: development only, too permissive
        response.header(ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_ALLOW_ORIGIN_ANYONE);

        return response.build();
    }
}
Felipe SS
  • 71
  • 2