Spring Cloud Netflix子模块综合整理-Zuul

路由器和过滤器:Zuul

路由是微服务架构的组成部分。 例如,/ 可以映射到您的Web应用程序,/api /users映射到用户服务,/api/ shop映射到购物服务。 Zuul是Netflix基于JVM的路由器和服务器端负载均衡器。

Netflix使用Zuul进行以下操作:

配置属性zuul.max.host.connections已被两个新属性替换,zuul.host.maxTotalConnections和zuul.host.maxPerRouteConnections,分别默认为200和20。

所有路由的默认Hystrix隔离模式(ExecutionIsolationStrategy)是SEMAPHORE(信号量)。zuul.ribbonIsolationStrategy 如果首选此隔离模式,则可以更改为THREAD(线程)。

如何引入Zuul

<!--引入zuul -->
   <dependency>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-zuul</artifactId>
  </dependency>

嵌入式Zuul反向代理

Spring Cloud已经创建了一个嵌入式Zuul代理,以方便开发一个非常常见的用例,其中UI应用程序希望代理对一个或多个后端服务的调用。该特性对于用户界面代理所需的后端服务非常有用,避免了对所有后端独立管理CORS和身份验证问题的需要。

要启用它,可以使用@EnableZuulProxy注释Spring Boot主类上,并将本地调用转发给相应的服务。根据约定,ID为“users”的服务将从位于/users的代理接收请求(去掉前缀)。 代理使用Ribbon来定位转发实例,并且所有请求都在hystrix命令中执行,因此故障将显示在Hystrix指标中,并且一旦电路打开,代理将不会尝试请求服务。

默认Zuul对所有服务都启用代理,要跳过默认代理方式,请将zuul.ignored-services配置不需要代理那些服务。 

application.yml

zuul:
  ignoredServices: '*'
  routes:
    users: /myusers/**

在上面配置中,除了“users”外,所有服务都被忽略。[忽略所有微服务,只路由指定微服务]

要扩充或更改代理路由,您可以添加如下所示的外部配置:

application.yml.
zuul.routes.指定微服务的ServiceId=指定路径 (path和serviceId的组合简洁配置)

zuul:
  routes:
    users: /myusers/**

这意味着对“/myusers”的http调用被转发到“users”服务(例如“/myusers/101”被转发到"users"服务的“/101”路径上)。

要对路径进行更细粒度的控制,可以单独指定路径和serviceId:

application.yml.

zuul:
  routes:
    users:
     path: /myusers/**
     serviceId: users_service

注意:上面users只是给路由起一个名称,可以随便起

这意味着对“/myusers”的http调用将转发到“users_service”服务。 但必须指定调用路径,“/myusers/ *”只匹配一个级别,但“/myusers / **”按层次匹配。

可以不指定serviceId,直接指定具体的路径

application.yml.

zuul:
  routes:
   users:
    path: /myusers/**    
    url: http://example.com/users_service

上面配置 url 为指定的url  ,path为url对应的路径。

注意:这些简单的url-routes不会作为HystrixCommand执行,也不会使用Ribbon对多个URL进行负载均衡。 要做到这一点,你
可以使用静态服务器列表指定serviceId:。

 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
   

另一种方法是指定服务路由并为serviceId配置Ribbon客户端(这需要禁用Eureka对Ribbon的支持,默认情况下Ribbon会根据服务发现机制来获取配置服务对应的实例清单,但是这里并没有整合类似Eureka之类的服务治理框架,如果加入了服务治理框架就不需要禁用ribbon也不需要配置 <rooter>.ribbon.listOfServers)【以下也是最常用的】

zuul:
  routes:
    users:
      path: /myusers/**
      serviceId: users

ribbon:
  eureka:
    enabled: false

users:
  ribbon:
    listOfServers: localhost:8000,localhost:80001  //也可以为具体的域名

users为随便起的路由名

上述配置即指定了path与serviceId,又不会破坏zuul的hystrix与ribbon的特性,即 使用path和url的映射关系来配置路由规则的时候,路由转发请求不会采用HystrixCommand来包装,所有请求没有线程隔离和断路器保护,并且不会采用负载均衡。因此我们在使用zuul的时候尽量使用path和serviceId组合来配置,这样不仅保证API网关的健壮性和稳定性,也能用到Ribbon的负载均衡。

您可以使用regexmapper在serviceId和路由之间提供约定。 它使用名为groups的正则表达式从serviceId中提取变量并将它们注入路由模式。

在SpringBoot启动类中添加如下配置:

@Bean
public PatternServiceRouteMapper serviceRouteMapper() {
   return new PatternServiceRouteMapper(
  "(?<name>^.+)-(?<version>v.+$)",
    "${version}/${name}");
}

这意味着serviceId为“myusers-v1”将映射到路由“/v1/ myusers/ **”。 接受任何正则表达式,但所有命名组必须同时出现在servicePattern【(?<name>^.+)-(?<version>v.+$)】正则表达式为服务名配置模式)和routePattern【${version}/${name}为路由映射模式】中。 如果servicePattern与serviceId不匹配,则使用默认行为(即完整服务名作为前缀的路径表达式)。 在上面的示例中,serviceId“myusers”将映射到路由“/ myusers / **”(未检测到版本)此功能默认情况下处于禁用状态,仅适用于已发现的服务。

要为所有映射添加前缀,请将zuul.prefix设置为值,例如/ api。 在默认转发请求之前,会从请求中删除代理前缀(使用zuul.stripPrefix = false关闭此行为)。 您还可以关闭从各个路由中剥离特定于服务的前缀,例如

 zuul:
  routes:
    users:
      path: /myusers/**
      stripPrefix: false

注意:zuul.stripPrefix仅适用于zuul.prefix中设置的前缀。 它对给定路径的路径中定义的前缀没有任何影响。

在上面配置,对“/ myusers/101”的请求将转发到“/myusers/101”上的“users”服务。

zuul.routes配置的属性信息实际上绑定到ZuulProperties类型的对象。 如果查看该对象的属性,您将看到它还具有“可重试”标志。 将该标志设置为“true”以使Ribbon客户端自动重试失败的请求(如果需要,可以使用Ribbon客户端配置修改重试操作的参数)。

 默认情况下,X-Forwarded-Host标头会添加到转发的请求中。 要关闭它,请设置zuul.addProxyHeaders = false。 默认情况下,前缀路径被剥离,对后端的请求会选择一个标题“X-Forwarded-Prefix”。

如果需要更细粒度的忽略,则可以指定要忽略的特定模式。 这些模式在路径定位过程开始时进行评估,这意味着前缀应包含在模式中以保证匹配。 忽略的模式跨越所有服务并取代任何其他路由规范。

application.yml.

zuul:
  ignoredPatterns: /**/admin/**
  routes:
    users: /myusers/**

这意味着所有诸如“/ myusers /101”之类的请求将被转发到“users”服务上的“/ 101”。 但包括“/ admin /”在内的路径请求将被忽略。

Zuul Http客户端

zuul使用的默认HTTP客户端现在由Apache HTTP Client支持,而不是由不推荐使用的Ribbon RestClient支持。 要使用RestClient或使用okhttp3.OkHttpClient设置ribbon.restclient.enabled = true或ribbon.okhttp.enabled = true。 如果要自定义Apache HTTP客户端或OK HTTP客户端,请提供ClosableHttpClient或OkHttpClient类型的bean。

Cookies和敏感 Headers

在同一系统中的服务之间共享请求头是可以的,但您可能不希望敏感的请求头向下游泄漏到外部服务器。 您可以在路由配置中指定忽略的请求头列表。 Cookie起着特殊的作用,因为它们在浏览器中具有明确定义的语义,并且它们始终被视为敏感。 如果您的代理的消费者是浏览器,那么下游服务的cookie也会给用户带来问题,因为它们都会混乱(所有下游服务看起来都来自同一个地方)。

如果您对服务的设计非常小心,例如,如果只有一个下游服务设置了cookie,那么您可以让它们从后端一直流到调用者。 此外,如果您的代理设置了cookie并且您的所有后端服务都是同一系统的一部分,那么简单地共享它们就很自然(例如使用Spring Session将它们链接到某个共享状态)。 除此之外,由下游服务设置的任何cookie都可能对调用者不是很有用,因此建议您(至少)将“Set Cookie”和“Cookie”设置为非敏感的标头 您域名的一部分。 即使对于属于您域的路由,在允许cookie在它们与代理之间流动之前,请仔细考虑它的含义。

可以通过路由指定一些列敏感的请求头:

zuul:
  routes:
    users:
      path: /myusers/**
      sensitiveHeaders: Cookie,Set-Cookie,Authorization
      url: https://downstream

上述sensitiveHeaders配置是sensitiveHeaders的默认值,因此除非您希望它不同,否则无需进行设置。注: 这是Spring Cloud Netflix 1.1中的新功能(在1.0中,用户无法控制标题,所有Cookie都在两个方向上流动)。

sensitiveHeaders是黑名单(即就是配置的将不会向下传递),默认不为空,因此要使Zuul发送所有标题(“忽略”标题除外),您必须将其明确设置为空列表。 如果要将cookie或授权标头传递给后端,则必须执行此操作。

zuul:
  routes:
    users:
      path: /myusers/**
      sensitiveHeaders:
      url: https://downstream

也可以通过设置zuul.sensitiveHeaders来全局设置敏感标头(不建议这样做)。 如果在路由上设置了sensitiveHeaders,则会覆盖全局sensitiveHeaders设置。

忽略Headers

可以使用zuul.ignoredHeaders属性丢弃一些Header.

zuul.ignoredHeaders:Header1,Header2 这样将不会传播到其他微服务中。

默认情况下zuul.ignoredHeaders是控制,但是spring-security在项目的calsspath那么zuul.ignoredHeaders的默认值就是Pragma,Cache-Control,X-Frame-Options,X-Content-Type-Options,X-XSS-Protection,Expires.所以,当Spring security在项目的classpath中,同时又需要使用下游微服务的spring security的请求头,可以你将zuul.ignoreSecurityHeaders设置为false。 这可能很有用如果您在Spring Security中禁用了HTTP安全响应标头,并希望获得下游服务提供的值。

管理端点

如果您将@EnableZuulProxy与Spring Boot Actuator一起使用,您将启用(默认情况下)两个额外的端点:

  • Routes
  • Filters

routes端点

到/routes的路由端点的GET将返回映射路由的列表:

GET /routes.

{
/stores/**: "http://localhost:8081"
}

可以通过将?format = details查询字符串添加到/ routes来请求其他路由详细信息。 这将产生以下输出:

GET /routes?format=details

{
  "/stores/**": {
    "id": "stores",
    "fullPath": "/stores/**",
    "location": "http://localhost:8081",
    "path": "/**",
    "prefix": "/stores",
    "retryable": false,
    "customSensitiveHeaders": false,
    "prefixStripped": true
  }
}

POST将强制刷新现有路由。 您可以禁用此端点通过将endpoints.routes.enabled设置为false。

Filters 端点

对于过滤器端点/fliters的GET将按类型返回Zuul过滤器的映射。 对于地图中的每种过滤器类型,您将找到该类型的所有过滤器及其详细信息的列表。

使用Zuul进行文件上传

如果您使用@EnableZuulProxy,您可以使用代理路径上传文件,只要文件很小,它就可以正常工作。 对于大型文件,有一个替代路径绕过“/zuul/*”中的Spring DispatcherServlet(以避免多部分处理)。即如果zuul.routes.customers = /customers/ **那么您可以将大文件POST到“/zuul/customers/ *”,也就是对于大文件的上传需要在原路径上加zuul。 servlet路径通过zuul.servletPath外部化。 如果代理路由引导您完成Ribbon负载均衡器,则极大文件也需要提升超时设置,如进行如下设置:

hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 60000
ribbon:
  ConnectTimeout: 3000
  ReadTimeout: 60000

查询字符串编码

处理传入请求时,将对查询参数进行解码,以便它们可用于Zuul过滤器中的可能修改。 然后在路由过滤器中构建后端请求时重新编码它们。 如果使用Javascript的encodeURIComponent() 方法编码,结果可能与原始输入不同。 虽然这在大多数情况下不会引起任何问题,但某些Web服务器可能会因复杂查询字符串的编码而变得挑剔。

要强制查询字符串的原始编码,可以将特殊标志传递给ZuulProperties,以便使用HttpServletRequest :: getQueryString方法获取查询字符串:

 zuul:
  forceOriginalQueryStringEncoding: true

普通嵌入式Zuul

如果使用@EnableZuulServer(而不是@EnableZuulProxy),您也可以在没有代理的情况下运行Zuul服务器,或者选择性地切换代理平台的某些部分。 您添加到ZuulFilter类型的应用程序的任何bean都将自动安装,与@EnableZuulProxy一样,但不会自动添加任何代理过滤器。

在这种情况下,仍然通过配置“zuul.routes.*”来指定进入Zuul服务器的路由,但是没有服务发现且没有代理,因此忽略“serviceId”和“url”设置。 例如:

application.yml.

 zuul:
  routes:
    api: /api/**

将“/api/ **”中的所有路径映射到Zuul过滤器链。

禁用Zuul过滤器

Spring Cloud默认zuul编写并启用了一些列过滤器,在代理和服务器模式下都默认启用了这些ZuulFilter bean。 请参阅zuul filters包以获取已启用的可能过滤器。 如果要禁用一个,只需设置zuul.<SimpleClassName>.<filterType> .disable = true。 按照惯例,过滤器后的包是Zuul过滤器类型。 例如,要禁用org.springframework.cloud.netflix.zuul.filters.post.SendResponseFilter,请设置zuul.SendResponseFilter.post.disable = true。

为路由提供Hystrix降级

当Zuul中给定路径的电路短路时,您可以通过创建ZuulFallbackProvider类型的bean来提供回退响应。 在此bean中,您需要指定回退所针对的路由ID,并提供ClientHttpResponse作为回退返回。 以下是非常简单的ZuulFallbackProvider实现。

class MyFallbackProvider implements ZuulFallbackProvider {
    @Override
    public String getRoute() {
        //表明是为那个微服务提供的回退
        return "customers";
    }

    @Override
    public ClientHttpResponse fallbackResponse() {
        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;
            }
        };
    }
}

 

以下为路由配置:

以下是path和serviceId的简介配置zuul.routes.<serviceId>=<path>

zuul:
  routes:
    customers: /customers/**

如果您希望为所有路由提供默认回退,则可以创建ZuulFallbackProvider类型的bean并使getRoute方法返回*或null。

class MyFallbackProvider implements ZuulFallbackProvider {
    @Override
    public String getRoute() {
        return "*";
    }

    @Override
    public ClientHttpResponse fallbackResponse() {
        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;
            }
        };
    }
}

  

如果您想根据失败原因选择响应,请使用FallbackProvider,它将取代未来版本中的ZuulFallbackProvder。

class MyFallbackProvider implements FallbackProvider {

    @Override
    public String getRoute() {
        return "*";
    }

    @Override
    public ClientHttpResponse fallbackResponse(final Throwable cause) {
        if (cause instanceof HystrixTimeoutException) {
            return response(HttpStatus.GATEWAY_TIMEOUT);
        } else {
            return fallbackResponse();
        }
    }

    @Override
    public ClientHttpResponse fallbackResponse() {
        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超时

服务发现配置

如果Zuul正在使用服务发现(即将Zuul注册到注册中心),则需要关注两个超时,Hystrix超时(因为默认情况下所有路由都包含在Hystrix命令中)和Ribbon超时。 Hystrix超时需要考虑ribbon读取和连接超时将为该服务发生的重试总次数。 默认情况下,Spring Cloud Zuul会尽力为您计算Hystrix超时,除非您明确指定Hystrix超时。

Hystrix超时使用以下公式计算:

(ribbon.ConnectTimeout + ribbon.ReadTimeout) * (ribbon.MaxAutoRetries + 1) * (ribbon.MaxAutoRetriesNextServer + 1)

例如,如果在应用程序属性中设置以下属性:

application.yml

ribbon:
  ReadTimeout:100
  ConnectTimeout:500
  MaxAutoRetries:1
  MaxAutoRetriesNextServer:1

对于以上配置Hystrix超时(对于这种情况下的所有路由)将设置为2400ms。

您可以使用service.ribbon.*属性为各个路由配置Hystrix超时。

如果您选择不配置上述属性,则将使用默认值,因此默认的Hystrix超时将设置为4000毫秒。

如果设置hystrix.command.commandKey.execution.isolation.thread.timeoutInMilliseconds(该参数用来设置API网关中路由转发请求的HystrixCommand执行超时单位毫秒,当路由转发请求的命令执行时间超过该设置的时间Hystrix会将执该命令标记为超时并抛出异常,Zuul会对该异常进行处理并返回Json格式的超时信息),其中commandKey是路由ID,或者设置hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds比这些值将用于全局配置。 如果您设置了这些属性中的任何一个,则您有责任确保这些属性它考虑了功能区连接和读取超时以及可能发生的任何重试。

URL配置

如果您通过指定URL配置Zuul路由,则需要使用zuul.host.connect-timeout-millis和zuul.host.socket-timeout-millis进行超时配置。

Zuul开发人员指南

zuul Servlet

Zuul是作为Servlet实现的。 对于一般情况,Zuul嵌入到Spring Dispatch机制中。 这允许Spring MVC控制路由。 在这种情况下,Zuul配置为缓冲请求。 如果需要在没有缓冲请求的情况下通过Zuul(例如,对于大型文件上载),则Servlet也安装在Spring Dispatcher之外。 默认情况下,它位于/zuul。 可以使用zuul.servlet-path属性更改此路径。

Zuul RequestContext

为了在过滤器之间传递信息,Zuul使用RequestContext。 它的数据保存在特定于每个请求的ThreadLocal中。 有关在何处路由请求,错误以及实际的HttpServletRequest和HttpServletResponse的信息都存储在那里。 RequestContext扩展了ConcurrentHashMap,因此任何东西都可以存储在上下文中.

@EnableZuulProxy 和@EnableZuulServer

  Spring Cloud Netflix安装了许多过滤器,根据这些过滤器使用注解来启用Zuul。 @EnableZuulProxy是@EnableZuulServer的超集。 换句话说,@ EnableZuulProxy包含@EnableZuulServer安装的所有过滤器。 “代理”中的其他过滤器启用路由功能。 如果你想要一个“空白”Zuul,你应该使用@EnableZuulServer。

@EnableZuulProxy 过滤器

过滤概述
Zuul的中心是一系列过滤器,能够在HTTP请求和响应的路由过程中执行一系列操作。

以下是Zuul过滤器的主要特征:

类型:通常在应用过滤器时在路由流程中定义阶段(尽管它可以是任何自定义字符串)
执行顺序:在类型中应用,定义跨多个过滤器的执行顺序
标准:执行过滤器所需的条件
操作:满足条件时要执行的操作
Zuul提供了一个动态读取,编译和运行这些过滤器的框架。过滤器不直接相互通信 - 而是通过RequestContext共享状态,RequestContext对每个请求都是唯一的。

过滤器目前用Groovy编写,尽管Zuul支持任何基于JVM的语言。每个Filter的源代码都写入Zuul服务器上的一组指定目录,这些目录会定期轮询更改。更新的过滤器从磁盘读取,动态编译到正在运行的服务器中,并由Zuul为每个后续请求调用。

                                                     (生命周期)

如何编写PRE过滤器

Pre过滤器用于在RequestContext中设置数据,以便在下游过滤器中使用。 主要用例是设置路由过滤器所需的信息。

import javax.servlet.http.HttpServletRequest;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;

import static com.netflix.zuul.context.RequestContext.getCurrentContext;

/**
 * @author Spencer Gibb
 */
public class QueryParamPortPreFilter extends ZuulFilter {

    public int filterOrder() {
        // run after PreDecorationFilter
        return 5 + 1;
    }

    public String filterType() {
        return "pre";
    }

    @Override
    public boolean shouldFilter() {
        RequestContext ctx = getCurrentContext();
        return ctx.getRequest().getParameter("port") != null;
    }

    public Object run() {
        RequestContext ctx = getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        // put the serviceId in `RequestContext`
        String port = request.getParameter("port");
        try {
            URL url = UriComponentsBuilder.fromUri(ctx.getRouteHost().toURI())
                    .port(new Integer(port))
                    .build().toUri().toURL();
            //代理路径增加一个端口
            ctx.setRouteHost(url);
        } catch (Exception e) {
            ReflectionUtils.rethrowRuntimeException(e);
        }
        return null;
    }
}

 

如何编写路由过滤器

路由过滤器在pre滤器之后运行,用于向其他服务发出请求。 这里的大部分工作是将请求和响应数据转换为客户端所需的模型。

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)过滤器

后置过滤器通常会操纵响应。 在下面的过滤器中,我们添加一个随机UUID作为X-Foo标头。 其他操作(例如转换响应主体)要复杂得多且计算密集。

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;

import javax.servlet.http.HttpServletResponse;
import java.util.UUID;

/**
 * @author Spencer Gibb
 */
public class AddResponseHeaderFilter extends ZuulFilter {
    public String filterType() {
        return "post";
    }

    public int filterOrder() {
        return 999;
    }

    public boolean shouldFilter() {
        return true;
    }

    public Object run() {
        RequestContext context = RequestContext.getCurrentContext();
        HttpServletResponse servletResponse = context.getResponse();
        servletResponse.addHeader("X-Foo",
                UUID.randomUUID().toString());
        return null;
    }
}

 

 Zuul错误如何工作

如果在Zuul过滤器生命周期的任何部分期间抛出异常,则执行错误过滤器。 仅当RequestContext.getThrowable()不为null时,才会运行SendErrorFilter。 然后,它在请求中设置特定的javax.servlet.error.*属性,并将请求转发到Spring Boot错误页面。

 微信公众号

 

 

posted @ 2020-01-14 09:07  盲目的拾荒者  阅读(488)  评论(0编辑  收藏  举报