一、什么是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)); } }