SpringCloud Study - (七) Zuul 网关

 

目标:

创建一个Zuul 网关服务模块,通过服务网关访问内部的服务。

步骤:

1.创建网关服务模块 SpringbootGW ,在pom文件中加入Zuul的支持依赖,网关服务模块会通过注册中心访问服务提供者,同时也需要加入Eureka Client 支持。

2. 在Application.properties 中加入相关配置,使用默认Zuul的配置规则,暂时不配置Zuul的路由。

server.port=6060
spring.application.name=GW
eureka.client.serviceUrl.defaultZone=http://eureka1:9001/eureka/,http://eureka1:9002/eureka/,http://eureka1:9003/eureka/

3. 在启动类上加Zuul的注解 

@EnableZuulProxy

4. 添加一个用户获取RestTemplate对象的 配置类

@Configuration
public class BeanConfig {
    @LoadBalanced
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

5. 创建网关访问controller , 在测试controller 中网关通过Eureka 调用服务提供者。

package com.shamusoft.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class GWController {
    @Autowired
    private RestTemplate restTemplate;
    @RequestMapping("/sayGWHello")
    public String sayGWHello(){

     //   RestTemplate restTemplate = new RestTemplate();
        String message = restTemplate.getForEntity("http://provider/providerHello",String.class).getBody();
        return "GW Hello"+message;
    }
  
}

6. 启动网关模块测试。

在浏览器中通过网关调用注册在Eureka 上的服务提供者. 请注意分析 地址 http://localhost:6060/provider/providerHello

6.1 通过网关访问服务时把Eureka上服务提供者名字换成自定义的服务名,例如加myapi . 修改Application.properties 文件.

两种方式,第一种:配置path 和servericeId.

第二种:直接配置服务提供者

server.port=6060
spring.application.name=GW
eureka.client.serviceUrl.defaultZone=http://eureka1:9001/eureka/,http://eureka1:9002/eureka/,http://eureka1:9003/eureka/


#通过zuul 访问
zuul.routes.myapi.path=/myapi/**
zuul.routes.myapi.serviceId=provider

#Zuul
zuul.routes.PROVIDER=/myapi2/**

打开浏览器测试:

第一种配置的访问规则:

第二种配置的访问规则:

默认的访问规则:

6.2 禁用默认的访问规则

6.2.1 创建一个子服务提供者SpringbootProvider3Sub,禁止网关外的访问提供服务

6.2.2 在pom文件中加入springboot 基础依赖和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.shamusoft</groupId>
    <artifactId>SpringbootProvider3Sub</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.5.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>Greenwich.SR1</spring-cloud.version>
    </properties>

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

        <!-- 增加Eureka的支持 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</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>

</project>

6.2.3 配置Apllication.properties 文件

server.port=8003
#不要用大小写字母 有可能会出问题
spring.application.name=provider-sub
#每间隔2s,向服务端发送一次心跳,证明自己依然"存活"
eureka.instance.lease-renewal-interval-in-seconds=2
#告诉服务端,如果我10s之内没有给你发心跳,就代表我故障了,将我踢出掉
eureka.instance.lease-expiration-duration-in-seconds=10
eureka.client.serviceUrl.defaultZone=http://eureka1:9001/eureka,http://eureka2:9002/eureka,http://eureka3:9003/eureka

#springboot的监控端点访问权限,*表示所有的访问端点都允许访问
management.endpoints.web.exposure.include=*

6.2.4 在配置类上添加Euireka的注解

6.2.5 创建测试controller

@RestController
public class ProviderSay3Sub {

    @RequestMapping("/providerSubHello")
    public String providerHello(){

        return "provider3Sub hello";
    }
}

6.2.6 启动访问测试服务 ,刷新Eureka服务页面查看是否创建的服务注册成功.

6.2.7 因为没有给provider-sub 配置路由规则,所以启用默认路由规则。

http://192.168.242.1:6060/provider-sub/providerSubHello

6.2.7 禁用provider-sub 的默认路由配置规则,让provider-sub 通过网关对外不可访问。修改网关模块的Application.properties 文件。

server.port=6060
spring.application.name=GW
eureka.client.serviceUrl.defaultZone=http://eureka1:9001/eureka/,http://eureka1:9002/eureka/,http://eureka1:9003/eureka/

###默认的路由规则
## http://192.168.242.1:6060/provider/providerHello

#zuul.routes.provider.path=/provider/**
#zuul.routes.provider.serviceId=provider

#通过zuul 访问
zuul.routes.myapi.path=/myapi/**
zuul.routes.myapi.serviceId=provider

#Zuul
zuul.routes.PROVIDER=/myapi2/**


#禁用zuul自带的过滤器
#zuul.SendErrorFilter.error.disable=true

#忽略服务提供者的默认规则
zuul.ignored-services=provider-sub




6.2.8 重新启动网关模块访问 http://192.168.242.1:6060/provider-sub/providerSubHello

出现 404 页面.

7. 禁止服务提供者的某些服务不能通过网关对外访问。

7.1 在服务提供者中增加测试方法 

package com.shamusoft.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ProviderSay1 {
    @RequestMapping("/providerHello")
    public String providerHello(){

        return "provider1 hello";
    }

    @RequestMapping("/system/sec")
    public String systemSec(){

        return "安全信息,不对外提供服务";
    }

}

7.2 重新启动 SpringbootProvider1 ,测试是否能正常访问

URL : http://localhost:6060/provider/system/sec

7.3 在网关服务模块中配置禁用模块的服务地址对外访问

server.port=6060
spring.application.name=GW
eureka.client.serviceUrl.defaultZone=http://eureka1:9001/eureka/,http://eureka1:9002/eureka/,http://eureka1:9003/eureka/

###默认的路由规则
## http://192.168.242.1:6060/provider/providerHello

#zuul.routes.provider.path=/provider/**
#zuul.routes.provider.serviceId=provider

#通过zuul 访问
zuul.routes.myapi.path=/myapi/**
zuul.routes.myapi.serviceId=provider

#Zuul
zuul.routes.PROVIDER=/myapi2/**

#zuul.routes.lgw.path=/lgw/**
#zuul.routes.lgw.url=forward:/mLocal/gwPage

#禁用zuul自带的过滤器
#zuul.SendErrorFilter.error.disable=true

#忽略服务提供者的默认规则
zuul.ignored-services=provider-sub

##禁止某些匹配地址对外访问
zuul.ignored-patterns=/**/system/**



7.4 启动测试 

http://localhost:6060/provider/system/sec  页面出现404 

8.1 启用统一的网关访问前缀 , 修改网关模块的Application.properties 文件.

server.port=6060
spring.application.name=GW
eureka.client.serviceUrl.defaultZone=http://eureka1:9001/eureka/,http://eureka1:9002/eureka/,http://eureka1:9003/eureka/

###默认的路由规则
## http://192.168.242.1:6060/provider/providerHello

#zuul.routes.provider.path=/provider/**
#zuul.routes.provider.serviceId=provider

#通过zuul 访问
zuul.routes.myapi.path=/myapi/**
zuul.routes.myapi.serviceId=provider

#Zuul
zuul.routes.PROVIDER=/myapi2/**

#zuul.routes.lgw.path=/lgw/**
#zuul.routes.lgw.url=forward:/mLocal/gwPage

#禁用zuul自带的过滤器
#zuul.SendErrorFilter.error.disable=true

#忽略服务提供者的默认规则
zuul.ignored-services=provider-sub

##禁止某些匹配地址对外访问
zuul.ignored-patterns=/**/system/**
###为路由统一提供前缀
zuul.prefix=/papi




8.12重新启动网关模块测试:

     之前访问地址:http://192.168.242.1:6060/myapi2/providerHello

    加入统一前缀后的地址:http://192.168.242.1:6060/papi/myapi2/providerHello

9.1 当访问某些路径的时候 ,可以让请求转发到某些指定的路径。在配置文件Application.properties中配置转发

server.port=6060
spring.application.name=GW
eureka.client.serviceUrl.defaultZone=http://eureka1:9001/eureka/,http://eureka1:9002/eureka/,http://eureka1:9003/eureka/

###默认的路由规则
## http://192.168.242.1:6060/provider/providerHello

#zuul.routes.provider.path=/provider/**
#zuul.routes.provider.serviceId=provider

#通过zuul 访问
zuul.routes.myapi.path=/myapi/**
zuul.routes.myapi.serviceId=provider

#Zuul
zuul.routes.PROVIDER=/myapi2/**

#zuul.routes.lgw.path=/lgw/**
#zuul.routes.lgw.url=forward:/mLocal/gwPage

#禁用zuul自带的过滤器
zuul.SendErrorFilter.error.disable=true

#忽略服务提供者的默认规则
zuul.ignored-services=provider-sub

##禁止某些匹配地址对外访问
zuul.ignored-patterns=/**/system/**
###为路由统一提供前缀
zuul.prefix=/papi

# 注意这里转发只是把路径中的xxx 替换成转发路径,但是转发路径后面的参数任然不变
# /xxx/abcpage 转发后的地址是 /mlocal/abcpage
zuul.routes.xxx.path=/xxx/**
zuul.routes.xxx.url=forward:/mlocal

9.2 在网关controller中创建一个测试controller.


@RestController
public class GWController {
    @Autowired
    private RestTemplate restTemplate;
    @RequestMapping("/sayGWHello")
    public String sayGWHello(){

     //   RestTemplate restTemplate = new RestTemplate();
        String message = restTemplate.getForEntity("http://provider/providerHello",String.class).getBody();
        return "GW Hello"+message;
    }
    @RequestMapping("/callError")
    public String callError(){
        String message = restTemplate.getForEntity("http://provider/errorPage",String.class).getBody();
        return "call Error";
    }

    @RequestMapping("/mlocal/gwpage")
    public String apiLocal(){

        return "show GW page content.";
    }

  
}

9.3 重启网关模块测试转发请求.

    在此实验中遇到的黑坑,以为转发会把 匹配的路径完全替换转发到新的路径,经过实验测试发现转发只是替换其中的匹配路径而不是全部路径。

    例如: http://192.168.242.1:6060/xxx/gwpage?token=1231231 ,可以显示 show GW page content. 此路径中会把xxx 替换成 /mlocal 

                实际转发后的路径为: http://192.168.242.1:6060/mlocal/gwpage?token=1231231

              但是我们任意输入一个路径 :http://192.168.242.1:6060/papi/xxx/gwpage1111?token=1231231 

              实际转发后的路径为:http://192.168.242.1:6060/papi/mlocal/gwpage1111?token=1231231 但是这个路径不存在,会出现404页面,测试实验中配置了全局异常页面所以进入了全局异常控制处理器。

10 .Zuul 过滤器,Zuul 过滤器类型包括 pre,routing , post ,和error . 当pre ,routing , post 中有Zuul Exception 抛出时, error 类型的过滤器就会捕获异常进行处理。

10.1 定义一个pre 过滤器,当有请求到达时候,判断请求路径是否包含token , 如果不包含token 返回非法访问信息。

@Component
public class AuthFilter extends ZuulFilter {
    @Override
    public String filterType() {
        return "pre";
    }

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

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

    @Override
    public Object run() throws ZuulException {

       //int i = 1/0;

        //http://192.168.242.1:6060/provider/providerHello?token=12333
        System.out.println(">>>>>this is pre filter");
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        String token = request.getParameter("token");
        if (token == null) {
            ctx.setSendZuulResponse(false); //非法访问首先设置response 为false
            ctx.setResponseStatusCode(401);
            ctx.addZuulResponseHeader("content-type","text/html;charset=utf-8");
            ctx.setResponseBody("非法访问");
        }
        return null;
    }
}

10.2 重启网关服务模块 ,输入访问地址:

http://192.168.242.1:6060/papi/provider/providerHello

http://192.168.242.1:6060/papi/provider/providerHello?token=1231231

11.定义错误error过滤器,是在pre, routing, post 中发生的异常都可以被error 拦截器拦截。 修改配置文件Application.properties 禁用自带的错误过滤器.

server.port=6060
spring.application.name=GW
eureka.client.serviceUrl.defaultZone=http://eureka1:9001/eureka/,http://eureka1:9002/eureka/,http://eureka1:9003/eureka/

###默认的路由规则
## http://192.168.242.1:6060/provider/providerHello

#zuul.routes.provider.path=/provider/**
#zuul.routes.provider.serviceId=provider

#通过zuul 访问
zuul.routes.myapi.path=/myapi/**
zuul.routes.myapi.serviceId=provider

#Zuul
zuul.routes.PROVIDER=/myapi2/**

#zuul.routes.lgw.path=/lgw/**
#zuul.routes.lgw.url=forward:/mLocal/gwPage

#禁用zuul自带的过滤器
zuul.SendErrorFilter.error.disable=true

#忽略服务提供者的默认规则
zuul.ignored-services=provider-sub

##禁止某些匹配地址对外访问
zuul.ignored-patterns=/**/system/**
###为路由统一提供前缀
zuul.prefix=/papi

# 必须设置prefix 否则不会被zuul转发
zuul.routes.index.path=/index/**
zuul.routes.index.url=forward:/mlocal/gwpage

11.1 编写error 过滤器 MErrorFilter


@Component
public class MErrorFilter extends ZuulFilter {
    @Override
    public String filterType() {
        //只有pre routing,post 中发生的异常才可以拦截。对于服务提供方发生的异常不能拦截
        return "error";
    }

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

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

    @Override
    public Object run() throws ZuulException {
        try {

            RequestContext context = RequestContext.getCurrentContext();
            ZuulException exception = (ZuulException)context.getThrowable();

            HttpServletResponse response = context.getResponse();
            response.setContentType("application/json; charset=utf8");
            response.setStatus(exception.nStatusCode);

            PrintWriter writer = null;
            try {
                writer = response.getWriter();
                writer.print("{code:"+ exception.nStatusCode +", message:\""+ exception.getMessage() +"\"}");
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if(writer!=null){
                    writer.close();
                }
            }
        } catch (Exception e) {
          e.printStackTrace();
        }
        return null;
    }
}

11.2  在 pre 过滤器AuthFilter中增加错误异常代码

@Component
public class AuthFilter extends ZuulFilter {
    @Override
    public String filterType() {
        return "pre";
    }

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

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

    @Override
    public Object run() throws ZuulException {

         int i = 1/0;

        //http://192.168.242.1:6060/provider/providerHello?token=12333
        System.out.println(">>>>>this is pre filter");
        RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        String token = request.getParameter("token");
        if (token == null) {
            ctx.setSendZuulResponse(false); //非法访问首先设置response 为false
            ctx.setResponseStatusCode(401);
            ctx.addZuulResponseHeader("content-type","text/html;charset=utf-8");
            ctx.setResponseBody("非法访问");
        }
        return null;
    }
}

11.3 重启网关服务模块访问测试。

URL : http://192.168.242.1:6060/papi/provider/providerHello?token=1231231

出现以下界面说明被error 拦截器拦住了,进入到了我们error自定义的处理方法中。

11.4  移除 pre 过滤器中的 错误代码 int i = 1/0 ,直接访问有异常的提供者观察是否可以被error拦截器拦截。

11.4.1 在provider 1 中增加错误访问方法controller 

11.4.2 浏览器测试访问:

URL : http://192.168.242.1:6060/papi/provider/errorPage?token=1231231

出现此页面说明没有被定义的拦截方法拦截。

11.4.3 增加一个post 过滤器,在过滤器中获取远程服务返回的code ,如果code 不是200就抛出一个Zuul 异常.

 

@Component
public class MPostFilter extends ZuulFilter {
    @Override
    public String filterType() {
        return "post";
    }

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

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


    @Override
    public Object run() throws ZuulException {
        RequestContext context = RequestContext.getCurrentContext();

        int code =context.getResponse().getStatus();

        Throwable t =context.getThrowable();
        System.out.println("获取服务访问返回的code:" + code);
        if(code !=200){
            System.out.println("抛出异常,远程服务出错");
            throw new ZuulException("抛出异常",code,"远程服务出错");
        }

        System.out.println("这是post tt过滤器........."+t+"---code:"+code);

        System.out.println(">>>>>this is post filter");

        return null;
    }

11.4.4 重启网关模块测试

URL :http://192.168.242.1:6060/papi/provider/errorPage?token=1231231

进入到了定义的错误方法拦截器 。

12 . 自定义全局的错误页面 

@RestController
public class MyErrorHandlerController implements ErrorController {
    
    @Override
    public String getErrorPath() {
        System.out.println(">>>>>>ErrorHandlerController  -->getErrorPath");
        return "/error";
    }

    @RequestMapping("/error")
    public Object error() {
//        RequestContext ctx = RequestContext.getCurrentContext();
//        ZuulException exception = (ZuulException)ctx.getThrowable();
//        return exception.nStatusCode + "--" + exception.getMessage();
        
        return "全局异常:服务器忙.....";
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值