Jersey的过滤器(8)

转自https://www.jianshu.com/p/88f97b90963c

服务器端过滤器(Server Filter)

Jersey中的过滤器分为两块,针对服务器端的过滤器和针对客户端的过滤器,先介绍服务器端的过滤器。
我们知道Servlet中的过滤器Filter,是一种双向的过滤器,即一个过滤器可以对请求进行一次过滤,然后调用执行链,让请求向下运行,然后再返回响应的时候,再次通过过滤器,在这个时候就可以对响应进行处理。而在JAX-RS中,过滤器是单向的,换句话说,要针对请求进行过滤,要选择针对请求的过滤器,要针对响应进行过滤,就要选择针对响应的过滤器:

javax.ws.rs.container.ContainerRequestFilter:针对请求的过滤器;
javax.ws.rs.container.ContainerResponseFilter:针对响应的过滤器;

基本使用

先来做一个最简单的过滤器测试:
首先实现一个ContainerRequestFilter:

public class MyRequestTestFilter implements ContainerRequestFilter {

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        System.out.println("===my request test filter===");
    }

}

我们的代码很简单,在filter方法中打印一行数据;

然后再实现一个ContainerResponseFilter:

public class MyResponseTestFilter implements ContainerResponseFilter {

    @Override
    public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
            throws IOException {
        System.out.println("===my response filter test===");
    }

}

同样,在filter方法中打印一行数据。注意,现在先不用去关心filter方法的ContainerRequestContext,ContainerResponseContext等参数,后面介绍。

完成两个过滤器之后,进行代码测试,先创建一个资源:

@Path("filter1")
@GET
public String resource1() {
    return "success";
}

注意,要让过滤器生效,需要在启动Jersey之前,注册我们的过滤器:

public RestApplication() {
    this.packages("cn.wolfcode.jersey");
    this.register(MultiPartFeature.class);
    this.register(MyRequestTestFilter.class).register(MyResponseTestFilter.class);
}

完成一个测试代码:

@Test
public void test() {
    String responseText = ClientBuilder.newClient()
            .target("http://localhost:8082/webapi").path("filter/filter1")
            .request(MediaType.TEXT_PLAIN).get(String.class);
    System.out.println(responseText);
}

运行测试,在服务端能够看到输出:

===my request test filter===
===my response filter test===

更多细节

首先来看下两个过滤器的执行方式,在上面的测试代码中,当我们正常完成一个资源的请求,其执行流程为:
requestFilter--->resource-->responseFilter
但是如果我们执行以下测试:

@Test
public void test() {
    String responseText = ClientBuilder.newClient()
            .target("http://localhost:8082/webapi").path("filter/filter2")
            .request(MediaType.TEXT_PLAIN).get(String.class);
    System.out.println(responseText);
}

我们请求了一个不存在的资源地址,返回404,但是服务端输出:

===my response filter test===

意思就是,requestFilter一定要资源请求到了之后,才会执行,而responseFilter是只要有响应返回即可执行,我们可以对404等异常响应处理。

其次,在ContainerRequestFilter中,filter方法提供了一个ContainerRequestContext参数,我们来看看这个类的功能:

ContainerRequestContext

ContainerRequestContext:包装了请求相关的内容,比如请求URI,请求方法,请求实体,请求头等等信息,这是一个可变的类,可以在请求过滤器中被修改;
我们在本节中不做过多的例子,我们来分析一个Jersey内提供的ContainerRequestFilter——HttpMethodOverrideFilter。我们前面提到过,如果客户端是通过网页表单请求,但是我们知道表单只有POST和GET两种请求方式,怎么模拟PUT,DELETE等请求方式呢?在SpringMVC中,可以通过表单提交一个_method域,在该域中设置要模拟的请求类型,比如DELETE,在服务端配置一个HttpMethodOverrideFilter过滤器即可。

同理,在Jersey中,也存在这种请求问题。Jersey就是使用HttpMethodOverrideFilter来完成请求方法的转化。我们来看看代码(为了方便查看,代码进行了简化,完整代码请查看源代码即可):

public final class HttpMethodOverrideFilter implements ContainerRequestFilter {

@Override
public void filter(final ContainerRequestContext request) {
    if (!request.getMethod().equalsIgnoreCase("POST")) {
        return;
    }

    final String header = getParamValue(Source.HEADER, request.getHeaders(), "X-HTTP-Method-Override");
    final String query = getParamValue(Source.QUERY, request.getUriInfo().getQueryParameters(), "_method");

    final String override;
    if (header == null) {
        override = query;
    } else {
        override = header;
        if (query != null && !query.equals(header)) {
            // inconsistent query and header param values
            throw new BadRequestException();
        }
    }

    if (override != null) {
        request.setMethod(override);
        if (override.equals("GET")) {
            if (request.getMediaType() != null
                    && MediaType.APPLICATION_FORM_URLENCODED_TYPE.getType().equals(request.getMediaType().getType())) {
                final UriBuilder ub = request.getUriInfo().getRequestUriBuilder();
                final Form f = ((ContainerRequest) request).readEntity(Form.class);
                for (final Map.Entry<String, List<String>> param : f.asMap().entrySet()) {
                    ub.queryParam(param.getKey(), param.getValue().toArray());
                }
                request.setRequestUri(request.getUriInfo().getBaseUri(), ub.build());
            }
        }
    }
}
}

可以简单的来看看执行流程:
1,请求比如是POST方法(调用getMethod获取本次请求方法);
2,两种方式可以完成请求方法的指定,第一种可以通过设置请求头中的X-HTTP-Method-Override属性,第二种可以通过添加_method查询参数,(这里分别调用getHeaders方法和getUriInfo方法获取头信息和请求URI信息);
3,设置请求方法,通过调用request.setMethod方法完成;
4,如果请求方式是POST,但是想转成GET方式,就需要把提交的表单内容变成URI的查询参数,具体参考代码即可。

ContainerResponseContext

对于ContainerResponseFilter来说,filter方法里面提供了两个参数:
ContainerRequestContext和ContainerResponseContext,第一个ContainerRequestContext我们前面已经说过,但是注意,在ResponseFilter中,requestContext已经是不能修改的!!ContainerResponseContext包装了响应相关的内容,这个对象在responseFilter中是可以修改的,比如下面的例子:

public class PoweredByResponseFilter implements ContainerResponseFilter {
     
    @Override
    public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
        throws IOException {
            responseContext.getHeaders().add("X-Powered-By", "wolfcode.cn");
    }
}

在这段代码中,我们通过responseContext为每一个响应都添加了一个额外的X-Powered-By响应头信息。更多的ContainerResponseContext方法,请查看API文档。

PreMatch和PostMatch

针对requestFilter,我们前面演示的所有的requestFilter都属于PostMatch,这也是默认的匹配时机,即过滤器只有当一个资源方法被正确的找到并准备执行的时候,过滤器执行过滤。换句话说,PostMath过滤器不能影响资源方法的匹配过程。所以我们上面看到的HttpMethodOverrideFilter在默认情况下(PostMatch)是不可能生效的,因为方法的匹配是在资源匹配之前完成的。

在Jersey中提供了@PreMatching注解,只需要在对应的requestFilter类上添加该标签,请求过滤器会在执行资源匹配之前提前运行。

所以,我们之前介绍的HttpMethodOverrideFilter,完整的类声明如下:

    @PreMatching
    @Priority(Priorities.HEADER_DECORATOR + 50) 
    public final class HttpMethodOverrideFilter implements ContainerRequestFilter {

可以看到,确实是标记为@PreMatching注解,那么就可以在该requestFilter中使用request.setMethod修改请求方法了。如果我们是在PostMatch请求过滤器中调用setMethod方法,那么过滤器会抛出IllegalArgumentException异常。
关于优先级@Priority标签,下文介绍。

客户端过滤器(Client Filter)

和服务端过滤器类似,Jersey也提供了两种客户端过滤器:

javax.ws.rs.client.ClientRequestFilter:客户端请求过滤器;
javax.ws.rs.client.ClientResponseFilter:客户端响应过滤器;

一个客户端过滤器示例代码:

public class CheckRequestFilter implements ClientRequestFilter {
     
    @Override
    public void filter(ClientRequestContext requestContext)
                        throws IOException {
        if (requestContext.getHeaders(
                        ).get("Client-Name") == null) {
            requestContext.abortWith(
                        Response.status(Response.Status.BAD_REQUEST)
                .entity("Client-Name header must be defined.")
                        .build());
         }
    }
}

该代码起一个演示作用,可以看到,首先CheckRequestFilter实现了ClientRequestFilter,即客户端请求拦截器,作用于客户端向服务端发送请求的过程,在filter方法中,判断请求头中是否包含Client-Name这个属性,如果没有包含,则直接调用requestContext的abortWith方法,传入一个状态为Response.Status.BAD_REQUEST(400)的响应,换句话说,该过滤器直接阻止了客户端向服务端的请求发送。

测试代码:

@Test
public void testClientFilter() {
    String responseText = ClientBuilder.newClient()
            .register(CheckRequestFilter.class)
            .target("http://localhost:8082/webapi").path("filter/filter1")
            .request(MediaType.TEXT_PLAIN)
            .header("Client-Name", "wolfcode.cn").get(String.class);
    System.out.println(responseText);
}

当然,在什么地方注册这个客户端过滤器无所谓,在ClientConfig,ClientBuilder或者WebTarget中都可以。
如果按照正常的流程,在header中增加Client-Name,能够正常执行,返回success;如果把测试代码修改为:

@Test
public void testClientFilter() {
    String responseText = ClientBuilder.newClient()
            .register(CheckRequestFilter.class)
            .target("http://localhost:8082/webapi").path("filter/filter1")
            .request(MediaType.TEXT_PLAIN)
            .get(String.class);
    System.out.println(responseText);
}

那么测试失败,直接抛出400的异常提示。

image.png

关于ClientRequestContext,ClientResponseContext这两个参数,可以参考服务端过滤器的使用,参考API即可。

其他

关于Filter的Name Binding,动态绑定和优先级,因为和拦截器(Interceptor)是相同的,所以这三个内容我们在下一节Interceptor中介绍。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值