网关
为什么需要网关
在整个微服务架构中,包含了许多不同的应用,比如下图所示的mst-user-service、mst-good-service和mst-order-service是三个独立的应用,这些服务都需要对客户端的请求的进行鉴权(Authentication),最简单粗暴的方法就是像图中一样,为每个微服务应用都实现一套用于校验的过滤器或拦截器。
但这种做法相当于做了很多不必要的重复工作,有了网关之后,可以在网关服务中做统一鉴权操作,如下图所示
网关的基本功能
- 网关核心功能是路由转发,因此不要有耗时操作在网关上处理,让请求快速转发到后端服务上。
- 网关还能做统一的熔断、限流、认证、日志监控等。
关于Spring Cloud Gateway
Spring Cloud Gateway是由spring官方基于Spring5.0、Spring Boot2.0、Project Reactor等技术开发的网关,使用非阻塞API,它支持Websockets,目的是代替原先版本中的Spring Cloud Netfilx Zuul,目前Netfilx已经开源了Zuul2.0,但Spring 没有考虑集成,而是推出了自己开发的Spring Cloud GateWay。Spring Cloud Gateway使用的netty+webflux实现。
代码示例
需要用到3个项目,eureka-server、gateway、biz-user-service。
eureka-server
eureka注册中心的搭建前面的文章也已经介绍过了,这里直接贴代码。
pom.xml文件中引入依赖
1
2
3
4<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>application.yml配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13server:
port: 9110
spring:
application:
name: eureka-server
eureka:
client:
service-url:
defaultZone: http://localhost:9110/eureka #注册中心地址,其他程序通过此URL进行注册
register-with-eureka: false #当前应用就是Eureka Server,不需要注册到eureka
fetch-registry: false #因为euraka-server是单个节点,不需要同步其他节点的数据
logging:
config: classpath:logback.xml启动引导类
1
2
3
4
5
6
7
8
9
public class EurekaServerApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaServerApplication.class, args);
}
}
biz-user-service
biz-user-service是一个用户业务的服务,在前面的文章里也用到过。
pom.xml文件中引入依赖
1
2
3
4
5
6
7
8<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>application.yml配置文件
1
2
3
4
5
6
7
8
9
10
11server:
port: 8001
spring:
application:
name: biz-user-service
eureka:
client:
service-url:
defaultZone: http://localhost:9110/eureka
logging:
config: classpath:logback.xmlcontroller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
274j
"/user", method = {RequestMethod.GET, RequestMethod.POST}) (value =
public class UserController {
private UserService userService;
"/getById") (
public ResponseData getById(@RequestParam Long id) {
User user = userService.getById(id);
log.info("用户|查询用户|id={}|{}", id, user);
return ResponseData.data(user);
}
"/getCoupon") (
public ResponseData getCoupon(@RequestParam Long userId, @RequestParam String type) {
log.info("用户|查询用户的优惠券|userId={},type={}", userId, type);
return ResponseData.success();
}
}启动引导类
1
2
3
4
5
6
7
8
9
10
public class UserServiceBootstrap {
public static void main(String[] args) {
SpringApplication.run(UserServiceBootstrap.class, args);
}
}
gateway网关
这里开始就是编写网关项目了。
pom.xml文件中引入依赖
1
2
3
4
5
6
7
8<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>application.yml配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15server:
port: 8015
spring:
application:
name: gateway
cloud:
gateway:
discovery:
locator:
# 是否和服务注册与发现组件结合,设置为 true 后可以直接使用应用名称调用服务
enabled: true
eureka:
client:
service-url:
defaultZone: http://localhost:9110/eureka定义一个全局的过滤器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95package com.lzumetal.springcloud.gateway.filter;
import io.netty.buffer.ByteBufAllocator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
4j
public class ParameterGlobalFilter implements GlobalFilter, Ordered {
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest serverHttpRequest = exchange.getRequest();
String uriString = serverHttpRequest.getURI().toString();
String method = serverHttpRequest.getMethodValue();
if ("POST".equals(method)) {
//从请求里获取Post请求体
String bodyStr = resolveBodyFromRequest(serverHttpRequest);
log.info("【POST请求】|{}|{}", uriString, bodyStr);
//TODO 得到Post请求的请求参数后,做你想做的事
//下面的将请求体再次封装写回到request里,传到下一级,否则,由于请求体已被消费,后续的服务将取不到值
URI uri = serverHttpRequest.getURI();
ServerHttpRequest request = serverHttpRequest.mutate().uri(uri).build();
DataBuffer bodyDataBuffer = stringBuffer(bodyStr);
Flux<DataBuffer> bodyFlux = Flux.just(bodyDataBuffer);
request = new ServerHttpRequestDecorator(request) {
public Flux<DataBuffer> getBody() {
return bodyFlux;
}
};
//封装request,传给下一级
return chain.filter(exchange.mutate().request(request).build());
} else if ("GET".equals(method)) {
Map requestQueryParams = serverHttpRequest.getQueryParams();
log.info("【GET请求】|{}|{}", uriString, requestQueryParams);
//TODO 得到Get请求的请求参数后,做你想做的事
return chain.filter(exchange);
}
return chain.filter(exchange);
}
/**
* 从Flux<DataBuffer>中获取字符串的方法
*
* @return 请求体
*/
private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest) {
//获取请求体
Flux<DataBuffer> body = serverHttpRequest.getBody();
AtomicReference<String> bodyRef = new AtomicReference<>();
body.subscribe(buffer -> {
CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
DataBufferUtils.release(buffer);
bodyRef.set(charBuffer.toString());
});
//获取request body
return bodyRef.get();
}
private DataBuffer stringBuffer(String value) {
byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);
buffer.write(bytes);
return buffer;
}
public int getOrder() {
return 0;
}
}配置路由
1
2
3
4
5
6
7
8
9
10
public class UserServiceRouteConfig {
public void addRoute(RouteLocatorBuilder.Builder routeBuilder) {
routeBuilder.route("user-service",
predicateSpec -> predicateSpec.path("/user/**")
.uri("lb://biz-user-service"));
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
public class RouteConfig {
private UserServiceRouteConfig userServiceRouteConfig;
public RouteLocator activityRouter(RouteLocatorBuilder builder) {
RouteLocatorBuilder.Builder builderRoutes = builder.routes();
userServiceRouteConfig.addRoute(builderRoutes);
return builderRoutes.build();
}
}启动引导类
1
2
3
4
5
6
7
8
9
public class GatewayBootstrap {
public static void main(String[] args) {
SpringApplication.run(GatewayBootstrap.class, args);
}
}
验证网关
启动上面的3个服务,在浏览器中访问:http://localhost:9110/ 。可以看到 gateway 和 biz-user-service 两个服务已经启动并注册了。
直接访问 biz-user-service 的接口:
通过网关服务访问biz-user-service 的接口:
并且看到有日志打印:1
INFO 38264 --- [ctor-http-nio-3] c.l.s.g.filter.ParameterGlobalFilter : 【GET请求】|http://localhost:8015/user/getById?id=2|{id=[2]}