在实现中设置 JAX-RS 响应标头,而不在接口中公开 HttpServletResponse



我有一个 RESTful 服务器实现以及一个供客户端进行调用的库,所有这些都使用 JAX-RS。服务器组件分为接口FooResource和实现FooResourceService

为了使客户端和服务器库共享 RESTful 路径和其他定义,我想将FooResource接口拆分到它自己的项目中:

@Path(value = "foo")
public interface FooResource {
  @GET
  public Bar getBar(@PathParam(value = "{id}") int id) {

我想在响应中设置一些标头。执行此操作的一种简单方法是在方法签名中使用@Context HttpServletResponse

  public Bar getBar(@PathParam(value = "{id}") int id, @Context HttpServletResponse servletResponse) {

但问题是这会在接口中公开实现细节。更具体地说,它突然需要我的 REST 定义项目(在客户端和服务器库之间共享)拉入javax.servlet-api依赖项---客户端不需要(或不需要)的东西。

我的 RESTful 资源服务实现如何在不拉入资源接口中该依赖项的情况下设置 HTTP 响应标头?

我看到一篇文章建议我将HttpServletResponse作为类成员注入。但是,如果我的资源服务实现是单一实例,这将如何工作?它是否使用某种带有线程局部变量的代理,或者即使单例类同时由多个线程使用,也可以找出正确的 servlet 响应?还有其他解决方案吗?

正确的答案似乎是在实现的成员变量中注入HttpServletResponse,正如我注意到另一篇文章所指出的那样。

@Context  //injected response proxy supporting multiple threads
private HttpServletResponse servletResponse;

尽管 peeskillet 表示泽西岛的半官方列表没有将HttpServletResponse列为可代理类型之一,但当我跟踪代码时,至少 RESTEasy 似乎正在创建一个代理(org.jboss.resteasy.core.ContextParameterInjector$GenericDelegatingProxy@xxxxxxxx)。因此,据我所知,似乎正在发生单例成员变量的线程安全注入。

另请参阅 https://stackoverflow.com/a/10076327/421049 。

所以注入HttpServletResponse似乎是不行的。只有某些可代理的类型可以注入到单例中。我相信完整的列表如下:

HttpHeaders, Request, UriInfo, SecurityContext

这在 JAX-RS 规范中有所指出,但在泽西岛参考指南中解释得更清楚。

对于甚至可以注入构造函数或类字段的特定请求对象,存在例外。对于这些对象,运行时将注入能够同时处理更多请求的代理。这些请求对象分别是HttpHeadersRequestUriInfoSecurityContext。可以使用@Context注释注入这些代理。

SecurityContext可能是泽西岛特有的,因为它没有在规格中说明,但我不确定。

现在,上面提到的那些类型对您并没有多大作用,因为它们都是请求上下文,没有设置响应的内容。

不过,一个想法是使用javax.ws.rs.container.ContainerResponseFilter,以及HttpHeaders来设置临时请求标头。可以通过传递给 filter 方法的ContainerRequestContext访问该标头。 然后只需通过ContainerResponseContext设置响应标头,也传递给filter方法。如果标头不特定于该资源方法的上下文,则更容易。只需在过滤器中设置标题即可。

但是,假设标头依赖于资源方法的执行。然后你可以做一些类似的事情

@Singleton
@Path("/singleton")
public class SingletonResource {
    @Context
    javax.ws.rs.core.HttpHeaders headers;
    @GET
    public String getHello() {
        String result = resultFromSomeCondition(new Object());
        headers.getRequestHeaders().putSingle("X-HELLO", result);
        return "Hello World";
    }
    private String resultFromSomeCondition(Object condition) {
        return "World";
    }
}

那么ContainerResponseFilter可能看起来像这样

@Provider
public class SingletonContainerResponseFilter 
                            implements ContainerResponseFilter {
    @Override
    public void filter(ContainerRequestContext crc, 
            ContainerResponseContext crc1) throws IOException {
        String header = crc.getHeaderString("X-HELLO");
        crc1.getHeaders().putSingle("X-HELLO", "World");
    } 
}

正因为只有单例类通过这个过滤器运行,我们可以简单地使用@NameBinding注释

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.ws.rs.NameBinding;
@NameBinding
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface SingletonHeader {}
...
@SingletonHeader
public class SingletonResource {
...
@SingletonHeader
public class SingletonContainerResponseFilter 
                        implements ContainerResponseFilter {

这是我能想到的处理这种情况的唯一方法。


资源:

  • 过滤器和拦截器
  • 名称绑定
@Path("/foo")
public interface FooResource {
    @GET
    @Path("{id}")
    public Response getBar(@PathParam("id") int id) {
        Bar bar = new Bar();
        //Do some logic on bar
        return Response.ok().entity(bar).header("header-name", "header-value").build()
    }
}

返回状态代码为 200 且标头header-name值为 header-valuebar 实例的 JSON 表示形式。它应该看起来像这样:

{
    "bar-field": "bar-field-value",
    "bar-field-2": "bar-field-2"
}

最新更新