第5.1.2 SpringCloud Gateway初步

2018年3月份选型时,刚好了解到springboot2的第二次发布,之前springboot2在国内发布,曾经出现​Spring Boot 2.0 同步至 Maven 仓库出错,不过最终我还是选择了springboot2,我相信它应该不会再犯同样的错误。springboot1我用了,但是用的并不全,像erueka、zuul我们都没有用,当初只是想将我的服务轻量化,而我的服务治理2016年时就已经采用当当的dubbox,平台30多个系统,不能做太大的变化。现在选择springcloud做微服务,主要原因它主流,新系统可以试点,然后改造单点登录系统,让他兼容新旧框架就可以。
当我对zuul有点映像的时候,它已经过时了,SpringCloud Gateway就诞生,这让我们这些33岁的老程序员怎么活呢。看看纠错帖:Zuul & Spring Cloud Gateway & Linkerd性能对比,就明白一是因为spring专家也在探索,二呢zuul的设计模式可能影响到其他功能的扩展。不过也有不同的声音微服务API网关NGINX、ZUUL、Spring Cloud Gateway与。无论是gateway,还是zuul,难道希望做到性能跟nginx相比吗,应该是做不到的把。那么gateway到底是干啥的呢。nginx用作静态页面代理,而gateway或zuul用作微服务的负载均衡,毕竟他们是一个体系内的东西。
下图是spring cloud官网中显示gateway设计原理,看起来比zuul更为简洁。
1
zuul入门(1)zuul 的概念和原理,这篇文章别人整理出来的,看起来就复杂多了
2
我不想做性能测试,去质疑官方给出的结论,因为我做不了官方那么好的产品出来,而且官方不久也会推陈出新,所以干脆我就不管zuul了,闷头做gateway的调研得了。
下图是我这边的配置,你也可以参考Spring Cloud Gateway基于服务发现的默认路由规则中的说明。从下图可以看出这个api网关,管理那些微服务,那么就需要路由上面配置。官网上有多种配置策略,我的实际项目Route Predicate Factory中选择了Path,而GatewayFilter Factory选择的是StripPrefix。Path很好理解,就是根据路径去匹配,那么StripPrefix又是什么呢?其实很简单,就是跳过前缀的数量,下图描述比较清楚了。

When a request is made through the gateway to /name/bar/foo the request made to nameservice will look like http://nameservice/foo.

听了官网的注释,回过头想想,为什么会有这样的设计呢?聊聊spring cloud gateway的PrefixPath及StripPrefix功能,这样的设计莫非是为了省事,毕竟路径匹配也好花费时间,还是为了统一化呢?再看看这篇文章,SpringCloud Finchley基础教程:3,spring cloud gateway网关,如果id为空,那么就是uuid,那么也就是说api后台对应的系统可以是任意多台,甚至是重复的。相同的程序,但是对应的服务id不一样就可以,这样确实足够灵活,而且非常容易扩展,节点与节点之前还不会相互影响。
对下面语法不理解可以参考YAML最最基础语法

spring:
  cloud:
    gateway:
      locator:
        enabled: true
      routes:
      - id: gateway-auth
        uri: lb://gateway-auth
        predicates:
        - Path=/api/auth/**
        filters:
          - StripPrefix=2
      - id: user-server
        uri: lb://user-server
        predicates:
        - Path=/api/user/**
        filters:
          - StripPrefix=1
      - id: dcm-server
        uri: lb://dcm-server
        predicates:
        - Path=/api/dcm/**
        filters:
          - StripPrefix=2

下面看一下GlobalFilter,下面介绍的过滤器,针对内部平台可能适用,比如有一些需要登录认证的,用户群体是单一的。如果内部用户,和外部用户都使用此网关,这个过滤器需要做调整。

package com.dzmsoft.gateway.api.filter;


import com.alibaba.fastjson.JSONObject;
import com.dzmsoft.gateway.api.feign.IUserService;
import com.dzmsoft.gateway.api.feign.ServiceAuthFeign;
import com.dzmsoft.gateway.auth.client.config.UserAuthConfig;
import com.dzmsoft.gateway.common.response.BaseResponse;
import com.dzmsoft.gateway.common.response.TokenForbiddenResponse;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.List;


@Configuration
@Slf4j
public class AccessGatewayFilter implements GlobalFilter {
    @Autowired
    @Lazy
    private IUserService userService;

    @Autowired
    @Lazy
    private ServiceAuthFeign serviceAuthFeign;

    @Value("${gate.ignore.startWith}")
    private String startWith;

    @Autowired
    private UserAuthConfig userAuthConfig;


    @Override
    public Mono<Void> filter(ServerWebExchange serverWebExchange, GatewayFilterChain gatewayFilterChain) {
        log.info("check token and user permission....");
        ServerHttpRequest request = serverWebExchange.getRequest();
        final String requestUri = request.getPath().pathWithinApplication().value();
        final String method = request.getMethod().toString();

        ServerHttpRequest.Builder mutate = request.mutate();
        // 不进行拦截的地址
        if (isStartWith(requestUri)) {
            ServerHttpRequest build = mutate.build();
            return gatewayFilterChain.filter(serverWebExchange.mutate().request(build).build());
        }
        String userId = null;
        try {
            userId = getJWTUser(request, mutate);
        } catch (Exception e) {
            log.error("用户Token过期异常", e);
            return getVoidMono(serverWebExchange, new TokenForbiddenResponse("用户token错误或已过期!"));
        }
        ServerHttpRequest build = mutate.build();
        return gatewayFilterChain.filter(serverWebExchange.mutate().request(build).build());

    }

    /**
     * 返回jwt中的用户信息
     *
     * @param request
     * @param ctx
     * @return
     */
    private String getJWTUser(ServerHttpRequest request, ServerHttpRequest.Builder ctx) throws Exception {
        List<String> strings = request.getHeaders().get(userAuthConfig.getTokenHeader());
        String authToken = null;
        if (strings != null) {
            authToken = strings.get(0);
        }
        if (StringUtils.isBlank(authToken)) {
            strings = request.getQueryParams().get("token");
            if (strings != null) {
                authToken = strings.get(0);
            }
        }
        return serviceAuthFeign.getInfo(authToken);
    }

    /**
     * 网关抛异常
     *
     * @param body
     */
//    @NotNull
    private Mono<Void> getVoidMono(ServerWebExchange serverWebExchange, BaseResponse body) {
        serverWebExchange.getResponse().setStatusCode(HttpStatus.OK);
        byte[] bytes = JSONObject.toJSONString(body).getBytes(StandardCharsets.UTF_8);
        DataBuffer buffer = serverWebExchange.getResponse().bufferFactory().wrap(bytes);
        return serverWebExchange.getResponse().writeWith(Flux.just(buffer));
    }

    /**
     * URI是否以什么打头
     *
     * @param requestUri
     * @return
     */
    private boolean isStartWith(String requestUri) {
        boolean flag = false;
        for (String s : startWith.split(",")) {
            if (requestUri.startsWith(s)) {
                return true;
            }
        }
        return flag;
    }

    /**
     * 网关抛异常
     *
     * @param body
     * @param code
     */
    private Mono<Void> setFailedRequest(ServerWebExchange serverWebExchange, String body, int code) {
        serverWebExchange.getResponse().setStatusCode(HttpStatus.OK);
        return serverWebExchange.getResponse().setComplete();
    }

}

1.[Spring Cloud Gateway全局过滤器GlobalFilter初探](https://segmentfault.com/a/1190000016252868),这篇文章可以了解一下GlobalFilter是干啥的,我这里主要是过滤token的获取的。
2.@Lazy懒加载,等同于<bean id="xxx" class="xx.xx.xx" lazy-init="true">,启动的时候并不装载,而在用的时候才会加载。那么什么时候不能加,什么时候能加呢?首先可以明确,一定是跟springboot启动有关的类,才有可能加@lazy。
3.mono是什么?参考[Mono入门应用](https://blog.csdn.net/songhaifengshuaige/article/details/79248343),这篇文章有些晦涩。
4 没有使用SpringCloudConfig的API网关配置

server:
  port: 8765
  servlet:
    context-path: /api
spring:
  application:
    name: gateway-api
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
    time-zone: GMT+8
    default-property-inclusion: non_null
  cloud:
    gateway:
      locator:
        enabled: true
      routes:
      - id: gateway-auth
        uri: lb://gateway-auth
        predicates:
        - Path=/api/auth/**
        filters:
          - StripPrefix=2
      - id: user-server
        uri: lb://user-server
        predicates:
        - Path=/api/user/**
        filters:
          - StripPrefix=1
      - id: dcm-server
        uri: lb://dcm-server
        predicates:
        - Path=/api/dcm/**
        filters:
          - StripPrefix=2
      - id: mp-server
        uri: lb://mp-server
        predicates:
        - Path=/api/mp/**
        filters:
        - StripPrefix=2
gate:
  ignore:
    startWith: /jwt
auth:
  serviceId: gateway-auth
  user:
    token-header: Authorization
eureka:
  instance:
    preferIpAddress: true
    statusPageUrlPath: /actuator/info
    healthCheckUrlPath: /actuator/health
  client:
    service-url:
      defaultZone: http://192.168.5.102:8761/eureka
    enabled: true
feign:
  httpclient:
    enabled: false
  okhttp:
    enabled: true
  compression:
    request:
      enabled: true
      mime-types: text/xml,application/xml,application/json
      min-request-size: 2048
    response:
      enabled: true
logging:
  file: ${spring.application.name}.log
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

warrah

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值