一、api网关简述
zuul是Netflix开源的微服务网关,zuul的核心是一系列的过滤器。api网关的设计是为了实现自动路由,动态校验过滤以及负载均衡。除此之外,它还有很多功能,比如说可以和spring boot admin 配合使用实现服务监控,还可以与服务治理框架结合、请求转发时的熔断机制、服务的聚合等一系列的高级功能。
二、api网关构建
1.pom.xml配置如下
<?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.wangcongming.shop</groupId> <artifactId>api-gateway</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.12.RELEASE</version> <relativePath/> </parent> <properties> <!--设置字符编码及java版本--> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <java.version>1.8</java.version> </properties> <dependencies> <!--eureka--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> </dependency> <!--zuul网关依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zuul</artifactId> </dependency> <!-- 集群redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.7</version> </dependency> <!-- lombok bean生成规则,简化bean代码量 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> </dependencies> <!--依赖管理,用于管理spring-cloud的依赖,其中Camden.SR3是版本号--> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Edgware.SR3</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>
2.创建入口类,使用注解@EnableZuulProxy标注
package com.wangcongming.shop.api; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; /** * @author wangcongming * @Package com.wangcongming.shop.api * @Description: * @date 2018/5/9 10:26 */ @SpringBootApplication @EnableDiscoveryClient @EnableZuulProxy public class GateWayApplication { public static void main(String[] args) { SpringApplication.run(GateWayApplication.class,args); } }
3.添加配置文件
spring.application.name=api-gateway server.port=8803 eureka.client.serviceUrl.defaultZone=http://localhost:8802/eureka/
如此一个简单的网关就搭建完成了
三、路由规则
前面已经说过,api网关的一个重要功能就是路由,那么下面就来说说怎么配置路由
传统路由规则:(不与Eureka结合使用)
单实例配置:
zuul.routes.<serviceId>.path= zuul.routes.<serviceId>.url=
以user-server服务为例
zuul.routes.user-server.path=/user-server/** zuul.routes.user-server.url=http://localhost:8806/
当用户请求http://localhost:8806/user-server/hello ,则经过api网关路由之后的请求路径是 http://localhost:8806/hello
多实例配置
zuul.routes.user-server.path=/user-server/** zuul.routes.user-server.serviceId=user-server ribbon.eureka.enabled=false user-server.ribbon.listOfServers=http://localhost:8806/,http://localhost:8807/
多实例配置与单实例类似,只是在配置过滤的时候将对应的url改为了对应的serviceId
与Eureka结合的路由规则配置
zuul.routes.user-server.path=/user-server/** zuul.routes.user-server.serviceId=user-server
与传统多实例路由规则区别不多,传统多实例路由规则需要指明服务列表,与Eureka结合则不需要,前文已经说过服务注册中心Eureka保存有所有的服务部署信息。通过Eureka我们可以直接拿到服务实例信息。
另外 Eureka可以不用配置路由规则,spring cloud Eureka 默认的路由规则就是,所有Eureka上的服务都会被zuul自动地创建映射关系来进行路由。
如果我们希望一些服务不被外部访问到,可以通过zuul.ignored-services 来进行指定。设置zuul.ignored-services =* 的时候则认为是所有的服务都不自动建立路由规则。
本地跳转
在实现api网关路由功能中,还支持forward形式的微服务端跳转配置,实现方式非常简单,只需要通过上文中提到的path、url配置就可以实现。示例如下:
zuul.routes.user-server.path=/user-server/** zuul.routes.user-server.serviceId=forward:/local
如果这么配置,请求http://localhost:8801/user-server/hello则会被转发到网关的/local/hello接口上进行本地处理
四、api网关其他配置
#HTTP cookie和头信息中敏感词过滤问题 #指定某些服务可以获取 本次制定user-system服务可以获取到 zuul.routes.user-system.customSensitiveHeaders=true #或者配置 #zuul.routes.user-producer-server.sensitiveHeaders= #解决shiro登陆之后的重定向问题 zuul.add-host-header=true #hystrix和ribbon支持 #api网关中路由请求的hystrixCommand执行超时时间,单位毫秒 网关路由请求熔断时间 hystrix.command.default.execution.isolation.thread.timeoutInMillseconds=15000 #设置路由转发请求的时候,创建请求的超时时间 ribbon.ConnectTimeOut=500 #设置路由转发请求的超时时间 ribbon.ReadTimeout=1000
这一部分可以查阅官方文档进行学习
五、zuul的过滤器
1.过滤器类型与生命周期
过滤器类型:
pre:可以在请求被路由之前调用
routing:在路由请求时被调用
post:在routing和error过滤器之后被调用
error:处理请求时发生错误时被调用
请求生命周期
从上图中,我们可以看到,当外部HTTP请求到达API网关服务的时候,首先它会进入第一个阶段pre,在这里它会被pre类型的过滤器进行处理,该类型的过滤器主要目的是在进行请求路由之前做一些前置加工,比如请求的校验等。
在完成了pre类型的过滤器处理之后,请求进入第二个阶段routing,也就是之前说的路由请求转发阶段,请求将会被routing类型过滤器处理,这里的具体处理内容就是将外部请求转发到具体服务实例上去的过程,当服务实例将请求结果都返回之后,routing阶段完成,请求进入第三个阶段post,此时请求将会被post类型的过滤器进行处理,这些过滤器在处理的时候不仅可以获取到请求信息,还能获取到服务实例的返回信息,所以在post类型的过滤器中,我们可以对处理结果进行一些加工或转换等内容。
另外,还有一个特殊的阶段error,该阶段只有在上述三个阶段中发生异常的时候才会触发,但是它的最后流向还是post类型的过滤器,因为它需要通过post过滤器将最终结果返回给请求客户端(实际实现上还有一些差别)
2.编写过滤器
具体实现代码如下:
package com.wangcongming.shop.api.filter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import com.wangcongming.shop.api.rao.RedisRao; import com.wangcongming.shop.api.util.ResultMap; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletRequest; import java.util.Objects; /** * @author wangcongming * @Package com.example.apigateway.filter * @Description: * @date 2018/3/31 18:01 */ @Slf4j @Component public class AccessFilter extends ZuulFilter{ @Autowired private RedisRao redisRao; private final static String USER_TOKEN = "user:token"; /** * filterType:返回一个字符串代表过滤器的类型,在zuul中定义了四种不同生命周期的过滤器类型,具体如下: * pre:可以在请求被路由之前调用 * route:在路由请求时候被调用 * post:在route和error过滤器之后被调用 * error:处理请求时发生错误时被调用 * * @return */ @Override public String filterType() { return "pre";//定义前置过滤器 } /** * filter的执行顺序 数字越小优先级越高 * @return */ @Override public int filterOrder() { return 0; } /** * 返回一个boolean类型来判断该过滤器是否要执行, * 所以通过此函数可实现过滤器的开关。 * * 本例中我们选择请求中存在uid的进行过滤 * * @return */ @Override public boolean shouldFilter() { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); String userId = request.getHeader("userId"); if(StringUtils.isEmpty(userId)){ return false; } return true; } /** * 过滤器的具体逻辑。需要注意,这里我们通过ctx.setSendZuulResponse(false)令zuul过滤该请求,不对其进行路由, * 然后通过ctx.setResponseStatusCode(401)设置了其返回的错误码 * @return */ @Override public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); try { HttpServletRequest request = ctx.getRequest(); log.info(String.format("%s AccessPasswordFilter request to %s", request.getMethod(), request.getRequestURL().toString())); String userId = request.getHeader("userId"); String userToken = request.getHeader("userToken"); String sysToken = redisRao.getHashKeyStringValue(USER_TOKEN, userId); if(StringUtils.isEmpty(userToken) || !Objects.equals(sysToken,userToken)){ log.info("用户:{}token验证失败",userId); ctx.setSendZuulResponse(false); ctx.setResponseStatusCode(401); ctx.setResponseBody(ResultMap.error("401","没有权限").toString()); ctx.getResponse().setContentType("text/html;charset=UTF-8"); return null; } ctx.setSendZuulResponse(true); ctx.setResponseStatusCode(200); return null; } catch (Exception e) { log.error("权限过滤器处理{}失败",AccessFilter.class,e); ctx.set("error.status_code", 500); ctx.set("error.exception", e); } return null; } }
此外还有动态过滤器,这里就不再介绍了,有兴趣的同学,可以去官网查看资料。