Best way to implement method OPTIONS in REST services
Solution 1
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)
}
Solution 2
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:
- Makes sure the CORS header you need are applied to the response.
- 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.
Solution 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.
Solution 4
You can use @Path("{path:.*}").
@OPTIONS
@Path("{path:.*}")
public Response handleCORSRequest() throws Exception {
Response.ResponseBuilder builder = Response.ok();
return builder.build();
}
Solution 5
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();
}
}
Eduardo Vendruscolo
Updated on June 08, 2022Comments
-
Eduardo Vendruscolo almost 2 years
I'm doing a REST application. I have made the
GET
method without issues, however, when I implement thePOST
method, it says that I don't have theOPTIONS
method implemented for it. I am doing theOPTIONS
method for URI:http://192.168.1.26:8080/sellAppWeb/api/object/
I have the
POST
andOPTIONS
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 aOPTIONS
method. Then I need to make anotherOPTIONS
method, which has anID
in the URI end. For example to delete a object withid = 3
:http://192.168.1.26:8080/sellAppWeb/api/object/3
I need to have another
OPTIONS
with same structure ofDELETE
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>
-
Buhake Sindi over 8 yearsWhy 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.
-
Eduardo Vendruscolo over 8 yearsI modify my web.xml like your examle, but the server don't starts. And the @Consumes annotation, dont solve the problem too.
-
Eduardo Vendruscolo over 8 yearsorg.jboss.resteasy.spi.DefaultOptionsMethodException: No resource method found for options, return OK with Allow header
-
Eduardo Vendruscolo over 8 yearsI 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");
-
Paul Samsotha over 8 yearsAdd 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 over 8 yearsYou may also want to just try and use RESTeasy's filter instead of your own.
-
Buhake Sindi over 8 yearsI 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/
? -
Eduardo Vendruscolo over 8 yearsi get this error, but i this it's correct. because i dont have path mapped to this address
<response> <message> Could not find resource for full path: http://192.168.1.103:8080/sellAppWeb/api/ </message> </response>
-
Eduardo Vendruscolo over 8 yearsYes, 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
-
Paul Samsotha over 8 yearsPlease try and use the RESTeasy filter
-
Eduardo Vendruscolo over 8 yearswhich 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.
-
Paul Samsotha over 8 yearsYou don't need to implement anything. I meant register resteasys
CorsFilter
. Look at my answer