完成API网关功能

api-gateway-netflix-zuul
DerekYRC 2 years ago
parent c42a145cd5
commit d54e680315

@ -12,21 +12,13 @@
希望本项目对你有所帮助,请给个**STAR吧谢谢!!!**
## 功能
#### 基础篇
* [服务注册](https://github.com/DerekYRC/mini-spring-cloud/blob/main/changelog.md#服务注册)
* [服务发现](https://github.com/DerekYRC/mini-spring-cloud/blob/main/changelog.md#服务发现)
* [负载均衡](https://github.com/DerekYRC/mini-spring-cloud/blob/main/changelog.md#集成ribbon实现客户端负载均衡)
* [集成Feign简化调用方式](https://github.com/DerekYRC/mini-spring-cloud/blob/main/changelog.md#集成Feign简化调用方式)
* [API网关](https://github.com/DerekYRC/mini-spring-cloud/blob/main/changelog.md#API网关)
* [流量控制]()
* [熔断降级]()
* [API 网关]()
#### 扩展篇
> TODO 基础篇遗留的功能点
* [自定义负载均衡规则]()
## 使用方法
阅读 [changelog.md](https://github.com/DerekYRC/mini-spring-cloud/blob/main/changelog.md)

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

@ -1313,3 +1313,372 @@ public interface EchoService {
访问```http://localhost:8080/bar```
# [API网关](#API网关)
> 代码分支: api-gateway-netflix-zuul
## 关于Netflix Zuul
Netflix Zuul是一个提供动态路由、监控、弹性容量、安全等功能的基于第七层网络协议的应用程序网关。
#### Zuul核心框架和执行流程
![](./assets/zuul-framework.png)
ZuulServlet负责拦截http请求然后将http请求交给由ZuulFilter组成的过滤器链处理ZuulFilter加载模块负责加载ZuulFilter。
可见ZuulFilter过滤器是zuul框架中的核心API网关的鉴权、限流、权限、熔断、协议转换、错误码统一、缓存、日志、监控、告警等等功能可以实现ZuulFilter过滤器来实现。
#### ZuulFilter过滤器类型及执行顺序
ZuulFilter过滤器分为四种类型
- pre类型调用远程服务之前执行
- route路由、发起远程调用
- post向客户端输出响应报文
- error处理过滤器链执行过程中出现的错误
ZuulServlet.service方法:
![](./assets/zuul-servlet.png)
从ZuulServlet.service方法中能看出四种类型的过滤器的执行顺序如下图所示
![](./assets/zuul-filter.png)
## 功能实现
EnableZuulProxy注解启用API网关功能
```java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(ZuulServerAutoConfiguration.class)
public @interface EnableZuulProxy {
}
```
EnableZuulProxy注解引入配置类ZuulServerAutoConfiguration该配置类配置了ZuulServlet、过滤器加载模块的FilterRegistry、实现的三个ZuulFilter以及PreDecorationFilter过滤器需要使用的路由定位器RouteLocator。
```java
@Configuration
@EnableConfigurationProperties({ZuulProperties.class})
public class ZuulServerAutoConfiguration {
@Autowired
protected ZuulProperties zuulProperties;
/**
* 注册ZuulServlet用于拦截处理http请求
*/
@Bean
public ServletRegistrationBean zuulServlet() {
return new ServletRegistrationBean<>(new ZuulServlet(), zuulProperties.getServletPath());
}
/**
* 路由定位器
*/
@Bean
public RouteLocator simpleRouteLocator() {
return new SimpleRouteLocator(zuulProperties);
}
/**
* pre类型过滤器根据RouteLocator来进行路由规则的匹配
*/
@Bean
public ZuulFilter preDecorationFilter(RouteLocator routeLocator) {
return new PreDecorationFilter(routeLocator);
}
/**
* route类型过滤器使用ribbon负载均衡器进行http请求
*/
@Bean
ZuulFilter ribbonRoutingFilter(LoadBalancerClient loadBalancerClient) {
return new RibbonRoutingFilter(loadBalancerClient);
}
/**
* post类型过滤器向客户端输出响应报文
*/
@Bean
ZuulFilter sendResponseFilter() {
return new SendResponseFilter();
}
/**
* 注册过滤器
*/
@Bean
public FilterRegistry filterRegistry(Map<String, ZuulFilter> filterMap) {
FilterRegistry filterRegistry = FilterRegistry.instance();
filterMap.forEach((name, filter) -> {
filterRegistry.put(name, filter);
});
return filterRegistry;
}
}
```
只针对正常流程实现了以下三个过滤器,想了解更多过滤器可以参考这篇文章: [**Spring Cloud 源码分析Zuul核心过滤器**](https://blog.didispace.com/spring-cloud-source-zuul/)
- pre类型过滤器PreDecorationFilter使用路由定位器RouteLocator根据请求路径匹配路由将路由信息放进请求上下文RequestContext中
```java
/**
* pre类型过滤器根据RouteLocator来进行路由规则的匹配
*/
public class PreDecorationFilter extends ZuulFilter {
private static Logger logger = LoggerFactory.getLogger(PreDecorationFilter.class);
private RouteLocator routeLocator;
public PreDecorationFilter(RouteLocator routeLocator) {
this.routeLocator = routeLocator;
}
@Override
public String filterType() {
return PRE_TYPE;
}
@Override
public int filterOrder() {
return 5;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext requestContext = RequestContext.getCurrentContext();
String requestURI = requestContext.getRequest().getRequestURI();
//获取匹配的路由
Route route = routeLocator.getMatchingRoute(requestURI);
if (route != null) {
requestContext.put(REQUEST_URI_KEY, route.getPath());
requestContext.set(SERVICE_ID_KEY, route.getLocation());
} else {
logger.error("获取不到匹配的路由, requestURI: {}", requestContext);
}
return null;
}
}
```
路由定位器:
```java
/**
* 路由定位器
*/
public interface RouteLocator {
/**
* 获取匹配的路由
*
* @param path
* @return
*/
Route getMatchingRoute(String path);
}
```
```java
/**
* 路由定位器实现类
*/
public class SimpleRouteLocator implements RouteLocator {
private ZuulProperties zuulProperties;
private PathMatcher pathMatcher = new AntPathMatcher();
public SimpleRouteLocator(ZuulProperties zuulProperties) {
this.zuulProperties = zuulProperties;
}
@Override
public Route getMatchingRoute(String path) {
for (Map.Entry<String, ZuulProperties.ZuulRoute> entry : zuulProperties.getRoutes().entrySet()) {
ZuulProperties.ZuulRoute zuulRoute = entry.getValue();
String pattern = zuulRoute.getPath();
if (pathMatcher.match(pattern, path)) {
String targetPath = path.substring(pattern.indexOf("*") - 1);
return new Route(targetPath, zuulRoute.getServiceId());
}
}
return null;
}
}
```
- route类型过滤器RibbonRoutingFilter根据PreDecorationFilter过滤器匹配的路由信息发起远程调用将调用结果放进请求上下文RequestContext
```java
/**
* route类型过滤器使用ribbon负载均衡器进行http请求
*/
public class RibbonRoutingFilter extends ZuulFilter {
private static Logger logger = LoggerFactory.getLogger(RibbonRoutingFilter.class);
private LoadBalancerClient loadBalancerClient;
public RibbonRoutingFilter(LoadBalancerClient loadBalancerClient) {
this.loadBalancerClient = loadBalancerClient;
}
@Override
public String filterType() {
return ROUTE_TYPE;
}
@Override
public int filterOrder() {
return 10;
}
@Override
public boolean shouldFilter() {
RequestContext requestContext = RequestContext.getCurrentContext();
return requestContext.get(SERVICE_ID_KEY) != null;
}
@Override
public Object run() throws ZuulException {
try {
RequestContext requestContext = RequestContext.getCurrentContext();
//使用ribbon的负载均衡能力发起远程调用
//TODO 简单实现,熔断降级章节再完善
String serviceId = (String) requestContext.get(SERVICE_ID_KEY);
ServiceInstance serviceInstance = loadBalancerClient.choose(serviceId);
if (serviceInstance == null) {
logger.error("根据serviceId查询不到服务示例serviceId: {}", serviceId);
return null;
}
String requestURI = (String) requestContext.get(REQUEST_URI_KEY);
String url = serviceInstance.getUri().toString() + requestURI;
HttpRequest httpRequest = HttpUtil.createRequest(Method.POST, url);
HttpResponse httpResponse = httpRequest.execute();
//将响应报文的状态码和内容写进请求上下文中
requestContext.setResponseStatusCode(httpResponse.getStatus());
requestContext.setResponseDataStream(httpResponse.bodyStream());
return httpResponse;
} catch (Exception e) {
rethrowRuntimeException(e);
}
return null;
}
}
```
- post类型过滤器SendResponseFilter将RibbonRoutingFilter过滤器发起远程调用的结果作为响应报文输出给客户端
```java
/**
* post类型过滤器向客户端输出响应报文
*/
public class SendResponseFilter extends ZuulFilter {
private static Logger logger = LoggerFactory.getLogger(SendResponseFilter.class);
@Override
public String filterType() {
return POST_TYPE;
}
@Override
public int filterOrder() {
return 1000;
}
@Override
public boolean shouldFilter() {
return RequestContext.getCurrentContext()
.getResponseDataStream() != null;
}
@Override
public Object run() throws ZuulException {
//向客户端输出响应报文
RequestContext requestContext = RequestContext.getCurrentContext();
InputStream inputStream = requestContext.getResponseDataStream();
try {
HttpServletResponse servletResponse = requestContext.getResponse();
servletResponse.setCharacterEncoding("UTF-8");
OutputStream outStream = servletResponse.getOutputStream();
StreamUtils.copy(inputStream, outStream);
} catch (Exception e) {
rethrowRuntimeException(e);
} finally {
//关闭输入输出流
if (inputStream != null) {
try {
inputStream.close();
} catch (Exception e) {
logger.error("关闭输入流失败", e);
}
}
//Servlet容器会自动关闭输出流
}
return null;
}
}
```
## 测试:
启动API网关ApiGatewayApplication
API网关代码:
```java
@EnableZuulProxy
@SpringBootApplication
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
}
```
配置application.yml:
```yaml
spring:
application:
name: api-gateway-application
cloud:
tutu:
discovery:
server-addr: localhost:6688
service: ${spring.application.name}
server:
port: 8888
zuul:
servlet-path: /*
routes:
route_provider_application:
path: /provider-application/**
service-id: provider-application
```
访问```http://localhost:8888/provider-application/echo```
# 流量控制和熔断降级
TODO 待研究Sentinel完再写本章节估计得隔一段时间~~~
Loading…
Cancel
Save