网关简单的说就是微服务提供的统一的对外访问的接口,换言之就是所有的服务在请求到达服务之前,都要先经过网关,那么,这样的设计就便于我们做统一的权限验证等工作!
与eureka一样,zuul也是作为一个独立的服务存在
同时附上我练习时搭建的一个简单的SpringCloud项目,其中包含了feign、swagger-ui、rabbitmq、redis、aop、定时任务、文件上传于下载、excel导出、多数据源配置等demo,该项目也包含了SpringCloud的常用组件:
下载链接://download.youkuaiyun.com/download/weixin_45417573/12104123
一、创建服务
创建一个Springboot服务
二、添加依赖
<!-- 网关 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
当然除了添加zuul的依赖还需要添加eurekaclient的依赖和web依赖
三、添加主启动类的注解
@EnableZuulProxy
四、修改yml配置
zuul:
host:
#超时时间
connect-timeout-millis: 20000
socket-timeout-millis: 60000
#哪些服务
routes:
jt-order:
path: /order/**
serviceId: jt-order
#去除前缀
stripPrefix: false
jt-user:
path: /user/**
serviceId: jt-user
stripPrefix: false
jt-web:
path: /web/**
serviceId: jt-web
stripPrefix: false
到这里zuul服务就已经完成了,后面,就说说zuulFilter过滤器的使用了
五、过滤器
注意,自定义过滤器需继承ZuulFilter,并重写其方法
package com.wwy.filter;
import org.springframework.stereotype.Component;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.exception.ZuulException;
/**
*
* @author wwy
* @date 2019年11月29日
* @version v0.0.1
*用户验证过滤器
*/
@Component
public class Filter extends ZuulFilter{
/**
* 过滤器过滤哪些服务(过滤全部直接返回true)
*/
@Override
public boolean shouldFilter() {
//如果要过滤部分服务可以通过判断访问的服务名来返回不同的boolean值
RequestContext ctx = RequestContext.getCurrentContext();
String serviceId = (String) ctx.get(FilterConstants.SERVICE_ID_KEY);
if(serviceId.equals("jt-order")) {
return true;
}
return false;
}
/**
* 过滤器中执行的代码
*/
@Override
public Object run() throws ZuulException {
//TODO 验证权限的代码,在这里写一段代码模拟做一个权限的验证,仅供参考
//注意,这里如果需要用到request或者response我们可以通过以下方式获取
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
HttpServletResponse response = ctx.getResponse();
//通过request获取token
String token=request.getHeader("token");
if(token==null){
//如果没有token,则阻止请求转发到相应的服务
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(200);
ctx.setResponseBody(JsonResult.err().code(JsonResult.NOT_LOGIN).toString());
}
return null;//这个返回值目前没有实际意义(留着将来扩折用的)
}
/**
* 过滤器顺序
*/
@Override
public int filterOrder() {
//这里的过滤器顺序必须要大于5
return 6;
}
@Override
public String filterType() {
//指定过滤器的类型,比如前置、后置等
return FilterConstants.PRE_TYPE;
}
}
截止到这里一个基本的API网关就搭建完成了,但是还有很多可以优化的地方,例如我们上面说到,zuul整合了ribbon和hystirx,那么当我们服务转发不成功的时候,我们是否要进行重试?是否需要降级代码?
六、重试与降级
(1)添加依赖:
<!-- ribbon重试 -->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
(2)增加配置:
#开启重试
zuul:
retryable: true
#超时时间和重试次数的配置
ribbon:
ConnectTimeout: 1000
ReadTimeout: 1000
MaxAutoRetriesNextServer: 1
MaxAutoRetries: 1
(3)新建降级类,实现FallbackProvider接口并重写方法
package com.wwy.fb;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import lombok.extern.slf4j.Slf4j;
/**
* 服务的降级类
* 当zuul转发请求失败时执行该类中的方法
* @author wwy
* @date 2020年1月6日
* @version v0.0.1
*
*/
@Slf4j
@Component
public class CarFB implements FallbackProvider{
/**
* 指定服务转发失败时执行该代码
*/
@Override
public String getRoute() {
log.error("转发失败了哟");
return null;
//*和null为通配,如果要使用一个降级类通配所有的服务降级,则返回*或者null
}
/**
* 返回相应结果
*/
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return response();
}
/**
*
* @return
*/
private ClientHttpResponse response() {
return new ClientHttpResponse() {
@Override
public InputStream getBody() throws IOException {
//返回错误信息
return new ByteArrayInputStream("服务器后台错误".getBytes("UTF-8"));
}
@Override
public HttpHeaders getHeaders() {
//返回请求头格式为application/json(json格式)
HttpHeaders h=new HttpHeaders();
h.setContentType(MediaType.APPLICATION_JSON);
return h;
}
@Override
public HttpStatus getStatusCode() throws IOException {
//返回状态信息
return HttpStatus.INTERNAL_SERVER_ERROR;
}
@Override
public int getRawStatusCode() throws IOException {
//返回状态码
return HttpStatus.INTERNAL_SERVER_ERROR.value();
}
@Override
public String getStatusText() throws IOException {
return HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase();
}
@Override
public void close() {
}
};
}
}
现在网关的搭建就基本完成了,这里要注意两个问题:
1、测试时访问相应的服务必须要使用网关ip:网关端口+服务地址
2、zuul会在eureka注册中心拉取服务信息,也就是说即使zuul.routes:
中我们什么都不配,服务依旧能转发成功。如果需要关闭方便测试,可使用如下配置
:
#ribbon:
# eureka:
# enabled: false
3、出于安全考虑,zuul默认会过滤掉一些敏感的请求头,例如:Cookie,set-Cookie,Authorization,如果开发中需要这些协议头,可以通过以下配置关闭对相应协议头的过滤:
zuul:
sensitive-headers: xxx