为何须要网关呢?html
咱们知道咱们要进入一个服务自己,很明显咱们没有特别好的办法,直接输入IP地址+端口号,咱们知道这样的作法很糟糕的,这样的作法大有问题,首先暴露了咱们实体机器的IP地址,别人一看你的IP地址就知道服务部署在哪里,让别人很方便的进行攻击操做。java
第二,咱们这么多服务,咱们是否是要挨个调用它呀,咱们这里假设作了个权限认证,咱们每个客户访问的都是跑在不一样机器上的不一样的JVM上的服务程序,咱们每个服务都须要一个服务认证,这样作烦不烦呀,明显是很烦的。spring
那么咱们这时候面临着这两个极其重要的问题,这时咱们就须要一个办法解决它们。首先,咱们看IP地址的暴露和IP地址写死后带来的单点问题,我是否是对这么服务自己我也要动态的维护它服务的列表呀,我须要调用这服务自己,是否是也要一个负载均衡同样的玩意,后端
还有关于IP地址暴露的玩意,我是否是须要作一个代理呀,像Nginx的反向代理同样的东西,还有这玩意上部署公共的模块,好比全部入口的权限校验的东西。所以咱们如今须要Zuul API网关。它就解决了上面的问题,你想调用某个服务,它会给你映射,把你服务的IP地址映射成api
某个路径,你输入该路径,它匹配到了,它就去替你访问这个服务,它会有个请求转发的过程,像Nginx同样,服务机器的具体实例,它不会直接去访问IP,它会去Eureka注册中心拿到服务的实例ID,即服务的名字。我再次使用客户端的负载均衡ribbon访问其中服务实例中的一台。安全
API网关主要为了服务自己对外的调用该怎么调用来解决的,还有解决权限校验的问题,你能够在这里整合调用一系列过滤器的,例如整合shiro,springsecurity之类的东西。cookie
Zuul能够经过加载动态过滤机制,从而实现如下各项功能:网络
1.验证与安全保障: 识别面向各种资源的验证要求并拒绝那些与要求不符的请求。app
2.审查与监控: 在边缘位置追踪有意义数据及统计结果,从而为咱们带来准确的生产状态结论。负载均衡
3.动态路由: 以动态方式根据须要将请求路由至不一样后端集群处。
4.压力测试: 逐渐增长指向集群的负载流量,从而计算性能水平。
5.负载分配: 为每一种负载类型分配对应容量,并弃用超出限定值的请求。
6.静态响应处理: 在边缘位置直接创建部分响应,从而避免其流入内部集群。
7.多区域弹性: 跨越AWS区域进行请求路由,旨在实现ELB使用多样化并保证边缘位置与使用者尽量接近。
接着下来进行实战小Demo
第一步,在原来的工程下,新建一个Zuul模块,引入依赖,代码以下:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-eureka</artifactId> <version>1.3.5.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-zuul</artifactId> <version>1.3.5.RELEASE</version> </dependency>
接着在启动类上打上@EnableZuulProxy注解,代码以下:
server: port: 5000 spring: application: name: api-geteway zuul: routes: #标识你服务的名字,这里能够本身定义,通常方便和规范来说仍是跟本身服务的名字同样 hello-service: #服务映射的路径,经过这路径就能够从外部访问你的服务了,目的是为了避免爆露你机器的IP,面向服务的路由了,给你选一个可用的出来, #这里zuul是自动依赖hystrix,ribbon的,不是面向单机 path: /hello-service/** #这里必定要是你Eureka注册中心的服务的名称,是因此这里配置serviceId由于跟eureka结合了,若是单独使用zuul,那么就必须写本身机器的IP了, #如url:http://localhost:8080/ 这样的很差就是写死IP了,万一这IP挂了,这高可用性,服务注册那套东西就用不起来了 serviceId: hello-service eureka: #客户端 client: #注册中心地址 service-url: defaultZone: http://localhost:8888/eureka/,http://localhost:8889/eureka/
接着启动先前文章中的注册中心和两个hello-service服务提供者,接着咱们运行,看一下它的请求转发功能,看他有没有轮询进入两个服务,
输入localhost:5000/hello-service/hello,以下:
接着再刷新一遍:
能够看到zuul进行了请求分发了。它是根据你的服务名字hello-servie来映射到具体的机器上,这不就是一个反向代理的功能吗?
zuul还能进行请求过滤,那么咱们进行一下token校验来演示一下,首先咱们须要先新建一个TokenFilter类来继承ZuulFilter这个类,实现它的四个接口,代码以下:
package hjc.zuul; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import javax.servlet.http.HttpServletRequest; /** * Created by cong on 2018/5/18. */ public class TokenFilter extends ZuulFilter { //四种类型:pre,routing,error,post //pre:主要用在路由映射的阶段是寻找路由映射表的 //routing:具体的路由转发过滤器是在routing路由器,具体的请求转发的时候会调用 //error:一旦前面的过滤器出错了,会调用error过滤器。 //post:当routing,error运行完后才会调用该过滤器,是在最后阶段的 @Override public String filterType() { return "pre"; } //自定义过滤器执行的顺序,数值越大越靠后执行,越小就越先执行 @Override public int filterOrder() { return 0; } //控制过滤器生效不生效,能够在里面写一串逻辑来控制 @Override public boolean shouldFilter() { return true; } //执行过滤逻辑 @Override public Object run() { RequestContext context = RequestContext.getCurrentContext(); HttpServletRequest request = context.getRequest(); String token = request.getParameter("token"); if (token == null){ context.setSendZuulResponse(false); context.setResponseStatusCode(401); context.setResponseBody("unAuthrized"); return null; } return null; } }
filterType:返回一个字符串表明过滤器的类型,在zuul中定义了四种不一样生命周期的过滤器类型,具体以下:
1.pre
:能够在请求被路由以前调用,用在路由映射的阶段是寻找路由映射表的
2.route
:在路由请求时候被调用,具体的路由转发过滤器是在routing路由器具体的请求转发的时候会调用
3.error
:处理请求时发生错误时被调用
4.post
:当routing,error运行完后才会调用该过滤器,是在最后阶段的
这里声明一下zuul过滤器执行网络请求发生的异常,过滤器里面是不能直接将try-catch捕捉的异常抛出给页面的。应用程序抛出的异常是能够返回出的需解决办法就是在catch里面用context.set()方法返回给页面。以下:
try{ 业务逻辑...... }catch(Exception e){ RequestContext context = RequestContext.getCurrentContext(); context.set("error.status_code",401); context.set("error.exception",e); context.set("error.message","sfdfsdf"); }
接着,你还须要把这个过滤器加入spring中,让spring管理,代码以下:
package hjc; import hjc.zuul.TokenFilter; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; import org.springframework.context.annotation.Bean; @SpringBootApplication @EnableZuulProxy public class ZuulApplication { public static void main(String[] args) { SpringApplication.run(ZuulApplication.class, args); } //将过滤器交给Spring管理 @Bean public TokenFilter tokenFilter(){ return new TokenFilter(); } }
接着,让咱们启动启动类,先进行不带token的访问,以下:
能够看到,返回一个没权限的信息,这里要说一下,Token通常都是放在请求头中的,这里咱们只是为了演示才没那么干,
接着将token带上再去访问,以下:
能够看到这是已经将咱们的请求放过去了。
这里我还要讲一下什么是默认路由,将zuul的配置删除路由配置,以下:
server: port: 5000 spring: application: name: api-geteway eureka: #客户端 client: #注册中心地址 service-url: defaultZone: http://localhost:8888/eureka/,http://localhost:8889/eureka/
接着,重启继续访问,以下:
、
能够看到,仍是能继续访问,咱们什么都没配,竟然还能访问,那是由于,这里默认用你的服务名字hello-service自动声明了。
那么,若是说我不想让它帮我自动声明,我要我本身定义,那么能够在yml配置文件中使用zuu.ignored-services就能够把本身像过滤的过滤,以下:”
zuul: #若是ignored-services:* 表示全部的默认路由都失效了,要本身一个个配,没人会那么操蛋,除非遇到奇葩业务 ignored-services:
接着咱们再说一下映射规则,比方说
zuul: routes: #标识你服务的名字,这里能够本身定义,通常方便和规范来说仍是跟本身服务的名字同样 hello-service: #服务映射的路径,经过这路径就能够从外部访问你的服务了,目的是为了避免爆露你机器的IP,面向服务的路由了,给你选一个可用的出来, #这里zuul是自动依赖hystrix,ribbon的,不是面向单机 path: /hello-service/** #这里必定要是你Eureka注册中心的服务的名称,是因此这里配置serviceId由于跟eureka结合了,若是单独使用zuul,那么就必须写本身机器的IP了, #如url:http://localhost:8080/ 这样的很差就是写死IP了,万一这IP挂了,这高可用性,服务注册那套东西就用不起来了 serviceId: hello-service zuul: routes: hello-service: path: /hello-service/ext/** serviceId: hello-service
这里的两个zuul配置映射路径都有/hello-service/,能够看到/hello-service/**是包括/hello-service/ext/**的,这两个路径进行匹配的时候是否是有冲突呀,怎么处理呢?谁先匹配呢?
这里是yml中定义的顺序来匹配的。若是是application.properties格式的配置文件,它这个顺序是不能保证的,yml格式的配置文件是有顺序的,能够保证,这里要注意下一下。
若是咱们想定义一下匹配规则怎么办呢?那么咱们就须要在启动类中定义一个bean,这个类就是决定你的路由的,以下:
这里就不演示了,须要用到的时候本身再去慢慢查找资料吧。
还有就是ignored-patterns:,以下:
zuul: routes: #标识你服务的名字,这里能够本身定义,通常方便和规范来说仍是跟本身服务的名字同样 hello-service: #服务映射的路径,经过这路径就能够从外部访问你的服务了,目的是为了避免爆露你机器的IP,面向服务的路由了,给你选一个可用的出来, #这里zuul是自动依赖hystrix,ribbon的,不是面向单机 path: /hello-service/** #这里必定要是你Eureka注册中心的服务的名称,是因此这里配置serviceId由于跟eureka结合了,若是单独使用zuul,那么就必须写本身机器的IP了, #如url:http://localhost:8080/ 这样的很差就是写死IP了,万一这IP挂了,这高可用性,服务注册那套东西就用不起来了 serviceId: hello-service ignored-patterns: /hello/**
ignored-patterns:表示屏蔽掉/hello/**的路径,就算你/hello-service/hello/**也不行,照样屏蔽。这个配置咱们能够进一步细化,好比说我不想给/hello接口路由,那咱们能够按照上面方式配置
若是咱们还想配置一个服务的前缀该怎么办?代码以下:
zuul: routes: #标识你服务的名字,这里能够本身定义,通常方便和规范来说仍是跟本身服务的名字同样 hello-service: #服务映射的路径,经过这路径就能够从外部访问你的服务了,目的是为了避免爆露你机器的IP,面向服务的路由了,给你选一个可用的出来, #这里zuul是自动依赖hystrix,ribbon的,不是面向单机 path: /hello-service/** #这里必定要是你Eureka注册中心的服务的名称,是因此这里配置serviceId由于跟eureka结合了,若是单独使用zuul,那么就必须写本身机器的IP了, #如url:http://localhost:8080/ 这样的很差就是写死IP了,万一这IP挂了,这高可用性,服务注册那套东西就用不起来了 serviceId: hello-service prefix: /api/**
能够看到那么你访问的服务都必需要加/api/前缀,例如/api/hello-service/**
若是咱们还想进行一个路径访问就跳转到个人本地,那该怎么办呢?
我但愿用户在访问/local时可以自动跳转到这个方法上来处理,那么此时咱们须要用到Zuul的本地跳转,配置方式以下:
zuul: prefix: /api ignored-patterns: /**/hello/** routes: local: path: /hello-service/** url: forward:/local
咱们经常使用的一些,对接springsecurity,或者是一些第三方组件,它们会获取你的一些cookie信息,那么Zuul网关为了安全起见,把你的cookie信息都给干掉了,这个是没办法去搞cookie的。它是默认干掉的。
这里Zuul提供了zuul.sensitive-headers来给你搞这些cookie,header,这些信息不要进行过滤。控制你的敏感信息。
默认状况下,敏感的头信息没法通过API网关进行传递,咱们能够经过以下配置使之能够传递:
zuul: routes: hello-service: path: /hello-service/** serviceId: hello-service sensitive-headers: cookie,header之类额东西
还能够配合Hystrix的一些详细配置一块儿使用,前面也讲过了。这里就不说了