前言
Zuul的基本使用FallBack+Filter。
-
Zuul Filter
Filter是Zuul的核心,用来实现对外服务的控制。Filter的生命周期有4个,分别是“PRE”、“ROUTING”、“POST”、“ERROR”,整个生命周期可以用下图来表示:
-
Zuul大部分功能都是通过过滤器来实现的,这些过滤器类型对应于请求的典型生命周期。
- PRE: 这种过滤器在请求被路由之前调用。我们可利用这种过滤器实现身份验证、在集群中选择请求的微服务、记录调试信息等。
- ROUTING:这种过滤器将请求路由到微服务。这种过滤器用于构建发送给微服务的请求,并使用Apache HttpClient或Netfilx Ribbon请求微服务。
- POST:这种过滤器在路由到微服务以后执行。这种过滤器可用来为响应添加标准的HTTP Header、收集统计信息和指标、将响应从微服务发送给客户端等。
- ERROR:在其他阶段发生错误时执行该过滤器。 除了默认的过滤器类型,Zuul还允许我们创建自定义的过滤器类型。例如,我们可以定制一种STATIC类型的过滤器,直接在Zuul中生成响应,而不将请求转发到后端的微服务。
-
Zuul中默认实现的Filter
-
禁用指定的Filter
zuul: FormBodyWrapperFilter: pre: disable: true
案例
-
Eureka Server端编写(参考前例)。
-
Eureka Client端服务调用方编写。
-
项目结构
-
CoreCode
<?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> <artifactId>microservice-deal-gateway-zuul-filter</artifactId> <packaging>jar</packaging> <name>microservice-deal-gateway-zuul-filter</name> <description>Demo project for Spring Boot</description> <parent> <groupId>com.example</groupId> <artifactId>microservice-deal-parent</artifactId> <version>0.0.1-SNAPSHOT</version> </parent> <dependencies> <!-- Zuul --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> </dependencies> </project>
@Component public class MyFallbackProvider implements FallbackProvider { @Override public String getRoute() { // 表明是为哪个微服务提供回退,*表示为所有微服务提供回退 return "*"; } @Override public ClientHttpResponse fallbackResponse(String route, Throwable cause) { return new ClientHttpResponse() { @Override public HttpStatus getStatusCode() throws IOException { return HttpStatus.OK; // 请求网关成功了,所以是ok } @Override public int getRawStatusCode() throws IOException { return HttpStatus.OK.value(); } @Override public String getStatusText() throws IOException { return HttpStatus.OK.getReasonPhrase(); } @Override public void close() { } @Override public InputStream getBody() throws IOException { JSONObject json = new JSONObject(); json.put("state", "501"); json.put("msg", "后台接口错误"); return new ByteArrayInputStream(json.toJSONString().getBytes("UTF-8")); // 返回前端的内容 } @Override public HttpHeaders getHeaders() { HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8); // 设置头 return httpHeaders; } }; } }
@Component public class ErrorFilter extends ZuulFilter { private static Logger log = LoggerFactory.getLogger(ErrorFilter.class); @Override public String filterType() { // 异常过滤器 return "error"; } @Override public int filterOrder() { // 优先级,数字越大,优先级越低 return 0; } @Override public boolean shouldFilter() { // 是否执行该过滤器,true代表需要过滤 return true; } @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); log.info("进入异常过滤器"); System.out.println(ctx.getResponseBody()); ctx.setResponseBody("出现异常"); return null; }
@Component public class AccessUserNameFilter extends ZuulFilter { @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); System.out.println(String.format("%s AccessUserNameFilter request to %s", request.getMethod(), request.getRequestURL().toString())); String username = request.getParameter("username");// 获取请求的参数 if (null != username && username.equals("Dustyone")) {// 如果请求的参数不为空,且值为chhliu时,则通过 ctx.setSendZuulResponse(true);// 对该请求进行路由 ctx.setResponseStatusCode(200); ctx.set("isSuccess", true);// 设值,让下一个Filter看到上一个Filter的状态 return null; } else { ctx.setSendZuulResponse(false);// 过滤该请求,不对其进行路由 ctx.setResponseStatusCode(401);// 返回错误码 ctx.setResponseBody("{\"result\":\"username is not correct!\"}");// 返回错误内容 ctx.set("isSuccess", false); return null; } } @Override public boolean shouldFilter() { return true;// 是否执行该过滤器,此处为true,说明需要过滤 } @Override public int filterOrder() { return 0;// 优先级为0,数字越大,优先级越低 } @Override public String filterType() { return "pre";// 前置过滤器 }
@Component public class AccessPasswordFilter extends ZuulFilter { @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); System.out.println(String.format("%s AccessPasswordFilter request to %s", request.getMethod(), request.getRequestURL().toString())); String username = request.getParameter("password"); if (null != username && username.equals("123456")) { ctx.setSendZuulResponse(true); ctx.setResponseStatusCode(200); ctx.set("isSuccess", true); return null; } else { ctx.setSendZuulResponse(false); ctx.setResponseStatusCode(401); ctx.setResponseBody("{\"result\":\"password is not correct!\"}"); ctx.set("isSuccess", false); return null; } } @Override public boolean shouldFilter() { RequestContext ctx = RequestContext.getCurrentContext(); return (boolean) ctx.get("isSuccess");// 如果前一个过滤器的结果为true,则说明上一个过滤器成功了,需要进入当前的过滤,如果前一个过滤器的结果为false,则说明上一个过滤器没有成功,则无需进行下面的过滤动作了,直接跳过后面的所有过滤器并返回结果 } @Override public int filterOrder() { return 1; // 优先级设置为1 } @Override public String filterType() { return "pre"; }
public class AccessTokenFilter extends ZuulFilter { @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); System.out.println(String.format("%s AccessTokenFilter request to %s", request.getMethod(), request.getRequestURL().toString())); ctx.setSendZuulResponse(true); ctx.setResponseStatusCode(200); ctx.setResponseBody("{\"name\":\"Dustyone\"}");// 输出最终结果 return null; } @Override public boolean shouldFilter() { return true; } @Override public int filterOrder() { return 0; } @Override public String filterType() { return "post";// 在请求被处理之后,会进入该过滤器 }
@SpringBootApplication @EnableDiscoveryClient @EnableZuulProxy public class MicroserviceDealGatewayZuulFilterApplication { public static void main(String[] args) { SpringApplication.run(MicroserviceDealGatewayZuulFilterApplication.class, args); } /** * 该示例是将userservice-v1映射到/v1/uservice/中,常用于版本管理中,如APP端调用的API带有版本信息(服务-版本), * Zuul为这些不同版本的微服务应用生成以版本代号作为路由前缀定义的路由规则。 * 通过具有版本号前缀的URL路径,可以很容易通过路径表达式来归类和管理这些具有版本信息的微服务 * @return */ @Bean public PatternServiceRouteMapper serviceRouteMapper() { // 调用构造函数PatternServiceRouteMapper(String servicePattern, String // routePattern) // servicePattern指定微服务的正则 // routePattern指定路由正则 return new PatternServiceRouteMapper("(?<name>^.+)-(?<version>v.+$)", "${version}/${name}"); }
server: port: 8040 spring: application: name: microservice-deal-gateway-zuul-fallback eureka: client: service-url: #defaultZone: http://localhost:8080/eureka/ defaultZone: http://Dustyone:bai5331359@localhost:8080/eureka/ instance: prefer-ip-address: true #zuul:routes:deal-route: path: /deal/** service-id: microservice-deal-cloud # 这样访问Zuul的/deal/1路径,请求将会被转发到microservice-deal-cloud的/deal/1,可查看日志打印,有助于理解。 zuul: routes: microservice-deal-cloud: path: /api-deal/** strip-prefix: true #设置feign的hystrix响应超时时间(必须) hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 5000 ribbon: eureka: enabled: true # 禁用掉ribbon的eureka使用。详见:http://cloud.spring.io/spring-cloud-static/Camden.SR3/#_example_disable_eureka_use_in_ribbon NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RoundRobinRule #Ribbon均衡负载策略 management: security: enabled: false
-
-
Eureka Client端服务提供方编写
-
访问:
-
Hystrix熔断机制:
@GetMapping("/deal/{id}") public Deal findById(@PathVariable Integer id) throws InterruptedException { Thread.sleep(3000); Deal deal = new Deal(id, "Dustyone", "Heyt", 22, 18,port); return deal; }
-
重启microservice-deal-cloud服务节点,访问:http://localhost:8040/api-deal/deal/1?username=Dustyone&password=123456:
小结
- 本案小节使用的案例:microservice-deal-eureka-authentication、microservice-deal-cloud、microservice-deal-gateway-zuul-filter。