服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|JavaScript|易语言|

服务器之家 - 编程语言 - Java教程 - SpringCloud Finchley Gateway 缓存请求Body和Form表单的实现

SpringCloud Finchley Gateway 缓存请求Body和Form表单的实现

2021-07-02 14:53Evans Java教程

在接入Spring-Cloud-Gateway时,可能有需求进行缓存Json-Body数据或者Form-Urlencoded数据的情况。这篇文章主要介绍了SpringCloud Finchley Gateway 缓存请求Body和Form表单的实现,感兴趣的小伙伴们可以参考一下

在接入spring-cloud-gateway时,可能有需求进行缓存json-body数据或者form-urlencoded数据的情况。

由于spring-cloud-gateway是以webflux为基础的响应式架构设计,所以在原有zuul基础上迁移过来的过程中,传统的编程思路,并不适合于reactor stream的开发。

网络上有许多缓存案例,但是在测试过程中出现各种bug问题,在缓存body时,需要考虑整体的响应式操作,才能更合理的缓存数据

下面提供缓存json-body数据或者form-urlencoded数据的具体实现方案,该方案经测试,满足各方面需求,以及避免了网络上其他缓存方案所出现的问题

定义一个gatewaycontext类,用于存储请求中缓存的数据

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import lombok.getter;
import lombok.setter;
import lombok.tostring;
import org.springframework.util.linkedmultivaluemap;
import org.springframework.util.multivaluemap;
 
@getter
@setter
@tostring
public class gatewaycontext {
 
  public static final string cache_gateway_context = "cachegatewaycontext";
 
  /**
   * cache json body
   */
  private string cachebody;
  /**
   * cache formdata
   */
  private multivaluemap<string, string> formdata;
  /**
   * cache reqeust path
   */
  private string path;
}

实现globalfilter和ordered接口用于缓存请求数据

1 . 该示例只支持缓存下面3种mediatype

  • application_json--json数据
  • application_json_utf8--json数据
  • application_form_urlencoded--formdata表单数据

2 . 经验总结:

  • 在缓存body时,不能够在filter内部直接进行缓存,需要按照响应式的处理方式,在异步操作路途上进行缓存body,由于body只能读取一次,所以要读取完成后要重新封装新的request和exchange才能保证请求正常传递到下游
  • 在缓存formdata时,formdata也只能读取一次,所以在读取完毕后,需要重新封装request和exchange,这里要注意,如果对formdata内容进行了修改,则必须重新定义header中的content-length已保证传输数据的大小一致
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
import com.choice.cloud.architect.usergate.option.filterorderenum;
import com.choice.cloud.architect.usergate.support.gatewaycontext;
import io.netty.buffer.bytebufallocator;
import lombok.extern.slf4j.slf4j;
import org.springframework.cloud.gateway.filter.gatewayfilterchain;
import org.springframework.cloud.gateway.filter.globalfilter;
import org.springframework.core.ordered;
import org.springframework.core.io.bytearrayresource;
import org.springframework.core.io.buffer.databuffer;
import org.springframework.core.io.buffer.databufferutils;
import org.springframework.core.io.buffer.nettydatabufferfactory;
import org.springframework.http.httpheaders;
import org.springframework.http.mediatype;
import org.springframework.http.codec.httpmessagereader;
import org.springframework.http.server.reactive.serverhttprequest;
import org.springframework.http.server.reactive.serverhttprequestdecorator;
import org.springframework.util.multivaluemap;
import org.springframework.web.reactive.function.server.handlerstrategies;
import org.springframework.web.reactive.function.server.serverrequest;
import org.springframework.web.server.serverwebexchange;
import reactor.core.publisher.flux;
import reactor.core.publisher.mono;
 
import java.io.unsupportedencodingexception;
import java.net.urlencoder;
import java.nio.charset.charset;
import java.nio.charset.standardcharsets;
import java.util.list;
import java.util.map;
 
@slf4j
public class gatewaycontextfilter implements globalfilter, ordered {
 
  /**
   * default httpmessagereader
   */
  private static final list<httpmessagereader<?>> messagereaders = handlerstrategies.withdefaults().messagereaders();
 
  @override
  public mono<void> filter(serverwebexchange exchange, gatewayfilterchain chain) {
    /**
     * save request path and serviceid into gateway context
     */
    serverhttprequest request = exchange.getrequest();
    string path = request.getpath().pathwithinapplication().value();
    gatewaycontext gatewaycontext = new gatewaycontext();
    gatewaycontext.getallrequestdata().addall(request.getqueryparams());
    gatewaycontext.setpath(path);
    /**
     * save gateway context into exchange
     */
    exchange.getattributes().put(gatewaycontext.cache_gateway_context,gatewaycontext);
    httpheaders headers = request.getheaders();
    mediatype contenttype = headers.getcontenttype();
    long contentlength = headers.getcontentlength();
    if(contentlength>0){
      if(mediatype.application_json.equals(contenttype) || mediatype.application_json_utf8.equals(contenttype)){
        return readbody(exchange, chain,gatewaycontext);
      }
      if(mediatype.application_form_urlencoded.equals(contenttype)){
        return readformdata(exchange, chain,gatewaycontext);
      }
    }
    log.debug("[gatewaycontext]contenttype:{},gateway context is set with {}",contenttype, gatewaycontext);
    return chain.filter(exchange);
 
  }
 
 
  @override
  public int getorder() {
    return integer.min_value;
  }
 
  /**
   * readformdata
   * @param exchange
   * @param chain
   * @return
   */
  private mono<void> readformdata(serverwebexchange exchange,gatewayfilterchain chain,gatewaycontext gatewaycontext){
    httpheaders headers = exchange.getrequest().getheaders();
    return exchange.getformdata()
        .doonnext(multivaluemap -> {
          gatewaycontext.setformdata(multivaluemap);
          log.debug("[gatewaycontext]read formdata:{}",multivaluemap);
        })
        .then(mono.defer(() -> {
          charset charset = headers.getcontenttype().getcharset();
          charset = charset == null? standardcharsets.utf_8:charset;
          string charsetname = charset.name();
          multivaluemap<string, string> formdata = gatewaycontext.getformdata();
          /**
           * formdata is empty just return
           */
          if(null == formdata || formdata.isempty()){
            return chain.filter(exchange);
          }
          stringbuilder formdatabodybuilder = new stringbuilder();
          string entrykey;
          list<string> entryvalue;
          try {
            /**
             * remove system param ,repackage form data
             */
            for (map.entry<string, list<string>> entry : formdata.entryset()) {
              entrykey = entry.getkey();
              entryvalue = entry.getvalue();
              if (entryvalue.size() > 1) {
                for(string value : entryvalue){
                  formdatabodybuilder.append(entrykey).append("=").append(urlencoder.encode(value, charsetname)).append("&");
                }
              } else {
                formdatabodybuilder.append(entrykey).append("=").append(urlencoder.encode(entryvalue.get(0), charsetname)).append("&");
              }
            }
          }catch (unsupportedencodingexception e){
            //ignore urlencode exception
          }
          /**
           * substring with the last char '&'
           */
          string formdatabodystring = "";
          if(formdatabodybuilder.length()>0){
            formdatabodystring = formdatabodybuilder.substring(0, formdatabodybuilder.length() - 1);
          }
          /**
           * get data bytes
           */
          byte[] bodybytes = formdatabodystring.getbytes(charset);
          int contentlength = bodybytes.length;
          serverhttprequestdecorator decorator = new serverhttprequestdecorator(
              exchange.getrequest()) {
            /**
             * change content-length
             * @return
             */
            @override
            public httpheaders getheaders() {
              httpheaders httpheaders = new httpheaders();
              httpheaders.putall(super.getheaders());
              if (contentlength > 0) {
                httpheaders.setcontentlength(contentlength);
              } else {
                httpheaders.set(httpheaders.transfer_encoding, "chunked");
              }
              return httpheaders;
            }
 
            /**
             * read bytes to flux<databuffer>
             * @return
             */
            @override
            public flux<databuffer> getbody() {
              return databufferutils.read(new bytearrayresource(bodybytes),new nettydatabufferfactory(bytebufallocator.default),contentlength);
            }
          };
          serverwebexchange mutateexchange = exchange.mutate().request(decorator).build();
          log.debug("[gatewaycontext]rewrite form data :{}",formdatabodystring);
          return chain.filter(mutateexchange);
        }));
  }
 
  /**
   * readjsonbody
   * @param exchange
   * @param chain
   * @return
   */
  private mono<void> readbody(serverwebexchange exchange,gatewayfilterchain chain,gatewaycontext gatewaycontext){
    /**
     * join the body
     */
    return databufferutils.join(exchange.getrequest().getbody())
        .flatmap(databuffer -> {
          /**
           * read the body flux<databuffer>
           */
          databufferutils.retain(databuffer);
          flux<databuffer> cachedflux = flux.defer(() -> flux.just(databuffer.slice(0, databuffer.readablebytecount())));
          /**
           * repackage serverhttprequest
           */
          serverhttprequest mutatedrequest = new serverhttprequestdecorator(exchange.getrequest()) {
            @override
            public flux<databuffer> getbody() {
              return cachedflux;
            }
          };
          /**
           * mutate exchage with new serverhttprequest
           */
          serverwebexchange mutatedexchange = exchange.mutate().request(mutatedrequest).build();
          /**
           * read body string with default messagereaders
           */
          return serverrequest.create(mutatedexchange, messagereaders)
              .bodytomono(string.class)
              .doonnext(objectvalue -> {
                gatewaycontext.setcachebody(objectvalue);
                log.debug("[gatewaycontext]read jsonbody:{}",objectvalue);
              }).then(chain.filter(mutatedexchange));
        });
  }
 
}

在后续filter中,可以直接从serverexchange中获取gatewaycontext,就可以获取到缓存的数据,如果需要缓存其他数据,则可以根据自己的需求,添加到gatewaycontext中即可

 

复制代码 代码如下:
gatewaycontext gatewaycontext = exchange.getattribute(gatewaycontext.cache_gateway_context);

 

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

原文链接:https://segmentfault.com/a/1190000017898354

延伸 · 阅读

精彩推荐
  • Java教程深入理解Maven的坐标与依赖

    深入理解Maven的坐标与依赖

    这篇文章主要介绍了深入理解Maven的坐标与依赖,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    温柔狠角色5692021-07-01
  • Java教程idea中导入别人的springboot项目的方法(图文)

    idea中导入别人的springboot项目的方法(图文)

    这篇文章主要介绍了idea中导入别人的springboot项目的方法(图文),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要...

    小毛桃学习猿3542020-09-16
  • Java教程java教程之java程序编译运行图解(java程序运行)

    java教程之java程序编译运行图解(java程序运行)

    最近重新复习了一下java基础,在使用javap的过程中遇到了一些问题,这里便讲讲对于一个类文件如何编译、运行、反编译的。也让自己加深一下印象 ...

    java教程网5602019-11-13
  • Java教程Java获取当前时间年月日的方法

    Java获取当前时间年月日的方法

    这篇文章主要为大家详细介绍了Java获取当前时间年月日的方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    牛顿吃香蕉10632021-06-15
  • Java教程java实现的RSA加密算法详解

    java实现的RSA加密算法详解

    这篇文章主要介绍了java实现的RSA加密算法,结合实例形式详细分析了RSA加密解密的原理、java实现方法及相关注意事项,需要的朋友可以参考下...

    听着music睡2202020-11-24
  • Java教程java必学必会之equals方法

    java必学必会之equals方法

    java必学必会之equals方法,equals方法是 java.lang.Object 类的方法,想要了解更多关于equals方法的朋友,可以参考下文 ...

    孤傲苍狼4232020-03-07
  • Java教程Java实现获取指定个数的不同随机数

    Java实现获取指定个数的不同随机数

    今天小编就为大家分享一篇关于Java实现获取指定个数的不同随机数,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随...

    希尔伯特11422021-06-27
  • Java教程Spring Boot应用发布到Docker的实现

    Spring Boot应用发布到Docker的实现

    这篇文章主要介绍了Spring Boot应用发布到Docker的实现,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    一书生VOID的博客10402021-05-07