当前位置:首页 > 服务端 > SpringCloud实战十一:Gateway之 Spring Cloud Gateway

SpringCloud实战十一:Gateway之 Spring Cloud Gateway

2022年11月09日 15:54:18服务端8
1.网关是怎么演化来的
  • 单体应用拆分成多个服务后,对外需要一个统一入口,解耦客户端与内部服务
    SpringCloud实战十一:Gateway之 Spring Cloud Gateway _ JavaClub全栈架构师技术笔记
2.网关的基本功能
  • 网关核心功能是路由转发,因此不要有耗时操作在网关上处理,让请求快速转发到后端服务上
  • 网关还能做统一的熔断、限流、认证、日志监控等
    SpringCloud实战十一:Gateway之 Spring Cloud Gateway _ JavaClub全栈架构师技术笔记
3.关于Spring Cloud Gateway

Spring Cloud Gateway是由spring官方基于Spring5.0、Spring Boot2.0、Project Reactor等技术开发的网关,使用非阻塞API,Websockets得到支持,目的是代替原先版本中的Spring Cloud Netfilx Zuul,目前Netfilx已经开源了Zuul2.0,但Spring 没有考虑集成,而是推出了自己开发的Spring Cloud GateWay。这里需要注意一下gateway使用的netty+webflux实现,不要加入web依赖(不要引用webmvc),否则初始化会报错 ,需要加入webflux依赖。

gateway与zuul的简单比较:gateway使用的是异步请求,zuul是同步请求,gateway的数据封装在ServerWebExchange里,zuul封装在RequestContext里,同步方便调式,可以把数据封装在ThreadLocal中传递。

Spring Cloud Gateway有三个核心概念:路由、断言、过滤器
过滤器:gateway有两种filter:GlobalFilter、GatewayFilter,全局过滤器默认对所有路由有效。
文档地址:https://cloud.spring.io/spring-cloud-static/Finchley.SR2/multi/multi_spring-cloud.html

网关作为所有请求流量的入口,在实际生产环境中为了保证高可靠和高可用,尽量避免重启,需要用到动态路由配置,在网关运行过程中更改路由配置

4.代码实践

需要用到3个项目,eureka-server、gateway、consumer-service

  • 1.eureka-server 服务发现注册,供gateway转发请求时获取服务实例 ip+port,使用前面博客中的示例代码

  • 2.新建 gateway 网关项目,项目引用如下:

    org.springframework.cloud spring-cloud-starter-gateway org.springframework.cloud spring-cloud-starter-netflix-eureka-client org.springframework.boot spring-boot-starter-actuator

在主类上启用服务发现注册注解 @EnableDiscoveryClient
配置文件内容如下:

server:
  port: 9999
spring:
  profiles:
    active: dev
  application:
    name: gateway-service
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
          # 服务名小写
          lower-case-service-id: true
      routes:
      - id: consumer-service
        # lb代表从注册中心获取服务,且已负载均衡方式转发
        uri: lb://consumer-service
        predicates:
        - Path=/consumer/**
        # 加上StripPrefix=1,否则转发到后端服务时会带上consumer前缀
        filters:
        - StripPrefix=1

# 注册中心
eureka:
  instance:
    prefer-ip-address: true
  client:
    service-url:
      defaultZone: http://zy:zy123@localhost:10025/eureka/


# 暴露监控端点
management:
  endpoints:
    web:
      exposure:
        include: '*'
  endpoint:
    health:
      show-details: always

上面就完成了网关代码部分,下面新建consumer-service

  • 3.consumer-service 消费者服务 ,通过网关路由转发到消费者服务,并返回信息回去,因此是个mvc的项目
    项目引用如下:

    org.springframework.boot spring-boot-starter-web org.springframework.cloud spring-cloud-starter-netflix-eureka-client

在主类上启用服务发现注册注解 @EnableDiscoveryClient
在配置文件中添加配置:

server.port=9700
spring.application.name=consumer-service
eureka.instance.prefer-ip-address=true
# 配置eureka-server security的账户信息
eureka.client.serviceUrl.defaultZone=http://zy:zy123@localhost:10025/eureka/

新建 IndexController ,添加一个 hello 方法,传入name参数,访问后返回 hi + name 字符串

@RestController
public class IndexController {

    @RequestMapping("/hello")
    public String hello(String name){
        return "hi " + name;
    }
}
  • 4.分别启动3个项目,访问 http://localhost:10025 看eureka上gateway与consumer-service实例是否注册了,可以看到已经注册了,分别在9700、9999端口
    SpringCloud实战十一:Gateway之 Spring Cloud Gateway _ JavaClub全栈架构师技术笔记

通过网关访问consumer-service的hello方法,http://localhost:9999/consumer/hello?name=zy ,效果如下,说明请求已经转发到consumer-service服务上了
SpringCloud实战十一:Gateway之 Spring Cloud Gateway _ JavaClub全栈架构师技术笔记

以上完成了网关的基本代码,下面继续介绍一些常用的过滤器,通过过滤器实现统一认证鉴权、日志、安全等检验

  • 5.在网关项目中添加 GlobalFilter 全局过滤器,打印每次请求的url,代码如下:

    /**

    • 全局过滤器

    • 所有请求都会执行

    • 可拦截get、post等请求做逻辑处理
      */
      @Component
      public class RequestGlobalFilter implements GlobalFilter,Ordered {

      //执行逻辑
      @Override
      public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) {
      ServerHttpRequest serverHttpRequest= exchange.getRequest();
      String uri = serverHttpRequest.getURI().toString();
      System.out.println(" uri : " + uri);//打印每次请求的url
      String method = serverHttpRequest.getMethodValue();
      if (“POST”.equals(method)) {
      //从请求里获取Post请求体
      String bodyStr = resolveBodyFromRequest(serverHttpRequest);
      //TODO 得到Post请求的请求参数后,做你想做的事

           //下面的将请求体再次封装写回到request里,传到下一级,否则,由于请求体已被消费,后续的服务将取不到值
           URI uri = serverHttpRequest.getURI();
           ServerHttpRequest request = serverHttpRequest.mutate().uri(uri).build();
           DataBuffer bodyDataBuffer = stringBuffer(bodyStr);
           Flux<DataBuffer> bodyFlux = Flux.just(bodyDataBuffer);
      
           request = new ServerHttpRequestDecorator(request) {
               @Override
               public Flux<DataBuffer> getBody() {
                   return bodyFlux;
               }
           };
           //封装request,传给下一级
           return chain.filter(exchange.mutate().request(request).build());
       } else if ("GET".equals(method)) {
           Map requestQueryParams = serverHttpRequest.getQueryParams();
           //TODO 得到Get请求的请求参数后,做你想做的事
      
           return chain.filter(exchange);
       }
       return chain.filter(exchange);
      

      }
      /**

      • 从Flux中获取字符串的方法

      • @return 请求体
        */
        private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest) {
        //获取请求体
        Flux body = serverHttpRequest.getBody();

        AtomicReference bodyRef = new AtomicReference<>();
        body.subscribe(buffer -> {
        CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
        DataBufferUtils.release(buffer);
        bodyRef.set(charBuffer.toString());
        });
        //获取request body
        return bodyRef.get();
        }

      private DataBuffer stringBuffer(String value) {
      byte[] bytes = value.getBytes(StandardCharsets.UTF_8);

       NettyDataBufferFactory nettyDataBufferFactory = new NettyDataBufferFactory(ByteBufAllocator.DEFAULT);
       DataBuffer buffer = nettyDataBufferFactory.allocateBuffer(bytes.length);
       buffer.write(bytes);
       return buffer;
      

      }

      //执行顺序
      @Override
      public int getOrder() {
      return 1;
      }
      }

重新运行网关项目,并访问 http://localhost:9999/consumer/hello?name=zy ,查看控制台,可看到 uri 日志被打印出来了
SpringCloud实战十一:Gateway之 Spring Cloud Gateway _ JavaClub全栈架构师技术笔记

  • 6.在网关项目中添加 GatewayFilter 过滤器 ,我们给consumer-service 添加 token 认证过滤器 ,和全局过滤器u同的是,GatewayFilter需要在配置文件中指定那个服务使用此过滤器,代码如下:

    /**

    • 可对客户端header 中的 Authorization 信息进行认证
      */
      @Component
      public class TokenAuthenticationFilter extends AbstractGatewayFilterFactory {

      private static final String Bearer_ = "Bearer ";

      @Override
      public GatewayFilter apply(Object config) {
      return (exchange, chain) -> {
      ServerHttpRequest request = exchange.getRequest();
      ServerHttpRequest.Builder mutate = request.mutate();
      ServerHttpResponse response = exchange.getResponse();
      try {
      //String token = exchange.getRequest().getQueryParams().getFirst(“authToken”);
      //1.获取header中的Authorization
      String header = request.getHeaders().getFirst(“Authorization”);
      if (header == null || !header.startsWith(Bearer_)) {
      throw new RuntimeException(“请求头中Authorization信息为空”);
      }
      //2.截取Authorization Bearer
      String token = header.substring(7);
      //可把token存到redis中,此时直接在redis中判断是否有此key,有则校验通过,否则校验失败
      if(!StringUtils.isEmpty(token)){
      System.out.println(“验证通过”);
      //3.有token,把token设置到header中,传递给后端服务
      mutate.header(“userDetails”,token).build();
      }else{
      //4.token无效
      System.out.println(“token无效”);
      DataBuffer bodyDataBuffer = responseErrorInfo(response , HttpStatus.UNAUTHORIZED.toString() ,“无效的请求”);
      return response.writeWith(Mono.just(bodyDataBuffer));
      }
      }catch (Exception e){
      //没有token
      DataBuffer bodyDataBuffer = responseErrorInfo(response , HttpStatus.UNAUTHORIZED.toString() ,e.getMessage());
      return response.writeWith(Mono.just(bodyDataBuffer));
      }
      ServerHttpRequest build = mutate.build();
      return chain.filter(exchange.mutate().request(build).build());
      };
      }

      /**

      • 自定义返回错误信息

      • @param response

      • @param status

      • @param message

      • @return
        */
        public DataBuffer responseErrorInfo(ServerHttpResponse response , String status ,String message){
        HttpHeaders httpHeaders = response.getHeaders();
        httpHeaders.add(“Content-Type”, “application/json; charset=UTF-8”);
        httpHeaders.add(“Cache-Control”, “no-store, no-cache, must-revalidate, max-age=0”);

        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        Map<String,String> map = new HashMap<>();
        map.put(“status”,status);
        map.put(“message”,message);
        DataBuffer bodyDataBuffer = response.bufferFactory().wrap(map.toString().getBytes());
        return bodyDataBuffer;
        }
        }

在配置文件中指定consumer-service服务使用 TokenAuthenticationFilter ,配置如下:

routes:
- id: consumer-service
  uri: lb://consumer-service
  predicates:
  - Path=/consumer/**
  filters:
  # 进行token过滤
  - TokenAuthenticationFilter
  - StripPrefix=1

运行项目,再次访问 http://localhost:9999/consumer/hello?name=zy
SpringCloud实战十一:Gateway之 Spring Cloud Gateway _ JavaClub全栈架构师技术笔记

  • 7.前后端分离项目解决网关跨域问题,在网关主类中添加以下代码:

    @Bean
    public WebFilter corsFilter() {
    return (ServerWebExchange ctx, WebFilterChain chain) -> {
    ServerHttpRequest request = ctx.getRequest();
    if (!CorsUtils.isCorsRequest(request)) {
    return chain.filter(ctx);
    }

    		HttpHeaders requestHeaders = request.getHeaders();
    		ServerHttpResponse response = ctx.getResponse();
    		HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod();
    		HttpHeaders headers = response.getHeaders();
    		headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin());
    		headers.addAll(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders.getAccessControlRequestHeaders());
    		if (requestMethod != null) {
    			headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name());
    		}
    		headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
    		headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "all");
    		headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, "3600");
    		if (request.getMethod() == HttpMethod.OPTIONS) {
    			response.setStatusCode(HttpStatus.OK);
    			return Mono.empty();
    		}
    		return chain.filter(ctx);
    	};
    }
    

代码已上传至码云,源码,项目使用的版本信息如下:

- SpringBoot 2.0.6.RELEASE
- SpringCloud Finchley.SR2

作者:普通网友
来源链接:https://blog.csdn.net/m0_67394230/article/details/123811698

版权声明:
1、JavaClub(https://www.javaclub.cn)以学习交流为目的,由作者投稿、网友推荐和小编整理收藏优秀的IT技术及相关内容,包括但不限于文字、图片、音频、视频、软件、程序等,其均来自互联网,本站不享有版权,版权归原作者所有。

2、本站提供的内容仅用于个人学习、研究或欣赏,以及其他非商业性或非盈利性用途,但同时应遵守著作权法及其他相关法律的规定,不得侵犯相关权利人及本网站的合法权利。
3、本网站内容原作者如不愿意在本网站刊登内容,请及时通知本站(javaclubcn@163.com),我们将第一时间核实后及时予以删除。


本文链接:https://www.javaclub.cn/server/68822.html

分享给朋友:

“SpringCloud实战十一:Gateway之 Spring Cloud Gateway” 的相关文章

记一次SpringBoot项目使用RedisTemplate无法反序列化字节数组问题困扰8小时巨坑

记一次SpringBoot项目使用RedisTemplate无法反序列化字节数组问题困扰8小时巨坑

一、问题描述 问题描述: 前提:向Redis中SET值的是一个JFinal项目,使用的Jedis客户端API操作的,把一个对象转成byte[]形式存入Redis中。 然后在另一个SpringBoot项目中我通过Spring自带的RedisTempl...

前后端分离,SpringBoot如何实现验证码操作

前后端分离,SpringBoot如何实现验证码操作

验证码的功能是防止非法用户恶意去访问登录接口而设置的一个功能,今天我们就来看看在前后端分离的项目中,SpringBoot是如何提供服务的。SpringBoot版本本文基于的Spring Boot的版本是2.6.7 。引入依赖captcha一款超简单的验证码生成,还挺好玩的.还有中文验证码,动态验证码...

Spring Cloud 学习推荐

学习 Spring Boot Spring tutorials | Java Web Development, Spring Cloud Programming tutorials Spring Boot为您提供了Spring Framewo...

org.springframework.beans.factory.BeanCreationException

错误:org.springframework.beans.factory.BeanCreationException: Error creating bean with name '/company/list': Injection of resource fields failed;...

springboot启动报:Error creating bean with name 'dataSource' defined in class path resource

需要在启动类的@EnableAutoConfiguration或@SpringBootApplication中添加exclude = {DataSourceAutoConfiguration.class},排除此类的autoconfig。启动以后就可以正常运行。 ...

当spring 对象@Autowired 注入失败或者创建对象Bean失败、No qualifying bean/Error creating bean 的失败情形分析和解决方案

   错误信息 今天开发的过程中突然出现如下错误: Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean o...

使用Mybatis整合spring时报错Injection of autowired dependencies failed;

这是无法自动注入,通过查找,我出错的原因在于配置文件的路径没有写对,在applicationContext.xml中是这样写的。 <bean id="sqlSessionFactoryBean" class="org.mybatis.spring.SqlSessi...

Springboot整合shiro框架

Springboot整合shiro框架

shiro概述 Apache Shiro是Java的一个安全框架 Shiro是一个强大的简单易用的Java安全框架,主要用来更便捷的认证、授权、加密、会话管理、与Web集成、缓存等 Shiro使用起来小而简单 spring中有s...

springboot集成mongoDB遇到的一些坑

1.当mongoDB设置了账号密码时,且设置的db为admin(角色为root),此时登录且操作都是OK的,连接可视化工具进行各种操作也是ok的,但是springboot项目里面却是一直超时 原因:设置的账号密码是admin数据库的,然后连接的是自己的其他数据库(cloud...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。