location /headerfilterdemo {content_handler_type 'java'; content_handler_name 'com.bolingcavalry.simplehello.HelloHandler'; # header filter的类型是java header_filter_type 'java'; # header header_filter_name 'com.bolingcavalry.filterdemo.RemoveAndAddMoreHeaders';}
实行header filter功能的类是RemoveAndAddMoreHeaders.java,如下所示,修正了Content-Type,还增加了两个header项Xfeep-Header和Server:
package com.bolingcavalry.filterdemo;import nginx.clojure.java.Constants;import nginx.clojure.java.NginxJavaHeaderFilter;import java.util.Map;public class RemoveAndAddMoreHeaders implements NginxJavaHeaderFilter { @Override public Object[] doFilter(int status, Map<String, Object> request, Map<String, Object> responseHeaders) { // 先删再加,相称于修正了Content-Type的值 responseHeaders.remove("Content-Type"); responseHeaders.put("Content-Type", "text/html"); // 增加两个header responseHeaders.put("Xfeep-Header", "Hello2!"); responseHeaders.put("Server", "My-Test-Server"); // 返回PHASE_DONE表示奉告nginx-clojure框架,当前filter正常,可以连续实行其他的filter和handler return Constants.PHASE_DONE; }}
将simple-hello和filter-demo两个maven工程都编译构建,会得到simple-hello-1.0-SNAPSHOT.jar和filter-demo-1.0-SNAPSHOT.jar这两个jar,将其都放入nginx/jars目录下,然后重启nginx用postman要求/headerfilterdemo,并将相应的header与/java做比拟,如下图,可见先删再加、添加都正常,其余,由于Server配置项本来就存在,以是filter中的put操作的结果便是修正了配置项的值:
接下来的实战再次用到之前的HelloHandler.java作为content handler,由于它返回的body是字符串先增加一个location配置,body_filter_type和body_filter_name是body filter的配置项:
# body filter的demo,response body是字符串类型location /stringbodyfilterdemo {content_handler_type 'java';content_handler_name 'com.bolingcavalry.simplehello.HelloHandler';# body filter的类型是javabody_filter_type 'java'; # body filter的类 body_filter_name 'com.bolingcavalry.filterdemo.StringFacedUppercaseBodyFilter';}
StringFacedUppercaseBodyFilter.java源码如下(请重点阅读注释),可见该filter的功能是将原始body改为大写,并且,代码中检讨了isLast的值,isLast即是false的时候,status的值保持为null,这样才能确保doFilter的调用不会提前结束,如此才能返回完全的body:
package com.bolingcavalry.filterdemo;import nginx.clojure.java.StringFacedJavaBodyFilter;import java.io.IOException;import java.util.Map;public class StringFacedUppercaseBodyFilter extends StringFacedJavaBodyFilter { @Override protected Object[] doFilter(Map<String, Object> request, String body, boolean isLast) throws IOException { if (isLast) { // isLast即是true,表示当前web要求过程中末了一次调用doFilter方法, // body是完全response body的末了一部分, // 此时返回的status该当不为空,这样nginx-clojure框架就会完成body filter的实行流程,将status和聚合后的body返回给客户端 return new Object[] {200, null, body.toUpperCase()}; }else { // isLast即是false,表示当前web要求过程中,doFilter方法还会被连续调用,当前调用只是多次中的一次而已, // body是完全response body的个中一部分, // 此时返回的status该当为空,这样nginx-clojure框架就连续body filter的实行流程,连续调用doFilter return new Object[] {null, null, body.toUpperCase()}; } }}
编译,构建,支配之后,用postman访问/stringbodyfilterdemo,得到的相应如下,可见body的内容已经全部大写了,符合预期:

doFilter方法有个入参名为bodyChunk,这表示真实相应body的一部分(假设一次web要求有十次doFilter调用,可以将每次doFilter的bodyChunk认为是完全相应body的十分之一),这里有个重点把稳的地方:bodyChunk只在当前doFilter实行过程中有效,不要将bodyChunk保存下来用于其他地方(例如放入body filter的成员变量中)连续看doFilter方法的返回值,刚刚提到返回值是一维数组,只有三个元素:status, headers, filtered_chunk,对付status和headers,如果之前已经设置好了(例如content handler或者header filter中),那么此时返回的status和headers值就会被忽略掉(也便是说,实在nginx-clojure框架只判断status是否为空,用于结束body filter的处理流程,至于status的详细值是多少并不关心)再看doFilter方法的返回值的第三个元素filtered_chunk,它可以是以下四种类型之一:File, viz. java.io.FileStringInputStreamArray/Iterable, e.g. Array/List/Set of above types接下来进入实战了,详细步骤如下图:首先是开拓一个返回二进制流的web接口,为了大略省事儿,直接用nginx-clojure的另一个能力来实现:clojure类型的做事,在nginx.conf中添加以下内容即可,代码虽然不是java但也能勉强看懂(能看懂就行,毕竟不是重点),便是持续写入1024行字符串,每行的内容都是’123456789’:
location /largebody {content_handler_type 'clojure'; content_handler_code ' (do (use \'[nginx.clojure.core]) (fn[req] {:status 200 :headers {} :body (for [i (range 1024)] "123456789\n")}) )';}
接下来是重点面向二进制流的body filter,StreamFacedBodyFilter.java,用来处理二进制流的body filter,可见这是非常大略的逻辑,您可以按照实际须要去利用这个InputStream:
package com.bolingcavalry.filterdemo;import nginx.clojure.NginxChainWrappedInputStream;import nginx.clojure.NginxClojureRT;import nginx.clojure.java.NginxJavaBodyFilter;import java.io.IOException;import java.io.InputStream;import java.util.Map;public class StreamFacedBodyFilter implements NginxJavaBodyFilter { @Override public Object[] doFilter(Map<String, Object> request, InputStream bodyChunk, boolean isLast) throws IOException { // 这里仅将二进制文件长度打印到日志,您可以按照业务实际情形自行修正 NginxClojureRT.log.info("isLast [%s], total [%s]", String.valueOf(isLast), String.valueOf(bodyChunk.available())); // NginxChainWrappedInputStream的成员变量index记录的读取的位置,本次用完后要重置位置,由于doFilter之外的代码中可能也会读取bodyChunk ((NginxChainWrappedInputStream)bodyChunk).rewind(); if (isLast) { // isLast即是true,表示当前web要求过程中末了一次调用doFilter方法, // body是完全response body的末了一部分, // 此时返回的status该当不为空,这样nginx-clojure框架就会完成body filter的实行流程,将status和聚合后的body返回给客户端 return new Object[] {200, null, bodyChunk}; }else { // isLast即是false,表示当前web要求过程中,doFilter方法还会被连续调用,当前调用只是多次中的一次而已, // body是完全response body的个中一部分, // 此时返回的status该当为空,这样nginx-clojure框架就连续body filter的实行流程,连续调用doFilter return new Object[] {null, null, bodyChunk}; } }}
还要在nginx.conf上做好配置,让StreamFacedBodyFilter处理/largebody返回的body,如下所示,新增一个接口/streambodyfilterdemo,该接口会直接透传到/largebody,而且会用StreamFacedBodyFilter处理相应body:
location /streambodyfilterdemo { # body filter的类型是java body_filter_type java; body_filter_name 'com.bolingcavalry.filterdemo.StreamFacedBodyFilter'; proxy_http_version 1.1; proxy_buffering off; proxy_pass http://localhost:8080/largebody; }
写完后,编译出jar文件,复制到jars目录下,重启nginx在postman上访问/streambodyfilterdemo,相应如下,符合预期:
2022-02-15 21:34:38[info][23765][main]isLast [false], total [3929]2022-02-15 21:34:38[info][23765][main]isLast [false], total [4096]2022-02-15 21:34:38[info][23765][main]isLast [false], total [2215]2022-02-15 21:34:38[info][23765][main]isLast [true], total [0]
至此,咱们一同完成了header和body的filter和学习实践,nginx-clojure的大体功能咱们已经理解得差不多了,但是《Java扩展Nginx》系列还没结束呢,还有精彩的内容会陆续登场,敬请关注,欣宸原创必不辜负您的期待~源码下载《Java扩展Nginx》的完全源码可在GitHub下载到,地址和链接信息如下表所示(https://github.com/zq2599/blog_demos):