目标:
创建一个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 "全局异常:服务器忙.....";
}
}