idea使用java10 maven 支持java10

oyhk 学习笔记


maven 支持java10

<?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>artifactId</artifactId>
    <version>1.0-SNAPSHOT</version>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.7.0</version>
                <configuration>
                    <release>10</release>
                    <compilerVersion>10</compilerVersion>
                </configuration>
                <dependencies>
                    <dependency>
                        <groupId>org.ow2.asm</groupId>
                        <artifactId>asm</artifactId>
                        <version>6.2</version> <!-- Use newer version of ASM -->
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>
</project>

如发现以下错误

打开setting 如下图

修改为 10

就可以解决问题了

java 设计模式-责任链模式(仿java servlet filter)

spring cloud gateway GatewayFilter 网关过滤器

oyhk 学习笔记
spring cloud gateway 过滤器分为两种

  • GlobalFilter : 全局过滤器,不需要在配置文件中配置,作用在所有的路由上,最终通过GatewayFilterAdapter包装成GatewayFilterChain可识别的过滤器
  • GatewayFilter : 需要通过spring.cloud.routes.filters 配置在具体路由下,只作用在当前路由上或通过spring.cloud.default-filters配置在全局,作用在所有路由上

下面先探探GatewayFilter如何使用,spring cloud gateway 版本为 Finchley.RELEASE。


graph LR
A[Square Rect] — Link text –> B((Circle))
A –> C(Round Rect)
B –> D{Rhombus}
C –> D

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版本)

wordpress IP验证不当漏洞(4.9.8版本)

oyhk 学习笔记

近日使用阿里云服务器搭建wordpress个人博客系统,云盾里检查到又漏洞。

漏洞信息

  • 漏洞名称:wordpress IP验证不当漏洞
  • 提示信息:wordpress /wp-includes/http.php文件中的wp_http_validate_url函数对输入IP验证不当,导致黑客可构造类似于012.10.10.10这样的畸形IP绕过验证,进行SSRF。

如何解决

找到对应的 /wp-includes/http.php,我的wordpress 4.9.8 版本在文件中的 533 行,代码截图

我们需要把这行代码修改为

具体代码
旧代码

if ( isset( $parsed_home['host'] ) ) {
        $same_host = strtolower( $parsed_home['host'] ) === strtolower( $parsed_url['host'] );
} else {
        $same_host = false;
}

修改为

if ( isset( $parsed_home['host'] ) ) {
        $same_host = (strtolower($parsed_home['host']) === strtolower($parsed_url['host']) || 'localhost' === strtolower($parsed_url['host']));
} else {
        $same_host = false;
}

验证漏洞是否修复

再次点击阿里云验证,如下图

原文链接:wordpress IP验证不当漏洞(4.9.8版本)

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版本)