spring cloud gateway RequestRateLimiter Redis RateLimiter 请求限流

oyhk 学习笔记

在高并发的互联网时代,有很多手段来保护系统,其中的保护手段就是请求限流。限流的目的是通过对并发访问/请求进行限速,一旦并发量达到限制速率则可以拒绝服务。本文章主要是使用spring cloud gateway Redis RateLimiter,下面来探探怎么使用。
redis RateLimiter 属于分布式系统限流
spring cloud gateway 版本为 Finchley.RELEASE。


环境要求

  • java8 以上版本
  • maven3.3 以上版本
  • git

拉取代码

git clone https://gitee.com/381895649/mkfree-sample.git

demo目录结构

redis准备

redis 当已经准备好了,至于怎么安装什么的,上网一搜一大把。
我设备上随意启动了一个 127.0.0.1 6379端口,大家自行修改对应的配置文件吧。
生产环境使用最好是 redis 集群的方式,不要使用单点。

Redis RateLimiter 请求限流

RedisRateLimiter.java

RedisRateLimiter 使用算法是令牌桶算法,至于这个 算法原理

RedisRateLimiter.java 源代码原理

主要实现方法 RedisRateLimiter.isAllowed,调用request_rate_limiter.lua来获取令牌。

    /**
     * This uses a basic token bucket algorithm and relies on the fact that Redis scripts
     * execute atomically. No other operations can run between fetching the count and
     * writing the new count.
     */
    @Override
    @SuppressWarnings("unchecked")
    public Mono<Response> isAllowed(String routeId, String id) {
        if (!this.initialized.get()) {
            throw new IllegalStateException("RedisRateLimiter is not initialized");
        }

        Config routeConfig = getConfig().getOrDefault(routeId, defaultConfig);

        if (routeConfig == null) {
            throw new IllegalArgumentException("No Configuration found for route " + routeId);
        }

        // How many requests per second do you want a user to be allowed to do?
        int replenishRate = routeConfig.getReplenishRate();

        // How much bursting do you want to allow?
        int burstCapacity = routeConfig.getBurstCapacity();

        try {
            List<String> keys = getKeys(id);


            // The arguments to the LUA script. time() returns unixtime in seconds.
            List<String> scriptArgs = Arrays.asList(replenishRate + "", burstCapacity + "",
                    Instant.now().getEpochSecond() + "", "1");
            // allowed, tokens_left = redis.eval(SCRIPT, keys, args)
            Flux<List<Long>> flux = this.redisTemplate.execute(this.script, keys, scriptArgs);
                    // .log("redisratelimiter", Level.FINER);
            return flux.onErrorResume(throwable -> Flux.just(Arrays.asList(1L, -1L)))
                    .reduce(new ArrayList<Long>(), (longs, l) -> {
                        longs.addAll(l);
                        return longs;
                    }) .map(results -> {
                        boolean allowed = results.get(0) == 1L;
                        Long tokensLeft = results.get(1);

                        Response response = new Response(allowed, getHeaders(routeConfig, tokensLeft));

                        if (log.isDebugEnabled()) {
                            log.debug("response: " + response);
                        }
                        return response;
                    });
        }
        catch (Exception e) {
            /*
             * We don't want a hard dependency on Redis to allow traffic. Make sure to set
             * an alert so you know if this is happening too much. Stripe's observed
             * failure rate is 0.01%.
             */
            log.error("Error determining if user allowed from redis", e);
        }
        return Mono.just(new Response(true, getHeaders(routeConfig, -1L)));
    }

request_rate_limiter.lua 网关已经提供了

local tokens_key = KEYS[1]
local timestamp_key = KEYS[2]
--redis.log(redis.LOG_WARNING, "tokens_key " .. tokens_key)

local rate = tonumber(ARGV[1])
local capacity = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local requested = tonumber(ARGV[4])

local fill_time = capacity/rate
local ttl = math.floor(fill_time*2)

--redis.log(redis.LOG_WARNING, "rate " .. ARGV[1])
--redis.log(redis.LOG_WARNING, "capacity " .. ARGV[2])
--redis.log(redis.LOG_WARNING, "now " .. ARGV[3])
--redis.log(redis.LOG_WARNING, "requested " .. ARGV[4])
--redis.log(redis.LOG_WARNING, "filltime " .. fill_time)
--redis.log(redis.LOG_WARNING, "ttl " .. ttl)

local last_tokens = tonumber(redis.call("get", tokens_key))
if last_tokens == nil then
  last_tokens = capacity
end
--redis.log(redis.LOG_WARNING, "last_tokens " .. last_tokens)

local last_refreshed = tonumber(redis.call("get", timestamp_key))
if last_refreshed == nil then
  last_refreshed = 0
end
--redis.log(redis.LOG_WARNING, "last_refreshed " .. last_refreshed)

local delta = math.max(0, now-last_refreshed)
local filled_tokens = math.min(capacity, last_tokens+(delta*rate))
local allowed = filled_tokens >= requested
local new_tokens = filled_tokens
local allowed_num = 0
if allowed then
  new_tokens = filled_tokens - requested
  allowed_num = 1
end

--redis.log(redis.LOG_WARNING, "delta " .. delta)
--redis.log(redis.LOG_WARNING, "filled_tokens " .. filled_tokens)
--redis.log(redis.LOG_WARNING, "allowed_num " .. allowed_num)
--redis.log(redis.LOG_WARNING, "new_tokens " .. new_tokens)

redis.call("setex", tokens_key, ttl, new_tokens)
redis.call("setex", timestamp_key, ttl, now)

return { allowed_num, new_tokens }

应用代码

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>groupId</groupId>
    <artifactId>spring-cloud-gateway-RedisRateLimiter</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!--这里是继承 spring-cloud-gateway pom-->
    <parent>
        <groupId>com.mkfree.sample.spring-cloud-gateway</groupId>
        <artifactId>spring-cloud-gateway</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <dependencies>
        <!-- 网关依赖 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!--基于 reactive stream 的 redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
        </dependency>
    </dependencies>
</project>

BootstrapGateway.java

package com.mkfree.sample.RedisRateLimiter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.util.Objects;

@SpringBootApplication
public class BootstrapGateway {

    private static Logger log = LoggerFactory.getLogger(BootstrapGateway.class);

    public static void main(String[] args) {
        ConfigurableApplicationContext configurableApplicationContext = SpringApplication.run(BootstrapGateway.class, args);
        String[] strings = configurableApplicationContext.getBeanDefinitionNames();
    }

    public static final String API_HOST = "http://127.0.0.1:9011";

    /**
     * 自定义限流标志的key,多个维度可以从这里入手
     * exchange对象中获取服务ID、请求信息,用户信息等
     */
    @Bean
    KeyResolver customKeyResolver() {
        return exchange -> {
            String hostname = Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getHostName();
            return Mono.just(hostname);
        };
    }
}

customKeyResolver 主要是定义redis key 名称,目前是请求用户hostname,生成的key规则如下,当请求的时候在redis 输入keys*

127.0.0.1:6379> keys *
1) "request_rate_limiter.{localhost}.timestamp"
2) "request_rate_limiter.{localhost}.tokens"

application.yml

# EMBEDDED SERVER CONFIGURATION (ServerProperties)
spring.application.name: gateway

server:
  port: 9000
  tomcat.uri-encoding: UTF-8


spring:
  cloud:
    gateway:
      routes:
      - id: api
        uri: http://127.0.0.1:9011 #转发代理服务
        predicates:
        - Path=/api/v1/user/findOne
        filters:
        - name: RequestRateLimiter
          args:
            redis-rate-limiter.replenishRate: 10
            redis-rate-limiter.burstCapacity: 10
            key-resolver: "#{@customKeyResolver}" #SPEL表达式去的对应的bean
  # redis config
  redis:
    host: 127.0.0.1
    port: 6379
  • filter名称必须是RequestRateLimiter
  • redis-rate-limiter.replenishRate:允许远程ip每秒处理多少个请求
  • redis-rate-limiter.burstCapacity:令牌桶的容量,允许在一秒钟内完成的最大请求数
  • key-resolver:使用SpEL按名称引用bean

验证限流

这里使用手动验证,使用postman手动点击请求。那么我们来修改一下以下参数

            redis-rate-limiter.replenishRate: 1
            redis-rate-limiter.burstCapacity: 1

以上参数配置为一秒内只允许一个请求,令牌桶内只有一个令牌。
那么postman 请求 http://127.0.0.1:9000/api/v1/user/findOne?id=1

  • 一秒内请求数没超过一个时,http code 为 200
  • 一秒内请求数超过1一个时,http code 为 429 (Too Many Requests 太多请求)

总结

限流在高并发系统问题上可以说是如虎添翼,不用担心瞬间大量流量导致系统挂掉或雪崩,最终效果可以做到有损服务而不是不服务。限流需要评估好,千万不要乱用,否则系统出现奇怪的问题,而导致用户抱怨。

spring cloud gateway GatewayFilter Factories(网关过滤工厂)

oyhk 学习笔记

这次来探探网关工厂使用方式,有时间再探探网关的源代码以及实现原理。一般嘛都是先学会怎么使用,再探实现原理,spring cloud gateway 版本为 Finchley.RELEASE。


环境要求

  • java8 以上版本
  • maven3.3 以上版本
  • git

拉取代码

git clone https://gitee.com/381895649/mkfree-sample.git

demo目录结构

api代码

UserController.java

package com.mkfree.sample.springCloudGatewayGatewayFilterFactoriesApi.controller;

import com.mkfree.sample.springCloudGatewayGatewayFilterFactoriesApi.domain.User;
import org.springframework.web.bind.annotation.*;

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

@RestController
public class UserController {

    private static Map<Integer, User> userMap = new HashMap<>();

    // 这里模拟存在用户
    static {
        userMap.put(1, new User(1, "oyhk"));
        userMap.put(2, new User(2, "路人甲"));
    }

    /**
     * 通过id获取用户
     * @param id
     * @return
     */
    @GetMapping(value = "/api/v1/user/findOne")
    public User findOne(int id) {
        return userMap.get(id);
    }

    /**
     * 保存用户
     * @param user
     * @return
     */
    @PostMapping(value = "/api/v1/user/save")
    public User save(@RequestBody User user) {
        userMap.put(user.getId(), user);
        return userMap.get(user.getId());
    }

    /**
     * 更新
     * @param user
     * @return
     */
    @PutMapping(value = "/api/v1/user/update")
    public User update(@RequestBody User user) {
        userMap.put(user.getId(), user);
        return userMap.get(user.getId());
    }

    /**
     * 删除
     * @param user
     */
    @DeleteMapping(value = "/api/v1/user/delete")
    public void delete(@RequestBody User user) {
        userMap.remove(user.getId());
    }
}

User.java

package com.mkfree.sample.springCloudGatewayGatewayFilterFactoriesApi.domain;

public class User {

    private int id;
    private String name;

    public User() {
    }
    public User(int id) {
        this.id = id;
    }

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

BootstrapApi.java

package com.mkfree.sample.springCloudGatewayGatewayFilterFactoriesApi;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class BootstrapApi {

    private static Logger log = LoggerFactory.getLogger(BootstrapApi.class);

    public static void main(String[] args) {
        ConfigurableApplicationContext configurableApplicationContext = SpringApplication.run(BootstrapApi.class, args);
        String[] strings = configurableApplicationContext.getBeanDefinitionNames();
    }

}

application.yml

# EMBEDDED SERVER CONFIGURATION (ServerProperties)
spring.application.name: api

server:
  port: 9011
  tomcat.uri-encoding: UTF-8

postman 访问 http://127.0.0.1:9011/api/v1/user/findOne?id=1
response

{
    "id": 1,
    "name": "oyhk"
}


api 部署成功了

GatewayFilter Factories (网关工厂)

路由过滤器可以再http请求或http响应介入,还可以通过指定条件下才介入。 Spring Cloud Gateway包含许多内置的GatewayFilter工厂,下面我们来一一尝试。

AddRequestHeader GatewayFilter Factory 添加请求头网关过滤器工厂

// 添加请求头
.route(r -> r.path("/api/v1/user/findOneRouteAddRequestHeader").filters(gatewayFilterSpec -> {
    gatewayFilterSpec.addRequestHeader("headerName1", "headerValue1");
    return gatewayFilterSpec;
}).uri(API_HOST))

AddRequestParameter GatewayFilter Factory 添加请求参数网关过滤器工厂

// 添加请求参数
.route(r -> r.path("/api/v1/user/findOneRouteAddRequestParameter").filters(gatewayFilterSpec -> {
    gatewayFilterSpec.addRequestParameter("parameterName1", "parameterValue1");
    return gatewayFilterSpec;
}).uri(API_HOST))

AddResponseHeader GatewayFilter Factory 添加响应请求头网关过滤器工厂

// 添加响应请求头
.route(r -> r.path("/api/v1/user/findOneRouteAddResponseHeader").filters(gatewayFilterSpec -> {
    gatewayFilterSpec.addResponseHeader("headerName1", "headerValue1");
    return gatewayFilterSpec;
}).uri(API_HOST))

以上三者合并

// 添加以上三者
.route(r -> r.path("/api/v1/user/findOneRouteAddResponseHeader").filters(gatewayFilterSpec -> {
    gatewayFilterSpec.addRequestHeader("requestHeaderName1", "requestHeaderValue1");
    gatewayFilterSpec.addRequestParameter("parameterName1", "parameterValue1");
    gatewayFilterSpec.addResponseHeader("responseHeaderName1", "responseHeaderValue1");
    return gatewayFilterSpec;
}).uri(API_HOST))

PrefixPath GatewayFilter Factory 路径前缀网关过滤器工厂

下面例子相当于请求 http://127.0.0.1/api/api/v1/user/findOneRoutePrefixPath

// 添加前缀路劲
.route(r -> r.path("/api/v1/user/findOneRoutePrefixPath").filters(gatewayFilterSpec -> {
    gatewayFilterSpec.prefixPath("/api");
    return gatewayFilterSpec;
}).uri(API_HOST))

PreserveHostHeader GatewayFilter Factory

.route(r -> r.path("/api/v1/user/findOneRoutePreserveHostHeader").filters(gatewayFilterSpec -> {
    gatewayFilterSpec.preserveHostHeader();
    return gatewayFilterSpec;
}).uri(API_HOST))

RequestRateLimiter GatewayFilter Factory 请求限流网关过滤工厂

spring cloud gateway RequestRateLimiter Redis RateLimiter 请求限流

spring cloud gateway readBody to Object throw NullPointException(Finchley.RELEASE版本)

oyhk 学习笔记

这篇文章主要讲网关使用readBody方法时第一个参数时转换直接报NullPointException


环境要求

  • java8 以上版本
  • maven3.3 以上版本
  • git

拉取代码

git clone https://gitee.com/381895649/mkfree-sample.git

demo目录结构

api代码

UserController.java

package com.mkfree.sample.springCloudGatewayReadbodyNullPointExceptionApi.controller;

import com.mkfree.sample.springCloudGatewayReadbodyNullPointExceptionApi.domain.User;
import org.springframework.web.bind.annotation.*;

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

@RestController
public class UserController {

    private static Map<Integer, User> userMap = new HashMap<>();

    // 这里模拟存在用户
    static {
        userMap.put(1, new User(1, "oyhk"));
        userMap.put(2, new User(2, "路人甲"));
    }

    /**
     * 通过id获取用户
     * @param id
     * @return
     */
    @GetMapping(value = "/api/v1/user/findOne")
    public User findOne(int id) {
        return userMap.get(id);
    }

    /**
     * 保存用户
     * @param user
     * @return
     */
    @PostMapping(value = "/api/v1/user/save")
    public User save(@RequestBody User user) {
        userMap.put(user.getId(), user);
        return userMap.get(user.getId());
    }

    /**
     * 更新
     * @param user
     * @return
     */
    @PutMapping(value = "/api/v1/user/update")
    public User update(@RequestBody User user) {
        userMap.put(user.getId(), user);
        return userMap.get(user.getId());
    }

    /**
     * 删除
     * @param user
     */
    @DeleteMapping(value = "/api/v1/user/delete")
    public void delete(@RequestBody User user) {
        userMap.remove(user.getId());
    }
}

User.java

package com.mkfree.sample.springCloudGatewayReadbodyNullPointExceptionApi.domain;

public class User {

    private int id;
    private String name;

    public User() {
    }
    public User(int id) {
        this.id = id;
    }

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

application.yml

# EMBEDDED SERVER CONFIGURATION (ServerProperties)
spring.application.name: api

server:
  port: 9011
  tomcat.uri-encoding: UTF-8

BootstrapApi.java

package com.mkfree.sample.springCloudGatewayReadbodyNullPointExceptionApi;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class BootstrapApi {

    private static Logger log = LoggerFactory.getLogger(BootstrapApi.class);

    public static void main(String[] args) {
        ConfigurableApplicationContext configurableApplicationContext = SpringApplication.run(BootstrapApi.class, args);
        String[] strings = configurableApplicationContext.getBeanDefinitionNames();
    }

}

postman 请求 http://127.0.0.1:9011/api/v1/user/save
reqeust body

{
    "id":3,
    "name":"oyhk 学习笔记"
}

response

{
    "id": 3,
    "name": "oyhk 学习笔记"
}

表明api部署成功了

gateway代码

User.java

package com.mkfree.sample.springCloudGatewayReadbodyNullPointException.dto;

public class User {
    private int id;
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

BootstrapGateway.java
postman 请求 http://127.0.0.1:9011/api/v1/user/findOne?id=1
response

{
    "id": 1,
    "name": "oyhk"
}

表明gateway部署成功了

readBody 方法介绍

下面截图是源代码

– 这个方法api说明好像是:此规则是BETA版本,有可能在未来版本里发生变化,大概是这样吧。
– 用于检查request body

会报NullPointException的写法

public BooleanSpec readBody(Class inClass, Predicate predicate);
第一个参数:转换类型,目测自定义对象还是Map.class 其他都会抛异常。

.route(r -> r.readBody(User.class, user -> {
    log.info("body user.id : {}", user.getId());
    log.info("body user.name : {}", user.getName());
    return true;
}).and().path("/api/v1/user/save").uri(API_HOST))
    .build();

下面是输出结果:

为什么会发生这种情况?暂时查询不到资料

正确写法

readBody inClass 为 String.class可以解决问题,但是需要手动转换为对象

// 正确写法
.route(r -> r.readBody(String.class, body -> {
    User user = null;
    try {
        user = objectMapper.readValue(body, User.class);
    } catch (IOException e) {
        log.error(e.getMessage());
    }
    if (user == null) {
        return false;
    }
    log.info("readBody2");
    log.info("body user.id : {}", user.getId());
    log.info("body user.name : {}", user.getName());
    return true;
}).and().path("^/api/v1/user/save2").uri(API_HOST))

完整代码

package com.mkfree.sample.springCloudGatewayReadbodyNullPointException;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.mkfree.sample.springCloudGatewayReadbodyNullPointException.dto.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;

import java.io.IOException;

@SpringBootApplication
public class BootstrapGateway {

    private static Logger log = LoggerFactory.getLogger(BootstrapGateway.class);

    public static void main(String[] args) {
        ConfigurableApplicationContext configurableApplicationContext = SpringApplication.run(BootstrapGateway.class, args);
        String[] strings = configurableApplicationContext.getBeanDefinitionNames();
    }

    private static final String API_HOST = "http://127.0.0.1:9011";

    @Autowired
    private ObjectMapper objectMapper;

    /**
     * 自定义路由方式
     *
     * @param builder
     * @return
     */
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                .route(r -> r.path("/api/v1/user/findOne").uri(API_HOST))


                // 正确写法
                .route(r -> r.readBody(String.class, body -> {
                    User user = null;
                    try {
                        user = objectMapper.readValue(body, User.class);
                    } catch (IOException e) {
                        log.error(e.getMessage());
                    }
                    if (user == null) {
                        return false;
                    }
                    log.info("readBody2");
                    log.info("body user.id : {}", user.getId());
                    log.info("body user.name : {}", user.getName());
                    return true;
                }).and().path("^/api/v1/user/save2").uri(API_HOST))
                // 抛异常写法
                .route("readBody1", r -> r.readBody(User.class, user -> {
                    log.info("readBody1");
                    log.info("body user.id : {}", user.getId());
                    log.info("body user.name : {}", user.getName());
                    return true;
                }).and().path("/api/v1/user/save").uri(API_HOST))



                .build();
    }
}

注意 readBody 方法超过一次使用抛异常

java.lang.IllegalStateException: Only one connection receive subscriber allowed.
相关链接:Reading request body in filter produces an IllegalStateException

spring cloud gateway 路由Fluent Java Routes API 高级用法(Finchley.RELEASE版本)

oyhk 学习笔记

这篇文章主要围绕各种条件结合的用法


读取request body

验证request body里 accessToken,保存一个用户

BaseDto.java

package com.mkfree.sample.spring_cloud_gateway_route.dto;

public class Auth {

   private String accessToken;

    public String getAccessToken() {
        return accessToken;
    }

    public void setAccessToken(String accessToken) {
        this.accessToken = accessToken;
    }
}

具体路由代码

// 读取request body做指定条件访问
.route(r -> r.readBody(String.class, body -> {
    /*
      * 注意这里有一个问题,readBody 里inClass目测只能是String.class,
      * 直接转换对象时网关报java.lang.NullPointerException,不清楚是不是bug
      */
    Auth auth = null;
    try {
        auth = objectMapper.readValue(body, Auth.class);
    } catch (IOException e) {
        e.printStackTrace();
    }

    // 这里可以写很多逻辑代码
    return auth != null && "123".equals(auth.getAccessToken());
}).and().path("/api/v1/user/findOneRouteReadBody").uri(API_HOST))

请求url post:http://127.0.0.1:9000/api/v1/user/findOneRouteReadBody
request body

{
    "accessToken":"123",
    "id":"3",
    "name":"oyhk 学习笔记"
}

postman 请求截图

多条件结合1

匹配路径、指定body、指定header、指定host、指定时间前 等等

// 匹配路径、指定body、指定header、指定host、指定时间前 等等
.route(r -> r.readBody(String.class, body -> {
    /*
      * 注意这里有一个问题,readBody 里inClass目测只能是String.class,
      * 直接转换对象时网关报java.lang.NullPointerException,不清楚是不是bug
      */
    Auth auth = null;
    try {
        auth = objectMapper.readValue(body, Auth.class);
    } catch (IOException e) {
        e.printStackTrace();
    }

    // 这里可以写很多逻辑代码
    return auth != null && "123".equals(auth.getAccessToken());
}).and().host("**127.0.0.1**").and()
       .before(LocalDateTime.of(2018, 12, 30, 0, 0, 0).atZone(ZoneId.systemDefault())).and()
       .header("accessToken").and()
       .path("/api/v1/user/findOneRouteCondition").uri(API_HOST))

多条件结合2

匹配路径、添加自定义规则(http method 为 get,请求头 accessToken 为 123456)、添加自定义请求头

// 匹配路径、添加自定义规则(http method 为 get,请求头 accessToken 为 123456)、添加自定义请求头
.route(r -> r.predicate(serverWebExchange -> {
    // 添加指定规则

    // 例如下面为 http method 为 get , 请求头包含 accessToken value = 123456
    HttpRequest httpRequest = serverWebExchange.getRequest();
    HttpMethod httpMethod = httpRequest.getMethod();

    boolean isMethod = httpMethod.matches(HttpMethod.GET.toString());

    String accessToken = httpRequest.getHeaders().getFirst("accessToken");

    return isMethod && "123456".equals(accessToken);
}).and().path("/api/v1/user/findOneRouteCondition2")
       // 添加请求头
       .filters(gatewayFilterSpec -> {
           gatewayFilterSpec.addRequestHeader("headerName1", "headerValue1");
           gatewayFilterSpec.addRequestHeader("headerName2", "headerValue2");
           return gatewayFilterSpec;
       })
       .uri(API_HOST))

完整代码

package com.mkfree.sample.spring_cloud_gateway_route;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.mkfree.sample.spring_cloud_gateway_route.dto.Auth;
import org.reactivestreams.Publisher;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping;
import org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.PredicateSpec;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.support.HttpRequestWrapper;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.server.ServerWebExchange;

import java.io.IOException;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Map;

@Configuration
public class RoutesConfig {
    private static final String API_HOST = "http://127.0.0.1:9011";

    @Autowired
    private ObjectMapper objectMapper;

    /**
     * 自定义路由方式
     *
     * @param builder
     * @return
     */
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                // 路由为/api/v1 前缀的转发到 127.0.0.1:9011

                // 指定当前时间后可以访问
                .route(r -> r.after(LocalDateTime.now()
                        .atZone(ZoneId.systemDefault())).and()
                        .path("/api/v1/user/findOneRouteAfter").uri(API_HOST))

                // 指定当前时间前可以访问
                .route(r -> r.before(LocalDateTime.now()
                        .atZone(ZoneId.systemDefault())).and()
                        .path("/api/v1/user/findOneRouteBefore").uri(API_HOST))
                // 指定 2018-12-30 前可以访问
                .route(r -> r.before(LocalDateTime.of(2018, 12, 30, 0, 0, 0)
                        .atZone(ZoneId.systemDefault())).and()
                        .path("/api/v1/user/findOneRouteBefore").uri(API_HOST))

                // 指定时间区间内可以访问 区间为:当前时间 - 2018-12-30
                .route(r -> r.between(LocalDateTime.now().atZone(ZoneId.systemDefault()),
                        LocalDateTime.of(2018, 12, 30, 0, 0, 0).atZone(ZoneId.systemDefault())).and()
                        .path("/api/v1/user/findOneRouteBetween").uri(API_HOST))

                // 指定 cookie,name:accessToken value:123456 可以访问
                .route(r -> r.cookie("accessToken", "123456").and()
                        .path("/api/v1/user/findOneRouteCookie").uri(API_HOST))

                // 指定 header,name = accessToken value = 123456
                .route(r -> r.header("accessToken", "123456").and()
                        .path("/api/v1/user/findOneRouteHeader").uri(API_HOST))

                // 指定 header,name = host value = 127.0.0.1,这里需要注意:其实host的用法其实就是请求头中的host对应的值
                .route(r -> r.host("**127.0.0.1**").and()
                        .path("/api/v1/user/findOneRouteHost").uri(API_HOST))

                // 指定 method 为 get 可以请求
                .route(r -> r.method(HttpMethod.GET).and()
                        .path("/api/v1/user/findOneRouteMethodGet").uri(API_HOST))
                // 指定 method 为 post 可以请求
                .route(r -> r.method(HttpMethod.POST).and()
                        .path("/api/v1/user/findOneRouteMethodPost").uri(API_HOST))

                // 指定 路径参数可以请求
                .route(r -> r.path("/api/v1/user/findOneRoutePath/{id}").uri(API_HOST))

                // 指定 查询参数可以请求
                .route(r -> r.query("accessToken", "123456").and()
                        .path("/api/v1/user/findOneRouteQuery").uri(API_HOST))

                // 指定 远程ip可以请求
                .route(r -> r.remoteAddr("127.0.0.1").and()
                        .path("/api/v1/user/findOneRouteRemoteAddr").uri(API_HOST))

                // 读取request body做指定条件访问
                .route(r -> r.readBody(String.class, body -> {
                    /*
                     * 注意这里有一个问题,readBody 里inClass目测只能是String.class,
                     * 直接转换对象时网关报java.lang.NullPointerException,不清楚是不是bug
                     */
                    Auth auth = null;
                    try {
                        auth = objectMapper.readValue(body, Auth.class);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }

                    // 这里可以写很多逻辑代码
                    return auth != null && "123".equals(auth.getAccessToken());
                }).and().path("/api/v1/user/findOneRouteReadBody").uri(API_HOST))

                // 匹配路径、指定body、指定header、指定host、指定时间前 等等
                .route(r -> r.readBody(String.class, body -> {

                    /*
                     * 注意这里有一个问题,readBody 里inClass目测只能是String.class,
                     * 直接转换对象时网关报java.lang.NullPointerException,不清楚是不是bug
                     */
                    Auth auth = null;
                    try {
                        auth = objectMapper.readValue(body, Auth.class);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }

                    // 这里可以写很多逻辑代码
                    return auth != null && "123".equals(auth.getAccessToken());
                }).and().host("**127.0.0.1**").and()
                        .before(LocalDateTime.of(2018, 12, 30, 0, 0, 0).atZone(ZoneId.systemDefault())).and()
                        .header("accessToken").and()
                        .path("/api/v1/user/findOneRouteCondition").uri(API_HOST))


                // 匹配路径、添加自定义规则(http method 为 get,请求头 accessToken 为 123456)、添加自定义请求头
                .route(r -> r.predicate(serverWebExchange -> {
                    // 添加指定规则

                    // 例如下面为 http method 为 get , 请求头包含 accessToken value = 123456
                    HttpRequest httpRequest = serverWebExchange.getRequest();
                    HttpMethod httpMethod = httpRequest.getMethod();

                    boolean isMethod = httpMethod.matches(HttpMethod.GET.toString());

                    String accessToken = httpRequest.getHeaders().getFirst("accessToken");

                    return isMethod && "123456".equals(accessToken);
                }).and().path("/api/v1/user/findOneRouteCondition2")
                        // 添加请求头
                        .filters(gatewayFilterSpec -> {
                            gatewayFilterSpec.addRequestHeader("headerName1", "headerValue1");
                            gatewayFilterSpec.addRequestHeader("headerName2", "headerValue2");
                            return gatewayFilterSpec;
                        })
                        .uri(API_HOST))
                // 匹配前缀
//                .route(r -> r.path("/api/v1/**").uri(API_HOST))

                .build();
    }
}

有问题欢迎大家指正

原文链接:spring cloud gateway 路由Fluent Java Routes API 高级用法(Finchley.RELEASE版本)

spring cloud gateway 路由Route Predicate Factories(Finchley.RELEASE版本)

oyhk 学习笔记

此文章上文为 spring cloud gateway 路由Fluent Java Routes API(Finchley.RELEASE版本) ,这次主要编写配置文件方式路由(Route Predicate Factories)


Route Predicate Factories 路由方式

Spring Cloud Gateway将路由作为Spring WebFlux HandlerMapping基础结构的一部分进行匹配。 Spring Cloud Gateway包含许多内置的Route Predicate Factories。 所有Route Predicate 都可以随意组合,以并且关系存在。

After Route Predicate Factory 指定当前时间后匹配

spring:
  cloud:
    gateway:
      routes:
        - id: after
          uri: http://127.0.0.1:9011
          predicates:
          - After=2018-08-06T11:42:39.482858+08:00[Asia/Shanghai]
          - Path=/api/v2/user/findOneRouteAfter

注意:Asia/Shanghai 这里的意思是 亚洲/上海 时间,了解更多 请查看 ZonedDateTime.java

Before Route Predicate Factory 指定当前时间前匹配

spring:
  cloud:
    gateway:
      routes:
        - id: before
          uri: http://127.0.0.1:9011
          predicates:
          - Before=2018-12-30T11:42:39.482858+08:00[Asia/Shanghai]
          - Path=/api/v2/user/findOneRouteBefore

Between Route Predicate Factory 指定时间区间内匹配

spring:
  cloud:
    gateway:
      routes:
        - id: between
          uri: http://127.0.0.1:9011
          predicates:
          - Between=2018-01-01T00:00:00.000000+08:00[Asia/Shanghai],2018-12-30T00:00:00.000000+08:00[Asia/Shanghai]
          - Path=/api/v2/user/findOneRouteBetween

Cookie Route Predicate Factory cookie参数匹配

spring:
  cloud:
    gateway:
      routes:
        - id: cookie
          uri: http://127.0.0.1:9011
          predicates:
          - Cookie=accessToken,123456
          - Path=/api/v2/user/findOneRouteCookie

Header Route Predicate Factory 请求头匹配

spring:
  cloud:
    gateway:
      routes:
        - id: header
          uri: http://127.0.0.1:9011
          predicates:
          - Header=accessToken,123456
          - Path=/api/v2/user/findOneRouteHeader

Host Route Predicate Factory host请求头匹配

spring:
  cloud:
    gateway:
      routes:
        - id: host
          uri: http://127.0.0.1:9011
          predicates:
          - Host=**127.0.0.1**
          - Path=/api/v2/user/findOneRouteHost

Method Route Predicate Factory http 方法匹配

spring:
  cloud:
    gateway:
      routes:
        - id: methodGet
          uri: http://127.0.0.1:9011
          predicates:
          - Method=GET
          - Path=/api/v2/user/findOneRouteMethodGet
        - id: methodPost
          uri: http://127.0.0.1:9011
          predicates:
          - Method=POST
          - Path=/api/v2/user/findOneRouteMethodPost

Path Route Predicate Factory 路劲参数匹配

spring:
  cloud:
    gateway:
      routes:
        - id: path
          uri: http://127.0.0.1:9011
          predicates:
          - Path=/api/v2/user/findOneRoutePath/{id}

Query Route Predicate Factory 查询参数匹配

spring:
  cloud:
    gateway:
      routes:
        - id: query1 #包含参数accessToken就可以访问
          uri: http://127.0.0.1:9011
          predicates:
          - Query=accessToken
          - Path=/api/v2/user/findOneRouteQuery        
        - id: query2 #包含参数accessToken value:123456 就可以访问
          uri: http://127.0.0.1:9011
          predicates:
          - Query=accessToken,123456
          - Path=/api/v2/user/findOneRouteQuery
        - id: query3 #包含参数accessToken value:123/12345/12333 匹配前缀,都可以访问
          uri: http://127.0.0.1:9011
          predicates:
          - Query=accessToken,123.
          - Path=/api/v2/user/findOneRouteQuery

注意:- Query=accessToken,123. 这个(123.)的意思就是匹配 123前缀后面什么都可以

RemoteAddr Route Predicate Factory 远程IP地址匹配

spring:
  cloud:
    gateway:
      routes:
        - id: RemoteAddr
          uri: http://127.0.0.1:9011
          predicates:
          - RemoteAddr=192.168.1.1/24
          - Path=/api/v2/user/findOneRouteRemoteAddr

这个ip地址 192.168.1.1/24 科普一下。它表示的是一个网段,那个“/24”是子网掩码的缩写形式,表示你的子网掩码是255.255.255.0(前24位表示网络地址,因此缩写为/24),看起来你可以设定一个除192.168.1.0、192.168.1.1、192.168.1.255之外的任意ip尾数,比如192.168.1.2

完整配置文件代码

# EMBEDDED SERVER CONFIGURATION (ServerProperties)
spring.application.name: gateway

server:
  port: 9000
  tomcat.uri-encoding: UTF-8

spring:
  cloud:
    gateway:
      routes:
        - id: after
          uri: http://127.0.0.1:9011
          predicates:
          - After=2018-08-06T11:42:39.482858+08:00[Asia/Shanghai]
          - Path=/api/v2/user/findOneRouteAfter
        - id: before
          uri: http://127.0.0.1:9011
          predicates:
          - Before=2018-12-30T11:42:39.482858+08:00[Asia/Shanghai]
          - Path=/api/v2/user/findOneRouteBefore
        - id: between
          uri: http://127.0.0.1:9011
          predicates:
          - Between=2018-01-01T00:00:00.000000+08:00[Asia/Shanghai],2018-12-30T00:00:00.000000+08:00[Asia/Shanghai]
          - Path=/api/v2/user/findOneRouteBetween
        - id: cookie
          uri: http://127.0.0.1:9011
          predicates:
          - Cookie=accessToken,123456
          - Path=/api/v2/user/findOneRouteCookie
        - id: header
          uri: http://127.0.0.1:9011
          predicates:
          - Header=accessToken,123456
          - Path=/api/v2/user/findOneRouteHeader
        - id: host
          uri: http://127.0.0.1:9011
          predicates:
          - Host=**127.0.0.1**
          - Path=/api/v2/user/findOneRouteHost
        - id: methodGet
          uri: http://127.0.0.1:9011
          predicates:
          - Method=GET
          - Path=/api/v2/user/findOneRouteMethodGet
        - id: methodPost
          uri: http://127.0.0.1:9011
          predicates:
          - Method=POST
          - Path=/api/v2/user/findOneRouteMethodPost
        - id: query1 #包含参数accessToken就可以访问
          uri: http://127.0.0.1:9011
          predicates:
          - Query=accessToken
          - Path=/api/v2/user/findOneRouteQuery
        - id: query2 #包含参数accessToken value:123456 就可以访问
          uri: http://127.0.0.1:9011
          predicates:
          - Query=accessToken,123456
          - Path=/api/v2/user/findOneRouteQuery
        - id: query3 #包含参数accessToken value:123/12345/12333 匹配前缀,都可以访问
          uri: http://127.0.0.1:9011
          predicates:
          - Query=accessToken,123.
          - Path=/api/v2/user/findOneRouteQuery
        - id: RemoteAddr
          uri: http://127.0.0.1:9011
          predicates:
          - RemoteAddr=192.168.1.1/24
          - Path=/api/v2/user/findOneRouteRemoteAddr

相关文章

spring cloud gateway 路由Fluent Java Routes API(Finchley.RELEASE版本)

原文链接:spring cloud gateway 路由Route Predicate Factories(Finchley.RELEASE版本)

spring cloud gateway 路由Fluent Java Routes API(Finchley.RELEASE版本)

oyhk 学习笔记

初探spring cloud gateway各种用法,欢迎大家一起讨论。


本demo项目

准备环境

  • java8 以上版本
  • maven3.3 以上版本
  • git

拉取项目代码

git clone https://gitee.com/381895649/mkfree-sample.git

api 接口项目


项目中有两个版本User Api接口分别为 /api/v1/** 、 /api/v2/** ,v1用于“Fluent Java Routes API 路由方式”,v2用户“自定义路由方式”,两个版本都有对用户的CRUD方法,下面贴一下代码。
UserV1Controller.java

package com.mkfree.sample.spring_cloud_gateway_hello_world_api.controller;

import com.mkfree.sample.spring_cloud_gateway_hello_world_api.domain.User;
import org.springframework.web.bind.annotation.*;

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


@RestController
public class UserV1Controller {

    private static Map<Integer, User> userMap = new HashMap<>();

    // 这里模拟存在用户
    static {
        userMap.put(1, new User(1, "oyhk"));
        userMap.put(2, new User(2, "路人甲"));
    }

    /**
     * 通过id获取用户
     * @param id
     * @return
     */
    @GetMapping(value = "/api/v1/user/findOne")
    public User findOne(int id) {
        return userMap.get(id);
    }

    /**
     * 保存用户
     * @param user
     * @return
     */
    @PostMapping(value = "/api/v1/user/save")
    public User save(@RequestBody User user) {
        userMap.put(user.getId(), user);
        return userMap.get(user.getId());
    }

    /**
     * 更新
     * @param user
     * @return
     */
    @PutMapping(value = "/api/v1/user/update")
    public User update(@RequestBody User user) {
        userMap.put(user.getId(), user);
        return userMap.get(user.getId());
    }

    /**
     * 删除
     * @param user
     */
    @DeleteMapping(value = "/api/v1/user/delete")
    public void delete(@RequestBody User user) {
        userMap.remove(user.getId());
    }
}

User.java

package com.mkfree.sample.spring_cloud_gateway_hello_world_api.domain;

public class User {

    private int id;
    private String name;

    public User() {
    }
    public User(int id) {
        this.id = id;
    }

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

BootstrapApi.java

package com.mkfree.sample.spring_cloud_gateway_hello_world_api;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;

@SpringBootApplication
public class BootstrapApi {

    private static Logger log = LoggerFactory.getLogger(BootstrapApi.class);

    public static void main(String[] args) {
        ConfigurableApplicationContext configurableApplicationContext = SpringApplication.run(BootstrapApi.class, args);
        String[] strings = configurableApplicationContext.getBeanDefinitionNames();
    }

}

application.yml

# EMBEDDED SERVER CONFIGURATION (ServerProperties)
spring.application.name: api

server:
  port: 9011
  tomcat.uri-encoding: UTF-8

parent pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.mkfree.sample.spring-cloud-gateway</groupId>
    <artifactId>spring-cloud-gateway</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <!-- spring boot 2 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.3.RELEASE</version>
    </parent>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Finchley.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>groupId</groupId>
    <artifactId>spring-cloud-gateway-route-api</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!--这里是继承 spring-cloud-gateway pom-->
    <parent>
        <groupId>com.mkfree.sample.spring-cloud-gateway</groupId>
        <artifactId>spring-cloud-gateway</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <dependencies>
        <!--spring boot mvc-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
</project>

启动项目,直接运行BootstrapApi就可以了,Postman输入 http://127.0.0.1:9011/api/v1/user/findOne?id=1 ,数据返回下图
到此为止api已经部署成功,下面开始进入网关转发。

Fluent Java Routes Predicate Factories 路由方式

Spring Cloud Gateway将路由作为Spring WebFlux HandlerMapping基础结构的一部分进行匹配。 Spring Cloud Gateway包含许多内置的Route Predicate Factories。 所有Route Predicate 都可以随意组合,以并且关系存在。

After Route Predicate Factory 指定当前时间后匹配

// 指定当前时间后可以访问
.route(r -> r.after(LocalDateTime.now()
    .atZone(ZoneId.systemDefault())).and()
    .path("/api/v1/user/findOneRouteAfter").uri(API_HOST))

Before Route Predicate Factory 指定当前时间前匹配

// 指定当前时间前可以访问
.route(r -> r.after(LocalDateTime.now()
       .atZone(ZoneId.systemDefault())).and()
       .path("/api/v1/user/findOneRouteBefore").uri(API_HOST))

注意:以上的时间是当前时间,所以理论上请求 http://127.0.0.1:9000/api/v1/user/findOneRouteBefore?id=1 返回 404,修改一下代码

// 指定 2018-12-30 前可以访问
.route(r -> r.before(LocalDateTime.of(2018, 12, 30, 0, 0, 0)
       .atZone(ZoneId.systemDefault())).and()
       .path("/api/v1/user/findOneRouteBefore").uri(API_HOST))

Between Route Predicate Factory 指定时间区间内匹配

// 指定时间区间内可以访问 区间为:当前时间 - 2018-12-30
.route(r -> r.between(LocalDateTime.now().atZone(ZoneId.systemDefault()),
                      LocalDateTime.of(2018, 12, 30, 0, 0, 0).atZone(ZoneId.systemDefault())).and()
       .path("/api/v1/user/findOneRouteBetween").uri(API_HOST))

Cookie Route Predicate Factory 指定cookie匹配

// 指定 cookie ,name:accessToken value:123456 可以访问
.route(r -> r.cookie("accessToken","123456").and()
       .path("/api/v1/user/findOneRouteCookie").uri(API_HOST))

Header Route Predicate Factory 指定header匹配

// 指定 header,name = accessToken value = 123456
.route(r -> r.header("accessToken", "123456").and()
       .path("/api/v1/user/findOneRouteHeader").uri(API_HOST))

Host Route Predicate Factory 请求头host对应的值匹配

注意:其实host的用法其实就是请求头中的host对应的值

// 指定 header,name = host value = 127.0.0.1
.route(r -> r.host("**127.0.0.1**").and()
       .path("/api/v1/user/findOneRouteHost").uri(API_HOST))

Method Route Predicate Factory 指定http 方法匹配

// 指定 method 为 get 可以请求
.route(r -> r.method(HttpMethod.GET).and()
       .path("/api/v1/user/findOneRouteMethodGet").uri(API_HOST))
// 指定 method 为 post 可以请求
.route(r -> r.method(HttpMethod.POST).and()
       .path("/api/v1/user/findOneRouteMethodPost").uri(API_HOST))

Path Route Predicate Factory 路劲参数匹配

// 指定 路径参数可以请求
.route(r -> r.path("/api/v1/user/findOneRoutePath/{id}").uri(API_HOST))

http://127.0.0.1:9000/api/v1/user/findOneRoutePath/1

Query Route Predicate Factory 查询参数匹配

// 指定 参数可以请求
.route(r -> r.query("accessToken","123456").and()
       .path("/api/v1/user/findOneRouteQuery").uri(API_HOST))

RemoteAddr Route Predicate Factory 远程ip匹配

// 指定 远程ip可以请求
.route(r -> r.remoteAddr("127.0.0.1").and()
       .path("/api/v1/user/findOneRouteRemoteAddr").uri(API_HOST))

完整路由源代码

package com.mkfree.sample.spring_cloud_gateway_route;

import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;

import java.time.LocalDateTime;
import java.time.ZoneId;

@Configuration
public class RoutesConfig {
    private static final String API_HOST = "http://127.0.0.1:9011";

    /**
     * 自定义路由方式
     *
     * @param builder
     * @return
     */
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                // 路由为/api/v1 前缀的转发到 127.0.0.1:9011

                // 指定当前时间后可以访问
                .route(r -> r.after(LocalDateTime.now()
                        .atZone(ZoneId.systemDefault())).and()
                        .path("/api/v1/user/findOneRouteAfter").uri(API_HOST))

                // 指定当前时间前可以访问
                .route(r -> r.before(LocalDateTime.now()
                        .atZone(ZoneId.systemDefault())).and()
                        .path("/api/v1/user/findOneRouteBefore").uri(API_HOST))
                // 指定 2018-12-30 前可以访问
                .route(r -> r.before(LocalDateTime.of(2018, 12, 30, 0, 0, 0)
                        .atZone(ZoneId.systemDefault())).and()
                        .path("/api/v1/user/findOneRouteBefore").uri(API_HOST))

                // 指定时间区间内可以访问 区间为:当前时间 - 2018-12-30
                .route(r -> r.between(LocalDateTime.now().atZone(ZoneId.systemDefault()),
                        LocalDateTime.of(2018, 12, 30, 0, 0, 0).atZone(ZoneId.systemDefault())).and()
                        .path("/api/v1/user/findOneRouteBetween").uri(API_HOST))

                // 指定 cookie,name:accessToken value:123456 可以访问
                .route(r -> r.cookie("accessToken", "123456").and()
                        .path("/api/v1/user/findOneRouteCookie").uri(API_HOST))

                // 指定 header,name = accessToken value = 123456
                .route(r -> r.header("accessToken", "123456").and()
                        .path("/api/v1/user/findOneRouteHeader").uri(API_HOST))

                // 指定 header,name = host value = 127.0.0.1,这里需要注意:其实host的用法其实就是请求头中的host对应的值
                .route(r -> r.host("**127.0.0.1**").and()
                        .path("/api/v1/user/findOneRouteHost").uri(API_HOST))

                // 指定 method 为 get 可以请求
                .route(r -> r.method(HttpMethod.GET).and()
                        .path("/api/v1/user/findOneRouteMethodGet").uri(API_HOST))
                // 指定 method 为 post 可以请求
                .route(r -> r.method(HttpMethod.POST).and()
                        .path("/api/v1/user/findOneRouteMethodPost").uri(API_HOST))

                // 指定 路径参数可以请求
                .route(r -> r.path("/api/v1/user/findOneRoutePath/{id}").uri(API_HOST))

                // 指定 查询参数可以请求
                .route(r -> r.query("accessToken","123456").and()
                        .path("/api/v1/user/findOneRouteQuery").uri(API_HOST))

                // 指定 远程ip可以请求
                .route(r -> r.remoteAddr("127.0.0.1").and()
                        .path("/api/v1/user/findOneRouteRemoteAddr").uri(API_HOST))


                // 匹配前缀
//                .route(r -> r.path("/api/v1/**").uri(API_HOST))

                .build();
    }
}

相关文章

spring cloud gateway 路由Route Predicate Factories(Finchley.RELEASE版本)

原文链接:spring cloud gateway 路由Fluent Java Routes API(Finchley.RELEASE版本)

spring cloud gateway hello world(Finchley.RELEASE版本)

oyhk 学习笔记

spring cloud 最近发布了最新版本为 Finchley.RELEASE,Spring cloud ,今天来尝试一下最新版本的spring cloud gateway hello world。


api网关用处

API网关是一个服务器,是系统的唯一入口。从面向对象设计的角度看,它与外观模式类似。API网关封装了系统内部架构,为每个客户端提供一个定制的API。它可能还具有其它职责,如身份验证、监控、负载均衡、缓存、请求分片与管理、静态响应处理。
API网关方式的核心要点是,所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有的非业务功能。通常,网关也是提供REST/HTTP的访问API。服务端通过API-GW注册和管理服务

工作原理

spring cloud gateway 介绍(Finchley.RELEASE版本)

准备

  • jdk8 以上版本
  • maven3.3 以上版本
  • idea
  • git

拉去demo代码

git clone https://gitee.com/381895649/mkfree-sample.git

代码拉取后直接使用idea打开就可以了,目录结构如下

网关代理请求

  • 在项目spring-cloud-gateway-hello-world-api已提供简单的api接口,简单贴一下controller代码
import com.mkfree.sample.spring_cloud_gateway_hello_world_api.domain.User;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

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

@RestController
public class UserController {

    private static Map<Integer, User> userMap = new HashMap<>();

    // 这里模拟存在用户
    static {
        userMap.put(1, new User(1, "oyhk"));
        userMap.put(2, new User(2, "路人甲"));
    }

    /**
     * 通过id获取用户
     * @param id
     * @return
     */
    @GetMapping(value = "/api/v1/user/findOne")
    public User findOneV1(int id) {
        return userMap.get(id);
    }
    /**
     * 通过id获取用户
     * @param id
     * @return
     */
    @GetMapping(value = "/api/v2/user/findOne")
    public User findOneV2(int id){
        return userMap.get(id);
    }
}

把spring-cloud-gateway-hello-world-api跑起来,访问 http://127.0.0.1:9011/api/v1/user/findOne?id=1 ,可以返回模拟用户id为1的数据。
blob.jpg

  • 通过网关代理请求
    自定义路由方式代理,贴一下配置文件方式代码 application.yml
# EMBEDDED SERVER CONFIGURATION (ServerProperties)
spring.application.name: gateway

server:
  port: 9000
  tomcat.uri-encoding: UTF-8

spring:
  cloud:
    gateway:
      routes:
        - id: api
          uri: http://127.0.0.1:9011 #转发代理服务
          predicates:
          - Path=/api/v1/** #以 /api/v1 前缀

ok,以上就已经完成通过网关转发请求,在浏览器分别输入
http://127.0.0.1:9011/api/v1/user/findOne?id=1
http://127.0.0.1:9000/api/v1/user/findOne?id=1
blob.jpg
当以上请求能正常返回数据,那么网关最简单的hello world已经成功了!

  • Fluent Java Routes API 方式,贴一下具体代码
    这里我添加了/api/v2的版本,为了区分自定义路由方式和配置文件方式
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class BootstrapGateway {

    private static Logger log = LoggerFactory.getLogger(BootstrapGateway.class);

    public static void main(String[] args) {
        ConfigurableApplicationContext configurableApplicationContext = SpringApplication.run(BootstrapGateway.class, args);
        String[] strings = configurableApplicationContext.getBeanDefinitionNames();
    }


    /**
     * 自定义路由方式
     *
     * @param builder
     * @return
     */
    @Bean
    public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
        return builder.routes()
                // 路由为/api/v2 前缀的转发到 127.0.0.1:9000
                .route(r -> r.path("/api/v2/**").uri("http://127.0.0.1:9011"))
                .build();
    }
}

下面尝试一下访问
http://127.0.0.1:9011/api/v2/user/findOne?id=2
http://127.0.0.1:9000/api/v2/user/findOne?id=2
返回的数据一样就成功通过网关代理。
blob.jpg

原文链接:spring cloud gateway hello world(Finchley.RELEASE版本)

spring cloud gateway 介绍(Finchley.RELEASE版本)

oyhk 学习笔记

spring cloud gateway介绍

Spring Cloud Gateway是Spring官方基于Spring 5.0,Spring Boot 2.0和Project Reactor等技术开发的网关,Spring Cloud Gateway旨在为微服务架构提供一种简单而有效的统一的API路由管理方式。Spring Cloud Gateway作为Spring Cloud生态系中的网关,目标是替代Netflix ZUUL,其不仅提供统一的路由方式,并且基于Filter链的方式提供了网关基本的功能,例如:安全,监控/埋点,弹性,和限流等。
spring cloud gateway 官方网站

Spring Cloud Gateway 特征

  • Java 8
  • Spring Framework 5
  • Spring Boot 2
  • 动态路由
  • 内置到Spring Handler映射中的路由匹配
  • 基于HTTP请求的路由匹配 (Path, Method, Header, Host, etc…​)
  • 过滤器作用于匹配的路由
  • 过滤器可以修改下游HTTP请求和HTTP响应 (Add/Remove Headers, Add/Remove Parameters, Rewrite Path, Set Path, Hystrix, etc…​)
  • 通过API或配置驱动
  • 支持Spring Cloud DiscoveryClient配置路由,与服务发现与注册配合使用

spring cloud Gateway 工作流程


客户端向 Spring Cloud Gateway 发出请求。然后在 Gateway Handler Mapping 中找到与请求相匹配的路由,将其发送到 Gateway Web Handler。Handler 再通过指定的过滤器链来将请求发送到我们实际的服务执行业务逻辑,然后返回。
过滤器之间用虚线分开是因为过滤器可能会在发送代理请求之前(“pre”)或之后(“post”)执行业务逻辑。

vs Netflix Zuul

Zuul基于servlet 2.5(使用3.x),使用阻塞API。 它不支持任何长连接,如websockets。而Gateway建立在Spring Framework 5,Project Reactor和Spring Boot 2之上,使用非阻塞API。 Websockets得到支持,并且由于它与Spring紧密集成,所以将会是一个更好的开发体验。

原文链接:spring cloud gateway 介绍(Finchley.RELEASE版本)