在 REST 服务中实现方法 OPTIONS 的最佳方式

Posted

技术标签:

【中文标题】在 REST 服务中实现方法 OPTIONS 的最佳方式【英文标题】:Best way to implement method OPTIONS in REST services 【发布时间】:2015-11-28 03:19:38 【问题描述】:

我正在做一个 REST 应用程序。我已经毫无问题地制作了GET 方法,但是,当我实现POST 方法时,它说我没有为它实现OPTIONS 方法。我正在为 URI 使用 OPTIONS 方法:

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

我有 POSTOPTIONS 方法:

@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();

然后我创建了DELETE 方法,它再次说我没有OPTIONS 方法。然后我需要创建另一个OPTIONS 方法,它在URI 端有一个ID。比如用id = 3删除一个对象:

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

我需要另一个OPTIONSDELETE 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();

有没有人可以为所有 REST 请求做一个通用的OPTIONS

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>

【问题讨论】:

你是什么意思 它说没有为它实现 OPTIONS 方法 当我发出 POST 或 DELTE 请求时,应用程序会自动发出 OPTIONS 请求。 【参考方案1】:

在这种情况下,您不需要实现 OPTIONS HTTP VERB。由于您使用的是 RESTEasy,这是 Wildfly 使用的 JAX-RS 实现,所以我遇到的问题是由于 web.xml 上的 servlet-mapping。

我在 Eclipse 上添加 JAX-RS 方面并告诉它更新 web.xml 时遇到了这个问题。默认生成的包含 Restful 应用程序映射的 web.xml 不会将您的应用程序正确映射到您的 RESTful 资源路径。

这就是 web.xml 的外观,前提是您尚未创建自己的自定义 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>

确保您的 &lt;servlet-name&gt;&lt;servlet-mapping&gt; 如上例所示映射。如果您扩展了Application 类,只需在web.xml 中指定它,而不是如上所示的默认Application

另外,您的 @POST 资源方法,建议使用 @Consumes 注释指定 RESTful 数据的资源类型(在您的情况下为 DTO)。

例如。

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


【讨论】:

我像你的例子一样修改了我的 web.xml,但是服务器没有启动。还有@Consumes注解,也解决不了问题。 org.jboss.resteasy.spi.DefaultOptionsMethodException: 没有找到选项的资源方法,返回 OK 并带有 Allow header 我看到您正在使用 RESTEasy 指定配置。您能否也发布堆栈跟踪,以便我们可以看到会发生什么?另外,当你访问链接http://192.168.1.26:8080/sellAppWeb/api/时你会得到什么? 我得到这个错误,但我这是正确的。因为我没有映射到这个地址的路径&lt;response&gt; &lt;message&gt; Could not find resource for full path: http://192.168.1.103:8080/sellAppWeb/api/ &lt;/message&gt; &lt;/response&gt;【参考方案2】:

“但是,当我实现 POST 方法时,它说我没有为它实现 OPTIONS 方法。”

“当我发出 POST 或 DELTE 请求时,应用程序会自动发出 OPTIONS 请求”

这听起来绝对像是 CORS(跨源资源共享)问题。您可以在HTTP access control (CORS) 阅读有关它的更多信息。基本上 OPTIONS 请求是实际请求之前的预检请求。这将发生在某些类型的 AJAX 请求中。

为此,RESTeasy 有您可以注册的CorsFilter。您需要将过滤器配置为您想要允许的设置。另请参阅示例here,了解一种配置方式。

【讨论】:

我有一个 CORS 过滤器,它的实现如下: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"); 在过滤器的构造函数中添加一个打印语句以确保它被创建。其次,发出一个简单的 GET 请求,并查看标头以确保它们存在。如果您将标头添加到所有请求(就像您在过滤器中一样),标头甚至应该显示 GET 请求。 RESTesy 过滤器的好处是它被实现为仅发送预检(OPTIONS)请求的响应标头,因此不会为所有请求发送标头。如果为所有响应都设置了标头,这不是问题,它只会使响应更轻一些。 您可能还想尝试使用 RESTeasy 的过滤器而不是您自己的过滤器。 是的,构造函数在创建时在控制台打印,一个简单的GET请求的响应,是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 请尝试使用 RESTeasy 过滤器【参考方案3】:

我尝试了RestEasy's CorsFilter,但使用 OPTIONS 方法进行的调用返回了

RESTEASY003655:找不到选项的资源方法,返回 OK 允许标题

我写了一个简单的过滤器:

    确保将所需的 CORS 标头应用于响应。 使用 OPTIONS 方法调用端点时返回 HTTP 状态代码 200。您只需告诉客户端其 CORS 预检请求已被接受。

这里是代码。这是一个简化的版本,ditry - 但高效。如果您只想在查询“真实”端点时发回 200,请随意优化过滤器。

@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);

来自this post 和我的preferred CORS explanation。

【讨论】:

【参考方案4】:

你可以使用@Path("path:.*")。

@OPTIONS
@Path("path:.*")
public Response handleCORSRequest() throws Exception 
    Response.ResponseBuilder builder = Response.ok();
    return builder.build();

【讨论】:

【参考方案5】:

我推荐你使用 Spring Controllers 和 RequestMapping 注解,它们真的很容易使用:

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

您不需要实现 OPTIONS 方法,只需声明您的方法并使用注解将其定义为 POST/GET/PUT/DELETE 请求方法。 Here 有很多例子。

【讨论】:

当 Java EE 规范中有 JAX-RS 时,为什么你推荐使用 Spring? JBoss Wildlfy 自带 JAX-RS 支持,并且不使用任何 Spring REST 或 Spring Data。事实上,无需使用 Spring 和纯粹的 JAX-RS,就可以编写完整的 RESTful 应用程序。【参考方案6】:

对我来说,这是唯一的工作方式。

在您的 java restclient 项目中创建类。

    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();
    

【讨论】:

以上是关于在 REST 服务中实现方法 OPTIONS 的最佳方式的主要内容,如果未能解决你的问题,请参考以下文章

使用 netTcpBinding 时,在 WCF 中实现加密的最简单方法是啥?

REST:当我们可以在两者中实现相同的功能时,GET 和 DELETE 有啥区别?

使用 servlet 在 Java 中实现 REST Web 服务 [重复]

通过部分更新在 REST 中实现 PATCH 方法的官方方法

在 R Shiny 中实现 CRUD 工作流的最简洁方法是啥?

在 iOS 应用程序中实现可达性(无 Internet 弹出窗口)的最简单方法是啥? [复制]