转载请表明出处 https://blog.csdn.net/Amor_Leo/article/details/87885822 谢谢
Spring Cloud Zuul
Spring Cloud Zuul概述
当我们在使用微服务的时候,完成一个业务可能需要同时调用多个微服务,则需要分别请求多个服务接口,首先客户端需要请求不同的微服务,客户端的复杂性增大认证复杂,每个微服务都需要自己独立的认证方式,某些情况下会存在一些问题,例如跨域问题,每个服务存在的协议可能不同。
Spring Cloud Zuul是基于Netflix Zuul实现的API网关,他可以和Eureka,Ribbon,Hystrix等组件配合使用。他除了Zuul Core还整合了actuator,hystrix,ribbon,提供动态路由,监控,弹性,安全等的边缘服务。Zuul组件的核心是一系列的过滤器,这些过滤器可以完成以下功能:
- 身份认证和安全: 识别每一个资源的验证要求,并拒绝那些不符的请求
- 审查与监控:
- 动态路由:动态将请求路由到不同后端集群
- 压力测试:逐渐增加指向集群的流量,以了解性能
- 负载分配:为每一种负载类型分配对应容量,并弃用超出限定值的请求
- 静态响应处理:边缘位置进行响应,避免转发到内部集群
- 多区域弹性:跨域AWS Region进行请求路由,旨在实现ELB(ElasticLoad Balancing)使用多样化
Zuul使用的默认是Apache的HTTP Client,也可以使用Rest Client或者okhttp3.OkHttpClient。使用Rest Client可以设置ribbon.restclient.enabled=true,使用okhttp3.OkHttpClient可以设置ribbon.okhttp.enabled=true。
网关的优势
- 阻止将内部的敏感信息暴露给外部的客户端: API网关通过提供微服务绑定和解绑的能力来将外部的公开API与内部微服务API分离开。这样就具备重构和裁切微服务而不会影响外部绑定的客户端的能力。它同时对客户端隐藏了服务发现和服务版本这些细节,因为它对所有微服务提供单一的访问点。
- 为服务增加额外的安全层: API网关通过提供额外的安全层帮助阻止大规模攻击。这些攻击包括SQL注入,XML解析漏洞和DDOS攻击。
- 可以支持混合通讯协议: 不同于外部API一般使用HTTP或REST,内部微服务可以从使用不用通讯协议中收获益处。这些协议可以是ProtoBuf或AMQP,甚至是诸如SOAP,JSON-RPC或者XML-RPC这样的系统集成协议。API网关可以对这些协议提供统一的外部REST接口,这就允许开发团队挑选一款最适合于内部架构的协议。
- 降低微服务的复杂度: 微服务中有一些常见的要点,诸如使用API令牌进行授权,访问控制和调用频次限制。这每个点都需要每个服务区增加额外的时间去实现它们。API网关将这些要点从你的代码中提取出来,允许你的服务只关注于它们需要关注的任务。
- 微服务模拟和虚拟化: 随着微服务API从外部API中分离出来,你可以模拟你的服务去做设计验证或者很方便的做集成测试。
Spring Cloud Zuul搭建
基本
- pom
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- Application类上
@EnableZuulProxy //打开zuul
- yml
- 简单 请求按默认的方式处理(即通过http://zuulHost:zuulPort/服务名/请求路径)
server: port: 8040 spring: application: name: gateway-zuul-server eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true management: security: enabled: false endpoints: web: exposure: include: "*" #因为springboot2.1.必须加上,支持访问/actuator/hystrix.stream
- 自定义指定微服务的访问路径
server: port: 8040 spring: application: name: gateway-zuul-server eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true zuul: routes: provider-server: /provider/** consumer-server: /consumer/** management: security: enabled: false endpoints: web: exposure: include: "*" #因为springboot2.1.必须加上,支持访问/actuator/hystrix.stream
- 忽略指定微服务,代理其他微服务
server: port: 8040 spring: application: name: gateway-zuul-server eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true zuul: ignored-services: provider-server,consumer-server management: security: enabled: false endpoints: web: exposure: include: "*" #因为springboot2.1.必须加上,支持访问/actuator/hystrix.stream
- 忽略所有微服务,路由指定微服务
server: port: 8040 spring: application: name: gateway-zuul-server eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true zuul: ignored-services: '*' # 使用'*'可忽略所有微服务 routes: provider-server: /provider/** consumer-server: /consumer/** management: security: enabled: false endpoints: web: exposure: include: "*" #因为springboot2.1.必须加上,支持访问/actuator/hystrix.stream
- 同时指定微服务的serviceId和对应路径
server: port: 8040 spring: application: name: gateway-zuul-server eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true zuul: routes: provider-route: # 该配置方式中,provider-route只是给路由一个名称,可以任意起名。 service-id: provider-server path: /provider/** # service-id对应的路径 consumer-route: # 该配置方式中,consumer-route只是给路由一个名称,可以任意起名。 service-id: consumer-server path: /consumer/** # service-id对应的路径 management: security: enabled: false endpoints: web: exposure: include: "*" #因为springboot2.1.必须加上,支持访问/actuator/hystrix.stream
- 同时指定path和url
server: port: 8040 spring: application: name: gateway-zuul-server eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true zuul: routes: provider-route: # 该配置方式中,provider-route只是给路由一个名称,可以任意起名。 url: http://localhost:8000/ # 指定的url path: /provider/** # url对应的路径。 consumer-route: # 该配置方式中,consumer-route只是给路由一个名称,可以任意起名。 url: http://localhost:8010/ # 指定的url path: /consumer/** # url对应的路径。 management: security: enabled: false endpoints: web: exposure: include: "*" #因为springboot2.1.必须加上,支持访问/actuator/hystrix.stream
- 同时指定path和URL,并且不破坏Zuul的Hystrix、Ribbon特性
server: port: 8040 spring: application: name: gateway-zuul-server eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true zuul: routes: provider-route: # 该配置方式中,provider-route只是给路由一个名称,可以任意起名。 path: /provider/** service-id: provider-server ribbon: eureka: enabled: false # 禁用掉ribbon的eureka使用 provider-server: ribbon: listOfServers: localhost:8000,localhost:8001 management: security: enabled: false endpoints: web: exposure: include: "*" #因为springboot2.1.必须加上,支持访问/actuator/hystrix.stream
- 为Zuul添加映射前缀1
strip-prefix是剥离前缀的意思,设置为false就是不剥离前缀,Zuul默认是剥离前缀的。
server: port: 8040 spring: application: name: gateway-zuul-server eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true zuul: prefix: /api strip-prefix: false routes: provider-server: /provider/** logging: level: com.netflix: DEBUG management: security: enabled: false endpoints: web: exposure: include: "*" #因为springboot2.1.必须加上,支持访问/actuator/hystrix.stream # 访问Zuul的/api/provider-server/1路径,请求将会被转发到provider-server的/api/1,可查看日志打印,有助于理解。
- 为Zuul添加映射前缀2
server: port: 8040 spring: application: name: gateway-zuul-server eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true zuul: routes: provider-server: path: /provider/** strip-prefix: false logging: level: com.netflix: DEBUG management: security: enabled: false endpoints: web: exposure: include: "*" #因为springboot2.1.必须加上,支持访问/actuator/hystrix.stream # 这样访问Zuul的/provider/1路径,请求将会被转发到provider-server的/provider/1,可查看日志打印,有助于理解。
- 忽略某些路径
server: port: 8040 spring: application: name: gateway-zuul-server eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true zuul: ignoredPatterns: /**/admin/** # 忽略所有包括/admin/的路径 routes: provider-server: /provider/** management: security: enabled: false endpoints: web: exposure: include: "*" #因为springboot2.1.必须加上,支持访问/actuator/hystrix.stream
- 本地转发
server: port: 8040 spring: application: name: gateway-zuul-server eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true zuul: routes: route-name: path: /path-a/** url: forward:/path-b management: security: enabled: false endpoints: web: exposure: include: "*" #因为springboot2.1.必须加上,支持访问/actuator/hystrix.stream
- 使用正则表达式指定zuul的路由匹配规则
server: port: 8040 spring: application: name: gateway-zuul-server eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true management: security: enabled: false endpoints: web: exposure: include: "*" #因为springboot2.1.必须加上,支持访问/actuator/hystrix.stream
@SpringBootApplication @EnableZuulProxy public class ZuulApplication { @Bean public PatternServiceRouteMapper serviceRouteMapper() { // 调用构造函数PatternServiceRouteMapper(String servicePattern, String routePattern) // servicePattern指定微服务的正则 // routePattern指定路由正则 return new PatternServiceRouteMapper("(?<name>^.+)-(?<version>v.+$)", "${version}/${name}"); } public static void main(String[] args) { SpringApplication.run(ZuulApplication.class, args); } }
- Zuul的安全与Heard
如果不希望敏感Headers向下游泄漏到外部服务器,可以在路由配置中指定忽略的Headers列表
sensitive-headers是黑名单,默认不为空,因此要使Zuul发送所有Headers(“忽略”的Headers除外),您必须将其明确设置为空。 如果要将cookie或授权Headers传递给后端,则必须执行此操作。server: port: 8040 spring: application: name: gateway-zuul-server eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true zuul: routes: provider-route: # 该配置方式中,provider-route只是给路由一个名称,可以任意起名。 url: http://localhost:8000/ # 指定的url path: /provider/** # url对应的路径。 sensitive-headers: Cookie,Set-Cookie,Authorization # 过滤 management: security: enabled: false endpoints: web: exposure: include: "*" #因为springboot2.1.必须加上,支持访问/actuator/hystrix.stream
也可以通过设置zuul.sensitive-headers来全局设置敏感的Headers。 如果在路由上设置了sensitive-headers,则会覆盖全局sensitive-headers设置。server: port: 8040 spring: application: name: gateway-zuul-server eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true zuul: routes: provider-route: # 该配置方式中,provider-route只是给路由一个名称,可以任意起名。 url: http://localhost:8000/ # 指定的url path: /provider/** # url对应的路径。 sensitive-headers: # 不过滤,则须显式设为空。 management: security: enabled: false endpoints: web: exposure: include: "*" #因为springboot2.1.必须加上,支持访问/actuator/hystrix.stream
忽略某些headserver: port: 8040 spring: application: name: gateway-zuul-server eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true zuul: sensitive-headers: # 全局不过滤 routes: provider-route: # 该配置方式中,provider-route只是给路由一个名称,可以任意起名。 url: http://localhost:8000/ # 指定的url path: /provider/** # url对应的路径。 management: security: enabled: false endpoints: web: exposure: include: "*" #因为springboot2.1.必须加上,支持访问/actuator/hystrix.stream
默认情况下zuul.ignored-headers为空值,但如果是Spring Security在项目的classpath中,那么zuul.ignored-headers默认为Pragma,Cache-Control,X-Frame-Options,X-Content-Type-Options,X-XSS-Protection,Expires。如果需要使用下游微服务的Spring Security的Header头部信息,就可以设置zuul.ignoreSecurity-headers=false,保留Spring Security的安全头部信息和代理的头部信息。当然,我们也可以不设置这个值,仅仅获取来自代理的头部信息。server: port: 8040 spring: application: name: gateway-zuul-server eureka: client: service-url: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true zuul: ignored-headers: header1,header2 # header1,header2不会传播到其他服务 management: security: enabled: false endpoints: web: exposure: include: "*" #因为springboot2.1.必须加上,支持访问/actuator/hystrix.stream
- 通过Zuul上传文件
对于小文件(1M以内)上传,即可正常上传,对于大文件(10M以上)上传,需要为上传路径添加/zuul前缀,也可以使用zuul.servlet-path自定义前缀.如果zuul使用了Ribbon做负载均衡,那么对于超大的文件需要提升超时设置。
文件上传微服务:
pom
yml<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
Controllerserver: port: 8050 eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true spring: application: name: file-upload-server http: multipart: max-file-size: 2000Mb # Max file size,单个文件大小,默认1M max-request-size: 2500Mb # Max request size,总上传数据的大小,默认10M
Application类上@Controller public class FileUploadController { /** * 上传文件 * 测试方法: * 有界面的测试:http://localhost:8050/index.html * 使用命令:curl -F "file=@文件全名" localhost:8050/upload * ps.该示例比较简单,没有做IO异常、文件大小、文件非空等处理 * @param file 待上传的文件 * @return 文件在服务器上的绝对路径 * @throws IOException IO异常 */ @RequestMapping(value = "/upload", method = RequestMethod.POST) public @ResponseBody String handleFileUpload(@RequestParam(value = "file", required = true) MultipartFile file) throws IOException { byte[] bytes = file.getBytes(); File fileToSave = new File(file.getOriginalFilename()); FileCopyUtils.copy(bytes, fileToSave); return fileToSave.getAbsolutePath(); } }
Zuul-Server:@EnableEurekaClient
pom
yml<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency>
Application类上server: port: 8040 eureka: client: serviceUrl: defaultZone: http://localhost:8761/eureka/ instance: prefer-ip-address: true spring: application: name: gateway-zuul-server # 上传大文件得将超时时间设置长一些,否则会报超时异常 hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 60000 ribbon: ConnectTimeout: 3000 ReadTimeout: 60000
@EnableZuulProxy
Zuul的回退
- pom
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- yml
spring:
application:
name: gateway-zuul-fallback-server
server:
port: 8060
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
instance:
prefer-ip-address: true
zuul:
routes:
provide-rote:
service-id: provider-server
path: /provide/**
stripPrefix: false
- Application类上
@EnableZuulProxy
- 回退类
@Component
public class MyZuulFallbackProvider implements FallbackProvider {
@Override
public String getRoute() {
//api服务id,如果需要所有调用都支持回退,则return "*"或return null
return "provide-server";
// return "*";
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
return new ClientHttpResponse(){
@Override
public InputStream getBody() throws IOException {
JSONObject r = new JSONObject();
r.put("state", "9999");
r.put("msg", "系统错误,请求失败");
// 响应体
return new ByteArrayInputStream(r.toJSONString().getBytes("UTF-8"));
}
@Override
public HttpHeaders getHeaders() {
//headers设定
HttpHeaders headers = new HttpHeaders();
//和body中的内容编码一致,否则容易乱码
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
return headers;
}
/**
* 网关向api服务请求是失败了,但是消费者客户端向网关发起的请求是OK的,
* 不应该把api的404,500等问题抛给客户端
* 网关和api服务集群对于客户端来说是黑盒子
*/
@Override
public HttpStatus getStatusCode() throws IOException {
// fallback时的状态码
return HttpStatus.OK;
}
@Override
public int getRawStatusCode() throws IOException {
// 数字类型的状态码,本例返回的其实就是200,详见HttpStatus
return HttpStatus.OK.value();
}
@Override
public String getStatusText() throws IOException {
// 状态文本,本例返回的其实就是OK,详见HttpStatus
return HttpStatus.OK.getReasonPhrase();
}
@Override
public void close() {
}
};
}
}
Zuul的过滤
- pom
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
- yml
spring:
application:
name: gateway-zuul-filter-server
server:
port: 8060
eureka:
client:
service-url:
defaultZone: http://localhost:8761/eureka
instance:
prefer-ip-address: true
zuul:
routes:
provide-rote:
service-id: provider-server
path: /provide/**
stripPrefix: false
单个过滤器
- Application类上
@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
@Bean
public PreZuulFilter preZuulFilter() {
return new PreZuulFilter();
}
}
- Filter类
public class PreZuulFilter extends ZuulFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(PreZuulFilter.class);
/**
* @Description: 过滤器类型 顺序: pre ->routing -> post ,以上3个顺序出现异常时都可以触发error类型的filter
* pre 代表在路由代理之前执行
* route 代表代理的时候执行
* error 代表出现错的时候执行
* post 代表在route 或者是 error 执行完成后执行
* @method: filterType
* @Param:
* @return: java.lang.String
*/
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
/**
* @Description: 执行顺序 同filterType类型中,order值越大,优先级越低,order值越小,优先级越高
* filter 为链式过滤器,多个filter按顺序执行
* @method: filterOrder
* @Param:
* @return: int
*/
@Override
public int filterOrder() {
return 0;
}
/**
* @Description: 是否应该执行该过滤器,如果是false,则不执行该filter
* @method: shouldFilter
* @Param:
* @return: boolean
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* @Description: 过滤的逻辑,执行业务操作
* @method: run
* @Param:
* @return: java.lang.Object
*/
@Override
public Object run() throws ZuulException {
HttpServletRequest request = RequestContext.getCurrentContext().getRequest();
String host = request.getRemoteHost();
PreZuulFilter.LOGGER.info("请求的host:{}", host);
return null;
}
}
多个过滤器
- Application类上
@EnableZuulProxy
@SpringCloudApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
//http://192.168.0.1:8040/provide/find/1?prefilter01=true&prefilter02=true&post=true
@Bean
public TestPre01Filter testPre01Filter(){
return new TestPre01Filter();
}
@Bean
public TestPre02Filter testPre02Filter(){
return new TestPre02Filter();
}
@Bean
public TestPostFilter testPostFilter(){
return new TestPostFilter();
}
}
- Filter类
/**
* 第一个pre类型的filter,prefilter01=true才能通过
*/
public class TestPre01Filter extends ZuulFilter {
/**
* 是否应该执行该过滤器,如果是false,则不执行该filter
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* 过滤器类型
* 顺序: pre ->routing -> post ,以上3个顺序出现异常时都可以触发error类型的filter
*/
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
/**
* 同filterType类型中,order值越大,优先级越低
*/
@Override
public int filterOrder() {
return 1;
}
/**
* 执行业务操作,可执行sql,nosql等业务
*/
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String prefilter01 = request.getParameter("prefilter01");
System.out.println("执行pre01Filter .....prefilter01=" + prefilter01 );
//如果用户名和密码都正确,则继续执行下一个filter
if("true".equals(prefilter01) ){
//会进行路由,也就是会调用api服务提供者
ctx.setSendZuulResponse(true);
ctx.setResponseStatusCode(200);
//可以把一些值放到ctx中,便于后面的filter获取使用
ctx.set("isOK",true);
}else{
//不需要进行路由,也就是不会调用api服务提供者
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
//可以把一些值放到ctx中,便于后面的filter获取使用
ctx.set("isOK",false);
//返回内容给客户端,返回错误内容
ctx.setResponseBody("{\"result\":\"pre01Filter auth not correct!\"}");
}
return null;
}
}
/**
* prefilter02 校验 prefilter02=true才能通过
*/
public class TestPre02Filter extends ZuulFilter {
/**
* 是否应该执行该过滤器,如果是false,则不执行该filter
*/
@Override
public boolean shouldFilter() {
//上一个filter设置该值
return RequestContext.getCurrentContext().getBoolean("isOK");
}
/**
* 过滤器类型
* 顺序: pre ->routing -> post ,以上3个顺序出现异常时都可以触发error类型的filter
*/
@Override
public String filterType() {
return FilterConstants.PRE_TYPE;
}
/**
* 同filterType类型中,order值越大,优先级越低
*/
@Override
public int filterOrder() {
return 2;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String prefilter02 = request.getParameter("prefilter02");
System.out.println("执行pre02Filter .....prefilter02=" + prefilter02 );
//如果用户名和密码都正确,则继续执行下一个filter
if("true".equals(prefilter02) ){
ctx.setSendZuulResponse(true);//会进行路由,也就是会调用api服务提供者
ctx.setResponseStatusCode(200);
ctx.set("isOK",true);//可以把一些值放到ctx中,便于后面的filter获取使用
}else{
ctx.setSendZuulResponse(false);//不需要进行路由,也就是不会调用api服务提供者
ctx.setResponseStatusCode(401);
ctx.set("isOK",false);//可以把一些值放到ctx中,便于后面的filter获取使用
//返回内容给客户端
ctx.setResponseBody("{\"result\":\"pre02Filter auth not correct!\"}");// 返回错误内容
}
return null;
}
}
/**
* post类型的filter,post=true才能通过
*/
public class TestPostFilter extends ZuulFilter {
/**
* 是否应该执行该过滤器,如果是false,则不执行该filter
*/
@Override
public boolean shouldFilter() {
//上一个filter设置该值
return RequestContext.getCurrentContext().getBoolean("isOK");
}
/**
* 过滤器类型
* 顺序: pre ->routing -> post ,以上3个顺序出现异常时都可以触发error类型的filter
*/
@Override
public String filterType() {
return FilterConstants.ROUTE_TYPE;
}
/**
* 同filterType类型中,order值越大,优先级越低
*/
@Override
public int filterOrder() {
return 3;
}
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String post = request.getParameter("post");
System.out.println("执行postFilter .....post=" + post );
//如果用户名和密码都正确,则继续执行下一个filter
if("true".equals(post) ){
//会进行路由,也就是会调用api服务提供者
ctx.setSendZuulResponse(true);
ctx.setResponseStatusCode(200);
//可以把一些值放到ctx中,便于后面的filter获取使用
ctx.set("isOK",true);
}else{
//不需要进行路由,也就是不会调用api服务提供者
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
//可以把一些值放到ctx中,便于后面的filter获取使用
ctx.set("isOK",false);
//返回内容给客户端,返回错误内容
ctx.setResponseBody("{\"result\":\"post auth not correct!\"}");
}
return null;
}
}
禁用Zuul Filters
默认会使用很多filters,可采用如下方式禁止
zuul.SendResponseFilter.post.disable=true
Zuul的重试
- pom
<!-- 服务发现 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- hystrix dashboard的支持 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<!-- 服务网关 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<!-- 重试机制,必须配,否则重试不生效 -->
<dependency>
<groupId>org.springframework.retry</groupId>
<artifactId>spring-retry</artifactId>
</dependency>
- yml
spring:
application:
name: gateway-zuul-server
#启动负载均衡的重试机制,默认false
cloud:
loadbalancer:
retry:
enabled: true
server:
port: 8040
eureka:
client:
healthcheck:
enabled: true #开启健康检查
service-url:
defaultZone: http://localhost:8761/eureka
instance:
prefer-ip-address: true
#Hystrix是否启用超时时间
hystrix:
command:
default:
execution:
timeout:
enabled: true
#Hystrix断路器的超时时间,默认是1s,断路器的超时时间需要大于ribbon的超时时间,不然不会触发重试。
isolation:
thread:
timeoutInMilliseconds: 2000
#hystrix dashboard的信息收集频率,默认500毫秒
stream:
dashboard:
intervalInMilliseconds: 5000
#ribbon请求连接的超时时间
ribbon:
ConnectTimeout: 250
#请求处理的超时时间
ReadTimeout: 1000
#对所有请求操作都进行重试
OkToRetryOnAllOperations: true
#对当前服务的重试次数(第一次分配给9082的时候,如果404,则再重试MaxAutoRetries次,如果还是404,则切换到其他服务MaxAutoRetriesNextServer决定)
MaxAutoRetries: 0
#切换服务的次数(比如本次请求分配给9082处理,发现404,则切换分配给9081处理,如果还是404,则返回404给客户端)
MaxAutoRetriesNextServer: 1
zuul:
ignoredServices: '*' #只有下面配置的服务会路由,其他的不会路由
routes:
provide-api:
path: /provide-api/**
serviceId: provide-server #eureka中对应的服务名称
sensitiveHeaders:
retryable: true #重试,默认false
stripPrefix: false
consumer-api:
path: /consumer-api/**
serviceId: consumer-server #eureka中对应的服务名称
sensitiveHeaders:
retryable: true #重试,默认false
stripPrefix: false
#ribbo负载均衡策略配置,默认是依次轮询,可配置随机
#api-user-server.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule
logging:
level:
com.netflix: DEBUG
Zuul的限流
- pom
<!-- 服务发现 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 服务网关 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>com.marcosbarbero.cloud</groupId>
<artifactId>spring-cloud-zuul-ratelimit</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- yml
# redis
spring:
redis:
port: 6379
host: 127.0.0.1
timeout: 2000
zuul:
ratelimit:
# key-prefix: your-prefix #对应用来标识请求的key的前缀
enabled: true
repository: REDIS #对应存储类型(用来存储统计信息)
behind-proxy: true #代理之后
default-policy: #可选 - 针对所有的路由配置的策略,除非特别配置了policies
limit: 10 #可选 - 每个刷新时间窗口对应的请求数量限制
quota: 1000 #可选- 每个刷新时间窗口对应的请求时间限制(秒)
refresh-interval: 60 # 刷新时间窗口的时间,默认值 (秒)
type: #可选 限流方式
- user
- origin
- url
policies:
provide-server: #特定的路由
limit: 10 #可选- 每个刷新时间窗口对应的请求数量限制
quota: 1000 #可选- 每个刷新时间窗口对应的请求时间限制(秒)
refresh-interval: 60 # 刷新时间窗口的时间,默认值 (秒)
type: #可选 限流方式
- user
- origin
- url
- limit 单位时间内允许访问的次数
- quota 单位时间内允许访问的总时间(单位时间窗口期内,所有的请求的总时间不能超过这个时间限制)
- refresh-interval 单位时间设置
- type 限流类型type 限流类型
- url类型的限流就是通过请求路径区分
- origin是通过客户端IP地址区分
- user是通过登录用户名进行区分,也包括匿名用户
- default-policy 可选 - 针对所有的路由配置的策略,除非特别配置了policy-list
- policies 对特定的服务id进行限流; 缺点: 可以配置多个url,但是这些url都使用一个限流配置,没有办法指定每个url的限流配置
- policy-list 对特定的服务id进行限流; 优点: 可以为某个服务id的每个url 指定不同的限流配置
- 自定义Key策略
如果希望自己控制key的策略,可以通过自定义RateLimitKeyGenerator的实现来增加自己的策略逻辑。RateLimitKeyGenerator的实现:
@Bean
public RateLimitKeyGenerator rateLimitKeyGenerator(RateLimitProperties properties,RateLimitUtils rateLimitUtils) {
return new DefaultRateLimitKeyGenerator(properties, rateLimitUtils) {
@Override
public String key(final HttpServletRequest request,final Route route,final RateLimitProperties.Policy policy) {
final List<Type> types = policy.getType();
final StringJoiner joiner = new StringJoiner(":");
joiner.add(properties.getKeyPrefix());
if (route != null) {
joiner.add(route.getId());
}
if (!types.isEmpty()) {
if (types.contains(Type.URL) && route != null) {
joiner.add(route.getPath());
}
if (types.contains(Type.ORIGIN)) {
joiner.add(getRemoteAddr(request));
}
// 这个结合文末总结。
if (types.contains(Type.USER)) {
joiner.add(request.getUserPrincipal() != null ? request.getUserPrincipal().getName() : ANONYMOUS_USER);
}
}
return joiner.toString();
//return super.key(request, route, policy) + "_" + request.getMethod();
}
};
}
- 个性化错误处理
@Bean
public RateLimiterErrorHandler rateLimitErrorHandler() {
return new DefaultRateLimiterErrorHandler() {
@Override
public void handleSaveError(String key, Exception e) {
// implementation
}
@Override
public void handleFetchError(String key, Exception e) {
// implementation
}
@Override
public void handleError(String msg, Exception e) {
// implementation
}
};
}
Zuul跨域
- 在Application类里添加
@Bean
public CorsFilter corsFilter() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true); // 允许cookies跨域
config.addAllowedOrigin("*");// #允许向该服务器提交请求的URI,*表示全部允许,在SpringMVC中,如果设成*,会自动转成当前请求头中的Origin
config.addAllowedHeader("*");// #允许访问的头信息,*表示全部
config.setMaxAge(18000L);// 预检请求的缓存时间(秒),即在这个时间段里,对于相同的跨域请求不会再预检了
config.addAllowedMethod("OPTIONS");// 允许提交请求的方法,*表示全部允许
config.addAllowedMethod("HEAD");
config.addAllowedMethod("GET");// 允许Get的请求方法
config.addAllowedMethod("PUT");
config.addAllowedMethod("POST");
config.addAllowedMethod("DELETE");
config.addAllowedMethod("PATCH");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}