CrazyAirhead

疯狂的傻瓜,傻瓜也疯狂——傻方能执著,疯狂才专注!

0%

Solon Cloud —— 服务网关

说明

Solon Cloud Gateway 是 一个可 Java 编程的分布式网关,提供了服务路由的能力和各种拦截的支持,只要是 http 服务(不需要关心实现的语言)都可以通过 Solon Cloud Gateway 进行代理转发,代理转发的服务也不一定要注册到服务注册中心。

虽然 Solon Cloud 提供了网关的实现,但在其官网出于性能及资源等原因的考虑,推荐优先使用专业网关(ngix,apisix,kong, k8s ingress controller),其次才是 Java 实现的网关 Spring Cloud Gateway 和 Solon Cloud Gateway。因此,在实际的项目中需要结合自己的项目情况来选择。

以下是微服务框架配合网关的架构图,图片来自 Solon 官网。https://solon.noear.org/article/328

img

依赖

1
2
3
4
5
6
7
8
9
10
dependencies {
implementation platform(project(":demo-parent"))

implementation("org.noear:nacos2-solon-cloud-plugin")
implementation("org.noear:solon.cloud.gateway")

annotationProcessor("org.mapstruct:mapstruct-processor:${mapstructVersion}")

testImplementation("org.noear:solon-test-junit5")
}

实现

响应式

Solon Cloud Gateway 使用响应式接口,由 Solon-Rx 来实现,是基于 reactive-streams 封装的 RxJava 极简版。目前仅一个接口 Completable,意为:可完成的发布者。

接口 说明
Completable 作为返回类型
Completable::complete() 构建完成发布者
Completable::error(cause) 构建异常发布者
Completable::create((emitter)->{…}) 构建发射器发布者

路由检测器

内置路由检测器

匹配检测器工厂 本置前缀 说明与示例
AfterPredicateFactory After= After 时间检测器,ZonedDateTime 格式 (After=2017-01-20T17:42:47.789-07:00[America/Denver])
BeforePredicateFactory Before= After 时间检测器,ZonedDateTime 格式 (Before=2017-01-20T17:42:47.789-07:00[America/Denver])
CookiePredicateFactory Cookie= Cookie 检测器 (Cookie=token)(Cookie=token, ^user.)
HeaderPredicateFactory Header= Header 检测器 (Header=token)(Header=token, ^user.)
MethodPredicateFactory Method= Method 检测器 (Method=GET,POST)
PathPredicateFactory Path= Path 检测器(支持多路径匹配,以”,”号隔开) (Path=/demo/) ,(Path=/demo/,/hello/**)

通常情况下,我们使用 PathPredicateFactory 就够用,不需要额外的配置。

路由过滤器

内置过滤器

Solon 提供了一些内置的路由过滤器,可以通过直接的配置就可以使用。

过滤器工厂 本置前缀 说明与示例
AddRequestHeaderFilterFactory AddRequestHeader= 添加请求头 (AddRequestHeader=Demo-Ver,1.0)
AddResponseHeaderFilterFactory AddResponseHeader= 添加响应头 (AddResponseHeader=Demo-Ver,1.0)
PrefixPathFilterFactory PrefixPath= 附加路径前缀 (PrefixPath=/app)
RedirectToFilterFactory RedirectTo= 跳转到 (RedirectTo=302,http://demo.org/a,true)
RemoveRequestHeaderFilterFactory RemoveRequestHeader= 移除请求头 (RemoveRequestHeader=Demo-Ver,1.0)
RemoveResponseHeaderFilterFactory RemoveResponseHeader= 移除响应头 (RemoveResponseHeader=Demo-Ver,1.0)
StripPrefixFilterFactory StripPrefix= 移除路径前缀段数 (StripPrefix=1)

定制

通过RouteFilterFactory定制路由过滤规则,实现不同类型的接口过滤。

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
package com.example.demo.solon.filter;

import io.vertx.core.buffer.Buffer;
import lombok.extern.slf4j.Slf4j;
import org.dromara.hutool.core.text.StrUtil;
import org.noear.solon.annotation.Component;
import org.noear.solon.cloud.gateway.exchange.ExContext;
import org.noear.solon.cloud.gateway.exchange.ExFilter;
import org.noear.solon.cloud.gateway.exchange.ExFilterChain;
import org.noear.solon.cloud.gateway.route.RouteFilterFactory;
import org.noear.solon.rx.Completable;

/**
* @author airhead
*/
@Component
@Slf4j
public class AuthFilterFactory implements RouteFilterFactory {
public static final String TOKEN = "token";
public static final String EMPTY = "empty";

@Override
public String prefix() {
return "Auth";
}

@Override
public ExFilter create(String config) {
if (TOKEN.equals(config)) {
return new TokenFilter();
} else {
return new EmptyAuthFilter();
}
}

/** 默认的空鉴权 */
public static class EmptyAuthFilter implements ExFilter {
public EmptyAuthFilter() {}

@Override
public Completable doFilter(ExContext ctx, ExFilterChain chain) {
log.info("empty filter, do nothing");

return chain.doFilter(ctx);
}
}

/** token 鉴权 ,如果鉴权逻辑比较复杂的,用独立类 */
public static class TokenFilter implements ExFilter {
@Override
public Completable doFilter(ExContext ctx, ExFilterChain chain) {
String token = ctx.rawHeader("token");
if (StrUtil.isBlank(token)) {
ctx.newResponse().status(401);
ctx.newResponse().body(Buffer.buffer("token is empty"));
return Completable.complete();
}

return chain.doFilter(ctx);
}
}
}

其中 EmptyFilter 只是简单的记录日志,而 TokenFilter 会从头中获取 token 信息判断是否为空,为空是返回错误信息,不为空时正常情况。

配置

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
server.port: 8000

solon.app:
name: 'demo-cloud-gateway'
# group: "DEFAULT_GROUP" # 默认值为 DEFAULT_GROUP
# namespace: "public" # 默认值为 public

knife4j.enable: true

# 运行样例时,屏蔽,或者配置app-local.yml
solon.env: local

solon.cloud.nacos:
server: "localhost:8848" #nacos2 服务地址
config:
load: "demo-cloud-gateway.yml"

solon.cloud.gateway:
discover:
enabled: false
excludedServices:
- "self-service"
httpClient:
responseTimeout: 1800 #单位:秒
routes:
- id: demo
index: 0 #默认为0
target: "http://127.0.0.1:8081"
predicates:
- "Path=/demo/**"
filters:
- "Auth=empty"
- "StripPrefix=1"
timeout:
responseTimeout: 1800 #单位:秒
- id: service01
index: 0 #默认为0
target: lb://demo-cloud-service01
predicates:
- "Path=/service01/**"
filters:
- "Auth=token"
- "StripPrefix=1"
timeout:
responseTimeout: 1800 #单位:秒

这里使用 service01 作为上游服务,配置直接地址和使用服务注册的地址来分别测试。

使用直接地址时,使用了一个空的鉴权逻辑,然后利用 Solon 内置的StripPrefix 来移除url 中的第一段,然后请求实际的服务。

使用注册服务时,使用了一个简单的Token鉴权逻辑,然后利用 Solon 内置的StripPrefix 来移除url 中的第一段,然后请求实际的服务。

验证

启动 demo-cloud-gateway 和 demo-cloud-service01。

通过service01来调用,也就是通过注册服务来调用。

img

通过demo来调用,也就是通过直接地址来调用。

img

如果 servic01 没有启动的情况返回的是404的错误。

小结

Solon Cloud Gateway 通过配置的方式就可以对服务进行代理转发,在实际的业务使用中通常会与服务发现一起,实现负载均衡,也可以通过编程的方式进一步的定制自己的业务逻辑,实现统一鉴权,限流熔断,灰度发布等功能。

欢迎关注我的其它发布渠道