springcloudzuul那些你不知道的功能点(代码片段)

author author     2022-12-26     403

关键词:

本文摘自于 《Spring Cloud微服务 入门 实战与进阶》 一书。

1. /routes 端点

当@EnableZuulProxy与Spring Boot Actuator配合使用时,Zuul会暴露一个路由管理端点/routes。

借助这个端点,可以方便、直观地查看以及管理Zuul的路由。

将所有端点都暴露出来,增加下面的配置:

management.endpoints.web.exposure.include=*

访问 http://localhost:2103/actuator/routes 可以显示所有路由信息:



"/cxytiandi/**"
:

"http://cxytiandi.com"
,

"/hystrix-api/**"
:

"hystrix-feign-demo"
,

"/api/**"
:

"forward:/local"
,

"/hystrix-feign-demo/**"
:

"hystrix-feign-demo"

2. /filters 端点

/fliters端点会返回Zuul中所有过滤器的信息。可以清楚的了解Zuul中目前有哪些过滤器,哪些被禁用了等详细信息。

访问 http://localhost:2103/actuator/filters 可以显示所有过滤器信息:



"error"
:

[



"class"
:

"com.cxytiandi.zuul_demo.filter.ErrorFilter"
,

"order"
:

100
,

"disabled"
:

false
,

"static"
:

true



],

"post"
:

[



"class"
:

"org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter"
,

"order"
:

1000
,

"disabled"
:

false
,

"static"
:

true



],

"pre"
:

[



"class"
:

"com.cxytiandi.zuul_demo.filter.IpFilter"
,

"order"
:

1
,

"disabled"
:

false
,

"static"
:

true



],

"route"
:

[



"class"
:

"org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter"
,

"order"
:

10
,

"disabled"
:

false
,

"static"
:

true



]

3. 文件上传

创建一个新的Maven项目zuul-file-demo,编写一个文件上传的接口,如代码清单7-20所示。

代码清单 7-20 文件上传接口

@RestController
public

class

FileController



@PostMapping
(
"/file/upload"
)

public

String
 fileUpload
(
@RequestParam
(
value 
=

"file"
)

MultipartFile
 file
)

throws

IOException



byte
[]
 bytes 
=
 file
.
getBytes
();

File
 fileToSave 
=

new

File
(
file
.
getOriginalFilename
());

FileCopyUtils
.
copy
(
bytes
,
 fileToSave
);

return
 fileToSave
.
getAbsolutePath
();



将服务注册到Eureka中,服务名称为zuul-file-demo,通过PostMan来上传文件,如图7-4所示

技术图片

可以看到接口正常返回了文件上传之后的路径,接下来我们换一个大一点的文件,文件大小为1.7MB。

技术图片

可以看到报错了(如图7-5所示),通过Zuul上传文件,如果超过1M需要配置上传文件的大小, Zuul和上传的服务都要加上配置:

spring.servlet.multipart.max-file-size=1000Mb
spring.servlet.multipart.max-request-size=1000Mb

配置加完后重新上传就可以成功了,如图7-6所示。
技术图片

第二种解决办法是在网关的请求地址前面加上/zuul,就可以绕过Spring DispatcherServlet进行上传大文件。

# 正常的地址
http
:
//localhost:2103/zuul-file-demo/file/upload
# 绕过的地址
http
:
//localhost:2103/zuul/zuul-file-demo/file/upload

通过加上/zuul前缀可以让Zuul服务不用配置文件上传大小,但是接收文件的服务还是需要配置文件上传大小,否则文件还是会上传失败。

在上传大文件的时候,时间比较会比较长,这个时候需要设置合理的超时时间来避免超时。

ribbon.ConnectTimeout=3000
ribbon.ReadTimeout=60000

在Hystrix隔离模式为线程下zuul.ribbon-isolation-strategy=thread,需要设置Hystrix超时时间。

hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=60000

4. 请求响应信息输出

系统在生产环境出现问题时,排查问题最好的方式就是查看日志了,日志的记录尽量详细,这样你才能快速定位问题。

下面带大家学习如何在Zuul中输出请求响应的信息来辅助我们解决一些问题。

熟悉Zuul的朋友都知道,Zuul中有4种类型过滤器,每种都有特定的使用场景,要想记录响应数据,那么必须是在请求路由到了具体的服务之后,返回了才有数据,这种需求就适合用post过滤器来实现了。如代码清单7-21所示。

代码清单 7-21 Zull获取请求信息

HttpServletRequest
 req 
=

(
HttpServletRequest
)
RequestContext
.
getCurrentContext
().
getRequest
();
System
.
err
.
println
(
"REQUEST:: "

+
 req
.
getScheme
()

+

" "

+
 req
.
getRemoteAddr
()

+

":"

+
 req
.
getRemotePort
());
StringBuilder

params

=

new

StringBuilder
(
"?"
);
// 获取URL参数
Enumeration
<
String
>
 names 
=
 req
.
getParameterNames
();
if
(
 req
.
getMethod
().
equals
(
"GET"
)

)



while

(
names
.
hasMoreElements
())



String
 name 
=

(
String
)
 names
.
nextElement
();

params
.
append
(
name
);

params
.
append
(
"="
);

params
.
append
(
req
.
getParameter
(
name
));

params
.
append
(
"&"
);




if

(
params
.
length
()

>

0
)



params
.
delete
(
params
.
length
()-
1
,

params
.
length
());


System
.
err
.
println
(
"REQUEST:: > "

+
 req
.
getMethod
()

+

" "

+
 req
.
getRequestURI
()

+

params

+

" "

+
 req
.
getProtocol
());
Enumeration
<
String
>
 headers 
=
 req
.
getHeaderNames
();
while

(
headers
.
hasMoreElements
())



String
 name 
=

(
String
)
 headers
.
nextElement
();

String
 value 
=
 req
.
getHeader
(
name
);

System
.
err
.
println
(
"REQUEST:: > "

+
 name 
+

":"

+
 value
);


final

RequestContext
 ctx 
=

RequestContext
.
getCurrentContext
();
// 获取请求体参数
if

(!
ctx
.
isChunkedRequestBody
())



ServletInputStream
 inp 
=

null
;

try


         inp 
=
 ctx
.
getRequest
().
getInputStream
();

String
 body 
=

null
;

if

(
inp 
!=

null
)


            body 
=

IOUtils
.
toString
(
inp
);

System
.
err
.
println
(
"REQUEST:: > "

+
 body
);



catch

(
IOException
 e
)


                e
.
printStackTrace
();




输出效果如下:

技术图片

获取响应内容第一种方式,如代码清单7-22所示。

代码清单 7-22 获取响应内容(一)

try



Object
 zuulResponse 
=

RequestContext
.
getCurrentContext
().
get
(
"zuulResponse"
);

if

(
zuulResponse 
!=

null
)



RibbonHttpResponse
 resp 
=

(
RibbonHttpResponse
)
 zuulResponse
;

String
 body 
=

IOUtils
.
toString
(
resp
.
getBody
());

System
.
err
.
println
(
"RESPONSE:: > "

+
 body
);
          resp
.
close
();

RequestContext
.
getCurrentContext
().
setResponseBody
(
body
);




catch

(
IOException
 e
)


     e
.
printStackTrace
();

获取响应内容第二种方式,如代码清单7-23所示。

代码清单 7-23 获取响应内容(二)

InputStream
 stream 
=

RequestContext
.
getCurrentContext
().
getResponseDataStream
();
try



if

(
stream 
!=

null
)



String
 body 
=

IOUtils
.
toString
(
stream
);

System
.
err
.
println
(
"RESPONSE:: > "

+
 body
);

RequestContext
.
getCurrentContext
().
setResponseBody
(
body
);





catch

(
IOException
 e
)


      e
.
printStackTrace
();

为什么上面两种方式可以取到响应内容?

在RibbonRoutingFilter或者SimpleHostRoutingFilter中可以看到下面一段代码,如代码清单7-24所示。

代码清单 7-24 响应内容获取源码

public

Object
 run
()



RequestContext
 context 
=

RequestContext
.
getCurrentContext
();

this
.
helper
.
addIgnoredHeaders
();

try



RibbonCommandContext
 commandContext 
=
 buildCommandContext
(
context
);

ClientHttpResponse
 response 
=
 forward
(
commandContext
);
        setResponse
(
response
);

return
 response
;



catch

(
ZuulException
 ex
)



throw

new

ZuulRuntimeException
(
ex
);



catch

(
Exception
 ex
)



throw

new

ZuulRuntimeException
(
ex
);


forward()方法对服务调用,拿到响应结果,通过setResponse()方法进行响应的设置,如代码清单7-25所示。

代码清单 7-25 setResponse(一)

protected

void
 setResponse
(
ClientHttpResponse
 resp
)

throws

ClientException
,

IOException



RequestContext
.
getCurrentContext
().
set
(
"zuulResponse"
,
 resp
);

this
.
helper
.
setResponse
(
resp
.
getStatusCode
().
value
(),
    resp
.
getBody
()

==

null

?

null

:
 resp
.
getBody
(),
 resp
.
getHeaders
());

上面第一行代码就可以解释我们的第一种获取的方法,这边直接把响应内容加到了RequestContext中。

第二种方式的解释就在helper.setResponse的逻辑里面了,如代码清单7-26所示。

代码清单 7-26 setResponse(二)

public

void
 setResponse
(
int
 status
,

InputStream
 entity
,

MultiValueMap
<
String
,

String
>
 headers
)

throws

IOException



RequestContext
 context 
=

RequestContext
.
getCurrentContext
();
    context
.
setResponseStatusCode
(
status
);

if

(
entity 
!=

null
)


        context
.
setResponseDataStream
(
entity
);



// .....
  1. Zuul自带的Debug功能

Zuul中自带了一个DebugFilter,一开始我也没明白这个DebugFilter有什么用,看名称很容易理解,用来调试的,可是你看它源码几乎没什么逻辑,就set了两个值而已,如代码清单7-27所示。

代码清单 7-27 DebugFilter run方法

@Override
public

Object
 run
()



RequestContext
 ctx 
=

RequestContext
.
getCurrentContext
();
    ctx
.
setDebugRouting
(
true
);
    ctx
.
setDebugRequest
(
true
);

return

null
;

要想让这个过滤器执行就得研究下它的shouldFilter()方法,如代码清单7-28所示。

代码清单 7-28 DebugFilter shouldFilter 方法

@Override
public

boolean
 shouldFilter
()



HttpServletRequest
 request 
=

RequestContext
.
getCurrentContext
().
getRequest
();

if

(
"true"
.
equals
(
request
.
getParameter
(
DEBUG_PARAMETER
.
get
())))



return

true
;



return
 ROUTING_DEBUG
.
get
();

只要满足两个条件中的任何一个就可以开启这个过滤器,第一个条件是请求参数中带了某个参数=true就可以开启,这个参数名是通过下面的代码获取的,如代码清单7-29所示。

代码清单 7-29 DebugFilter启用参数(一)

private

static

final

DynamicStringProperty
 DEBUG_PARAMETER 
=

DynamicPropertyFactory

.
getInstance
().
getStringProperty
(
ZuulConstants
.
ZUUL_DEBUG_PARAMETER
,

"debug"
);

DynamicStringProperty是Netflix的配置管理框架Archaius提供的API,可以从配置中心获取配置,由于Netflix没有开源Archaius的服务端,所以这边用的就是默认值debug,如果大家想动态去获取这个值的话可以用携程开源的Apollo来对接Archaius,这个在后面章节给大家讲解。

可以在请求地址后面追加debug=true来开启这个过滤器,参数名称debug也可以在配置文件中进行覆盖,用zuul.debug.parameter指定,否则就是从Archaius中获取,没有对接Archaius那就是默认值debug。

第二个条件代码,如代码清单7-30所示。

代码清单 7-30 DebugFilter启用参数(二)

private

static

final

DynamicBooleanProperty
 ROUTING_DEBUG 
=

DynamicPropertyFactory

.
getInstance
().
getBooleanProperty
(
ZuulConstants
.
ZUUL_DEBUG_REQUEST
,

false
);

是通过配置zuul.debug.request来决定的,可以在配置文件中配置zuul.debug.request=true开启DebugFilter过滤器。

DebugFilter过滤器开启后,并没什么效果,在run方法中只是设置了DebugRouting和DebugRequest两个值为true,于是继续看源码,发现在很多地方有这么一段代码,比如com.netflix.zuul.FilterProcessor.runFilters(String)中,如代码清单7-31所示。

代码清单 7-31 Debug信息添加

if

(
RequestContext
.
getCurrentContext
().
debugRouting
())



Debug
.
addRoutingDebug
(
"Invoking "

+
 sType 
+

" type filters"
);

当debugRouting为true的时候就会添加一些Debug信息到RequestContext中。现在明白了DebugFilter中为什么要设置DebugRouting和DebugRequest两个值为true。

到这步后发现还是很迷茫,一般我们调试信息的话肯定是用日志输出来的,日志级别就是Debug,但这个Debug信息只是累加起来存储到RequestContext中,没有对使用者展示。

继续看代码吧,功夫不负有心人,在org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter.addResponseHeaders()这段代码中看到了希望。如代码清单7-32所示。

代码清单 7-32 Debug信息设置响应

private

void
 addResponseHeaders
()



RequestContext
 context 
=

RequestContext
.
getCurrentContext
();

HttpServletResponse
 servletResponse 
=
 context
.
getResponse
();

if

(
this
.
zuulProperties
.
isIncludeDebugHeader
())



@SuppressWarnings
(
"unchecked"
)

List
<
String
>
 rd 
=

(
List
<
String
>)
 context
.
get
(
ROUTING_DEBUG_KEY
);

if

(
rd 
!=

null
)



StringBuilder
 debugHeader 
=

new

StringBuilder
();

for

(
String
 it 
:
 rd
)


               debugHeader
.
append
(
"[[["

+
 it 
+

"]]]"
);


            servletResponse
.
addHeader
(
X_ZUUL_DEBUG_HEADER
,
 debugHeader
.
toString
());




核心代码在于this.zuulProperties.isIncludeDebugHeader(),只有满足这个条件才会把RequestContext中的调试信息作为响应头输出,在配置文件中增加下面的配置即可:

zuul
.
include
-
debug
-
header
=
true

最后在请求的响应头中可以看到调试内容,如图7-7所示。
技术图片
本文摘自于 《Spring Cloud微服务 入门 实战与进阶》 一书。

技术图片

尹吉欢

opencv4图像分割那些你不知道的api代码可直接移植使用

1.距离变换函数:cv2.distanceTransform()函数简介:当图像内的各个子图没有连接时,可以直接使用形态学的腐蚀操作确定前景对象,但是如果图像内的子图连接在一起时,就很难确定前景对象了。此时,借助于距离变换函数cv2.distanc... 查看详情

关于单元测试你不知道的那些事

查看详情

关于单元测试你不知道的那些事

查看详情

关于单元测试你不知道的那些事

查看详情

那些你不知道的炫酷开关交互效果(12种)(代码片段)

本文将继续更新那些炫酷交互效果系列文章,今天带来的是有关toggle开关相关的组件。以下是本次文章涉及到的开关组件总览图,总计收集12款不同交互效果,相信总有一款适合你。那些你不知道的炫酷交互效果系列:那些你不... 查看详情

vue.use内部那些你不知道的事儿(代码片段)

1.Vue.use的作用Vue.use的作用是注册全局插件强化Vue的功能 它也可以用来注册全局组件但是有一个条件注册的对象中必须提供 install 方法。如果插件是一个函数,它会被作为install方法。install方法调用时,会将Vue作为参数... 查看详情

你不知道的软件测试那些事?

 你不知道的软件测试那些事?  一、写在前言  作为开发人员,我们都知道我们应该测试我们的代码。我们应该写单元测试,但这也通常是我们发现没时间时跳过的第一步。  作为团队的领导者或者管理者我们都知道测... 查看详情

深度解析人生重开模拟器?那些你不知道的事。

1.前言要说最近最火的小游戏,莫过于《人生重开模拟器》,可谓是铺天盖地,上至网页版,下至APP,甚至在小程序里也有无数仿品的存在。其原因是源码早已公开,因此很容易复制生产。简单介绍一下这... 查看详情

字符串,那些你不知道的事(代码片段)

 Everythingyouthoughtyouknewaboutstringsiswrong.也许你会诧异,字符串有什么难的,即便遇到乱码的情况随便Google下就能找到解决方法,但是这样你不觉得有种被动的感觉嘛,我觉得和学习任何东西一样,学习编程首要是学习其思想,... 查看详情

那些你不知道的kotlin冷知识(代码片段)

Lambda表达式Lambda固然好用,但是你知道Kotlin是如何实现的吗?kotlin代码funfoo(item:Int)=print(item)转换为java字节码@NotNullpublicfinalFunction0foo(finalintitem)return(Function0)(newFunction0()//$FF:syntheticm 查看详情

[javascript实践篇]——那些你不知道的“奇淫巧技”

1.空(null,undefined)验证刚开始,我是比较蠢的验证(我还真是这样子验证的)if(variable1!==null||variable1!==undefined||variable1!==‘‘){letvariable2=variable1;}大哥教会了我这样子验证,你会惊叹一下的letvariable2=variable1||‘‘;如果你不信,在谷... 查看详情

物联网通信技术,那些你不知道的事

...本文分享自华为云社区《物联网通信技术之有线通信技术那些你不知道的事》,原文作者 查看详情

那些你不知道的ps大片摄影

 你以为这张照片是P出来的?才不是!人家是开个起重机吊着拍的!而且没有任何保护措施!   这样的照片战斗民族的摄影师才敢这样拍…要是在天朝这么做估计都找不到模特… 这张照片出自乌兹别克斯坦的... 查看详情

即时通讯:socket那些你不知道的事-心跳

参考技术A首先说下心跳包的主要作用是告知对方连接端,我还活着,心还在跳。所以,如上所述,如果NAT超时,连接被运营商移除,连接就会中断,而实际上,根据网上的一些说法,中移动2/3G下,NAT超时时间为5分钟,中国电信3G则大... 查看详情

阿里,京东,淘宝,美团,那些你不知道的事儿

B2B即businesstobusiness,2是to的谐音,即企业与企业之间,商家与商家之间,通过互联网进行产品、服务及信息的交换。交易的供需双方都是商家(或企业、公司)。如:阿里巴巴就是这种模式的一家公司。 B2C即businesstocustomer,... 查看详情

你不知道的线程池构造方法的那些趣事?(代码片段)

(手机横屏看源码更方便)注:java源码分析部分如无特殊说明均基于java8版本。简介ThreadPoolExecutor的构造方法是创建线程池的入口,虽然比较简单,但是信息量很大,由此也能引发一系列的问题,同样地,这也是面试中经常被问... 查看详情

那些你不知道的html知识,快来学习一下吧

前言HTML作为前端三大基础知识点之一,是每一个前端开发人员都要掌握的部分。今天这篇文章我们来看看一些平时不太会注意,却在面试时可能会问到的题目,来看看你都会吗?   如何使用div模拟实现textarea?我们都... 查看详情

你不知道的vscode代码高亮原理(代码片段)

全文5000字,解读vscode背后的代码高亮实现原理,欢迎点赞关注转发。Vscode的代码高亮、代码补齐、错误诊断、跳转定义等语言功能由两种扩展方案协同实现,包括:基于词法分析技术,识别分词token并应用高亮样式基于可编程语... 查看详情