本文摘自于 《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);
} // .....
}

5. 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微服务 入门 实战与进阶》 一书。

Spring Cloud Zuul 那些你不知道的功能点的更多相关文章

  1. 笔记:Spring Cloud Zuul 快速入门

    Spring Cloud Zuul 实现了路由规则与实例的维护问题,通过 Spring Cloud Eureka 进行整合,将自身注册为 Eureka 服务治理下的应用,同时从 Eureka 中获取了 ...

  2. Spring Cloud Zuul 快速入门

    Spring Cloud Zuul 实现了路由规则与实例的维护问题,通过 Spring Cloud Eureka 进行整合,将自身注册为 Eureka 服务治理下的应用,同时从 Eureka 中获取了 ...

  3. Spring Cloud Zuul API服务网关之请求路由

    目录 一.Zuul 介绍 二.构建Spring Cloud Zuul网关 构建网关 请求路由 请求过滤 三.路由详解 一.Zuul 介绍 ​ 通过前几篇文章的介绍,我们了解了Spring Cloud ...

  4. Spring Cloud Zuul 概览

    什么是API网关 网关这个词其实是一个硬件概念.因为按照定义,网络网关出现在网络的边缘,所以防火墙和代理服务器等相关功能 往往与之集成在一起.在家庭网络 和小型企业中,宽带路由器通常充当网络网关.它将 ...

  5. Spring Cloud Zuul网关 Filter、熔断、重试、高可用的使用方式。

    时间过的很快,写springcloud(十):服务网关zuul初级篇还在半年前,现在已经是2018年了,我们继续探讨Zuul更高级的使用方式. 上篇文章主要介绍了Zuul网关使用模式,以及自动转发机制 ...

  6. Spring Cloud Zuul 限流详解(附源码)(转)

    在高并发的应用中,限流往往是一个绕不开的话题.本文详细探讨在Spring Cloud中如何实现限流. 在 Zuul 上实现限流是个不错的选择,只需要编写一个过滤器就可以了,关键在于如何实现限流的算法. ...

  7. Spring Cloud Zuul 网关使用与 OAuth2.0 认证授权服务

    API 网关的出现的原因是微服务架构的出现,不同的微服务一般会有不同的服务地址,而外部客户端可能需要调用多个服务的接口才能完成一个业务需求,如果让客户端直接与各个微服务通信,会有以下的问题: 客户端会 ...

  8. 第七章 API网关服务:Spring Cloud Zuul

    API网关是一个更为智能的应用服务器, 它的定义类似于面向对象设计模式中的Facade模式, 它的存在就像是整个微服务架构系统的门面一样,所有的外部客户端访问都需要经过它来进行调度和过滤.它除了要实现 ...

  9. SpringCloud---API网关服务---Spring Cloud Zuul

    1.概述 1.1 微服务架构出现的问题   及  解决: 1.1.1 前言 每个微服务应用都提供对外的Restful API服务,它通过F5.Nginx等网络设备或工具软件实现对各个微服务的路由与负载 ...

随机推荐

  1. FS-Cache 调研

    最近需要使用到 FSCache,今天调研一下FS-Cache,主要记录一些索引,方便以后查阅: RedHat 文档:https://access.redhat.com/documentation/en ...

  2. TypeScript初体验

    第一次运行TypeScript 1.创建文件夹并初始化项目 mkdir ts-demo cd ts-demo npm init -y 2.安装typescript与ts-node # 局部安装 npm ...

  3. 关于java中三种初始化块的执行顺序

    许多小伙伴对于java中的三种初始化块的执行顺序一直感到头疼,接下来我们就来分析一下这三种初始化块到底是怎么运行的.有些公司也会将这个问题作为笔试题目. 下面通过一段代码来看看创建对象时这么初始化块是 ...

  4. mpvue快速入门

    主要特性 使用 mpvue 开发小程序,你将在小程序技术体系的基础上获取到这样一些能力: 彻底的组件化开发能力:提高代码复用性 完整的 Vue.js 开发体验 方便的 Vuex 数据管理方案:方便构建 ...

  5. Web安全测试学习笔记-DVWA-SQL注入-1

    SQL注入的定义网上很多,作为一个初学者,我对SQL注入的理解是这样的:网站应用一般都有后台数据库(不论是关系型还是非关系型),用户在网站上的绝大部分操作,最终都会跟数据库交互(也就是执行一串SQL语 ...

  6. 从web到游戏,走出舒适区

    最近很久没有更新博客了,实在太忙.因为在这段时间里我做了一个改变了我现在职业生涯的一个决定,而我现在正在为这个决定而加倍的努力付出. 我认为我还是有必要把这个比较重要的节点记录下来,我也是第一次在自己 ...

  7. spring = servlet + 依赖管理 + 业务逻辑

    spring = servlet + 依赖管理 + 业务逻辑

  8. 【nodejs原理&源码赏析(5)】net模块与通讯的实现

    目录 一. net模块简介 二. Client-Server的通讯 2.1 server的建立 2.2 Socket的建立 三. IPC通讯 四. 撸一个简易的cluster通讯模型 示例代码托管在: ...

  9. Python中使用cutecharts实现简单的手绘风格的图表

    场景 效果 cutecharts的Github: https://github.com/chenjiandongx/cutecharts 注: 博客: https://blog.csdn.net/ba ...

  10. 利用Azure虚拟机安装Dynamics 365 Customer Engagement之五:安装SQL Server

    我是微软Dynamics 365 & Power Platform方面的工程师罗勇,也是2015年7月到2018年6月连续三年Dynamics CRM/Business Solutions方面 ...