好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

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测试数据/a/1190000017898354

查看更多关于SpringCloud Finchley Gateway 缓存请求Body和Form表单的实现的详细内容...

  阅读:19次