Gateway service
这个章节中,介绍如何通过网关转发请求。java-chassis提供了非常灵活的网关服务,开发者能够非常简单的实现微服务之间的转发,网关拥有客户端一样的服务治理能力。同时,开发者可以使用vert.x暴漏的HTTP API,实现非常灵活的转发控制。
网关服务由一系列的VertxHttpDispatcher组成,开发者通过继承AbstractEdgeDispatcher,来实现自己的转发机制。
为了实现gateway-service将请求转发到file-service,定义了如下规则:
-
直接请求file-service: DELETE http://localhost:9091/delete
-
通过网关:DELETE http://localhost:9090/api/file-service/delete
达到这个目的的代码如下,在请求处理的时候,使用EdgeInvocation,可以实现请求转发,并开启各种治理功能。下面代码的核心内容是定义转发规则regex。
public class ApiDispatcher extends AbstractEdgeDispatcher {
@Override
public int getOrder() {
return 10002;
}
@Override
public void init(Router router) {
String regex = "/api/([^\\/]+)/(.*)";
router.routeWithRegex(regex).handler(CookieHandler.create());
router.routeWithRegex(regex).handler(createBodyHandler());
router.routeWithRegex(regex).failureHandler(this::onFailure).handler(this::onRequest);
}
protected void onRequest(RoutingContext context) {
Map<String, String> pathParams = context.pathParams();
String microserviceName = pathParams.get("param0");
String path = "/" + pathParams.get("param1");
EdgeInvocation invoker = new EdgeInvocation();
invoker.init(microserviceName, context, path, httpServerFilters);
invoker.edgeInvoke();
}
}
为了实现gateway-service将请求转发到porter-website,定义了如下规则:
-
直接请求porter-website: GET http://localhost:9093/index.html
UI静态页面信息不需要实现治理能力(服务治理能力需要契约,静态页面不存在接口契约),因此直接使用vert.x的API实现请求转发。在下面的代码中,还使用java chassis API做了服务发现,并实现了一个简单的RoundRobin负载均衡策略,从而允许porter-website也进行多实例部署。
public class UiDispatcher extends AbstractEdgeDispatcher {
private static Logger LOGGER = LoggerFactory.getLogger(UiDispatcher.class);
private static Vertx vertx = VertxUtils.getOrCreateVertxByName("web-client", null);
private static HttpClient httpClient = vertx.createHttpClient(new HttpClientOptions());
private Map<String, DiscoveryTree> discoveryTrees = new ConcurrentHashMapEx<>();
private AtomicInteger counter = new AtomicInteger(0);
@Override
public int getOrder() {
return 10001;
}
@Override
public void init(Router router) {
String regex = "/ui/([^\\/]+)/(.*)";
router.routeWithRegex(regex).failureHandler(this::onFailure).handler(this::onRequest);
}
protected void onRequest(RoutingContext context) {
Map<String, String> pathParams = context.pathParams();
String microserviceName = pathParams.get("param0");
String path = "/" + pathParams.get("param1");
URI uri = chooseServer(microserviceName);
if (uri == null) {
context.response().setStatusCode(404);
context.response().end();
return;
}
// 使用HttpClient转发请求
HttpClientRequest clietRequest =
httpClient.request(context.request().method(),
uri.getPort(),
uri.getHost(),
"/" + path,
clientResponse -> {
context.request().response().setChunked(true);
context.request().response().setStatusCode(clientResponse.statusCode());
context.request().response().headers().setAll(clientResponse.headers());
clientResponse.handler(data -> {
context.request().response().write(data);
});
clientResponse.endHandler((v) -> context.request().response().end());
});
clietRequest.setChunked(true);
clietRequest.headers().setAll(context.request().headers());
context.request().handler(data -> {
clietRequest.write(data);
});
context.request().endHandler((v) -> clietRequest.end());
}
private URI chooseServer(String serviceName) {
URI uri = null;
DiscoveryContext context = new DiscoveryContext();
context.setInputParameters(serviceName);
DiscoveryTree discoveryTree = discoveryTrees.computeIfAbsent(serviceName, key -> {
return new DiscoveryTree();
});
VersionedCache serversVersionedCache = discoveryTree.discovery(context,
RegistryUtils.getAppId(),
serviceName,
DefinitionConst.VERSION_RULE_ALL);
Map<String, MicroserviceInstance> servers = serversVersionedCache.data();
String[] endpoints = asArray(servers);
if (endpoints.length > 0) {
int index = Math.abs(counter.getAndIncrement() % endpoints.length);
String endpoint = endpoints[index];
try {
uri = new URI(endpoint);
} catch (URISyntaxException e) {
LOGGER.error("", e);
}
}
return uri;
}
private String[] asArray(Map<String, MicroserviceInstance> servers) {
List<String> endpoints = new LinkedList<>();
for (MicroserviceInstance instance : servers.values()) {
endpoints.addAll(instance.getEndpoints());
}
return endpoints.toArray(new String[endpoints.size()]);
}
}
完成VertxHttpDispatcher开发后,需要通过SPI的方式加载到系统中,需要增加META-INF/services/org.apache.servicecomb.transport.rest.vertx.VertxHttpDispatcher配置文件,并将增加的两个实现写入该配置文件中。
网关服务开发完成后,所有的用户请求都可以通过网关来发送。开发者通过通过设置防火墙等机制,限制用户直接访问内部服务,保证内部服务的安全。