欢送拜访我的GitHub
这里分类和汇总了欣宸的全副原创(含配套源码):https://github.com/zq2599/blog_demos
本篇概览
- 本文是《Java扩大Nginx》系列的第六篇,[前文]()的五大handler造成了nginx-clojure开发的根本框架,初步评估曾经能够撑持简略的需要开发了,但nginx-clojure并未止步于handler,还提供了丰盛的扩大能力,本篇的两大filter就是比拟罕用的能力
filter一共有两种:header filter和body filter,nginx-clojure对他们的定位别离是对header的解决和对body的解决,接下来别离细说
Nginx Header Filter
- header filter顾名思义,是用于header解决的过滤器,它具备如下特点:
- header filter是location级别的配置,能够开发一个header filter,而后配置在不同的location中应用
- header filter必须实现<font color="blue">NginxJavaHeaderFilter</font>接口,性能代码写在<font color="red">doFilter</font>办法中
- doFilter办法如果返回<font color="red">PHASE_DONE</font>,nginx-clojure框架会继续执行其余的filter和handler,如果返回的不是PHASE_DONE,nginx-clojure框架就会把以后filter当做一般的content handler来看待,将doFilter的返回值立刻返回给客户端
- 官网倡议用header filter来动静解决response的header(减少、删除、批改header项)
- 接下来开发一个header filter试试,还记得《Java扩大Nginx之一:你好,nginx-clojure》一文中的<font color="blue">/java</font>接口吗,那是个最简略的helloworld级别的location,content handler是HelloHandler.java,稍后验证header filter性能的时候会用到它
- 先用postman申请<font color="blue">/java</font>接口,看看没有应用header filter之前的response header,如下图:
接下来新增一个location,配置如下,content handler还是HelloHandler.java,减少了header_filter_type和header_filter_name:
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,如下所示,批改了<font color="blue">Content-Type</font>,还减少了两个header项<font color="red">Xfeep-Header</font>和<font color="red">Server</font>:
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; }}
- 将<font color="blue">simple-hello</font>和<font color="blue">filter-demo</font>两个maven工程都编译构建,会失去simple-hello-1.0-SNAPSHOT.jar和filter-demo-1.0-SNAPSHOT.jar这两个jar,将其都放入<font color="red">nginx/jars</font>目录下,而后重启nginx
- 用postman申请<font color="blue">/headerfilterdemo</font>,并将响应的header与<font color="blue">/java</font>做比照,如下图,可见先删再加、增加都失常,另外,因为<font color="blue">Server</font>配置项原本就存在,所以filter中的put操作的后果就是批改了配置项的值:
到这里header filter就介绍完了,接下来要看的是body filter,顾名思义,这是<font color="blue">用于解决响应body的过滤器</font>,与header filter不同的是,因为响应body有不同的类型,因而body filter也不能一概而论,须要分场景开发和应用
Nginx Body Filter的第一个场景:字符串body(string faced Java body filter)
- Body Filter的作用很明确:批改原响应body的值,而后返回给客户端
- 如果响应的body是字符串,那么body filter绝对简略一些,以下几个规定要留神:
- 继承抽象类StringFacedJavaBodyFilter,
- 解决一次web申请的时候,doFilter办法可能被调用屡次,有个名为<font color="blue">isLast</font>的入参,作用是标记以后调用是不是最初一次(true示意最初一次)
- doFilter办法的返回值与之前的NginxJavaRingHandler.invoke办法相似,是个一维数组,只有三个元素:status, headers, filtered_chunk,一旦status值不为空,nginx-clojure框架会用这次doFilter的返回值作为最初一次调用,返回给客户端
- 联合2和3的个性,咱们在编码时要留神了:假如一次web申请,doFilter会被调用10次(每次body入参的值都是整个response body的一部分),那么前9次的isLast都等于false,第10次的isLast等于true,假如第1次调用doFilter办法的时候返回的status不为空,就会导致前面9次的doFilter都不再被调用了!
- 接下来的实战再次用到之前的<font color="blue">HelloHandler.java</font>作为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的类型是java body_filter_type 'java'; # body filter的类 body_filter_name 'com.bolingcavalry.filterdemo.StringFacedUppercaseBodyFilter';}
StringFacedUppercaseBodyFilter.java源码如下(<font color="red">请重点浏览正文</font>),可见该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拜访<font color="blue">/stringbodyfilterdemo</font>,失去的响应如下,可见body的内容曾经全副大写了,合乎预期:
接下来要学习的还是body filter,只不过这次的body类型是二进制流(stream faced Java body filter)
Nginx Body Filter的第二个场景:二进制流body(stream faced Java body filter)
- 当响应body是二进制流的时候,如果想对响应body做读写操作,nginx-clojure的倡议是在body filter中执行,这种body filter是专门用在二进制流body的场景下,有以下特点:
- 实现接口NginxJavaBodyFilter(留神区别:字符串body的filter是继承抽象类StringFacedJavaBodyFilter),
- 解决一次web申请的时候,doFilter办法可能被调用屡次,有个名为<font color="blue">isLast</font>的入参,作用是标记以后调用是不是最初一次(true示意最初一次)
- doFilter办法的返回值与之前的NginxJavaRingHandler.invoke办法相似,是个一维数组,只有三个元素:status, headers, filtered_chunk,一旦status值不为空,nginx-clojure框架会用这次doFilter的返回值作为最初一次调用,返回给客户端
- 联合2和3的个性,咱们在编码时要留神了:假如一次web申请,doFilter会被调用10次(每次body入参的值都是整个response body的一部分),那么前9次的isLast都等于false,第10次的isLast等于true,假如第1次调用doFilter办法的时候返回的status不为空,就会导致前面9次的doFilter都不再被调用了!
- doFilter办法有个入参名为<font color="blue">bodyChunk</font>,这示意实在响应body的一部分(假如一次web申请有十次doFilter调用,能够将每次doFilter的bodyChunk认为是残缺响应body的十分之一),这里有个重点留神的中央:bodyChunk只在以后doFilter执行过程中无效,<font color="red">不要将bodyChunk保留下来用于其余中央(例如放入body filter的成员变量中)</font>
- 持续看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.File
- String
- InputStream
- Array/Iterable, e.g. Array/List/Set of above types
- 接下来进入实战了,具体步骤如下图:
首先是开发一个返回二进制流的web接口,为了简略省事儿,间接用nginx-clojure的另一个能力来实现:clojure类型的服务,在<font color="blue">nginx.conf</font>中增加以下内容即可,代码尽管不是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")}) )';}
接下来是重点<font color="blue">面向二进制流的body filter</font>,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}; } }}
还要在<font color="red">nginx.conf</font>上做好配置,让StreamFacedBodyFilter解决<font color="blue">/largebody</font>返回的body,如下所示,新增一个接口<font color="blue">/streambodyfilterdemo</font>,该接口会间接透传到/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上拜访<font color="blue">/streambodyfilterdemo</font>,响应如下,合乎预期:
再查看文件<font color="blue">nginx-clojure-0.5.2/logs/error.log</font>,见到了StreamFacedBodyFilter的日志,证实body filter的确曾经失效,另外还能够看出一次申请中,StreamFacedBodyFilter对象的doFilter办法会被neginx-clojure屡次调用:
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):
名称 | 链接 | 备注 |
---|---|---|
我的项目主页 | https://github.com/zq2599/blog_demos | 该我的项目在GitHub上的主页 |
git仓库地址(https) | https://github.com/zq2599/blog_demos.git | 该我的项目源码的仓库地址,https协定 |
git仓库地址(ssh) | git@github.com:zq2599/blog_demos.git | 该我的项目源码的仓库地址,ssh协定 |
- 这个git我的项目中有多个文件夹,本篇的源码在<font color="blue">nginx-clojure-tutorials</font>文件夹下的<font color="red">filter-demo</font>子工程中,如下图红框所示:
- 本篇波及到nginx.conf的批改,残缺的参考在此:https://raw.githubusercontent.com/zq2599/blog_demos/master/ng...
欢送关注思否:程序员欣宸
学习路上,你不孤独,欣宸原创一路相伴...