基於Spring-Cloud-Gateway開發API閘道器的思路
一、什麼是Spring-Cloud-Gateway
Spring Cloud Gateway
是Spring官方基於Spring 5.0,Spring Boot 2.0和Project Reactor等技術開發的閘道器,Spring Cloud Gateway旨在為微服務架構提供一種簡單而有效的統一的API路由管理方式。Spring Cloud Gateway作為Spring Cloud生態系中的閘道器,目標是替代Netflix ZUUL,其不僅提供統一的路由方式,並且基於Filter鏈的方式提供了閘道器基本的功能,例如:安全,監控/埋點,和限流等。
Gateway的路由規則:http://Gateway_HOST:Gateway_PORT/大寫的serviceId/*
詳見: http://xujin.org/sc/gw/gw05/
閘道器的開發關鍵在於定義多個依次執行的過濾器,在裡面進行,限流、token驗權,url校驗、轉發的操作
二、向閘道器注冊微服務節點(生產者)
1.使用spring-clould-zookeeper註冊服務發現
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
<version>1.2.2.RELEASE</version>
<exclusions>
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
</exclusions>
</dependency>
2.application類中新增@EnableDiscoveryClient註解
3.微服務配置示例程式碼如下:
@Configuration
public class SpringCloudZookeeperConfig {
/**
* zookeeper 配置資訊,根據微服務配置不同環境地址
* @return
*/
@Bean
public ZookeeperProperties zookeeperProperties() {
ZookeeperProperties zookeeperProperties = new ZookeeperProperties();
zookeeperProperties.setConnectString("172.17.0.2:2181");
return zookeeperProperties;
}
/**
* 服務註冊發現配置資訊
* @param inetUtils
* @return
*/
@Bean
public ZookeeperDiscoveryProperties zookeeperDiscoveryProperties(InetUtils inetUtils) {
ZookeeperDiscoveryProperties zookeeperDiscoveryProperties = new ZookeeperDiscoveryProperties(inetUtils);
zookeeperDiscoveryProperties.setPreferIpAddress(Boolean.TRUE);
return zookeeperDiscoveryProperties;
}
/**
* 微服務註冊基本資訊 微服務名稱 根據實際情況調整:spring.application.name
* @param context
* @param properties
* @return
*/
@Bean
public ServiceInstanceRegistration serviceInstanceRegistration(
ApplicationContext context, ZookeeperDiscoveryProperties properties) {
String appName = context.getEnvironment().getProperty("spring.application.name",
"application");
String host = properties.getInstanceHost();
if (!StringUtils.hasText(host)) {
throw new IllegalStateException("instanceHost must not be empty");
}
ZookeeperInstance zookeeperInstance = new ZookeeperInstance(context.getId(),
appName, properties.getMetadata());
ServiceInstanceRegistration.RegistrationBuilder builder = ServiceInstanceRegistration.builder().address(host)
.name(appName).payload(zookeeperInstance)
.uriSpec(properties.getUriSpec()).id(host);
if (properties.getInstanceSslPort() != null) {
builder.sslPort(properties.getInstanceSslPort());
}
if (properties.getInstanceId() != null) {
builder.id(properties.getInstanceId());
}
return builder.build();
}
}
通過以上配置,微服務在啟動時就可以向zk的service節點下注冊自己微服務id
微服務id下包括ip和埠號
insurance節點詳情
三、閘道器發現註冊的微服務節點(消費者)
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.curator.x.discovery.ServiceDiscovery;
import org.apache.curator.x.discovery.ServiceInstance;
import org.apache.zookeeper.KeeperException;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.zookeeper.discovery.ZookeeperDiscoveryClient;
import org.springframework.cloud.zookeeper.discovery.ZookeeperInstance;
import org.springframework.cloud.zookeeper.discovery.ZookeeperServiceInstance;
import org.springframework.cloud.zookeeper.discovery.dependency.ZookeeperDependencies;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import static org.springframework.util.ReflectionUtils.rethrowRuntimeException;
public class CustomerZookeeperDiscoveryClient extends ZookeeperDiscoveryClient {
private static final Log log = LogFactory.getLog(org.springframework.cloud.zookeeper.discovery.ZookeeperDiscoveryClient.class);
private final LogUtil logger = new LogUtil(this.getClass());
private final ZookeeperDependencies zookeeperDependencies;
private final ServiceDiscovery<ZookeeperInstance> serviceDiscovery;
public CustomerZookeeperDiscoveryClient(ServiceDiscovery<ZookeeperInstance> serviceDiscovery, ZookeeperDependencies zookeeperDependencies) {
super(serviceDiscovery, zookeeperDependencies);
this.serviceDiscovery = serviceDiscovery;
this.zookeeperDependencies = zookeeperDependencies;
}
@Override
public String description() {
return "Spring Cloud Zookeeper Discovery Client";
}
private static org.springframework.cloud.client.ServiceInstance createServiceInstance(String serviceId, ServiceInstance<ZookeeperInstance> serviceInstance) {
return new ZookeeperServiceInstance(serviceId, serviceInstance);
}
@Override
public List<org.springframework.cloud.client.ServiceInstance> getInstances(
final String serviceId) {
try {
if (getServiceDiscovery() == null) {
return Collections.EMPTY_LIST;
}
String serviceIdToQuery = getServiceIdToQuery(serviceId);
Collection<ServiceInstance<ZookeeperInstance>> zkInstances = getServiceDiscovery().queryForInstances(serviceIdToQuery);
List<org.springframework.cloud.client.ServiceInstance> instances = new ArrayList<>();
for (ServiceInstance<ZookeeperInstance> instance : zkInstances) {
instances.add(createServiceInstance(serviceIdToQuery, instance));
}
return instances;
} catch (KeeperException.NoNodeException e) {
if (log.isDebugEnabled()) {
log.debug("Error getting instances from zookeeper. Possibly, no service has registered.", e);
}
// this means that nothing has registered as a service yes
return Collections.emptyList();
} catch (Exception exception) {
// rethrowRuntimeException(exception);
logger.error("zkInstances error", exception);
}
return new ArrayList<>();
}
private ServiceDiscovery<ZookeeperInstance> getServiceDiscovery() {
return this.serviceDiscovery;
}
private String getServiceIdToQuery(String serviceId) {
if (this.zookeeperDependencies != null && this.zookeeperDependencies.hasDependencies()) {
String pathForAlias = this.zookeeperDependencies.getPathForAlias(serviceId);
return pathForAlias.isEmpty() ? serviceId : pathForAlias;
}
return serviceId;
}
@Override
public List<String> getServices() {
List<String> services = null;
if (getServiceDiscovery() == null) {
log.warn("Service Discovery is not yet ready - returning empty list of services");
return Collections.emptyList();
}
try {
Collection<String> names = getServiceDiscovery().queryForNames();
if (names == null) {
return Collections.emptyList();
}
services = new ArrayList<>(names);
} catch (KeeperException.NoNodeException e) {
if (log.isDebugEnabled()) {
log.debug("Error getting services from zookeeper. Possibly, no service has registered.", e);
}
// this means that nothing has registered as a service yes
return Collections.emptyList();
} catch (Exception e) {
rethrowRuntimeException(e);
}
return services;
}
}
import org.apache.curator.x.discovery.ServiceDiscovery;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.zookeeper.discovery.ZookeeperDiscoveryClient;
import org.springframework.cloud.zookeeper.discovery.ZookeeperInstance;
import org.springframework.cloud.zookeeper.discovery.dependency.ZookeeperDependencies;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ZkConfig {
@Autowired(required = false)
private ZookeeperDependencies zookeeperDependencies;
@Bean
//消費者訂閱服務
public ZookeeperDiscoveryClient zookeeperDiscoveryClient(
ServiceDiscovery<ZookeeperInstance> serviceDiscovery) {
return new CustomerZookeeperDiscoveryClient(serviceDiscovery, zookeeperDependencies);
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.gateway.config.GatewayProperties;
import org.springframework.cloud.gateway.discovery.DiscoveryClientRouteDefinitionLocator;
import org.springframework.cloud.gateway.discovery.DiscoveryLocatorProperties;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.filter.factory.AddResponseHeaderGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.RedirectToGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory;
import org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.zookeeper.ZookeeperProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
import static org.springframework.cloud.gateway.filter.factory.RedirectToGatewayFilterFactory.STATUS_KEY;
import static org.springframework.cloud.gateway.filter.factory.RedirectToGatewayFilterFactory.URL_KEY;
import static org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory.REGEXP_KEY;
import static org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory.REPLACEMENT_KEY;
import static org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory.PATTERN_KEY;
import static org.springframework.cloud.gateway.support.NameUtils.normalizeFilterFactoryName;
import static org.springframework.cloud.gateway.support.NameUtils.normalizeRoutePredicateName;
@Configuration
public class GatewayConfig {
@Autowired
private DubboConfig dubboConfig;
@Bean
public ZookeeperProperties zookeeperProperties() {
ZookeeperProperties zookeeperProperties = new ZookeeperProperties();
zookeeperProperties.setConnectString(dubboConfig.getRegistryAddress());
return zookeeperProperties;
}
//RouteDefinitionLocator 負責讀取路由配置
@Bean
public DiscoveryClientRouteDefinitionLocator discoveryClientRouteDefinitionLocator(DiscoveryClient discoveryClient, DiscoveryLocatorProperties properties) {
return new DiscoveryClientRouteDefinitionLocator(discoveryClient, properties);
}
//寫response的filter,filter在NettyWriteResponseFilte後執行,then方法在其前面執行
@Bean
public NettyResponseFilter nettyResponseFilter() {
return new NettyResponseFilter();
}
@Bean
public UrlEncodeFilter urlEncodeFilter() {
return new UrlEncodeFilter();
}
}
import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.timeout.ReadTimeoutException;
import io.netty.handler.timeout.ReadTimeoutHandler;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.config.HttpClientProperties;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter;
import org.springframework.cloud.gateway.support.TimeoutException;
import org.springframework.core.io.buffer.NettyDataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.AbstractServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import reactor.ipc.netty.NettyPipeline;
import reactor.ipc.netty.http.client.HttpClient;
import reactor.ipc.netty.http.client.HttpClientRequest;
import reactor.ipc.netty.http.client.HttpClientResponse;
import java.net.URI;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.springframework.cloud.gateway.filter.headers.HttpHeadersFilter.filterRequest;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.*;
public class NettyRoutingFilter extends org.springframework.cloud.gateway.filter.NettyRoutingFilter {
private final LogUtil logger = new LogUtil(this.getClass());
@Autowired
private HttpClient httpClient;
@Autowired
private ObjectProvider<List<HttpHeadersFilter>> headersFilters;
@Autowired
private HttpClientProperties properties;
public NettyRoutingFilter(HttpClient httpClient, ObjectProvider<List<HttpHeadersFilter>> headersFilters, HttpClientProperties properties) {
super(httpClient, headersFilters, properties);
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);
String scheme = requestUrl.getScheme();
if (isAlreadyRouted(exchange) || (!"http".equals(scheme) && !"https".equals(scheme))) {
return chain.filter(exchange);
}
setAlreadyRouted(exchange);
ServerHttpRequest request = exchange.getRequest();
final HttpMethod method = HttpMethod.valueOf(request.getMethod().toString());
final String url = requestUrl.toString();
HttpHeaders filtered = filterRequest(this.headersFilters.getIfAvailable(),
exchange);
final DefaultHttpHeaders httpHeaders = new DefaultHttpHeaders();
filtered.forEach(httpHeaders::set);
String transferEncoding = request.getHeaders().getFirst(HttpHeaders.TRANSFER_ENCODING);
boolean chunkedTransfer = "chunked".equalsIgnoreCase(transferEncoding);
boolean preserveHost = exchange.getAttributeOrDefault(PRESERVE_HOST_HEADER_ATTRIBUTE, false);
Mono<HttpClientResponse> responseMono = this.httpClient.request(method, url, req -> {
final HttpClientRequest proxyRequest = req.options(NettyPipeline.SendOptions::flushOnBoundary)
.headers(httpHeaders)
.chunkedTransfer(chunkedTransfer)
.failOnServerError(false)
.failOnClientError(false);
if (preserveHost) {
String host = request.getHeaders().getFirst(HttpHeaders.HOST);
proxyRequest.header(HttpHeaders.HOST, host);
}
if (properties.getResponseTimeout() != null) {
proxyRequest.context(ctx -> ctx.addHandlerFirst(
new ReadTimeoutHandler(properties.getResponseTimeout().toMillis(), TimeUnit.MILLISECONDS)));
}
return proxyRequest.sendHeaders()
.send(request.getBody().map(dataBuffer ->
((NettyDataBuffer) dataBuffer).getNativeBuffer()));
});
return responseMono.doOnNext(res -> {
ServerHttpResponse response = exchange.getResponse();
// put headers and status so filters can modify the response
HttpHeaders headers = new HttpHeaders();
res.responseHeaders().forEach(entry -> headers.add(entry.getKey(), entry.getValue()));
String contentTypeValue = headers.getFirst(HttpHeaders.CONTENT_TYPE);
if (StringUtils.hasLength(contentTypeValue)) {
exchange.getAttributes().put(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR, contentTypeValue);
}
HttpHeaders filteredResponseHeaders = HttpHeadersFilter.filter(
this.headersFilters.getIfAvailable(), headers, exchange, HttpHeadersFilter.Type.RESPONSE);
response.getHeaders().putAll(filteredResponseHeaders);
HttpStatus status = HttpStatus.resolve(res.status().code());
if (status != null) {
response.setStatusCode(status);
} else if (response instanceof AbstractServerHttpResponse) {
// https://jira.spring.io/browse/SPR-16748
((AbstractServerHttpResponse) response).setStatusCodeValue(res.status().code());
} else {
throw new IllegalStateException("Unable to set status code on response: " + res.status().code() + ", " + response.getClass());
}
// Defer committing the response until all route filters have run
// Put client response as ServerWebExchange attribute and write response later NettyWriteResponseFilter
exchange.getAttributes().put(CLIENT_RESPONSE_ATTR, res);
})
.onErrorMap(t -> properties.getResponseTimeout() != null && t instanceof ReadTimeoutException,
t -> new TimeoutException("Response took longer than timeout: " +
properties.getResponseTimeout()))
.then(chain.filter(exchange));
}
}
相關文章
- 開放API閘道器實踐(一) ——設計一個API閘道器API
- API 閘道器 KongAPI
- 微服務基礎——厲害了!API閘道器微服務API
- 基於Linux和IPSec的VPN閘道器Linux
- api閘道器設計API
- C# 開源一個基於 yarp 的 API 閘道器 Demo,支援繫結 Kubernetes ServiceC#API
- 閘道器 Spring-Cloud-Gateway 原始碼解析 —— 除錯環境搭建SpringCloudGateway原始碼除錯
- 微服務架構基礎之API閘道器微服務架構API
- API閘道器,企業級閘道器可擴充套件API套件
- 開放API閘道器實踐(三) —— 限流API
- 高效能API閘道器(1)、微服務API閘道器架構設計API微服務架構
- API 閘道器策略二三事API
- 長連線閘道器技術專題(八):B站基於微服務的API閘道器從0到1的演進之路微服務API
- 用友雲開放平臺之API閘道器API
- 基於gRPC、API閘道器和身份驗證的Go微服務原始碼專案RPCAPIGo微服務原始碼
- Spring Boot整合Zuul API閘道器Spring BootZuulAPI
- 億級流量架構之閘道器設計思路、常見閘道器對比架構
- .NET Core API閘道器Ocelot(一)【概覽,開始】API
- 如何開發一個開源閘道器?
- API閘道器:第8層網路API
- 微服務設計中的API閘道器模式微服務API模式
- kong 一個高效能的 API 閘道器API
- Zilla:一個事件驅動的API閘道器事件API
- API 閘道器的10個最常見用例API
- 雲原生2.0閘道器API標準發展趨勢API
- 基於Prometheus閘道器的監控完整實現參考Prometheus
- 測試開發:從0到1學習如何測試API閘道器API
- 物聯網閘道器開發:基於MQTT訊息匯流排的設計過程(上)MQQT
- 什麼是閘道器?閘道器的作用是什麼,閘道器的作用詳解
- 容器、服務網格和API閘道器:它始於邊緣API
- 拆輪子:閘道器GOKU-API-GatewayGoAPIGateway
- 微服務實踐分享(2)api閘道器微服務API
- 八步部署NGINX Plus API閘道器NginxAPI
- 如何建設企業級API閘道器API
- 探索使用Nginx +Lua 構建 API 閘道器NginxAPI
- 高效能API閘道器Kong介紹API
- Ocelot整合Consul實現api閘道器與服務發現API
- 問下 API 閘道器進行測試的方法API