一、Zuul Servlet
Zuul被实现为Servlet。对于一般情况,Zuul已嵌入到Spring Dispatch机制中。这使Spring MVC可以控制路由。在这种情况下,Zuul缓冲请求。如果需要在不缓冲请求的情况下进行Zuul操作(例如,对于大文件上传),则Servlet也会安装在Spring Dispatcher的外部。默认情况下,该servlet的地址为/zuul。可以使用zuul.servlet-path属性更改此路径。
(1)Zuul RequestContext
要在过滤器之间传递信息,Zuul使用RequestContext。它的数据保存在ThreadLocal每个请求的特定内容中。有关在何处路由请求,错误以及实际HttpServletRequest和HttpServletResponse的信息存储在此处。在RequestContext扩展ConcurrentHashMap,所以什么都可以存储在上下文。FilterConstants包含由Spring Cloud Netflix安装的过滤器使用的密钥(稍后会详细介绍)。
(2)@EnableZuulProxy与@EnableZuulServer
Spring Cloud Netflix安装了许多过滤器,具体取决于用于启用Zuul的注释。@EnableZuulProxy是的超集@EnableZuulServer。换句话说,@EnableZuulProxy包含所安装的所有过滤器@EnableZuulServer。“代理”中的其他过滤器启用路由功能。如果您想要“空白” Zuul,则应使用@EnableZuulServer。
(3)@EnableZuulServer筛选器
@EnableZuulServer创建一个SimpleRouteLocator可从Spring Boot配置文件加载路由定义的。
安装了以下过滤器(作为普通的Spring Bean):
-
前置过滤器:
-
ServletDetectionFilter:检测请求是否通过Spring Dispatcher。设置键为的布尔值FilterConstants.IS_DISPATCHER_SERVLET_REQUEST_KEY。 -
FormBodyWrapperFilter:解析表单数据并为下游请求重新编码。 -
DebugFilter:如果debug设置了请求参数,则将RequestContext.setDebugRouting()和设置RequestContext.setDebugRequest()为true。
-
-
路由过滤器:
-
SendForwardFilter:通过使用Servlet转发请求RequestDispatcher。转发位置存储在RequestContext属性中FilterConstants.FORWARD_TO_KEY。这对于转发到当前应用程序中的端点很有用。
-
-
后置过滤器:
-
SendResponseFilter:将代理请求的响应写入当前响应。
-
-
错误过滤器:
-
SendErrorFilter:如果RequestContext.getThrowable()不为null,则转发到/error(默认情况下)。您可以通过设置error.path属性来更改默认转发路径(/error)。
-
(4)@EnableZuulProxy筛选器
创建一个DiscoveryClientRouteLocator可从DiscoveryClient(例如Eureka)以及属性加载路线定义的。路线为每个创建serviceId从所述DiscoveryClient。添加新服务后,将刷新路由。
除了前面描述的过滤器之外,还安装了以下过滤器(作为普通的Spring Bean):
-
前置过滤器:
-
PreDecorationFilter:根据提供的确定位置和路线RouteLocator。它还为下游请求设置了各种与代理相关的标头。
-
-
路由过滤器:
-
RibbonRoutingFilter:使用Ribbon,Hystrix和可插拔HTTP客户端发送请求。服务ID在RequestContext属性中找到FilterConstants.SERVICE_ID_KEY。此过滤器可以使用不同的HTTP客户端:-
Apache
HttpClient:默认客户端。 -
Squareup
OkHttpClientv3:通过将com.squareup.okhttp3:okhttp库放在类路径上并设置来启用ribbon.okhttp.enabled=true。 -
Netflix Ribbon HTTP客户端:通过设置启用
ribbon.restclient.enabled=true。该客户端具有局限性,包括不支持PATCH方法,但还具有内置的重试功能。
-
-
SimpleHostRoutingFilter:通过Apache HttpClient将请求发送到预定的URL。网址位于中RequestContext.getRouteHost()。
-
二、自定义Zuul过滤器示例
下面的大多数“如何编写”示例都包含在示例Zuul过滤器项目中。在该存储库中也有一些处理请求或响应正文的示例。
(1)如何编写预过滤器
前置过滤器会在中设置数据,以RequestContext供下游过滤器使用。主要用例是设置路由过滤器所需的信息。以下示例显示了Zuul预过滤器:
public class QueryParamPreFilter extends ZuulFilter {@Overridepublic int filterOrder() {return PRE_DECORATION_FILTER_ORDER - 1; // run before PreDecoration}@Overridepublic String filterType() {return PRE_TYPE;}@Overridepublic boolean shouldFilter() {RequestContext ctx = RequestContext.getCurrentContext();return !ctx.containsKey(FORWARD_TO_KEY) // a filter has already forwarded&& !ctx.containsKey(SERVICE_ID_KEY); // a filter has already determined serviceId}@Overridepublic Object run() {RequestContext ctx = RequestContext.getCurrentContext();HttpServletRequest request = ctx.getRequest();if (request.getParameter("sample") != null) {// put the serviceId in `RequestContext`ctx.put(SERVICE_ID_KEY, request.getParameter("foo"));}return null;}
} 前面的过滤器SERVICE_ID_KEY从samplerequest参数填充。实际上,您不应该执行这种直接映射。相反,应该从sample相反的值中查找服务ID 。
现在SERVICE_ID_KEY已填充,PreDecorationFilter无法运行RibbonRoutingFilter。
注:如果要路由到完整URL,请致电ctx.setRouteHost(url)。
要修改路由过滤器转发到的路径,请设置REQUEST_URI_KEY。
(2)如何编写路由过滤器
路由过滤器在预过滤器之后运行,并向其他服务发出请求。此处的许多工作是在请求和响应数据与客户端所需的模型之间进行转换。以下示例显示了Zuul路由过滤器:
public class OkHttpRoutingFilter extends ZuulFilter {@Autowiredprivate ProxyRequestHelper helper;@Overridepublic String filterType() {return ROUTE_TYPE;}@Overridepublic int filterOrder() {return SIMPLE_HOST_ROUTING_FILTER_ORDER - 1;}@Overridepublic boolean shouldFilter() {return RequestContext.getCurrentContext().getRouteHost() != null&& RequestContext.getCurrentContext().sendZuulResponse();}@Overridepublic Object run() {OkHttpClient httpClient = new OkHttpClient.Builder()// customize.build();RequestContext context = RequestContext.getCurrentContext();HttpServletRequest request = context.getRequest();String method = request.getMethod();String uri = this.helper.buildZuulRequestURI(request);Headers.Builder headers = new Headers.Builder();Enumeration<String> headerNames = request.getHeaderNames();while (headerNames.hasMoreElements()) {String name = headerNames.nextElement();Enumeration<String> values = request.getHeaders(name);while (values.hasMoreElements()) {String value = values.nextElement();headers.add(name, value);}}InputStream inputStream = request.getInputStream();RequestBody requestBody = null;if (inputStream != null && HttpMethod.permitsRequestBody(method)) {MediaType mediaType = null;if (headers.get("Content-Type") != null) {mediaType = MediaType.parse(headers.get("Content-Type"));}requestBody = RequestBody.create(mediaType, StreamUtils.copyToByteArray(inputStream));}Request.Builder builder = new Request.Builder().headers(headers.build()).url(uri).method(method, requestBody);Response response = httpClient.newCall(builder.build()).execute();LinkedMultiValueMap<String, String> responseHeaders = new LinkedMultiValueMap<>();for (Map.Entry<String, List<String>> entry : response.headers().toMultimap().entrySet()) {responseHeaders.put(entry.getKey(), entry.getValue());}this.helper.setResponse(response.code(), response.body().byteStream(),responseHeaders);context.setRouteHost(null); // prevent SimpleHostRoutingFilter from runningreturn null;}
} 前面的过滤器将Servlet请求信息转换为OkHttp3请求信息,执行HTTP请求,并将OkHttp3响应信息转换为Servlet响应。
(3)如何编写后置过滤器
后置过滤器通常操纵响应。以下过滤器将随机数添加UUID为X-Sample标题:
public class AddResponseHeaderFilter extends ZuulFilter {@Overridepublic String filterType() {return POST_TYPE;}@Overridepublic int filterOrder() {return SEND_RESPONSE_FILTER_ORDER - 1;}@Overridepublic boolean shouldFilter() {return true;}@Overridepublic Object run() {RequestContext context = RequestContext.getCurrentContext();HttpServletResponse servletResponse = context.getResponse();servletResponse.addHeader("X-Sample", UUID.randomUUID().toString());return null;}
} 三、Zuul错误如何工作
如果在Zuul过滤器生命周期的任何部分抛出异常,则将执行错误过滤器。该SendErrorFilter如果只运行RequestContext.getThrowable()不null。然后,它javax.servlet.error.*在请求中设置特定的属性,并将请求转发到Spring Boot错误页面。
四、Zuul饥饿加载应用程序上下文
Zuul在内部使用Ribbon来调用远程URL。默认情况下,ribbon客户端在第一次调用时由Spring Cloud延迟加载。可以使用以下配置为Zuul更改此行为,这将导致在应用程序启动时即时加载与子Ribbon相关的应用程序上下文。以下示例显示了如何启用饥饿加载:
zuul:ribbon:eager-load:enabled: true