微服务Spring Cloud (六) 网关 Zuul

转载请表明出处 https://blog.csdn.net/Amor_Leo/article/details/87885822 谢谢

Spring Cloud Zuul概述

当我们在使用微服务的时候,完成一个业务可能需要同时调用多个微服务,则需要分别请求多个服务接口,首先客户端需要请求不同的微服务,客户端的复杂性增大认证复杂,每个微服务都需要自己独立的认证方式,某些情况下会存在一些问题,例如跨域问题,每个服务存在的协议可能不同。
Spring Cloud Zuul是基于Netflix Zuul实现的API网关,他可以和Eureka,Ribbon,Hystrix等组件配合使用。他除了Zuul Core还整合了actuator,hystrix,ribbon,提供动态路由,监控,弹性,安全等的边缘服务。Zuul组件的核心是一系列的过滤器,这些过滤器可以完成以下功能:

  1. 身份认证和安全: 识别每一个资源的验证要求,并拒绝那些不符的请求
  2. 审查与监控:
  3. 动态路由:动态将请求路由到不同后端集群
  4. 压力测试:逐渐增加指向集群的流量,以了解性能
  5. 负载分配:为每一种负载类型分配对应容量,并弃用超出限定值的请求
  6. 静态响应处理:边缘位置进行响应,避免转发到内部集群
  7. 多区域弹性:跨域AWS Region进行请求路由,旨在实现ELB(ElasticLoad Balancing)使用多样化

Zuul使用的默认是Apache的HTTP Client,也可以使用Rest Client或者okhttp3.OkHttpClient。使用Rest Client可以设置ribbon.restclient.enabled=true,使用okhttp3.OkHttpClient可以设置ribbon.okhttp.enabled=true。

网关的优势

  1. 阻止将内部的敏感信息暴露给外部的客户端: API网关通过提供微服务绑定和解绑的能力来将外部的公开API与内部微服务API分离开。这样就具备重构和裁切微服务而不会影响外部绑定的客户端的能力。它同时对客户端隐藏了服务发现和服务版本这些细节,因为它对所有微服务提供单一的访问点。
  2. 为服务增加额外的安全层: API网关通过提供额外的安全层帮助阻止大规模攻击。这些攻击包括SQL注入,XML解析漏洞和DDOS攻击。
  3. 可以支持混合通讯协议: 不同于外部API一般使用HTTP或REST,内部微服务可以从使用不用通讯协议中收获益处。这些协议可以是ProtoBuf或AMQP,甚至是诸如SOAP,JSON-RPC或者XML-RPC这样的系统集成协议。API网关可以对这些协议提供统一的外部REST接口,这就允许开发团队挑选一款最适合于内部架构的协议。
  4. 降低微服务的复杂度: 微服务中有一些常见的要点,诸如使用API令牌进行授权,访问控制和调用频次限制。这每个点都需要每个服务区增加额外的时间去实现它们。API网关将这些要点从你的代码中提取出来,允许你的服务只关注于它们需要关注的任务。
  5. 微服务模拟和虚拟化: 随着微服务API从外部API中分离出来,你可以模拟你的服务去做设计验证或者很方便的做集成测试。

Spring Cloud Zuul搭建

基本

  • pom
  <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  </dependency>
  • Application类上
@EnableZuulProxy  //打开zuul
  • yml
    • 简单 请求按默认的方式处理(即通过http://zuulHost:zuulPort/服务名/请求路径)
    server:
      port: 8040
    spring:
      application:
        name: gateway-zuul-server
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:8761/eureka/
      instance:
        prefer-ip-address: true
    management:
      security:
        enabled: false
      endpoints:
    	web:
      	  exposure:
        	include: "*"  #因为springboot2.1.必须加上,支持访问/actuator/hystrix.stream
    
    • 自定义指定微服务的访问路径
    server:
      port: 8040
    spring:
      application:
        name: gateway-zuul-server
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:8761/eureka/
      instance:
        prefer-ip-address: true
    zuul:
      routes:
        provider-server: /provider/**
    	consumer-server: /consumer/**
    management:
      security:
        enabled: false
      endpoints:
    	web:
      	  exposure:
        	include: "*"  #因为springboot2.1.必须加上,支持访问/actuator/hystrix.stream	  
    
    • 忽略指定微服务,代理其他微服务
    server:
      port: 8040
    spring:
      application:
        name: gateway-zuul-server
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:8761/eureka/
      instance:
        prefer-ip-address: true
    zuul:
      ignored-services: provider-server,consumer-server
    management:
      security:
        enabled: false
      endpoints:
    	web:
      	  exposure:
        	include: "*"  #因为springboot2.1.必须加上,支持访问/actuator/hystrix.stream	  	
    
    • 忽略所有微服务,路由指定微服务
    server:
      port: 8040
    spring:
      application:
        name: gateway-zuul-server
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:8761/eureka/
      instance:
        prefer-ip-address: true
    zuul:
      ignored-services: '*'   # 使用'*'可忽略所有微服务
      routes:
    	provider-server: /provider/**
    	consumer-server: /consumer/**
    management:
      security:
        enabled: false
      endpoints:
    	web:
      	  exposure:
        	include: "*"  #因为springboot2.1.必须加上,支持访问/actuator/hystrix.stream	  	
    
    • 同时指定微服务的serviceId和对应路径
    server:
      port: 8040
    spring:
      application:
        name: gateway-zuul-server
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:8761/eureka/
      instance:
        prefer-ip-address: true
    zuul:
      routes:
    	provider-route:                   # 该配置方式中,provider-route只是给路由一个名称,可以任意起名。
    	  service-id: provider-server
    	  path: /provider/**              # service-id对应的路径
    	consumer-route:                   # 该配置方式中,consumer-route只是给路由一个名称,可以任意起名。
    	  service-id: consumer-server
    	  path: /consumer/**              # service-id对应的路径
    management:
      security:
        enabled: false
      endpoints:
    	web:
      	  exposure:
        	include: "*"  #因为springboot2.1.必须加上,支持访问/actuator/hystrix.stream	  	
    
    • 同时指定path和url
    server:
      port: 8040
    spring:
      application:
        name: gateway-zuul-server
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:8761/eureka/
      instance:
        prefer-ip-address: true
    zuul:
      routes:
    	provider-route:                   # 该配置方式中,provider-route只是给路由一个名称,可以任意起名。
          url: http://localhost:8000/ # 指定的url
          path: /provider/**              # url对应的路径。
    	consumer-route:                   # 该配置方式中,consumer-route只是给路由一个名称,可以任意起名。
          url: http://localhost:8010/ # 指定的url
          path: /consumer/**              # url对应的路径。
    management:
      security:
        enabled: false
      endpoints:
    	web:
      	  exposure:
        	include: "*"  #因为springboot2.1.必须加上,支持访问/actuator/hystrix.stream	  	
    
    • 同时指定path和URL,并且不破坏Zuul的Hystrix、Ribbon特性
    server:
      port: 8040
    spring:
      application:
        name: gateway-zuul-server
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:8761/eureka/
      instance:
        prefer-ip-address: true
    zuul:
      routes:
    	provider-route:                   # 该配置方式中,provider-route只是给路由一个名称,可以任意起名。
          path: /provider/**
          service-id: provider-server
    ribbon:
      eureka:
        enabled: false    # 禁用掉ribbon的eureka使用
    provider-server:
      ribbon:
        listOfServers: localhost:8000,localhost:8001
    management:
      security:
        enabled: false
      endpoints:
    	web:
      	  exposure:
        	include: "*"  #因为springboot2.1.必须加上,支持访问/actuator/hystrix.stream	  	
    
    • 为Zuul添加映射前缀1
      strip-prefix是剥离前缀的意思,设置为false就是不剥离前缀,Zuul默认是剥离前缀的。
    server:
      port: 8040
    spring:
      application:
        name: gateway-zuul-server
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:8761/eureka/
      instance:
        prefer-ip-address: true
    zuul:
      prefix: /api
      strip-prefix: false
      routes:
        provider-server: /provider/**
    logging:
      level:
        com.netflix: DEBUG
    management:
      security:
        enabled: false
      endpoints:
    	web:
      	  exposure:
        	include: "*"  #因为springboot2.1.必须加上,支持访问/actuator/hystrix.stream	  	
    # 访问Zuul的/api/provider-server/1路径,请求将会被转发到provider-server的/api/1,可查看日志打印,有助于理解。
    
    • 为Zuul添加映射前缀2
    server:
      port: 8040
    spring:
      application:
        name: gateway-zuul-server
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:8761/eureka/
      instance:
        prefer-ip-address: true
    zuul:
      routes:
        provider-server: 
          path: /provider/**
          strip-prefix: false
    logging:
      level:
        com.netflix: DEBUG
    management:
      security:
        enabled: false
      endpoints:
    	web:
      	  exposure:
        	include: "*"  #因为springboot2.1.必须加上,支持访问/actuator/hystrix.stream	  	
    # 这样访问Zuul的/provider/1路径,请求将会被转发到provider-server的/provider/1,可查看日志打印,有助于理解。
    
    • 忽略某些路径
    server:
      port: 8040
    spring:
      application:
        name: gateway-zuul-server
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:8761/eureka/
      instance:
        prefer-ip-address: true
    zuul:
      ignoredPatterns: /**/admin/**   # 忽略所有包括/admin/的路径
      routes:
        provider-server: /provider/**
    management:
      security:
        enabled: false
      endpoints:
    	web:
      	  exposure:
        	include: "*"  #因为springboot2.1.必须加上,支持访问/actuator/hystrix.stream	  	
    
    • 本地转发
    server:
      port: 8040
    spring:
      application:
        name: gateway-zuul-server
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:8761/eureka/
      instance:
        prefer-ip-address: true
    zuul:
      routes:
        route-name:
          path: /path-a/**
          url: forward:/path-b
    management:
      security:
        enabled: false
      endpoints:
    	web:
      	  exposure:
        	include: "*"  #因为springboot2.1.必须加上,支持访问/actuator/hystrix.stream	  	
    
    • 使用正则表达式指定zuul的路由匹配规则
    server:
      port: 8040
    spring:
      application:
        name: gateway-zuul-server
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:8761/eureka/
      instance:
        prefer-ip-address: true
    management:
      security:
        enabled: false
      endpoints:
    	web:
      	  exposure:
        	include: "*"  #因为springboot2.1.必须加上,支持访问/actuator/hystrix.stream	  	
    
    @SpringBootApplication
    @EnableZuulProxy
    public class ZuulApplication {
    
      @Bean
      public PatternServiceRouteMapper serviceRouteMapper() {
        // 调用构造函数PatternServiceRouteMapper(String servicePattern, String routePattern)
        // servicePattern指定微服务的正则
        // routePattern指定路由正则
        return new PatternServiceRouteMapper("(?<name>^.+)-(?<version>v.+$)", "${version}/${name}");
      }
    
      public static void main(String[] args) {
        SpringApplication.run(ZuulApplication.class, args);
      }
    }
    
    • Zuul的安全与Heard
      如果不希望敏感Headers向下游泄漏到外部服务器,可以在路由配置中指定忽略的Headers列表
    server:
      port: 8040
    spring:
      application:
        name: gateway-zuul-server
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:8761/eureka/
      instance:
        prefer-ip-address: true
    zuul:
      routes:
    	provider-route:                   # 该配置方式中,provider-route只是给路由一个名称,可以任意起名。
          url: http://localhost:8000/ # 指定的url
          path: /provider/**              # url对应的路径。
          sensitive-headers: Cookie,Set-Cookie,Authorization  # 过滤
    management:
      security:
        enabled: false
      endpoints:
    	web:
      	  exposure:
        	include: "*"  #因为springboot2.1.必须加上,支持访问/actuator/hystrix.stream	  	
    
    sensitive-headers是黑名单,默认不为空,因此要使Zuul发送所有Headers(“忽略”的Headers除外),您必须将其明确设置为空。 如果要将cookie或授权Headers传递给后端,则必须执行此操作。
    server:
      port: 8040
    spring:
      application:
        name: gateway-zuul-server
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:8761/eureka/
      instance:
        prefer-ip-address: true
    zuul:
      routes:
    	provider-route:                   # 该配置方式中,provider-route只是给路由一个名称,可以任意起名。
          url: http://localhost:8000/ # 指定的url
          path: /provider/**              # url对应的路径。
          sensitive-headers: 			 # 不过滤,则须显式设为空。
    management:
      security:
        enabled: false
      endpoints:
    	web:
      	  exposure:
        	include: "*"  #因为springboot2.1.必须加上,支持访问/actuator/hystrix.stream	  	
    
    也可以通过设置zuul.sensitive-headers来全局设置敏感的Headers。 如果在路由上设置了sensitive-headers,则会覆盖全局sensitive-headers设置。
    server:
      port: 8040
    spring:
      application:
        name: gateway-zuul-server
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:8761/eureka/
      instance:
        prefer-ip-address: true
    zuul:
      sensitive-headers: 			 # 全局不过滤
      routes:
    	provider-route:                   # 该配置方式中,provider-route只是给路由一个名称,可以任意起名。
          url: http://localhost:8000/ # 指定的url
          path: /provider/**              # url对应的路径。
    management:
      security:
        enabled: false
      endpoints:
    	web:
      	  exposure:
        	include: "*"  #因为springboot2.1.必须加上,支持访问/actuator/hystrix.stream	  	
    
    忽略某些head
    server:
      port: 8040
    spring:
      application:
        name: gateway-zuul-server
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:8761/eureka/
      instance:
        prefer-ip-address: true
    zuul:
      ignored-headers: header1,header2   # header1,header2不会传播到其他服务
    management:
      security:
        enabled: false
      endpoints:
    	web:
      	  exposure:
        	include: "*"  #因为springboot2.1.必须加上,支持访问/actuator/hystrix.stream	  	
    
    默认情况下zuul.ignored-headers为空值,但如果是Spring Security在项目的classpath中,那么zuul.ignored-headers默认为Pragma,Cache-Control,X-Frame-Options,X-Content-Type-Options,X-XSS-Protection,Expires。如果需要使用下游微服务的Spring Security的Header头部信息,就可以设置zuul.ignoreSecurity-headers=false,保留Spring Security的安全头部信息和代理的头部信息。当然,我们也可以不设置这个值,仅仅获取来自代理的头部信息。
    • 通过Zuul上传文件
      对于小文件(1M以内)上传,即可正常上传,对于大文件(10M以上)上传,需要为上传路径添加/zuul前缀,也可以使用zuul.servlet-path自定义前缀.如果zuul使用了Ribbon做负载均衡,那么对于超大的文件需要提升超时设置。
      文件上传微服务:
      pom
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
      </dependency>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
      </dependency>
    
    yml
    server:
      port: 8050
    eureka:
      client:
        serviceUrl:
          defaultZone: http://localhost:8761/eureka/
      instance:
        prefer-ip-address: true
    spring:
      application:
        name: file-upload-server
      http:
        multipart:
          max-file-size: 2000Mb      # Max file size,单个文件大小,默认1M
          max-request-size: 2500Mb   # Max request size,总上传数据的大小,默认10M
    
    Controller
    @Controller
    public class FileUploadController {
      /**
       * 上传文件
       * 测试方法:
       * 有界面的测试:http://localhost:8050/index.html
       * 使用命令:curl -F "file=@文件全名" localhost:8050/upload
       * ps.该示例比较简单,没有做IO异常、文件大小、文件非空等处理
       * @param file 待上传的文件
       * @return 文件在服务器上的绝对路径
       * @throws IOException IO异常
       */
      @RequestMapping(value = "/upload", method = RequestMethod.POST)
      public @ResponseBody String handleFileUpload(@RequestParam(value = "file", required = true) MultipartFile file) throws IOException {
        byte[] bytes = file.getBytes();
        File fileToSave = new File(file.getOriginalFilename());
        FileCopyUtils.copy(bytes, fileToSave);
        return fileToSave.getAbsolutePath();
      }
    }
    
    Application类上
    @EnableEurekaClient
    
    Zuul-Server:
    pom
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
      </dependency>
      <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
      </dependency>
    
    yml
    server:
      port: 8040
    eureka:
      client:
        serviceUrl:
          defaultZone: http://localhost:8761/eureka/
      instance:
        prefer-ip-address: true
    spring:
      application:
        name: gateway-zuul-server
    	# 上传大文件得将超时时间设置长一些,否则会报超时异常
    hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 60000
    ribbon:
      ConnectTimeout: 3000
      ReadTimeout: 60000
    
    Application类上
    @EnableZuulProxy
    

Zuul的回退

  • pom
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

  • yml
spring:
  application:
    name: gateway-zuul-fallback-server
server:
  port: 8060
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka
  instance:
    prefer-ip-address: true
zuul:
  routes:
    provide-rote:
	  service-id: provider-server
      path: /provide/**
      stripPrefix: false
  • Application类上
@EnableZuulProxy
  • 回退类
@Component
public class MyZuulFallbackProvider implements FallbackProvider {

    @Override
    public String getRoute() {
        //api服务id,如果需要所有调用都支持回退,则return "*"或return null
        return "provide-server";
        // return "*";
    }

    @Override
    public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
        return new ClientHttpResponse(){

            @Override
            public InputStream getBody() throws IOException {
                JSONObject r = new JSONObject();
                r.put("state", "9999");
                r.put("msg", "系统错误,请求失败");
                // 响应体
                return new ByteArrayInputStream(r.toJSONString().getBytes("UTF-8"));
            }

            @Override
            public HttpHeaders getHeaders() {
            	//headers设定
                HttpHeaders headers = new HttpHeaders();
                //和body中的内容编码一致,否则容易乱码
                headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
                return headers;
            }

            /**
             * 网关向api服务请求是失败了,但是消费者客户端向网关发起的请求是OK的,
             * 不应该把api的404,500等问题抛给客户端
             * 网关和api服务集群对于客户端来说是黑盒子
             */
            @Override
            public HttpStatus getStatusCode() throws IOException {
				// fallback时的状态码
                return HttpStatus.OK;
            }

            @Override
            public int getRawStatusCode() throws IOException {
				// 数字类型的状态码,本例返回的其实就是200,详见HttpStatus
                return HttpStatus.OK.value();
            }

            @Override
            public String getStatusText() throws IOException {
			     // 状态文本,本例返回的其实就是OK,详见HttpStatus
                return HttpStatus.OK.getReasonPhrase();
            }

            @Override
            public void close() {

            }
        };
    }
}

Zuul的过滤

  • pom
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

  • yml
spring:
  application:
    name: gateway-zuul-filter-server
server:
  port: 8060
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka
  instance:
    prefer-ip-address: true
zuul:
  routes:
    provide-rote:
	  service-id: provider-server
      path: /provide/**
      stripPrefix: false

单个过滤器

  • Application类上
@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {
  public static void main(String[] args) {
    SpringApplication.run(ZuulApplication.class, args);
  }
  
  @Bean
  public PreZuulFilter preZuulFilter() {
    return new PreZuulFilter();
  }
}
  • Filter类
public class PreZuulFilter extends ZuulFilter {

    private static final Logger LOGGER = LoggerFactory.getLogger(PreZuulFilter.class);

    /**
     * @Description:  过滤器类型 顺序: pre ->routing -> post ,以上3个顺序出现异常时都可以触发error类型的filter
     * pre 代表在路由代理之前执行
     * route 代表代理的时候执行
     * error 代表出现错的时候执行
     * post 代表在route 或者是 error 执行完成后执行
     * @method: filterType
     * @Param:
     * @return: java.lang.String
     */
    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    /**
     * @Description: 执行顺序 同filterType类型中,order值越大,优先级越低,order值越小,优先级越高
     * filter 为链式过滤器,多个filter按顺序执行
     * @method: filterOrder
     * @Param:
     * @return: int
     */
    @Override
    public int filterOrder() {  
        return 0;
    }

    /**
     * @Description: 是否应该执行该过滤器,如果是false,则不执行该filter
     * @method: shouldFilter
     * @Param:
     * @return: boolean
     */
    @Override
    public boolean shouldFilter() {
        return true;
    }

    /**
     * @Description: 过滤的逻辑,执行业务操作
     * @method: run
     * @Param:
     * @return: java.lang.Object
     */
    @Override
    public Object run() throws ZuulException {
        HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
        String host = request.getRemoteHost();
        PreZuulFilter.LOGGER.info("请求的host:{}", host);
        return null;
    }
}

多个过滤器

  • Application类上
@EnableZuulProxy
@SpringCloudApplication
public class GatewayApplication {

    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
    
    //http://192.168.0.1:8040/provide/find/1?prefilter01=true&prefilter02=true&post=true
    @Bean
    public TestPre01Filter testPre01Filter(){
        return new TestPre01Filter();
    }

    @Bean
    public TestPre02Filter testPre02Filter(){
        return new TestPre02Filter();
    }

    @Bean
    public TestPostFilter testPostFilter(){
        return new TestPostFilter();
    }
}
  • Filter类
/**
 * 第一个pre类型的filter,prefilter01=true才能通过
 */
public class TestPre01Filter extends ZuulFilter {

	/**
	 * 是否应该执行该过滤器,如果是false,则不执行该filter
	 */
	@Override
	public boolean shouldFilter() {
		return true;
	}

	/**
	 * 过滤器类型
	 * 顺序: pre ->routing -> post ,以上3个顺序出现异常时都可以触发error类型的filter
	 */
	@Override
	public String filterType() {
		
		return FilterConstants.PRE_TYPE;
	}

	/**
	 * 同filterType类型中,order值越大,优先级越低
	 */
	@Override
	public int filterOrder() {
		
		return 1;
	}
	/**
	 * 执行业务操作,可执行sql,nosql等业务
	 */
	@Override
	public Object run() {
		
		RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
		
        String prefilter01 = request.getParameter("prefilter01");
        System.out.println("执行pre01Filter .....prefilter01=" + prefilter01 	);
        
        //如果用户名和密码都正确,则继续执行下一个filter
        if("true".equals(prefilter01) ){
			//会进行路由,也就是会调用api服务提供者
			ctx.setSendZuulResponse(true);
        	ctx.setResponseStatusCode(200);
			//可以把一些值放到ctx中,便于后面的filter获取使用
			ctx.set("isOK",true);
        }else{
			//不需要进行路由,也就是不会调用api服务提供者
			ctx.setSendZuulResponse(false);
        	ctx.setResponseStatusCode(401);
			//可以把一些值放到ctx中,便于后面的filter获取使用
			ctx.set("isOK",false);
        	//返回内容给客户端,返回错误内容
			ctx.setResponseBody("{\"result\":\"pre01Filter auth not correct!\"}");
        }
		return null;
	}
}
/**
 * prefilter02 校验  prefilter02=true才能通过
 */
public class TestPre02Filter extends ZuulFilter {

	/**
	 * 是否应该执行该过滤器,如果是false,则不执行该filter
	 */
	@Override
	public boolean shouldFilter() {
		//上一个filter设置该值
		return RequestContext.getCurrentContext().getBoolean("isOK");
	}

	/**
	 * 过滤器类型
	 * 顺序: pre ->routing -> post ,以上3个顺序出现异常时都可以触发error类型的filter
	 */
	@Override
	public String filterType() {
		return FilterConstants.PRE_TYPE;
	}

	/**
	 * 同filterType类型中,order值越大,优先级越低
	 */
	@Override
	public int filterOrder() {
		return 2;
	}

	@Override
	public Object run() {
		RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
		
        String prefilter02 = request.getParameter("prefilter02");
        System.out.println("执行pre02Filter .....prefilter02=" + prefilter02 	);
        
        //如果用户名和密码都正确,则继续执行下一个filter
        if("true".equals(prefilter02) ){
        	ctx.setSendZuulResponse(true);//会进行路由,也就是会调用api服务提供者
        	ctx.setResponseStatusCode(200);
        	ctx.set("isOK",true);//可以把一些值放到ctx中,便于后面的filter获取使用
        }else{
        	ctx.setSendZuulResponse(false);//不需要进行路由,也就是不会调用api服务提供者
        	ctx.setResponseStatusCode(401);
        	ctx.set("isOK",false);//可以把一些值放到ctx中,便于后面的filter获取使用
        	//返回内容给客户端
        	ctx.setResponseBody("{\"result\":\"pre02Filter auth not correct!\"}");// 返回错误内容  
        }          
		return null;
	}
}
/**
 * post类型的filter,post=true才能通过
 */
public class TestPostFilter extends ZuulFilter {

	/**
	 * 是否应该执行该过滤器,如果是false,则不执行该filter
	 */
	@Override
	public boolean shouldFilter() {
		//上一个filter设置该值
		return RequestContext.getCurrentContext().getBoolean("isOK");
	}

	/**
	 * 过滤器类型
	 * 顺序: pre ->routing -> post ,以上3个顺序出现异常时都可以触发error类型的filter
	 */
	@Override
	public String filterType() {
		return FilterConstants.ROUTE_TYPE;
	}

	/**
	 * 同filterType类型中,order值越大,优先级越低
	 */
	@Override
	public int filterOrder() {
		return 3;
	}

	@Override
	public Object run() {
		RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        String post = request.getParameter("post");
        System.out.println("执行postFilter .....post=" + post 	);
        //如果用户名和密码都正确,则继续执行下一个filter
        if("true".equals(post) ){
			//会进行路由,也就是会调用api服务提供者
        	ctx.setSendZuulResponse(true);
        	ctx.setResponseStatusCode(200);
			//可以把一些值放到ctx中,便于后面的filter获取使用
			ctx.set("isOK",true);
        }else{
			//不需要进行路由,也就是不会调用api服务提供者
			ctx.setSendZuulResponse(false);
        	ctx.setResponseStatusCode(401);
        	//可以把一些值放到ctx中,便于后面的filter获取使用
        	ctx.set("isOK",false);
        	//返回内容给客户端,返回错误内容
        	ctx.setResponseBody("{\"result\":\"post auth not correct!\"}");
        }
		return null;
	}
}

禁用Zuul Filters

默认会使用很多filters,可采用如下方式禁止

zuul.SendResponseFilter.post.disable=true

Zuul的重试

  • pom
        <!-- 服务发现 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- hystrix dashboard的支持 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
        </dependency>
        <!-- 服务网关 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>
        <!-- 重试机制,必须配,否则重试不生效 -->
        <dependency>
            <groupId>org.springframework.retry</groupId>
            <artifactId>spring-retry</artifactId>
        </dependency>
  • yml
spring:
  application:
    name: gateway-zuul-server
#启动负载均衡的重试机制,默认false
  cloud:
    loadbalancer:
      retry:
        enabled: true
server:
  port: 8040
eureka:
  client:
    healthcheck:
      enabled: true  #开启健康检查
    service-url:
      defaultZone: http://localhost:8761/eureka
  instance:
    prefer-ip-address: true
#Hystrix是否启用超时时间
hystrix:
  command:
    default:
      execution:
        timeout:
          enabled: true
#Hystrix断路器的超时时间,默认是1s,断路器的超时时间需要大于ribbon的超时时间,不然不会触发重试。
        isolation:
          thread:
            timeoutInMilliseconds: 2000
#hystrix dashboard的信息收集频率,默认500毫秒
  stream:
    dashboard:
      intervalInMilliseconds: 5000
#ribbon请求连接的超时时间
ribbon:
  ConnectTimeout: 250
#请求处理的超时时间
  ReadTimeout: 1000
#对所有请求操作都进行重试
  OkToRetryOnAllOperations: true
#对当前服务的重试次数(第一次分配给9082的时候,如果404,则再重试MaxAutoRetries次,如果还是404,则切换到其他服务MaxAutoRetriesNextServer决定)
  MaxAutoRetries: 0
#切换服务的次数(比如本次请求分配给9082处理,发现404,则切换分配给9081处理,如果还是404,则返回404给客户端)
  MaxAutoRetriesNextServer: 1
zuul:
  ignoredServices: '*'  #只有下面配置的服务会路由,其他的不会路由
  routes:
    provide-api:
      path: /provide-api/**
      serviceId: provide-server  #eureka中对应的服务名称
      sensitiveHeaders: 
      retryable: true  #重试,默认false
      stripPrefix: false
    consumer-api:
      path: /consumer-api/**
      serviceId: consumer-server  #eureka中对应的服务名称
      sensitiveHeaders: 
      retryable: true  #重试,默认false
      stripPrefix: false
#ribbo负载均衡策略配置,默认是依次轮询,可配置随机
#api-user-server.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
logging:
  level:
    com.netflix: DEBUG

Zuul的限流

  • pom
        <!-- 服务发现 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- 服务网关 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>
		<dependency>
		    <groupId>com.marcosbarbero.cloud</groupId>
		    <artifactId>spring-cloud-zuul-ratelimit</artifactId>
		    <version>2.1.0.RELEASE</version>
		</dependency>
		<dependency>
		    <groupId>org.springframework.boot</groupId>
		    <artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>
  • yml
# redis 
spring:
	redis:
		port: 6379 
		host: 127.0.0.1 
		timeout: 2000 
zuul:
    ratelimit:
       # key-prefix: your-prefix  #对应用来标识请求的key的前缀
        enabled: true
        repository: REDIS  #对应存储类型(用来存储统计信息)
        behind-proxy: true  #代理之后
        default-policy: #可选 - 针对所有的路由配置的策略,除非特别配置了policies
             limit: 10 #可选 - 每个刷新时间窗口对应的请求数量限制
             quota: 1000 #可选-  每个刷新时间窗口对应的请求时间限制(秒)
              refresh-interval: 60 # 刷新时间窗口的时间,默认值 (秒)
               type: #可选 限流方式
                    - user
                    - origin
                    - url
          policies:
                provide-server: #特定的路由
                      limit: 10 #可选- 每个刷新时间窗口对应的请求数量限制
                      quota: 1000 #可选-  每个刷新时间窗口对应的请求时间限制(秒)
                      refresh-interval: 60 # 刷新时间窗口的时间,默认值 (秒)
                      type: #可选 限流方式
                          - user
                          - origin
                          - url
  1. limit 单位时间内允许访问的次数
  2. quota 单位时间内允许访问的总时间(单位时间窗口期内,所有的请求的总时间不能超过这个时间限制)
  3. refresh-interval 单位时间设置
  4. type 限流类型type 限流类型
  5. url类型的限流就是通过请求路径区分
  6. origin是通过客户端IP地址区分
  7. user是通过登录用户名进行区分,也包括匿名用户
  8. default-policy 可选 - 针对所有的路由配置的策略,除非特别配置了policy-list
  9. policies 对特定的服务id进行限流; 缺点: 可以配置多个url,但是这些url都使用一个限流配置,没有办法指定每个url的限流配置
  10. policy-list 对特定的服务id进行限流; 优点: 可以为某个服务id的每个url 指定不同的限流配置
  • 自定义Key策略

如果希望自己控制key的策略,可以通过自定义RateLimitKeyGenerator的实现来增加自己的策略逻辑。RateLimitKeyGenerator的实现:

    @Bean
    public RateLimitKeyGenerator rateLimitKeyGenerator(RateLimitProperties properties,RateLimitUtils rateLimitUtils) {
        return new DefaultRateLimitKeyGenerator(properties, rateLimitUtils) {
            @Override
            public String key(final HttpServletRequest request,final Route route,final RateLimitProperties.Policy policy) {
                    final List<Type> types = policy.getType();
				    final StringJoiner joiner = new StringJoiner(":");
				    joiner.add(properties.getKeyPrefix());
				    if (route != null) {
				        joiner.add(route.getId());
				    }
				    if (!types.isEmpty()) {
				        if (types.contains(Type.URL) && route != null) {
				            joiner.add(route.getPath());
				        }
				        if (types.contains(Type.ORIGIN)) {
				            joiner.add(getRemoteAddr(request));
				        }
				        // 这个结合文末总结。
				        if (types.contains(Type.USER)) {
				            joiner.add(request.getUserPrincipal() != null ? request.getUserPrincipal().getName() : ANONYMOUS_USER);
				        }
				    }
				    return joiner.toString();
                //return super.key(request, route, policy) + "_" + request.getMethod();
            }
        };
    }
  • 个性化错误处理
     @Bean
     public RateLimiterErrorHandler rateLimitErrorHandler() {
         return new DefaultRateLimiterErrorHandler() {
             @Override
             public void handleSaveError(String key, Exception e) {
                 // implementation
             }
 
             @Override
             public void handleFetchError(String key, Exception e) {
                 // implementation
             }
  
            @Override 
            public void handleError(String msg, Exception e) { 
                // implementation 
            } 
        };
    }

Zuul跨域

  • 在Application类里添加
@Bean
public CorsFilter corsFilter() {
   final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
   final CorsConfiguration config = new CorsConfiguration();
   config.setAllowCredentials(true); // 允许cookies跨域
   config.addAllowedOrigin("*");// #允许向该服务器提交请求的URI,*表示全部允许,在SpringMVC中,如果设成*,会自动转成当前请求头中的Origin
   config.addAllowedHeader("*");// #允许访问的头信息,*表示全部
   config.setMaxAge(18000L);// 预检请求的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了
   config.addAllowedMethod("OPTIONS");// 允许提交请求的方法,*表示全部允许
   config.addAllowedMethod("HEAD");
   config.addAllowedMethod("GET");// 允许Get的请求方法
   config.addAllowedMethod("PUT");
   config.addAllowedMethod("POST");
   config.addAllowedMethod("DELETE");
   config.addAllowedMethod("PATCH");
   source.registerCorsConfiguration("/**", config);
   return new CorsFilter(source);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章