SpringCloud学习-(7)网关服务(Zuul)

1.Zuul是什么

zuul 是netflix开源的一个API Gateway 服务器, 本质上是一个web servlet应用。
zuul 在云平台上提供动态路由,监控,弹性,安全等边缘服务的框架。zuul 相当于是设备和 Netflix 流应用的 Web 网站后端所有请求的前门。
zuul的例子可以参考 netflix 在github上的 simple webapp,可以按照netflix 在github wiki 上文档说明来进行使用。

2.Zuul的工作原理
1、过滤器机制
zuul的核心是一系列的filters, 其作用可以类比Servlet框架的Filter,或者AOP。

zuul把Request route到 用户处理逻辑 的过程中,这些filter参与一些过滤处理,比如Authentication,Load Shedding等。
这里写图片描述

3.springcloud-zuul

Zuul的主要功能是路由转发和过滤器。路由功能是微服务的一部分,比如/api/user转发到到user服务,/api/shop转发到到shop服务。zuul默认和Ribbon结合实现了负载均衡的功能。

使用zuul可以实现以下功能:

Authentication认证
Insights观察
Stress Testing压力测试
Canary Testing
Dynamic Routing
Service Migration
Load Shedding
Security
Static Response handling
Active/Active traffic management
(摘自spring官网)

说一堆也不知道是啥,上一个小例子跑起来。

3.1 准备
springcloud-eureka-server服务注册中心,端口8761
springcloud-eureka-client1服务提供者1,服务名service-sayHello,方法sayHello,端口8762
springcloud-eureka-client2服务提供者2,服务名service-sayHello,方法sayHello,端口8763
springcloud-eureka-ribbon负载均衡器,服务名service-ribbon
springcloud-eureka-feign负载均衡器,服务名service-feign
这些都是前面几篇介绍过的,在使用zuul之前,需要准备好,并将服务注册到eureka上。

3.2 新建zuul工程
GroupId:com.tangjinyi
ArtifactId:springcloud-eureka-zuul

3.3 pom.xml配置
需要引入zuul的依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>

eureka依赖也是必须的

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.tangjinyi</groupId>
    <artifactId>springcloud-eureka-zuul</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>springcloud-eureka-feign</name>
    <description>Demo project for Spring Boot</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <spring-cloud.version>Finchley.M9</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>


</project>

3.4 application.properties配置文件
这里的c1,c2为自定义,api-a,api-b也为自定义

#将服务注册到eureka
eureka.client.service-url.defaultZone = http://localhost:8761/eureka/
#路由服务端口
server.port=8772
#服务名
spring.application.name=service-zuul
#所有以/c1/开头的请求都交由service-ribbon服务进行处理
zuul.routes.api-a.path=/c1/**
zuul.routes.api-a.serviceId=service-ribbon
#所有以/c2/开头的请求都交由service-feign服务进行处理
zuul.routes.api-b.path=/c2/**
zuul.routes.api-b.serviceId=service-feign

如果配置文件采用.yml文件,配置如下:
这里写图片描述

3.5 启动类配置
启动类添加@EnableZuulProxy注解,开启zuul的功能

package com.tangjinyi;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@SpringBootApplication
@EnableDiscoveryClient
@EnableZuulProxy
public class SpringcloudEurekaZuulApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringcloudEurekaZuulApplication.class, args);
    }

}

最终的zuul工程架构如下:
这里写图片描述

3.6 启动所有的工程
先启动eureka,然后将其他服务注册到eureka上。
访问eureka管控台:浏览器访问http://localhost:8761/
这里写图片描述

4 测试zuul路由
zuul服务的端口号为8772,访问地址为http://localhost:8772/c1/abc?name=123或者http://localhost:8772/c2/abc?name=123

先将请求路由到ribbon服务,由ribbon请求服务提供者,刷新浏览器端口号在8762和8763轮询切换,表示ribbon负载均衡没问题。
这里写图片描述
再将请求路由到feign服务,由feign请求服务提供者,刷新浏览器端口号在8762和8763轮询切换,表示feign负载均衡没问题。
这里写图片描述

5 Zuul过滤器
过滤器是一个很有用的机制,可以实现:token校验/安全认证、动态修改请求参数、灰度发布(Gated Launch/Gray Release)。

5.1 token校验/安全认证
网关直接暴露在公网上时,终端要调用某个服务,通常会把登录后的token传过来,网关层对token进行有效性验证,如果token无效(或没传token),提示重新登录或直接拒绝。另外,网关后面的微服务,如果设置了spring security中的basic Auth(即:不允许匿名访问,必须提供用户名、密码),也可以在Filter中处理。

介绍一下里面的一些方法:
filterType()方法返回值确定是在路由的什么时间进行过滤,如返回值为FilterConstants.PRE_TYPE表示是在请求被路由之前调用,FilterConstants.ROUTE_TYPE表示是在路由请求时候被调用,FilterConstants.POST_TYPE表示是在route和error过滤器之后被调用,FilterConstants.ERROR_TYPE表示是在处理请求时发生错误时被调用。

filterOrder()方法通过int值来定义过滤器的执行顺序。优先级为0,数字越大,优先级越低。

shouldFilter()方法返回一个boolean类型来判断该过滤器是否要执行,所以通过此函数可实现过滤器的开关。

run()方法是过滤器的具体逻辑。需要注意,通过ctx.setSendZuulResponse(false)令zuul过滤该请求,不对其进行路由,然后通过ctx.setResponseStatusCode(401)设置了其返回的错误码

需要使用@Component注解将该过滤器加入到Ioc容器中,否则该过滤器不会生效。

package com.tangjinyi.filters;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;

@Component
public class AccessFilter extends ZuulFilter{

    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    public int filterOrder() {
        return 0;
    }

    public boolean shouldFilter() {
        return true;
    }

    public Object run() throws ZuulException {
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();

        Object token = request.getParameter("token");

        //校验token
        if (token == null) {
            System.out.println("token为空,请重新输入!");
            ctx.setSendZuulResponse(false);
            ctx.setResponseStatusCode(401);
            try{
                ctx.getResponse().getWriter().write("token is empty");
            }catch (Exception e){

            }
            return null;
        } else {
            //TODO
        }

        //添加Basic Auth认证信息
        ctx.addZuulRequestHeader("Authorization", "Basic " );
        return null;
    }
}

5.2 动态参数
可以拦截所有请求参数,并对其进行修改,比如:终端发过来的数据,出于安全要求,可能是经过加密处理的,需要在网关层进行参数解密,再传递到后面的服务;再比如:用户传过来的token值,需要转换成userId/userName这些信息,再传递到背后的微服务。

package com.tangjinyi.filters;

import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import com.netflix.zuul.http.HttpServletRequestWrapper;
import com.netflix.zuul.http.ServletInputStreamWrapper;
import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants;
import org.springframework.util.StreamUtils;

import javax.servlet.ServletInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;

import static org.springframework.util.ReflectionUtils.rethrowRuntimeException;

@Component
public class ParamsFilter extends ZuulFilter {
    @Override
    public String filterType() {
        return FilterConstants.PRE_TYPE;
    }

    @Override
    public int filterOrder() {
        return 0;
    }

    @Override
    public boolean shouldFilter() {
        return true;
    }

    @Override
    public Object run() throws ZuulException {
        try {
            RequestContext context = RequestContext.getCurrentContext();
            InputStream in = (InputStream) context.get("requestEntity");
            if (in == null) {
                in = context.getRequest().getInputStream();
            }
            String body = StreamUtils.copyToString(in, Charset.forName("UTF-8"));
            body = "你好: " + body;
            byte[] bytes = body.getBytes("UTF-8");
            context.setRequest(new HttpServletRequestWrapper(RequestContext.getCurrentContext().getRequest()) {
                public ServletInputStream getInputStream() throws IOException {
                    return new ServletInputStreamWrapper(bytes);
                }

                public int getContentLength() {
                    return bytes.length;
                }

                public long getContentLengthLong() {
                    return bytes.length;
                }
            });
        } catch (IOException e) {
            rethrowRuntimeException(e);
        }
        return null;
    }
}

5.3 灰度发布

参考:https://www.cnblogs.com/lexiaofei/p/7080257.html

猜你喜欢

转载自blog.csdn.net/jinjin603/article/details/80450593