基于Spring-Cloud-Gateway开发API网关的思路

本文涉及的产品
注册配置 MSE Nacos/ZooKeeper,118元/月
任务调度 XXL-JOB 版免费试用,400 元额度,开发版规格
云原生 API 网关,700元额度,多规格可选
简介: 基于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));
    }
}


目录
打赏
0
0
0
0
25
分享
相关文章
淘宝店铺详情API接口的开发、应用与收益
淘宝开放平台提供了丰富的API接口,帮助开发者获取海量的商品和店铺数据。本文聚焦于淘宝店铺详情API接口的开发、应用及收益。首先,开发者需注册账号并创建应用以获取API密钥。接着,通过阅读接口文档,使用Python等语言编写代码调用API,处理返回的数据。该接口广泛应用于竞品分析、数据分析、价格监控、个性化推荐等领域,为开发者带来提高用户体验、降低运营成本、增加收入等多方面收益。同时,开发者需注意遵守法律法规、请求频率限制及数据安全等问题,确保合法合规地使用接口资源。
67 4
小红书笔记详情 API 接口的开发、应用与收益
小红书(RED)作为国内领先的生活方式分享平台,汇聚了大量用户生成内容(UGC),尤其是“种草”笔记。小红书笔记详情API接口为开发者提供了获取笔记详细信息的强大工具,包括标题、内容、图片、点赞数等。通过注册开放平台账号、申请API权限并调用接口,开发者可以构建内容分析工具、笔记推荐系统、数据爬虫等应用,提升用户体验和运营效率,创造新的商业模式。本文详细介绍API的开发流程、应用场景及潜在收益,并附上Python代码示例。
166 61
DeepClaude:结合 DeepSeek R1 和 Claude AI 各自优势开发的 AI 应用平台,支持 API 调用和零延迟的即时响应
DeepClaude 是一个开源的 AI 应用开发平台,结合了 DeepSeek R1 和 Claude 模型的优势,提供即时响应、端到端加密和高度可配置的功能。
196 4
DeepClaude:结合 DeepSeek R1 和 Claude AI 各自优势开发的 AI 应用平台,支持 API 调用和零延迟的即时响应
以项目登录接口为例-大前端之开发postman请求接口带token的请求测试-前端开发必学之一-如果要学会联调接口而不是纯写静态前端页面-这个是必学-本文以优雅草蜻蜓Q系统API为实践来演示我们如何带token请求接口-优雅草卓伊凡
以项目登录接口为例-大前端之开发postman请求接口带token的请求测试-前端开发必学之一-如果要学会联调接口而不是纯写静态前端页面-这个是必学-本文以优雅草蜻蜓Q系统API为实践来演示我们如何带token请求接口-优雅草卓伊凡
37 5
以项目登录接口为例-大前端之开发postman请求接口带token的请求测试-前端开发必学之一-如果要学会联调接口而不是纯写静态前端页面-这个是必学-本文以优雅草蜻蜓Q系统API为实践来演示我们如何带token请求接口-优雅草卓伊凡
淘宝商品描述 API 接口的开发、应用与收益
淘宝商品描述API接口是淘宝开放平台提供的服务,允许开发者编程获取商品详情,如标题、价格、图片等。通过注册账号、申请权限并调用API,可构建比价工具、推荐系统、自动化上架工具等应用,提升用户体验与运营效率,创造新的商业模式。该接口为电商从业者提供了强大的数据支持和创新机会。
71 22
基于 API 网关践行 API First 开发实践
API First 开发模式的核心在于:以 API 为先,将其视为“头等公民”,在构建应用、服务及集成之前,应优先定义并设计 API 及其配套。API First 作为一种相对较新的开发模式,它已逐渐流行并获得业内的广泛认可。
阿里巴巴热卖商品推荐 API 接口的开发、应用与收益
阿里巴巴热卖商品推荐API为开发者提供了获取平台热卖商品信息的强大工具,涵盖商品标题、价格、销量等数据。通过注册开放平台账号、申请API权限并调用接口,开发者可构建热卖商品推荐系统、数据分析工具及供应链管理系统等应用,提升用户体验与运营效率,创造新的商业模式。该API采用RESTful风格,支持多种应用场景,助力电商从业者实现创新与增值。
92 7
1688APP 原数据 API 接口的开发、应用与收益
1688作为阿里巴巴旗下的B2B平台,汇聚海量供应商和商品资源。其APP原数据API接口为开发者提供获取商品详细信息的强大工具,涵盖商品标题、价格、图片等。通过注册开放平台账号、申请API权限并调用接口,开发者可构建比价工具、供应链管理及自动化上架工具等应用,提升用户体验与运营效率,创造新的商业模式。示例代码展示了如何使用Python调用API并解析返回结果。
77 8
亚马逊详情 API 接口的开发、应用与收益
亚马逊详情API接口是亚马逊开放平台提供的强大工具,允许开发者编程获取商品的详细信息,如标题、价格、描述等。通过注册账号、申请权限并调用API,开发者可构建比价工具、推荐系统和数据分析工具等应用,提升用户体验与运营效率,创造新商业模式。本文详细介绍其开发流程、应用场景及潜在收益,并附代码示例。
34 6
SpringBoot是如何简化Spring开发的,以及SpringBoot的特性以及源码分析
Spring Boot 通过简化配置、自动配置和嵌入式服务器等特性,大大简化了 Spring 应用的开发过程。它通过提供一系列 `starter` 依赖和开箱即用的默认配置,使开发者能够更专注于业务逻辑而非繁琐的配置。Spring Boot 的自动配置机制和强大的 Actuator 功能进一步提升了开发效率和应用的可维护性。通过对其源码的分析,可以更深入地理解其内部工作机制,从而更好地利用其特性进行开发。
47 6

热门文章

最新文章