Java扩展Nginx之六:两大filter
欢迎访问我的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必须实现NginxJavaHeaderFilter接口,功能代码写在doFilter方法中
- doFilter方法如果返回PHASE_DONE,nginx-clojure框架会继续执行其他的filter和handler,如果返回的不是PHASE_DONE,nginx-clojure框架就会把当前filter当做普通的content handler来对待,将doFilter的返回值立即返回给客户端
- 官方建议用header filter来动态处理response的header(增加、删除、修改header项)
接下来开发一个header filter试试,还记得《Java扩展Nginx之一:你好,nginx-clojure》一文中的/java接口吗,那是个最简单的helloworld级别的location,content handler是HelloHandler.java,稍后验证header filter功能的时候会用到它
先用postman请求/java接口,看看没有使用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,如下所示,修改了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操作的结果就是修改了配置项的值:
- 到这里header filter就介绍完了,接下来要看的是body filter,顾名思义,这是用于处理响应body的过滤器,与header filter不同的是,由于响应body有不同的类型,因此body filter也不能一概而论,需要分场景开发和使用
Nginx Body Filter的第一个场景:字符串body(string faced Java body filter)
- Body Filter的作用很明确:修改原响应body的值,然后返回给客户端
- 如果响应的body是字符串,那么body filter相对简单一些,以下几个规则要注意:
- 继承抽象类StringFacedJavaBodyFilter,
- 处理一次web请求的时候,doFilter方法可能被调用多次,有个名为isLast的入参,作用是标记当前调用是不是最后一次(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都不再被调用了!
- 接下来的实战再次用到之前的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的类型是java
body_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的内容已经全部大写了,符合预期:
- 接下来要学习的还是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方法可能被调用多次,有个名为isLast的入参,作用是标记当前调用是不是最后一次(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方法有个入参名为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.File
String
InputStream
Array/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,响应如下,符合预期:
- 再检查文件nginx-clojure-0.5.2/logs/error.log,见到了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项目中有多个文件夹,本篇的源码在nginx-clojure-tutorials文件夹下的filter-demo子工程中,如下图红框所示:
本篇涉及到nginx.conf的修改,完整的参考在此:https://raw.githubusercontent.com/zq2599/blog_demos/master/nginx-clojure-tutorials/files/nginx.conf
欢迎关注博客园:程序员欣宸
Java扩展Nginx之六:两大filter的更多相关文章
- 03 Java的数据类型分为两大类 类型转换 八大基本类型
数据类型 强类型语言:要求变量的使用要严格符合规定,所有变量都必须先定义后才能使用 Java的数据类型分为两大类 基本类型(primitive type) 数值类型 整数类型 byte占1个字节范围: ...
- java的数据类型分为两大类
java的数据类型分为两大类 基本类型(primitive type) 数据类型 整数类型 byte占一个字节范围:-128-127 short占两个字节范围:-32768-32767 int占四个字 ...
- Flask框架简介,常用扩展包及两大核心
Flask诞生于2010年,是Armin ronacher(人名)用 Python 语言基于 Werkzeug 工具箱编写的轻量级Web开发框架. Flask 本身相当于一个内核,其他几乎所有的功能都 ...
- 【Java知识点专项练习】之 数据类型两大类
Java的数据类型分为两大类:基本类型和引用类型: 基本类型只能保存一些常量数据,引用类型除了可以保存数据,还能提供操作这些数据的功能: 为了操作基本类型的数据,java也对它们进行了封装, 得到八个 ...
- Java轻量级业务层框架Spring两大核心IOC和AOP原理
IoC(Inversion of Control): IOC的基本概念是:不创建对象,但是描述创建它们的方式.在代码中不直接与对象和服务连接,但在配置文件中描述哪一个组件需要哪一项服务.容器负责将这些 ...
- java环境中基于jvm的两大语言:scala,groovy
一.java环境中基于jvm的两大语言:scala,groovy 可以在java项目里混编这两种语言: scala:静态语言,多范式语言,糅合了面向对象.面向过程:可以与java和net互操作:融汇了 ...
- Java的两大数据类型
Java的两大数据类型 基本数据类型 byte,short,int,long,float,double,boolean,char byte 类别 内容 类型 byte 简介 byte 数据类型是8位. ...
- 首先java中集合类主要有两大分支
本文仅分析部分原理和集合类的特点,不分析源码,旨在对java的集合类有一个整体的认识,理解各个不同类的关联和区别,让大家在不同的环境下学会选择不同的类来处理. Java中的集合类包含的内容很多而且很重 ...
- Java 学习笔记 两大集合框架Map和Collection
两大框架图解 Collection接口 由第一张图,我们可以知道,Collection接口的子接口有三种,分别是List接口,Set接口和Queue接口 List接口 允许有重复的元素,元素按照添加的 ...
- Java入门到精通——框架篇之Spring源码分析Spring两大核心类
一.Spring核心类概述. Spring里面有两个最核心的类这是Spring实现最重要的部分. 1.DefaultListableBeanFactory 这个类位于Beans项目下的org.spri ...
随机推荐
- 研发运维双管齐下!Seal AppManager的正确打开方式
新一代应用统一部署管理平台 Seal AppManager 采用平台工程的理念,通过降低基础设施操作的复杂度为研发和运维团队提供易用.一致的应用管理和部署体验.Seal AppManager 帮助研发 ...
- 【从零开始】Docker Desktop:听说你小子要玩我
前言 缘由 捡起遗忘的Docker知识 由于本狗近期项目紧任务重,高强度的搬砖导致摸鱼时间下降.在上线项目时,看到运维大神一系列骚操作,docker+k8s的知识如过眼云烟,忘得干净的很.所以想重新恶 ...
- XSS的攻击
https://blog.csdn.net/m0_55854679/article/details/123028852
- Map集合案例:统计输入多个key值出现的次数
某商店想统计一下一天内所售出的商品以及商品的数量,请编写程序帮助实现,并展示.通过键盘录入商品名称模拟售出的商品, 录入一次表示商品售出一次,直到录入end结束.运行效果如下: 代码:
- ES6教程笔记
ES介绍 什么是ES ES全称 EcmaScript 是一种脚本语言的规范 Javascript为ES的实现 Ecma 是一个组织 Ecma组织 为什么要学习ES6? ES6的版本变动内容最多,具有里 ...
- Tars-Cpp 协程实现分析
作者:vivo 互联网服务器团队- Ye Feng 本文介绍了协程的概念,并讨论了 Tars Cpp 协程的实现原理和源码分析. 一.前言 Tars 是 Linux 基金会的开源项目(https:// ...
- nuxt下运行项目时内存溢出(out of memory)的一种情况
话不多说直接上代码: 如图,点红点的三行引入了一个组件,内容是同意注册协议的弹窗.但是在run dev的时候提示说内存溢出了(out of memory)...经过多方排查,定位到这个组件,警察叔叔就 ...
- Vue 前端开发团队风格指南(史上最全)
Vue官网的风格指南按照优先级(依次为必要.强烈推荐.推荐.谨慎使用)分类,本文根据项目实际情况整理了一份适用于团队开发的vue风格指南,供大家参考. 一.命名规范 常用的命名规范: camelCas ...
- [OS/Linux] Linux核心参数:net.core.somaxconn(高并发场景核心参数)
0 序言 近期工作在搞压力测试,我负责开发维护的.基于sring-cloud-gateway的大数据网关微服务,其底层是基于spring-webflux-->reactor-netty--> ...
- Sourcetree 提交顺序
总结:一共5个步骤 1.首先获取git主分支的代码. 2.暂存所需要上传的代码. 3.拉取代码(如发生文件冲突先暂不处理). 4.提交代码,然后再次拉取代码(不显示冲突跳下一步).如果还是显示文件冲突 ...