SpringCloud中如何使用Zuul路由网关,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。
网关可以将所有的api统一暴露,保护其他服务api接口和信息,以防止被外界直接调用
网关可以做身份认证和权限认证,防止非法请求操作API,保护服务
网关可以实现监控功能,实时日志输出,对请求进行记录
网关可以实现流量监控,便于在高流量情况下对服务进行降级
Zuul可以和Ribbon、Eureka相结合,实现智能路由和负载均衡
通过一定策略,把请求流量分发到集群状态的多个实例中
可以将API从内部接口中分离出来,方便单测
Zuul基于Servlet实现,通过自定义的ZuulServlet来对请求进行控制,Zuul中有一系列的过滤器,这些过滤器是同样是Zuul的实现关键,请求发起和响应期间,通过这些过滤器实现Zuul的功能。具体有以下四个:
PRE过滤器:在请求路由到具体的服务之前执行,用途:安全验证(身份校验,参数校验、ip黑白名单);
ROUTING过滤器 :在请求的服务到具体的微服务实例时执行,用途:进行网络请求(默认使用HttpClient);
POST过滤器:在请求路由到微服务之后执行,用途:统计信息,回传响应;
ERROR过滤器:在其他过滤器发生错误的时候执行,用途:保证请求能够正确响应;
ZuulFilter中的方法有以下四个,继承ZuulFilter并且重写以下四个方法即可实现一个过滤器。
public String filterType(); 返回该Filter的类型,即如上四种过滤器。
public int filterOrder(); 返回该过滤器的执行顺序。
public boolean shouldFilter(); 返回该过滤器是否需要执行。
public Object run(); 执行具体的过滤逻辑。
ZuulServlet
是Zuul的核心Servlet,负责初始化ZuulFilter
并且编排这些过滤器,具体代码在service()
方法中。
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { try { this.init((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse); RequestContext context = RequestContext.getCurrentContext(); context.setZuulEngineRan(); try { this.preRoute(); } catch (ZuulException var13) { this.error(var13); this.postRoute(); return; } try { this.route(); } catch (ZuulException var12) { this.error(var12); this.postRoute(); return; } try { this.postRoute(); } catch (ZuulException var11) { this.error(var11); } } catch (Throwable var14) { this.error(new ZuulException(var14, 500, "UNHANDLED_EXCEPTION_" + var14.getClass().getName())); } finally { RequestContext.getCurrentContext().unset(); } }
RequestContext
是ZuulFilter
中负责上下文衔接的角色,其本身是一个ConcurrentHashMap
,包含Request和Response、routeType、routeHost等上下文需要的对象。
父级项目zuul-test/pom.xml
<groupId>com.calvin.zuul</groupId> <artifactId>zuul-test</artifactId> <packaging>pom</packaging> <version>1.0-SNAPSHOT</version> <modules> <module>eureka-server</module> <module>user-service</module> <module>zuul-service</module> </modules> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.3.RELEASE</version> </parent> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Dalston.SR4</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
zuul-service/pom.xml
<parent> <artifactId>zuul-test</artifactId> <groupId>com.calvin.zuul</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>zuul-service</artifactId> <dependencies> <!-- Springboot支持--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--SpringCloud支持 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zuul</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
zuul-service/ZuulApplication.java
/** * <p> * 启动类 * </p> * @author Calvin * @date 2019/10/25 * @since 1.0 */ @SpringBootApplication @EnableEurekaClient @EnableZuulProxy public class ZuulServerApplication { public static void main(String[] args) { SpringApplication.run(ZuulServerApplication.class, args); } }
zuul-service/application.yml
spring: application: name: zuul-service server: port: 8030 eureka: client: service-url: defaultZone: http://localhost:8010/eureka/ instance: hostname: localhost zuul: routes: user-api: path: /user/** serviceId: user-service
user-service/pom.xml
<parent> <artifactId>zuul-test</artifactId> <groupId>com.calvin.zuul</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>user-service</artifactId> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-feign</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build>
user-service/application.yml
spring: application: name: user-service server: port: 8020 eureka: client: service-url: defaultZone: http://localhost:8010/eureka/ instance: hostname: localhost
user-service/UserApplication.java
/** * <p> * 启动类 * </p> * @author Calvin * @date 2019/10/25 * @since 1.0 */ @SpringBootApplication @EnableEurekaClient @EnableFeignClients public class UserApplication { public static void main(String[] args) { SpringApplication.run(UserApplication.class, args); } }
user-service/controller/UserController.java
/** * <p> </p> * * @author Calvin * @date 2019/10/25 * @since */ @RestController public class UserController { /** * 简单构建一个User */ @GetMapping("/userDetail/{userId}") public SysUser getUserDetail(@PathVariable("userId") String userId){ SysUser sysUser = new SysUser(); sysUser.setId(userId); sysUser.setAge(20); sysUser.setPassword(MD5Utils.digestAsHex("123456")); sysUser.setUsername("Calvin"); //图片来自百度 sysUser.setHeadImg("https://b-ssl.duitang.com/uploads/item/201510/17/20151017181645_c5hWE.thumb.700_0.jpeg"); return sysUser; } }
依次启动eureka-server, user-server, zuul-server
浏览器调用 http://localhost:8030/user/userDetail/1
从调用结果中可以看到我们从zuul-service中调用了user-service的方法,并且调用成功。从而证明路由配置可用;
如需配置版本号,我们只需要咱zuul-service/application.yml中添加配置:zuul.prefix=v1
如果需要在Zuul中实现服务熔断,只需要实现ZuulFallbackProvider
接口,重写其中两个方法,通过getRoute()
方法返回我们需要熔断的路由,通过fallbackResponse()
方法来重写熔断时执行的逻辑。
如下,我们实现第一个user-service的熔断器
/** * <p> * user-service熔断器 * </p> * * @author Calvin * @date 2019/10/27 * @since */ @Component public class UserServiceCallback implements ZuulFallbackProvider { @Override public String getRoute() { return "user-service"; } @Override public ClientHttpResponse fallbackResponse() { return new CommonClientResponse(); } }
贴上CommonClientResponse
的代码,就是针对ClientHttpResponse接口的封装
/** * <p>封装的通用返回类 </p> * * @author Calvin * @date 2019/10/27 * @since */ public class CommonClientResponse implements ClientHttpResponse { @Override public HttpStatus getStatusCode() throws IOException { return HttpStatus.OK; } @Override public int getRawStatusCode() throws IOException { return 0; } @Override public String getStatusText() throws IOException { return "success"; } @Override public void close() { } @Override public InputStream getBody() throws IOException { return new ByteArrayInputStream("hello , this is zuul fallback".getBytes()); } @Override public HttpHeaders getHeaders() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); return headers; } }
接下来做个测试,我们停掉user-service服务,然后再访问 http://localhost:8030/user/userDetail/1
结果当然是我们定义的熔断器中返回的内容了!
如果需要对其他服务使用同一个熔断器,只需要在
getRoute()
方法中返回通配符return "*"
就可以了
ZuulFilter
是Zuul实现过滤和网关的关键,此类有四个枚举值,分别代表Zuul中的过滤器类型。如果需要实现过滤,只需要继承ZuulFilter
,并且指定其过滤器类型,枚举值为:
/** * {@link ZuulFilter#filterType()} error type. */ String ERROR_TYPE = "error"; /** * {@link ZuulFilter#filterType()} post type. */ String POST_TYPE = "post"; /** * {@link ZuulFilter#filterType()} pre type. */ String PRE_TYPE = "pre"; /** * {@link ZuulFilter#filterType()} route type. */ String ROUTE_TYPE = "route";
简单实现一个过滤器
/** * <p> header过滤器 </p> * * @author Calvin * @date 2019/10/27 * @since */ @Component public class TokenFilter extends ZuulFilter { private static final Logger logger = LoggerFactory.getLogger(TokenFilter.class); @Override public String filterType() { return PRE_TYPE; } @Override public int filterOrder() { return 0; } @Override public boolean shouldFilter() { return true; } @Override public Object run() { RequestContext requestContext = RequestContext.getCurrentContext(); HttpServletRequest request = requestContext.getRequest(); logger.info("request_url : {}, request_headers: {}",request.getRequestURI(), JSON.toJSON(request.getHeaderNames()).toString()); return null; } }
重新启动zuul-service,调用服务控制台已经可以输出如下内容:
2019-11-12 22:04:36.726 INFO 58984 --- [nio-8030-exec-4] com.calvin.zuul.filter.TokenFilter : request_url : /user/userDetail/1, request_headers: ["host","connection","cache-control","upgrade-insecure-requests","user-agent","sec-fetch-user","accept","sec-fetch-site","sec-fetch-mode","accept-encoding","accept-language","cookie"]
若需要拦截请求,或者设置白名单等,在RequestContext中设置好自己的statusCode等,就可以了
requestContext.setSendZuulResponse(false); requestContext.setResponseStatusCode(401); ctx.getResponse().getWriter().write("there is no token found,please relogin!")
看完上述内容,你们掌握SpringCloud中如何使用Zuul路由网关的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注亿速云行业资讯频道,感谢各位的阅读!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。