第5.1.2 SpringCloud Gateway初步

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/warrah/article/details/82736302

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初探,这篇文章可以了解一下GlobalFilter是干啥的,我这里主要是过滤token的获取的。
2.@Lazy懒加载,等同于<bean id="xxx" class="xx.xx.xx" lazy-init="true">,启动的时候并不装载,而在用的时候才会加载。那么什么时候不能加,什么时候能加呢?首先可以明确,一定是跟springboot启动有关的类,才有可能加@lazy。
3.mono是什么?参考Mono入门应用,这篇文章有些晦涩。

猜你喜欢

转载自blog.csdn.net/warrah/article/details/82736302