SpingCloud-Gateway

一、簡介

Spring Cloud GateWay 是Spring官方基於Spring5.0 SpringBoot2.0 和 Project Reactor等技術開發的網關,Spring Cloud GateWay zhi在爲旨在爲微服務架構提供一種簡單有效的、統一的API路由管理方式。它不僅提供統一的路由方式,並且基於Filter鏈的方式提供了網關的基本功能,例如:安全、監控/埋點和限流等。

在Spring Cloud GateWay 有幾個概念需要了解一下:

Route:是網關的基礎元素,有ID 目標URI 、斷言、過濾器組成。當請求到達網關時由GateWay Handler Mapping 通過斷言進行路由匹配(Mapping) 當斷言爲真時匹配到路由。

Predicate: Predicate 時 java8 提供的一個函數。輸入類型是Spring Framework ServerWebExchange,它允許開發人員匹配來自HTTP的請求。

Filter: Filter 是GateWay 中的過濾器,可以在請求發出前後進行一些業務的處理。

二、簡單使用

創建一個springBoot的maven項目,添加下面的依賴

 <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
            <version> 2.2.2.RELEASE</version>
        </dependency>
<dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR4</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

在配置文件進行如下配置即可進行路由轉發

server:
  port: 2001
spring:
  application:
    name: spring-cloud-gateway
  cloud:
    gateway:
      routes:
        - id: path_route
          uri: https://www.baidu.com
          predicates:
            - Path=/user/**

這樣只要訪問 http://localhost:2001/user 就會跳轉到 https://www.baidu.com

注意:-path中的user/** 的** 表示任意層級

三、整合Eureka

1. 整合 Eureka 路由

引入依賴

       <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            <version>2.2.2.RELEASE</version>
        </dependency>

配置文件的配置如下

spring:
  application:
    name: spring-cloud-gateway
  cloud:
    gateway:
      routes:
        - id: eureka-client-user-service
          uri: lb://eureka-client-user-service
          predicates:
            - Path=/user/**
eureka:
  instance:
    prefer-ip-address: true
    hostname: ${spring.cloud.client.ip-address}
    instance-id: ${spring.application.name}:${spring.cloud.client.ip-address}:${server.port}
  client:
    serviceUrl:
      defaultZone: http://tttt:123456@localhost:8761/eureka/

注意:uri 以lb://開頭(lb 代表從註冊中心獲取服務),後面接的就是需要轉發到的服務名稱,這個服務名稱必須跟Eureka中的對應,否則會找不到服務。

這時訪問 http://localhost:2001/user/hello 就會轉發到 eureka-client-user-service 的 /user/hello 的接口中。

2. 整合Eureka的默認路由

Spring Cloud GateWay 可以爲所有的服務進行轉發操作,我們只需要在訪問路徑上指定要訪問的服務即可,通過這種方式就不用爲每個服務都去配置轉發規則,當新加了服務就不用去配置路由規則和重啓網關,配置如下:

spring:
  application:
    name: spring-cloud-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
#          可以通過小寫的服務名訪問的配置
          lower-case-service-id: true

注意:如果在配置文件中不設置  lower-case-service-id: true 只能通過大寫的服務名訪問,設置了之後只能通過小寫的服務名訪問。

四、路由斷言工廠的使用

1. path 路由斷言工廠

spring:
  application:
    name: spring-cloud-gateway
  cloud:
    gateway:
      routes:
        - id: eureka-client-user-service
          uri: lb://eureka-client-user-service
          predicates:
            - Path=/user/**

2. Qyery 路由斷言工廠

spring:
  application:
    name: spring-cloud-gateway
  cloud:
    gateway:
      routes:
        - id: query_route
          uri: https://www.baidu.com
          predicates:
            - Query=foo,ba.

如果請求包含一個值與ba匹配的foo查詢參數,則次路由將會匹配,因此第二個參數是正則表達式。

測試連接:http://localhost:2001/?foo=bar

3. Method 路由斷言工廠

spring:
  application:
    name: spring-cloud-gateway 
  cloud:
    gateway:
      routes:
        - id: method_route
          uri: https://www.baidu.com
          predicates:
            - Method=GET

只要是get請求都會被匹配。

其他更多的斷言工廠方法請參考官方文檔進行學習。

4. 自定義斷言工廠

import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;

import java.util.function.Predicate;

@Component
public class CheckAuthRoutePredicateFactory extends AbstractRoutePredicateFactory<CheckAuthRoutePredicateFactory.Config> {
    public CheckAuthRoutePredicateFactory() {
        super(Config.class);
    }

    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        return exchange->{
          System.err.println("進入了 CheckAuthRoutePredicateFactory "+config.getName());
          if (config.getName().equals("1234")){
              return true;
          }
          return false;
        };
    }

    public static class Config{
        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
}
  cloud:
    gateway:
      routes:
        - id: customer_route
          uri: https://www.baidu.com
          predicates:
            - name: CheckAuth
              args:
                name: 1234

五、過濾器使用

1. AddRequestHeader 過濾器的使用

  cloud:
    gateway:
      routes:
        - id: add_request_header_route
          uri: https://www.baidu.com
          filters:
            - AddRequestHeader=X-Request-Foo, Bar

匹配成功後將在headers裏添加 X-Request-Foo:Bar,並將其傳遞到後端服務。

2. RemoveRequestHeader 過濾器的使用

  cloud:
    gateway:
      routes:
        - id: add_request_header_route
          uri: https://www.baidu.com
          predicates:
            - name: CheckAuth
              args:
                name: 456
          filters:
            - RemoveRequestHeader=X-Request-Foo, Bar

3. SetSatus 過濾器工廠

  cloud:
    gateway:
      routes:
        - id: add_request_header_route
          uri: https://www.baidu.com
          predicates:
            - name: CheckAuth
              args:
                name: 456
          filters:
            - SetSatus=401

更多的過濾器工廠請參考官方文檔。

4. 自定義過濾器工廠

package com.example.gateway.springcloudgateway.common;

import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;

@Component
public class CheckAuth2GatewayFilterFactory extends AbstractGatewayFilterFactory<CheckAuth2GatewayFilterFactory.Config> {

    public CheckAuth2GatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
       return (exchange, chain) -> {
           System.err.println("進入了 CheckAuth2GatewayFilterFactory"+config.getName());
           ServerHttpRequest request = exchange.getRequest().mutate().build();
           return chain.filter(exchange.mutate().request(request).build());
       };
    }

    public static class Config{
        private String name;


        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
}

使用

          filters:
             - name: CheckAuth2
               args:
                 name: 123

5. 全局過濾器使用

import com.alibaba.fastjson.JSONObject;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;

@Component
public class IPCheckFilter implements GlobalFilter, Ordered {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        HttpHeaders headers = exchange.getRequest().getHeaders();
        if(getIp(headers).equals("127.0.0.2")){
            ServerHttpResponse response = exchange.getResponse();
            String data = "非法請求";
            JSONObject jsonObject = new JSONObject();
            jsonObject.put("data",data);
            byte[] datas = new byte[0];
            try {
                datas = data.getBytes(String.valueOf(StandardCharsets.UTF_8));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            DataBuffer buffer = response.bufferFactory().wrap(datas);
            response.setStatusCode(HttpStatus.UNAUTHORIZED);
            response.getHeaders().add("Content-Type","application/json;charset=UFT-8");
            Mono<Void> voidMono = response.writeWith(Mono.just(buffer));
            return voidMono;
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 0;
    }

    public String getIp(HttpHeaders headers){
        return "127.0.0.1";
    }
}

六、熔斷回退

引入依賴

       <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>

配置文件進行如下配置

spring:
  application:
    name: spring-cloud-gateway
  cloud:
    gateway:
      routes:
        - id: eureka-client-user-service
          uri: lb://eureka-client-user-service
          predicates:
            - Path=/user/**
          filters:
             - name: Hystrix
              args:
                name: fallbackcmd
                fallbackUri: forward:/fallback

上面配置了一個Hystrix的過濾器,該過濾器會使用 Hystrix 熔斷與回退,原理是將請求包裝成RouteHystrixCommand執行。

fallbackuri:是發生熔斷時回退的uri 地址,目前只支持forward 模式的uri。如果服務被降級後該請求會被轉發到該uri

    @GetMapping("/fallback")
    public String fallback(){

        return "fallback";
    }

七、跨域問題

package com.example.gateway.springcloudgateway.common;

import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.cors.reactive.CorsUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

@Component
public class CoresConfig {

    @Bean
    public WebFilter corsFilter() {
        return (ServerWebExchange ctx, WebFilterChain chain) -> {
            ServerHttpRequest request = ctx.getRequest();
            if (CorsUtils.isCorsRequest(request)) {
                HttpHeaders requestHeaders = request.getHeaders();
                ServerHttpResponse response = ctx.getResponse();
                HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod();
                HttpHeaders headers = response.getHeaders();
                headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin());
                headers.addAll(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders.getAccessControlRequestHeaders());
                if (null != requestMethod) {
                    headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name());
                }
                headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
                headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*");
                if (request.getMethod() == HttpMethod.OPTIONS) {
                    response.setStatusCode(HttpStatus.OK);
                    return Mono.empty();
                }
            }
            return chain.filter(ctx);
        };
    }
}

八、統一異常處理

自定義異常處理邏輯代碼如下:

package com.example.gateway.springcloudgateway.common;

import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler;
import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.cloud.gateway.support.NotFoundException;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpStatus;
import org.springframework.web.reactive.function.server.*;

import java.util.HashMap;
import java.util.Map;

public class JsonExceptionHandler extends DefaultErrorWebExceptionHandler {

    public JsonExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties, ErrorProperties errorProperties, ApplicationContext applicationContext) {
        super(errorAttributes, resourceProperties, errorProperties, applicationContext);
    }

    /**
     * 獲取異常屬性
     * @param request
     * @param options
     * @return
     */
    @Override
    protected Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
         int code = 500;
        Throwable error = super.getError(request);
        if(error instanceof NotFoundException){
            code = 404;
        }
        return response(code,this.buildMessage(request,error));
    }

    /**
     * 制定相應處理方法爲json數據格式
     * @param errorAttributes
     * @return
     */
    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
        return RouterFunctions.route(RequestPredicates.all(),this::renderErrorResponse);
    }

    /**
     * 根據code獲取對應的httpStatus
     * @param errorAttributes
     * @return
     */
    @Override
    protected int getHttpStatus(Map<String, Object> errorAttributes) {
        int statusCode = (int) errorAttributes.get("code");
        HttpStatus httpStatus = HttpStatus.valueOf(statusCode);
        return httpStatus.value();
    }

    /**
     * 構建異常信息
     * @param request
     * @param ex
     * @return
     */
    private String buildMessage(ServerRequest request, Throwable ex) {
        StringBuffer message = new StringBuffer("fail to handler request [");
        message.append(request.methodName());
        message.append(" ");
        message.append(request.uri());
        message.append("]");
        if(ex != null){
            message.append(": ");
            message.append(ex.getMessage());
        }
        return message.toString();
    }

    /**
     * 構建返回的json 數據格式
     * @param status
     * @param errorMessage
     * @return
     */
    public static Map<String,Object> response(int status,String errorMessage){
        Map<String,Object> map = new HashMap<>();
        map.put("code",status);
        map.put("message",errorMessage);
        map.put("data",null);
        return map;
    }
}

覆蓋默認的配置代碼如下:

package com.example.gateway.springcloudgateway.common;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.web.ResourceProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.reactive.error.ErrorAttributes;
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.web.reactive.result.view.ViewResolver;

import java.util.Collections;
import java.util.List;

@Configuration
@EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class})
public class ErrorHandlerConfiguration {

    private final ServerProperties serverProperties;

    private final ApplicationContext applicationContext;

    private final ResourceProperties resourceProperties;

    private final List<ViewResolver> viewResolverList;

    private final ServerCodecConfigurer serverCodecConfigurer;

    public ErrorHandlerConfiguration(ServerProperties serverProperties, ApplicationContext applicationContext, ResourceProperties resourceProperties,
                                     ObjectProvider<List<ViewResolver>> viewResolverProvider, ServerCodecConfigurer serverCodecConfigurer) {
        this.serverProperties = serverProperties;
        this.applicationContext = applicationContext;
        this.resourceProperties = resourceProperties;
        this.viewResolverList = viewResolverProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes){
        JsonExceptionHandler exceptionHandler = new JsonExceptionHandler(errorAttributes,this.resourceProperties,
                this.serverProperties.getError(),this.applicationContext);
        exceptionHandler.setViewResolvers(this.viewResolverList);
        exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
        exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
        return exceptionHandler;
    }
}

九、重試機制

spring:
  application:
    name: spring-cloud-gateway
  cloud:
    gateway:
      routes:
        - id: eureka-client-user-service
          uri: lb://eureka-client-user-service
          predicates:
            - Path=/user/**
          filters:
            - name: Retry
              args:
                retries: 3
                series: SERVER_ERROR

retries:重試次數,默認值是3次

series:狀態碼配置(分段),符合某段狀態碼纔會進行重試邏輯,默認值是 SERVER_ERROR 值是5,也就是5XX(5開頭的狀態碼),共有5個值,代碼如下:

    public static enum Series {
        INFORMATIONAL(1),
        SUCCESSFUL(2),
        REDIRECTION(3),
        CLIENT_ERROR(4),
        SERVER_ERROR(5);
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章