spring-cloud-gateway自定義組件實戰

自定義Handler

Handler
從請求頭中獲取usernamepassword,模擬登陸請求

@Component
public class UserHandler {
    private UserService userService;

    public UserHandler(UserService userService) {
        this.userService = userService;
    }

    public Mono<ServerResponse> login(ServerRequest serverRequest) {
        return ServerResponse.ok()
                .contentType(MediaType.APPLICATION_JSON)
                .body(Mono.fromSupplier(()->{
                    String username = serverRequest.headers().firstHeader("username");
                    String password = serverRequest.headers().firstHeader("password");
                    try{
                        boolean  success = userService.login(username, password);
                        return new Return(success,username+":"+password);
                    }catch (Exception e) {
                        return new Return(false,e.getMessage());
                    }

                }), Return.class);
    }
	
	//構造返回對象
    @Data
    @AllArgsConstructor
    public static class Return{
        private boolean success;
        private String msg;
    }
}

Service

@Component
public class UserService {

    public boolean login(String username,String password) {
        //todo form db;
        return true;
    }
}

Router

    @Bean
    public RouterFunction<ServerResponse> userRouter(UserHandler userhandler) {
        return nest(
                //相當於controller上的requestmapping
                path("/user"),
                route(GET("/login"), userhandler::login)
//                        .andRoute(GET("/date"), userha::getDate);
        );
    }

測試
在這裏插入圖片描述

自定義 Predicate

factory
自定義HeaderTokenRoutePredicateFactory,用來攔截所有請求頭中必須包含指定的header

public class HeaderTokenRoutePredicateFactory extends AbstractRoutePredicateFactory<HeaderTokenRoutePredicateFactory.Config> {

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

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("headers", "strategy");
    }

    @Override
    public Predicate<ServerWebExchange> apply(Config config) {
        return new GatewayPredicate() {
            @Override
            public boolean test(ServerWebExchange exchange) {
                HttpHeaders httpHeaders = exchange.getRequest().getHeaders();
                List<String> headers = config.getHeaders();
                if (headers == null || headers.isEmpty()) {
                    return true;
                }
                switch (config.getStrategy()) {
                    case AND:
                    	//策略是AND 時,請求頭中必須包含所有header
                        return headers.stream().allMatch(header -> existHeaderFunc.apply(httpHeaders,header));
                    default:
                    	//策略爲OR時, 請求頭包含任意一header即可
                        return headers.stream().anyMatch(header -> existHeaderFunc.apply(httpHeaders,header));
                }
            }

            @Override
            public String toString() {
                return String.format("Config: %s", config.toString());
            }
        };
    }

    BiFunction<HttpHeaders, String, Boolean> existHeaderFunc = (httpHeaders, header) -> Optional.ofNullable(httpHeaders.get(header)).filter(list -> !list.isEmpty()).isPresent();

    public enum Strategy {
        AND, OR;
    }

    public static class Config {
        //配置的header, 可以配置多個
        private List<String> headers = new ArrayList<>();

        //當配置多個時,校驗策略, and  還是 or
        private Strategy strategy = Strategy.OR;


        public List<String> getHeaders() {
            return headers;
        }

        public Strategy getStrategy() {
            return strategy;
        }

        public Config setHeaders(List<String> ignorePatterns) {
            this.headers = ignorePatterns;
            return this;
        }

        public void setStrategy(Strategy strategy) {
            this.strategy = strategy;
        }

        @Override
        public String toString() {
            return new ToStringCreator(this).append("headers", headers).append("strategy", strategy).toString();
        }
    }
}

configuration配置

    @Bean
    public HeaderTokenRoutePredicateFactory headerTokenRoutePredicateFactory() {
        return new HeaderTokenRoutePredicateFactory();
    }

yaml配置
配置predicate:類型爲HeaderToken

spring:
  cloud:
    gateway:
      routes:
        - id: header_token_route
          uri: forward:///user/login
          predicates:
            - name: HeaderToken
              args:
              #   所有請求,只要請求頭中包含username,password 
              ## 就 forward:///user/login
                headers: username,password
                strategy: AND

測試
訪問一個不存在的地址/xyz測試:
在這裏插入圖片描述

自定義 Filter

PreLog
preLog 向請求頭中添加屬性,記錄請求開始時間。

public class PreLogGatewayFilterFactory extends AbstractGatewayFilterFactory<PreLogGatewayFilterFactory.Config> implements Ordered {

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("level");
    }

    public static final String REQUEST_TIME_IN_KEY = "request_time_in";

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

    @Override
    public GatewayFilter apply(Config config) {
        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange,
                                     GatewayFilterChain chain) {
                //請求開始前記錄時間
               if( config.getLevel() == Level.DEBUG) {
                   System.out.println("------------------------preLog開始處理請求....."+ LocalDateTime.now());
               }
//             無法直接操作 header,會拋出 ReadOnlyHttpHeaders.add xxx 異常
//             exchange.getRequest().getHeaders().add(REQUEST_TIME_IN_KEY,String.valueOf(System.currentTimeMillis()));

                ServerHttpRequest changedRequest = exchange.getRequest()
                                                .mutate()
                                                .header(REQUEST_TIME_IN_KEY,String.valueOf(System.currentTimeMillis()))
                                                .build();

                return chain.filter(exchange.mutate().request(changedRequest).build());
            }

            @Override
            public String toString() {
                return filterToStringCreator(
                        PreLogGatewayFilterFactory.this)
                        .append(config.getLevel())
                        .toString();
            }
        };
    }

    public enum Level {
        DEBUG, INFO;
    }

    public static class Config {

        private Level level =Level.DEBUG;

        public Level getLevel() {
            return level;
        }

        public Config setLevel(Level level) {
            this.level = level;
            return this;
        }
    }


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

PostLog
PostLog 計算整個請求耗時:

@Override
    public GatewayFilter apply(Config config) {
        return new GatewayFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange,
                                     GatewayFilterChain chain) {
                return chain.filter(exchange).then(Mono.fromRunnable(()-> {
                    Optional.ofNullable(exchange.getRequest().getHeaders().get(REQUEST_TIME_IN_KEY)).ifPresent(list ->{
                       Long startTime =  Long.parseLong(list.get(0));
                        if( config.getLevel() == Level.INFO) {
                            System.out.println("----------------------耗時::" + (System.currentTimeMillis() - startTime));
                        }
                    });
                }));
            }

            @Override
            public String toString() {
                return filterToStringCreator(
                        PostLogGatewayFilterFactory.this)
                        .append(config.getLevel())
                        .toString();
            }
        };
    }

configuration配置

    @Bean
    public PostLogGatewayFilterFactory postLogGatewayFilterFactory() {
        return new PostLogGatewayFilterFactory();
    }

    @Bean
    public PreLogGatewayFilterFactory PreLogGatewayFilterFactory() {
        return new PreLogGatewayFilterFactory();
    }


yaml配置

spring:
  cloud:
    gateway:
      routes:
        - id: header_token_route
          uri: forward:///user/login
          predicates:
            - name: HeaderToken
              args:
                headers: username,password
                strategy: AND
          filters:
            - name: PreLog
              args:
                level: DEBUG
            - PostLog=INFO

測試
請求完畢後,後臺會記錄請求耗時日誌:
在這裏插入圖片描述

自定義 GlobalFilter

GlobalFilter 比較簡單, 只需要實現GlobalFilter接口,並注入容器即可。

@Component
public class MyGlobalFilter implements GlobalFilter, Ordered {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        //記錄所有請求信息
        URI url = exchange.getRequest().getURI();
        System.out.println("----------["+ LocalDateTime.now() +"]處理請求:"+ url.getRawPath() );
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return 10;
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章