乐趣区

关于后端: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 必须实现 <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…

欢送关注思否:程序员欣宸

学习路上,你不孤独,欣宸原创一路相伴 …

退出移动版