路由是微服务体系结构的组成部分。例如, Netflix 使用 Zuul 进行以下操作:
Zuul 的规则引擎允许规则和过滤器以基本上任何 JVM 语言编写,内置 Java 和 Groovy 支持。
要在项目中包含 Zuul,请使用 group ID 为 Spring Cloud 已经创建了一个嵌入的 Zuul 代理来简化一个公共用例的开发,在这个用例中,UI 应用程序希望对一个或多个后端服务进行代理调用。此功能对于代理所需后端服务的用户界面很有用,避免了独立管理所有后端的 CORS 和身份验证问题的需要。
要启用它,请使用
要跳过自动添加服务,请将 application.yml. zuul: ignoredServices: '*' routes: users: /myusers/**
在前面的示例中,除了 要增加或更改代理路由,可以添加外部配置,如下所示: application.yml. zuul: routes: users: /myusers/**
前面的示例意味着对 要对路由进行更细粒度的控制,可以独立地指定 path 和 serviceId,如下所示: application.yml. zuul: routes: users: path: /myusers/** serviceId: users_service
前面的示例意味着对
后端的位置可以指定为 application.yml. zuul: routes: users: path: /myusers/** url: http://example.com/users_service
这些简单的 url 路由不会作为 application.yml. zuul: routes: echo: path: /myusers/** serviceId: myusers-service stripPrefix: true hystrix: command: myusers-service: execution: isolation: thread: timeoutInMilliseconds: ... myusers-service: ribbon: NIWSServerListClassName: com.netflix.loadbalancer.ConfigurationBasedServerList listOfServers: http://example1.com,http://example2.com ConnectTimeout: 1000 ReadTimeout: 3000 MaxTotalHttpConnections: 500 MaxConnectionsPerHost: 100
另一种方法是指定服务路由并为 application.yml. zuul: routes: users: path: /myusers/** serviceId: users ribbon: eureka: enabled: false users: ribbon: listOfServers: example.com,google.com
你可以使用 ApplicationConfiguration.java. @Bean public PatternServiceRouteMapper serviceRouteMapper() { return new PatternServiceRouteMapper( "(?<name>^.+)-(?<version>v.+$)", "${version}/${name}"); }
前面的示例意味着
要向所有映射添加前缀,请将 application.yml. zuul: routes: users: path: /myusers/** stripPrefix: false
在前面的示例中,对
默认情况下,
如果设置默认路由( 如果需要更细粒度的忽略,可以指定要忽略的特定模式。这些模式在路由定位过程开始时进行评估,这意味着模式中应该包含前缀以保证匹配。忽略的模式跨越所有服务并取代任何其他路由规范。下面的示例演示如何创建被忽略的模式: application.yml. zuul: ignoredPatterns: /**/admin/** routes: users: /myusers/**
前面的示例意味着所有调用(如
application.yml. zuul: routes: users: path: /myusers/** legacy: path: /**
如果要使用属性文件,则
Zuul 使用的默认 HTTP 客户端现在由 Apache HTTP 客户端支持,而不是不推荐使用的 Ribbon 你可以在同一个系统中的服务之间共享头,但可能不希望敏感头向下游泄漏到外部服务器。可以将忽略的头列表指定为路由配置的一部分。Cookies 起着特殊的作用,因为它们在浏览器中有定义良好的语义,并且总是被视为敏感的。如果代理的使用者是浏览器,那么下游服务的 Cookies 也会给用户带来问题,因为它们都混在一起(所有下游服务看起来都来自同一个地方)。
如果你对服务的设计很小心(例如,如果只有一个下游服务设置 cookies),那么你可能能够让它们从后端一直流到调用者。此外,如果代理设置了 cookies,并且所有后端服务都是同一系统的一部分,那么只需共享它们(例如,使用 Spring 会话将它们链接到某个共享状态)是很自然的。除此之外,由下游服务设置的任何 cookies 可能对调用方无效,因此建议你(至少)将 敏感头可以配置为每个路由的逗号分隔列表,如下例所示: application.yml. zuul: routes: users: path: /myusers/** sensitiveHeaders: Cookie,Set-Cookie,Authorization url: https://downstream
application.yml. zuul: routes: users: path: /myusers/** sensitiveHeaders: url: https://downstream
还可以通过设置
除了路由敏感的头,还可以为值(请求和响应)设置一个名为
默认情况下,如果将
一个通过 GET /routes. { /stores/**: "http://localhost:8081" }
其他路由详细信息可以通过请求添加 GET /routes/details. { "/stores/**": { "id": "stores", "fullPath": "/stores/**", "location": "http://localhost:8081", "path": "/**", "prefix": "/stores", "retryable": false, "customSensitiveHeaders": false, "prefixStripped": true } }
到
迁移现有应用程序或 API 时的一个常见模式是 “strangle” 旧的端点,然后用不同的实现慢慢地替换它们。Zuul 代理是一个很有用的工具,因为你可以使用它来处理来自旧端点客户端的所有流量,但是可以将一些请求重定向到新端点。 以下示例展示 “strangle” 方案的配置详细信息: application.yml. zuul: routes: first: path: /first/** url: http://first.example.com second: path: /second/** url: forward:/second third: path: /third/** url: forward:/3rd legacy: path: /** url: http://legacy.example.com
在前面的示例中,我们将窒息 “legacy” 应用程序,它被映射到所有不匹配其他模式之一的请求。
如果使用 application.yml. hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 60000 ribbon: ConnectTimeout: 3000 ReadTimeout: 60000 请注意,为了使流式处理能够处理大型文件,你需要在请求中使用分块编码(某些浏览器默认不使用分块编码),如下例所示: $ curl -v -H "Transfer-Encoding: chunked" \ -F "file=@mylarge.iso" localhost:9999/zuul/simple/file
在处理传入请求时,查询参数被解码,以便在 Zuul 过滤器中进行可能的修改。然后对它们重新编码,在路由过滤器中重建后端请求。如果结果(例如)是用 Javascript 的
为了强制对查询字符串进行原始编码,可以向 application.yml. zuul: forceOriginalQueryStringEncoding: true
在处理传入请求时,请求 URI 在与路由匹配之前被解码。当在路由过滤器中重建后端请求时,请求 URI 将被重新编码。如果你的URI包含编码的 "/" 字符,这可能会导致一些意外的行为。
要使用原始请求 URI,可以向 'ZuulProperties'” 传递一个特殊标志,以便使用 application.yml. zuul: decodeUrl: false
如果你使用 在这种情况下,到 Zuul 服务器的路由仍然通过配置 "zuul.routes.*" 来指定,但不存在服务发现和代理。因此,忽略 "serviceId" 和 "url" 设置。以下示例将 "/api/**" 中的所有路径映射到 Zuul 过滤器链: application.yml. zuul: routes: api: /api/**
Spring Cloud 的 Zuul 附带了一些在代理和服务器模式下默认启用的
当 Zuul 中给定路由的断路断开时,可以通过创建 class MyFallbackProvider implements FallbackProvider { @Override public String getRoute() { return "customers"; } @Override public ClientHttpResponse fallbackResponse(String route, final Throwable cause) { if (cause instanceof HystrixTimeoutException) { return response(HttpStatus.GATEWAY_TIMEOUT); } else { return response(HttpStatus.INTERNAL_SERVER_ERROR); } } private ClientHttpResponse response(final HttpStatus status) { return new ClientHttpResponse() { @Override public HttpStatus getStatusCode() throws IOException { return status; } @Override public int getRawStatusCode() throws IOException { return status.value(); } @Override public String getStatusText() throws IOException { return status.getReasonPhrase(); } @Override public void close() { } @Override public InputStream getBody() throws IOException { return new ByteArrayInputStream("fallback".getBytes()); } @Override public HttpHeaders getHeaders() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); return headers; } }; } } 以下示例展示了上一个示例的路由配置可能出现的方式: zuul: routes: customers: /customers/**
如果要为所有路由提供默认回退,可以创建类型为 class MyFallbackProvider implements FallbackProvider { @Override public String getRoute() { return "*"; } @Override public ClientHttpResponse fallbackResponse(String route, Throwable throwable) { return new ClientHttpResponse() { @Override public HttpStatus getStatusCode() throws IOException { return HttpStatus.OK; } @Override public int getRawStatusCode() throws IOException { return 200; } @Override public String getStatusText() throws IOException { return "OK"; } @Override public void close() { } @Override public InputStream getBody() throws IOException { return new ByteArrayInputStream("fallback".getBytes()); } @Override public HttpHeaders getHeaders() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); return headers; } }; } } 如果要配置通过 Zuul 代理的请求的 socket 超时和读取超时,根据你的配置,有两个选项:
如果通过指定 URL 配置了 Zuul 路由,则需要使用
如果 Zuul 面对的是一个 web 应用程序,那么当 web 应用程序通过 import org.springframework.cloud.netflix.zuul.filters.post.LocationRewriteFilter; ... @Configuration @EnableZuulProxy public class ZuulConfig { @Bean public LocationRewriteFilter locationRewriteFilter() { return new LocationRewriteFilter(); } }
默认情况下,Zuul 将所有跨域请求(CORS)路由到服务。如果你希望 Zuul 来处理这些请求,可以通过提供自定义 @Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurer() { public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/path-1/**") .allowedOrigins("http://allowed-origin.com") .allowedMethods("GET", "POST"); } }; }
在上面的示例中,我们允许来自
Zuul 将在执行器度量端点下为路由请求时可能发生的任何故障提供度量。点击 有关 Zuul 工作方式的概述,请参阅 Zuul Wiki。
Zuul 作为 Servlet 实现。对于一般情况,Zuul 嵌入到 Spring Dispatch 机制中。这样可以让 Spring MVC 控制路由。在这种情况下,Zuul 缓冲请求。如果需要在不缓冲请求的情况下通过 Zuul(例如,对于大型文件上载),Servlet 也安装在 Spring Dispatcher 之外。默认情况下,servlet 的地址为
为了在过滤器之间传递信息,Zuul 使用了一个
Spring Cloud Netflix 安装了许多过滤器,这取决于使用哪个注解来启用 Zuul。
安装了以下过滤器(与普 Spring Bean 一样):
创建一个 除了前面描述的过滤器外,还安装了以下过滤器(作为普 Spring Bean):
下面的大多数“如何编写”示例包含在 Sample Zuul Filters 中。还有一些操作该存储库中请求或响应主体的示例。 本节包括以下示例:
前置过滤器在 public class QueryParamPreFilter extends ZuulFilter { @Override public int filterOrder() { return PRE_DECORATION_FILTER_ORDER - 1; // run before PreDecoration } @Override public String filterType() { return PRE_TYPE; } @Override public 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 } @Override public 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; } }
前面的过滤器从
既然已经填充了
要修改路由过滤器转发到的路径,请设置 路由过滤器在前置过滤器之后运行,并向其他服务发出请求。这里的大部分工作是将请求和响应数据转换为客户端所需的模型,并从中转换出来。以下示例展示 Zuul 路由过滤器: public class OkHttpRoutingFilter extends ZuulFilter { @Autowired private ProxyRequestHelper helper; @Override public String filterType() { return ROUTE_TYPE; } @Override public int filterOrder() { return SIMPLE_HOST_ROUTING_FILTER_ORDER - 1; } @Override public boolean shouldFilter() { return RequestContext.getCurrentContext().getRouteHost() != null && RequestContext.getCurrentContext().sendZuulResponse(); } @Override public 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 running return null; } } 前面的过滤器将 Servlet 请求信息转换为 OkHttp3 请求信息,执行 HTTP 请求,并将 OkHttp3 响应信息转换为 Servlet 响应。
Post 过滤器通常操作响应。以下过滤器将随机 public class AddResponseHeaderFilter extends ZuulFilter { @Override public String filterType() { return POST_TYPE; } @Override public int filterOrder() { return SEND_RESPONSE_FILTER_ORDER - 1; } @Override public boolean shouldFilter() { return true; } @Override public Object run() { RequestContext context = RequestContext.getCurrentContext(); HttpServletResponse servletResponse = context.getResponse(); servletResponse.addHeader("X-Sample", UUID.randomUUID().toString()); return null; } }
如果在 Zuul 过滤器生命周期的任何部分抛出异常,则执行错误过滤器。只有当 |