微服務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);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章