API网关服务Zuul-Spring Cloud学习第五天(非原创)
文章大纲
一、Zuul是什么
二、Zuul的基本实现
三、路由配置细节
四、异常处理细节
五、项目源码与参考资料下载
六、参考文章
![](https://upload-images.jianshu.io/upload_images/16598307-749e9652a5643174.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/858/format/webp)
一、Zuul是什么
到目前为止,我们Spring Cloud中的内容已经介绍了很多了,Ribbon、Hystrix、Feign这些知识点大家都耳熟能详了,我们在前文也提到过微服务就是把一个大的项目拆分成很多小的独立模块,然后通过服务治理让这些独立的模块配合工作等。那么大家来想这样两个问题:1.如果我的微服务中有很多个独立服务都要对外提供服务,那么对于开发人员或者运维人员来说,他要如何去管理这些接口?特别是当项目非常大非常庞杂的情况下要如何管理?2.权限管理也是一个老生常谈的问题,在微服务中,一个独立的系统被拆分成很多个独立的模块,为了确保安全,我难道需要在每一个模块上都添加上相同的鉴权代码来确保系统不被非法访问?如果是这样的话,那么工作量就太大了,而且维护也非常不方便。
为了解决上面提到的问题,我们引入了API网关的概念,API网关是一个更为智能的应用服务器,它有点类似于我们微服务架构系统的门面,所有的外部访问都要先经过API网关,然后API网关来实现请求路由、负载均衡、权限验证等功能。Spring Cloud中提供的Spring Cloud Zuul实现了API网关的功能,本文我们就先来看看Spring Cloud Zuul的一个基本使用。
二、Zuul的基本实现
1. 创建Spring Boot项目
项目名称为:api-gateway
![](https://upload-images.jianshu.io/upload_images/16598307-c3a15fdc449467e5.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1000/format/webp)
![](https://upload-images.jianshu.io/upload_images/16598307-edbcb57ad8cc4302.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/853/format/webp)
![](https://upload-images.jianshu.io/upload_images/16598307-316ddf23774b7663.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/853/format/webp)
![](https://upload-images.jianshu.io/upload_images/16598307-0abcdc1611d64815.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/872/format/webp)
创建后项目结构如下:
![](https://upload-images.jianshu.io/upload_images/16598307-e3518bc905aa8a72.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/498/format/webp)
2. pom.xml文件添加依赖
这里我们主要添加两个依赖spring-cloud-starter-zuul和spring-cloud-starter-eureka,spring-cloud-starter-zuul依赖中则包含了ribbon、hystrix、actuator等
<?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.wxc</groupId>
<artifactId>api-gateway</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.7.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>Dalston.SR3</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</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>
</project>
3. 静态资源添加配置
application.properties添加以下配置,application.properties文件中的配置可以分为两部分,一部分是Zuul应用的基础信息,还有一部分则是路由规则,如下:
# 基础信息配置
spring.application.name=api-gateway
server.port=2006
# 路由规则配置
zuul.routes.api-a.path=/api-a/**
zuul.routes.api-a.serviceId=feign-consumer
# API网关也将作为一个服务注册到eureka-server上
eureka.client.service-url.defaultZone=http://peer1:1111/eureka
我们在这里配置了路由规则所有符合/api-a/**的请求都将被转发到feign-consumer服务上,至于feign-consumer服务的地址到底是什么则由eureka-server去分析,我们这里只需要写上服务名即可。以上面的配置为例,如果我请求http://localhost:2006/api-a/hello1接口则相当于请求http://localhost:2005/hello1(我这里feign-consumer的地址为http://localhost:2005),我们在路由规则中配置的api-a是路由的名字,可以任意定义,但是一组path和serviceId映射关系的路由名要相同。
4. 创建项目启动入口类
com.wxc.test包下创建ApiGatewayApplication.java,入口类上添加@EnableZuulProxy注解表示开启Zuul的API网关服务功能
package com.wxc.test;
import com.wxc.test.filter.PermisFilter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.context.annotation.Bean;
@SpringBootApplication
@EnableZuulProxy
public class ApiGatewayApplication {
public static void main(String[] args) {
//测试访问地址:http://localhost:2006/api-a/hello1
SpringApplication.run(ApiGatewayApplication.class, args);
}
//配置过滤器
@Bean
PermisFilter permisFilter() {
return new PermisFilter();
}
}
5. 添加请求过滤
构建好了网关,接下来我们就来看看如何利用网关来实现一个简单的权限验证。这里就涉及到了Spring Cloud Zuul中的另外一个核心功能:请求过滤。请求过滤有点类似于Java中Filter过滤器,先将所有的请求拦截下来,然后根据现场情况做出不同的处理,这里我们就来看看Zuul中的过滤器要如何使用。很简单,两个步骤:
5.1 定义过滤器
com.wxc.test.filter包下创建PermisFilter.java
package com.wxc.test.filter;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import javax.servlet.http.HttpServletRequest;
/**
* 1.filterType方法的返回值为过滤器的类型,过滤器的类型决定了过滤器在哪个生命周期执行,pre表示在路由之前执行过滤器,
* 其他可选值还有post、error、route和static,当然也可以自定义。
* 2.filterOrder方法表示过滤器的执行顺序,当过滤器很多时,这个方法会有意义。
* 3.shouldFilter方法用来判断过滤器是否执行,true表示执行,false表示不执行,在实际开发中,
* 我们可以根据当前请求地址来决定要不要对该地址进行过滤,这里我直接返回true。
* 4.run方法则表示过滤的具体逻辑,假设请求地址中携带了login参数的话,则认为是合法请求,否
* 则就是非法请求,如果是非法请求的话,首先设置ctx.setSendZuulResponse(false);表示不对该请求进行路由,然后设置响应码和响应值。
* 这个run方法的返回值在当前版本(Dalston.SR3)中暂时没有任何意义,可以返回任意值。
*/
public class PermisFilter extends ZuulFilter {
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String login = request.getParameter("login");
if (login == null) {
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
ctx.addZuulResponseHeader("content-type","text/html;charset=utf-8");
ctx.setResponseBody("非法访问");
}
return null;
}
}
温馨提示:
(1)filterType方法的返回值为过滤器的类型,过滤器的类型决定了过滤器在哪个生命周期执行,pre表示在路由之前执行过滤器,其他可选值还有post、error、route和static,当然也可以自定义。
(2)filterOrder方法表示过滤器的执行顺序,当过滤器很多时,这个方法会有意义。
(3)shouldFilter方法用来判断过滤器是否执行,true表示执行,false表示不执行,在实际开发中,我们可以根据当前请求地址来决定要不要对该地址进行过滤,这里我直接返回true。
(4)run方法则表示过滤的具体逻辑,假设请求地址中携带了login参数的话,则认为是合法请求,否则就是非法请求,如果是非法请求的话,首先设置ctx.setSendZuulResponse(false);表示不对该请求进行路由,然后设置响应码和响应值。这个run方法的返回值在当前版本(Dalston.SR3)中暂时没有任何意义,可以返回任意值。
5.2 配置过滤器Bean
在项目入口类ApiGatewayApplication.java中配置
@Bean
PermisFilter permisFilter() {
return new PermisFilter();
}
6. 启动项目并访问
启动项目之前,确保服务注册中心、服务提供者、服务消费者都已开启,之后运行Zuul项目
![](https://upload-images.jianshu.io/upload_images/16598307-df7f5761fd0658ea.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/973/format/webp)
浏览器输入http://localhost:2006/api-a/hello1进行访问,出现以下结果,代表网关的拦截已生效
![](https://upload-images.jianshu.io/upload_images/16598307-95916114f1eb5a2f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/776/format/webp)
三、路由配置细节
1. 简介
上面内容我们介绍了API网关的基本构建方式以及请求过滤,小伙伴们对Zuul的作用应该已经有了一个基本的认识,但是对于路由的配置我们只是做了一个简单的介绍,本文我们就来看看路由配置的其他一些细节。
2. 配置详解
首先我们来回忆一下上篇文章我们配置路由规则的那两行代码:
zuul.routes.api-a.path=/api-a/**
zuul.routes.api-a.serviceId=feign-consumer
我们说当我的访问地址符合/api-a/**规则的时候,会被自动定位到feign-consumer服务上去,不过两行代码有点麻烦,我们可以用下面一行代码来代替,如下:
zuul.routes.feign-consumer=/api-a/**
zuul.routes后面跟着的是服务名,服务名后面跟着的是路径规则,这种配置方式显然更简单。
如果映射规则我们什么都不写,系统也给我们提供了一套默认的配置规则,默认的配置规则如下:
zuul.routes.feign-consumer.path=/feign-consumer/**
zuul.routes.feign-consumer.serviceId=feign-consumer
默认情况下,Eureka上所有注册的服务都会被Zuul创建映射关系来进行路由,但是对于我这里的例子来说,我希望提供服务的是feign-consumer,hello-service作为服务提供者只对服务消费者提供服务,不对外提供服务,如果使用默认的路由规则,则Zuul也会自动为hello-service创建映射规则,这个时候我们可以采用如下方式来让Zuul跳过hello-service服务,不为其创建路由规则:
zuul.ignored-services=hello-service
有的小伙伴可能为有疑问,我们定义路由规则/api-a/*的时候,为什么最后面是两个,一个可不可以呢?当然可以,不过意义可就不一样了,Zuul中的路由匹配规则使用了Ant风格定义,一共有三种不同的通配符:
![](https://upload-images.jianshu.io/upload_images/16598307-f2c98a9a5989adcc.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/692/format/webp)
有的时候我们还会遇到这样一个问题,比如我有两个服务,一个叫做feign-consumer,还有一个叫做feign-consumer-hello,此时我的路由配置规则可能这样来写:
zuul.routes.feign-consumer.path=/feign-consumer/**
zuul.routes.feign-consumer.serviceId=feign-consumer
zuul.routes.feign-consumer-hello.path=/feign-consumer/hello/**
zuul.routes.feign-consumer-hello.serviceId=feign-consumer-hello
此时我访问feign-consumer-hello的路径会同时被这两条规则所匹配,Zuul中的路径匹配方式是一种线性匹配方式,即按照路由匹配规则的存储顺序依次匹配,因此我们只需要确保feign-consumer-hello的匹配规则被先定义feign-consumer的匹配规则被后定义即可,但是在properties文件中我们不能保证这个先后顺序,此时我们需要用YAML来配置,这个时候我们可以删掉resources文件夹下的application.properties,然后新建一个application.yml,内容如下:
spring:
application:
name: api-gateway
server:
port: 2006
zuul:
routes:
feign-consumer-hello:
path: /feign-consumer/hello/**
serviceId: feign-consumer-hello
feign-consumer:
path: /feign-consumer/**
serviceId: feign-consumer
eureka:
client:
service-url:
defaultZone: http://localhost:1111/eureka/
这个时候我们就可以确保先加载feign-consumer-hello的匹配规则,后加载feign-consumer的匹配规则。
上文我们说了一个zuul.ignored-services=hello-service属性可以忽略掉一个服务,不给某个服务设置映射规则,这个配置我们可以进一步细化,比如说我不想给/hello接口路由,那我们可以按如下方式配置(后面我都用yaml配置):
zuul:
ignored-patterns: /**/hello/**
此时访问/hello接口就会报404错误,同时我们也可以看到后台打印如下日志:
![](https://upload-images.jianshu.io/upload_images/16598307-48998bebf19fd9ff.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/640/format/webp)
此外,我们也可以统一的为路由规则增加前缀,设置方式如下:
zuul:
prefix: /myapi
此时我们的访问路径就变成了http://localhost:2006/myapi/feign-consumer/hello1。
一般情况下API网关只是作为系统的统一入口,但是有的时候我们可能也需要在API网关上做一点业务逻辑操作,比如我现在在api-gateway项目中新建如下Controller:
@RestController
public class HelloController {
@RequestMapping("/local")
public String hello() {
return "hello api gateway";
}
}
我希望用户在访问/local时能够自动跳转到这个方法上来处理,那么此时我们需要用到Zuul的本地跳转,配置方式如下:
zuul:
prefix: /myapi
ignored-patterns: /**/hello/**
routes:
local:
path: /local/**
url: forward:/local
此时访问http://localhost:2006/myapi/local结果如下:
![](https://upload-images.jianshu.io/upload_images/16598307-4652628d5e062404.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/341/format/webp)
我们在使用Nginx的时候,会涉及到一个请求头信息的配置,防止页面重定向后跳转到上游服务器上去,这个问题在Zuul中一样存在,假设我的feign-consumer中提供了一个接口/hello4,当访问/hello4接口的时候,页面重定向到/hello,默认情况下,重定向的地址是具体的服务实例的地址,而不是API网关的跳转地址,这种做法会暴露真实的服务地址,所以需要在Zuul中配置,配置方式很简单,如下:
zuul:
add-host-header: true
表示API网关在进行请求路由转发前为请求设置Host头信息。
默认情况下,敏感的头信息无法经过API网关进行传递,我们可以通过如下配置使之可以传递:
zuul:
routes:
feign-consumer:
sensitiveHeaders:
在Zuul中,Ribbon和Hystrix的配置还是和之前的配置方式一致,这里我就不赘述了,如果我们想关闭Hystrix重试机制,可以通过如下方式:
关闭全局重试机制:
zuul:
retryable: false
关闭某一个服务的重试机制:
zuul:
routes:
feign-consumer:
retryable: false
四、异常处理细节
https://mp.weixin.qq.com/s/Y73q8XrVoEuVqCNpexiVHg
五、项目源码与参考资料下载
链接:https://pan.baidu.com/s/1EGzN_WvAgLZz4yFJ0t-fVQ
提取码:ln1k