61.2. 容器请求过滤器
概述
本节介绍如何实施和注册 容器请求过滤器,该过滤器 用于截获服务器(容器)侧的传入请求消息。容器请求过滤器通常用于处理服务器端上的标头,并可用于任何种类的通用请求处理(即处理独立于特定资源方法的处理)。
此外,容器请求过滤器是特殊情况的对象,因为它可以安装两个不同的扩展点: PreMatchContainerRequest
(在资源匹配步骤之前)和 ContainerRequest
(在资源匹配步骤后)。
containerRequestFilter 接口
javax.ws.rs.container.ContainerRequestFilter
接口定义如下:
// Java ... package javax.ws.rs.container; import java.io.IOException; public interface ContainerRequestFilter { public void filter(ContainerRequestContext requestContext) throws IOException; }
通过实施 ContainerRequestFilter
接口,您可以为服务器端的以下扩展点创建一个过滤器:
-
PreMatchContainerRequest
-
containerRequest
ContainerRequestContext 接口
ContainerRequestFilter
的 过滤器
方法接收单个参数,即 javax.ws.rs.container.ContainerRequestContext
,它可用于访问传入的请求消息及其关联的元数据。ContainerRequestContext
接口定义如下:
// Java ... package javax.ws.rs.container; import java.io.InputStream; import java.net.URI; import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Map; import javax.ws.rs.core.Cookie; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Request; import javax.ws.rs.core.Response; import javax.ws.rs.core.SecurityContext; import javax.ws.rs.core.UriInfo; public interface ContainerRequestContext { public Object getProperty(String name); public Collection getPropertyNames(); public void setProperty(String name, Object object); public void removeProperty(String name); public UriInfo getUriInfo(); public void setRequestUri(URI requestUri); public void setRequestUri(URI baseUri, URI requestUri); public Request getRequest(); public String getMethod(); public void setMethod(String method); public MultivaluedMap getHeaders(); public String getHeaderString(String name); public Date getDate(); public Locale getLanguage(); public int getLength(); public MediaType getMediaType(); public List getAcceptableMediaTypes(); public List getAcceptableLanguages(); public Map getCookies(); public boolean hasEntity(); public InputStream getEntityStream(); public void setEntityStream(InputStream input); public SecurityContext getSecurityContext(); public void setSecurityContext(SecurityContext context); public void abortWith(Response response); }
PreMatchContainerRequest 过滤器的实现示例
要为 PreMatchContainerRequest
扩展点实施容器请求过滤器(即,在资源匹配前执行过滤器),定义一个实施 ContainerRequestFilter
接口的类,确保为类添加 @PreMatching
注解(以选择 PreMatchContainerRequest
扩展点)。
例如,以下代码显示了在 PreMatchContainerRequest
扩展点中安装的简单容器请求过滤器示例,优先级为 20:
// Java package org.jboss.fuse.example; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestFilter; import javax.ws.rs.container.PreMatching; import javax.annotation.Priority; import javax.ws.rs.ext.Provider; @PreMatching @Priority(value = 20) @Provider public class SamplePreMatchContainerRequestFilter implements ContainerRequestFilter { public SamplePreMatchContainerRequestFilter() { System.out.println("SamplePreMatchContainerRequestFilter starting up"); } @Override public void filter(ContainerRequestContext requestContext) { System.out.println("SamplePreMatchContainerRequestFilter.filter() invoked"); } }
ContainerRequest 过滤器的实现示例
要为 ContainerRequest
扩展点实施容器请求过滤器(即,在资源匹配后执行过滤器),定义一个实施 ContainerRequestFilter
接口的类,而无需 @PreMatching
注释。
例如,以下代码显示了在 ContainerRequest
扩展点中安装的简单容器请求过滤器示例,优先级为 30:
// Java package org.jboss.fuse.example; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestFilter; import javax.ws.rs.ext.Provider; import javax.annotation.Priority; @Provider @Priority(value = 30) public class SampleContainerRequestFilter implements ContainerRequestFilter { public SampleContainerRequestFilter() { System.out.println("SampleContainerRequestFilter starting up"); } @Override public void filter(ContainerRequestContext requestContext) { System.out.println("SampleContainerRequestFilter.filter() invoked"); } }
注入 ResourceInfo
在 ContainerRequest
扩展点(即资源匹配 后 ),可以通过注入 ResourceInfo
类来访问匹配的资源类和资源方法。例如,以下代码演示了如何将 ResourceInfo
类注入 ContainerRequestFilter
类的字段:
// Java package org.jboss.fuse.example; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestFilter; import javax.ws.rs.container.ResourceInfo; import javax.ws.rs.ext.Provider; import javax.annotation.Priority; import javax.ws.rs.core.Context; @Provider @Priority(value = 30) public class SampleContainerRequestFilter implements ContainerRequestFilter { @Context private ResourceInfo resinfo; public SampleContainerRequestFilter() { ... } @Override public void filter(ContainerRequestContext requestContext) { String resourceClass = resinfo.getResourceClass().getName(); String methodName = resinfo.getResourceMethod().getName(); System.out.println("REST invocation bound to resource class: " + resourceClass); System.out.println("REST invocation bound to resource method: " + methodName); } }
中止调用
通过创建适合容器请求过滤器的实施,可以中止服务器端调用。通常,这可用于在服务器端实施安全功能:例如,实施身份验证功能或授权功能。如果传入的请求无法验证成功,您可以在容器请求过滤器中中止调用。
例如,以下预先匹配功能尝试从 URI 的查询参数中提取用户名和密码,并调用验证方法来检查用户名和密码凭据。如果身份验证失败,则调用 ContainerRequestContext
对象上的 abortWith
会中止,并传递要返回到客户端的错误响应。
// Java package org.jboss.fuse.example; import javax.annotation.Priority; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestFilter; import javax.ws.rs.container.PreMatching; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.ResponseBuilder; import javax.ws.rs.core.Response.Status; import javax.ws.rs.ext.Provider; @PreMatching @Priority(value = 20) @Provider public class SampleAuthenticationRequestFilter implements ContainerRequestFilter { public SampleAuthenticationRequestFilter() { System.out.println("SampleAuthenticationRequestFilter starting up"); } @Override public void filter(ContainerRequestContext requestContext) { ResponseBuilder responseBuilder = null; Response response = null; String userName = requestContext.getUriInfo().getQueryParameters().getFirst("UserName"); String password = requestContext.getUriInfo().getQueryParameters().getFirst("Password"); if (authenticate(userName, password) == false) { responseBuilder = Response.serverError(); response = responseBuilder.status(Status.BAD_REQUEST).build(); requestContext.abortWith(response); } } public boolean authenticate(String userName, String password) { // Perform authentication of 'user' ... } }
绑定服务器请求过滤器
要 绑定 服务器请求过滤器(即,将其安装到 Apache CXF 运行时),请执行以下步骤:
将
@Provider
注释添加到容器请求过滤器类,如以下代码片段所示:// Java package org.jboss.fuse.example; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestFilter; import javax.ws.rs.ext.Provider; import javax.annotation.Priority; @Provider @Priority(value = 30) public class SampleContainerRequestFilter implements ContainerRequestFilter { ... }
当容器请求过滤器实现加载到 Apache CXF 运行时,REST 实现会自动扫描加载的类,以搜索标有
@Provider
注释( 扫描阶段)的类。在 XML 中定义 JAX-RS 服务器端点时(例如,请参阅 第 18.1 节 “配置 JAX-RS 服务器端点”),将服务器请求过滤器添加到
jaxrs:providers
元素中的提供程序列表中。<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxrs="http://cxf.apache.org/blueprint/jaxrs" xmlns:cxf="http://cxf.apache.org/blueprint/core" ... > ... <jaxrs:server id="customerService" address="/customers"> ... <jaxrs:providers> <ref bean="filterProvider" /> </jaxrs:providers> <bean id="filterProvider" class="org.jboss.fuse.example.SampleContainerRequestFilter"/> </jaxrs:server> </blueprint>
注意这一步是 Apache CXF 的非标准要求。根据 JAX-RS 标准严格讲,
@Provider
注释应当全部满足绑定过滤器所必需的。但在实践中,标准方法有些不灵活,当大型项目中纳入多个库时,可能会导致供应商冲突。