gateway 简介
如果没有gateway,网页需要分别访问 house contract服务。 各服务还需要分别实现安全,流量控制。
一,基础使用
网关的作用-》 路由; 安全; 流量控制
1.添加依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2.路由配置
2.1 通过配置文件配置路由
spring:
application:
name: gateway-server
cloud:
gateway:
routes:
- id: user
# uri: http://localhost:8083 #直接指定服务器地址,不推荐
uri: lb://user # 指定服务器注册实例名
filters: #在请求被转向后端服务之前可以进行更改。
- StripPrefix=1 # 去掉前缀过滤器
predicates: #断言,只有满足断言的请求才会被放过去。
- Path=/services/** # 如果访问路径以services开头,则执行本路由规则
eureka: # 顶格:另外记得入口类添加@EnableEurekaClient
instance:
ip-address: 127.0.0.1
prefer-ip-address: true
client:
fetch-registry: true
register-with-eureka: true
service-url:
defaultZone: http://localhost:8761/eureka/
2.2 通过代码配置路由
一般不使用,了解就好。
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
StripPrefixGatewayFilterFactory factory = new StripPrefixGatewayFilterFactory();
StripPrefixGatewayFilterFactory.Config config = new StripPrefixGatewayFilterFactory.Config();
config.setParts(1);
GatewayFilter stripFilter = factory.apply(config);
return builder.routes()
.route("user", r-> r.path("/services/**")
.filters(gatewayFilterSpec -> gatewayFilterSpec.filter(stripFilter))
.uri("lb://user"))
.build();
}
}
2.3 概念解析:
路由中涉及到两个重要的新概念,断言和过滤器。
断言
断言就是判断,用来决定某个请求是不是应该由当前配置中的服务器来处理。我们最常用的就是Path断言,它是根据请求路径进行判断的。
过滤器
过滤器是对将要交给后端服务的请求进行进一步处理的地方。常用的是StripPrefix,一般和Path断言配合使用,用来将为了路由请求添加的路径去掉。
2.4 负载均衡
可以使用ribbon对路由进行负载均衡算法的自定义:
message:
ribbon:
NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule
3. 实例讲解
假设我们有house服务,有如下配置和代码:
server:
port: 8081
spring:
application:
name: house
以上配置确定house服务的访问端口是8081。 以下代码决定了访问路径为 /house/list
@RestController
@RequestMapping("/house")
public class HouseController {
@GetMapping("/list")
public Result<IPage<House>> list(@RequestParam Integer page, @RequestParam(required = false) Integer size) {
int _size = size != null ? size : defaultSize;
IPage<House> ipage = new Page<>(page, _size);
houseService.page(ipage);
return Result.ok(ipage);
}
综合以上信息我们可以得出,通过house服务自己的端口访问list的url应该为:
http://localhost:8091/house/list?page=1&size=10
如果要通过网关访问,则再假设我们的gateway网关路由配置如下:
server:
port: 8080
spring:
application:
name: gateway
cloud:
gateway:
routes:
- id: house
# uri: http://localhost:8083 #直接指定服务器地址,不推荐
uri: lb://house # 指定服务器注册实例名
filters: #对请求进行进一步处理
- StripPrefix=1 # 去掉前缀过滤器
predicates: #断言
- Path=/order/** # 如果访问路径以services开头,则执行本路由规则
则最终通过网关的访问路径为
http://localhsot:8080/house/house/list?page=1&size=10
当这样一个请求到达网关时具体的处理流程是怎样的呢?
- 首先经过断言判断,因为请求路径 /house/house/list?page=1&size=10 满足 正则表达式 */house/** *,所以该请求将会被转到house服务处理
- 过滤器StripPrefix=1,表示要将请求路径的前1级路径去掉,则经过处理后请求路径变为 /house/list?page=1&size=10
- 因为已经没有别的过滤器,所以请求被转到house服务
- house服务刚好有处理 house/list?page=1&size=10 请求的方法,则访问成功被处理。
二,Route Predicate 一些常见断言
- Path Route Predicate
- Path=/user/{id} # curl http://localhost:8080/services/another/1
- Method Route Predicate
访问方法断言
- Method=GET # curl http://localhost:8080/services/another/1
- Header Route Predicate
请求头路由
- Header=X-Request-Id,\d+ # curl http://localhost:8080/services/another/1 -H X-Request-Id:88
- Cookie Route Predicate
Cookie路由断言
- Cookie=sessionId,test # curl http://localhost:8080/services/another/1 --cookie sessionId=test
- Query Route Predicate
请求参数路由
- Query=name,pu. # http://localhost:8080/services/another/5?name=pu1 参数以name命名,值以pu开始共三位
- RemoteAddr Route Predicate 远端地址路由断言
- RemoteAddr=192.168.1.1/24
- Host Route Predicate 主机地址路由
- Host=**.baidu.com #
- Before Route Predicate
- Before=2022-10-24T16:30:00+08:00[Asia/Shanghai]
- After Route Predicate
- After=2019-09-24T16:30:00+08:00[Asia/Shanghai]
- Between Route Predicate
- Between=2019-09-24T16:30:00+08:00[Asia/Shanghai],2023-09-24T16:30:00+08:00[Asia/Shanghai]
三,Filter
部分过滤器
- AddRequestParameter
cloud:
gateway:
routes:
- id: user
uri: lb://user
filters:
- StripPrefix=1
- AddRequestParameter=username,bw00
- StripPrefix
cloud:
gateway:
routes:
- id: user
uri: lb://user
filters:
- StripPrefix=1
- PrefixPath
cloud:
gateway:
routes:
- id: user
uri: lb://user
filters:
- StripPrefix=1
- PrefixPath=/another
predicates:
- Path=/services/**
- Hystrix 除了之前Hystrix在单独的微服务中使用以外,Hystrix还可以在网关中使用。先添加依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
编写降级处理代码:
@RestController
@RequestMapping
public class IndexController {
@GetMapping("/badthinghappend")
public ResponseEntity fallback4Another() {
return ResponseEntity.ok("badthinghappend ");
}
}
然后通过配置确定当异常发生时将请求降级到上面的路径来处理:
filters:
- StripPrefix=1
- name: Hystrix
args:
name: fallbackcmd
fallbackUri: forward:/badthinghappend
- RequestRateLimiter 请求速率限制是一个重要的技能点,需要重点学习下。因为速率限制需要redis记录一些数据,所以需要先添加依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
然后还得添加redis配置:
spring:
redis:
host: localhost
port: 6379
最后在配置中添加过相关配置
filters:
- name: RequestRateLimiter
args:
redis-rate-limiter.replenishRate: 1 # 放令牌的速率
redis-rate-limiter.burstCapacity: 2 # 令牌桶最大放多少个
redis-rate-limiter.requestedTokens: 20 #每次请求消耗几个令牌(3.0以上支持)
key-resolver: "#{@ipKeyResolver}" # 根据userName来限流
上面的ipKeyResolver会引用项目中定义的实例,该实例需要在项目中定义,如下::
@Configuration
public class RedisRateLimiterConfig {
@Bean
KeyResolver ipKeyResolver() {
return new KeyResolver(){
@Override
public Mono<String> resolve(ServerWebExchange exchange) {
return Mono.just(exchange.getRequest().getRemoteAddress().getHostString());
}
};
}
}
四,全局过滤器(Global Filters)
添加global Filter定义
@Slf4j
@Component
public class LogGlobalFilter implements GlobalFilter, Ordered {
@Override
public int getOrder() {
return 10;
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String path = exchange.getRequest().getPath().value();
log.info("有人访问了: " + path);
return chain.filter(exchange);
}
}
在网关判断用户是否登录:
@Component
public class JWTFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String currentUrl = exchange.getRequest().getPath().value();
if(!"/user/user/login".equals(currentUrl)) {//登录url不要求用户已经登录
MultiValueMap<String, HttpCookie> cookies = exchange.getRequest().getCookies();
if(cookies != null && cookies.getFirst("token") != null) {
HttpCookie token = cookies.getFirst("token"); //获取jwt数据
String jwt = token.getValue();
boolean verify = JWTUtil.verify(jwt, "1234".getBytes()); //校验jwt是否合法
if(!verify) {
return needLogin(exchange);
}
}else {
return needLogin(exchange);
}
}
return chain.filter(exchange);
}
private Mono<Void> needLogin(ServerWebExchange exchange) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
@Override
public int getOrder() {
return 0;
}
}
五,CORS 配置
注意!!!,在网关添加了跨域后,后面微服务自己添加的跨域需要去掉,否则会出错。
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "*"
allowedHeaders: "*"
allowedMethods: "*"
allowCredentials: true
maxAge: 360000
六,常见错误
- 在gateway的pom文件或父pom文件中引入web依赖:
<!--gateway中不能引入!-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>