官网:
https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.3.RELEASE/reference/html/一句话:Spring Cloud Gateway 使用的Webflux中的reactor-netty响应式编程组件,底层使用了Netty通讯框架
能干嘛?
路由鉴权流量控制熔断日志监控过滤 。。。微服务架构中网关在哪里? 服务提供者和消费者都是微服务。 服务提供者:被其他微服务调用的微服务 服务消费者:调用的其他微服务的微服务
1.neflix不太靠谱,zuul2.0一直跳票,迟迟不发布 2.SpringCloud Gateway具有如下特性 3.Gateway与Zuul的区别
Zuul1.x模型: GateWay模型 gateway基于webflux,而webflux基于netty,netty的I/O操作时异步的,netty模型是同步非阻塞
核心逻辑:路由转发+执行过滤器链
新建一个子模块cloud-gateway-gateway9527 依赖:
<!--gateway--> <!-- springcloud版本要为Hoxton.SR6 gateway为2.2.3会报下面的异常--> <!-- Correct the classpath of your application so that it contains a single, compatible version of reactor.netty.resources.ConnectionProvider--> <!-- 把springcloud版本改成Hoxton.SR1--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency>启动类:
@SpringBootApplication @EnableEurekaClient public class GateWayMain9527 { public static void main(String[] args) { SpringApplication.run( GateWayMain9527.class,args); } }9527网关如何做路由映射??? 看看服务提供者8001的controller的访问地址,我们目前不想暴露8001端口,希望在8001外面套一层9527 配置文件:
server: port: 9527 spring: application: name: cloud-gateway cloud: gateway: routes: - id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名 uri: http://localhost:8001 #匹配后提供服务的路由地址 predicates: - Path=/payment/get/** #断言,路径相匹配的进行路由 - id: payment_routh2 uri: http://localhost:8001 predicates: - Path=/payment/lb/** #断言,路径相匹配的进行路由 eureka: instance: hostname: cloud-gateway-service client: service-url: register-with-eureka: true fetch-registry: true defaultZone: http://eureka7001.com:7001/eureka启动Eureka,服务提供者8001,网关9527 访问: 直接访问提供者的接口: http://localhost:8001/payment/get/1 再通过网关访问,效果一样 http://localhost:9527/payment/get/1
见前面步骤
官网案例: 示例:通过9527网关访问到外网的百度新闻网址()
http://news.baidu.com/guonei http://news.baidu.com/game编码:
@Configuration public class GateWayConfig { @Bean public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) { RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes(); //id->path_rote_atguigu 相当于配置文件里配的id //第二个地址表示localhost:9527/guonei,将转发到地址http://news.baidu.com/guonei routes.route("path_rote_atguigu", r -> r.path("/guonei").uri("http://news.baidu.com/guonei")).build(); routes.route("path_rote_atguigu1", r -> r.path("/game").uri("http://news.baidu.com/game")).build(); return routes.build(); } }启动:Eureka以及网关模块。 访问: http://localhost:9527/game http://localhost:9527/guonei 均能转发成功。
上面的案例做完,有一些问题。 第一,地址写死,如:localhost:8001 。 第二,服务提供者不可能只有一台机器,所以要实现负载均衡。
以前: 现在:架构还是那个架构,但是不再使用ribbon做负载均衡,而是使用网关,对外暴露统一的服务接口,就是9527网关服务端口,由网关实现负载均衡
默认情况下Gateway会根据注册中心的服务列表,以注册中心上微服务名为路径创建动态路由进行转发,从而实现动态路由的功能。 启动:一个eureka7001+两个服务提供者8001/8002 配置: 1.开启从注册中心动态创建路由的功能,利用微服务名进行路由 2.将写死的地址,改成从配置中心获取,lb,表示启用Gateway的负载均衡功能。
server: port: 9527 spring: application: name: cloud-gateway cloud: gateway: discovery: locator: enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由 routes: - id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名 # uri: http://localhost:8001 #匹配后提供服务的路由地址 uri: lb://cloud-payment-service #需要注意的是uri的协议为lb,表示启用Gateway的负载均衡功能。 predicates: - Path=/payment/get/** #断言,路径相匹配的进行路由 - id: payment_routh2 # uri: http://localhost:8001 uri: lb://cloud-payment-service #需要注意的是uri的协议为lb,表示启用Gateway的负载均衡功能。 predicates: - Path=/payment/lb/** #断言,路径相匹配的进行路由 eureka: instance: hostname: cloud-gateway-service client: service-url: register-with-eureka: true fetch-registry: true defaultZone: http://eureka7001.com:7001/eureka测试: 发现实现了负载均衡的功能。
http://localhost:9527/payment/lb解决了地址写死的问题,也解决了负载均衡的问题。 需要说明gateway的负载均衡底层使用的就是ribbon。
上面的是使用配置文件的方式,现在展示一下使用硬编码的方式
@Configuration public class GateWayConfig { @Bean public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) { RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes(); routes.route("payment_routh", r -> r.path("/payment/get/** ").uri("lb://cloud-payment-service")).build(); routes.route("payment_routh2", r -> r.path("/payment/lb/**").uri("lb://cloud-payment-service")).build(); return routes.build(); } }配置文件:
spring: application: name: cloud-gateway cloud: gateway: discovery: locator: enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由通过路由断言判断路由是否可用,匹配成功进行下一步处理,否则则失败。 启动网关子模块。 Route Predicate Factories这个是什么东东?
意思要在指定时间之后,路由才生效
获取当前时间时区:
public class ZonedDateTimeDemo { public static void main(String[] args) { //默认时区 ZonedDateTime zonedDateTime=ZonedDateTime.now(); System.out.println(zonedDateTime); //2020-07-05T12:19:02.779+08:00[Asia/Shanghai] } }测试: 加上 - After=2020-07-05T13:19:02.779+08:00[Asia/Shanghai]
spring: cloud: gateway: discovery: locator: enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由 routes: - id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名 # uri: http://localhost:8001 #匹配后提供服务的路由地址 uri: lb://cloud-payment-service #需要注意的是uri的协议为lb,表示启用Gateway的负载均衡功能。 predicates: - Path=/payment/get/** #断言,路径相匹配的进行路由 - After=2020-07-05T12:19:02.779+08:00[Asia/Shanghai] #将当前时间,意思要在当前时间之后,访问path中的请求才有效 - id: payment_routh2 # uri: http://localhost:8001 uri: lb://cloud-payment-service #需要注意的是uri的协议为lb,表示启用Gateway的负载均衡功能。 predicates: - Path=/payment/lb/** #断言,路径相匹配的进行路由 - After=2020-07-05T12:19:02.779+08:00[Asia/Shanghai] #将当前时间,意思要在当前时间之后,访问path中的请求才有效因为当前是时间,已经过去了,所以按照预期,重启服务,肯定能访问接口lb和get。 重启网关。 访问http://localhost:9527/payment/lb
把lb接口对应的-After时间调后一小时:- After=2020-07-05T13:19:02.779+08:00[Asia/Shanghai] 按照预期应该是访问不了改接口了 同理断言Before Route Predicate,Between Route Predicate用法是和After Route Predicate一样的。
- After=2020-03-08T10:59:34.102+08:00[Asia/Shanghai] - Before=2020-03-08T10:59:34.102+08:00[Asia/Shanghai] - Between=2020-03-08T10:59:34.102+08:00[Asia/Shanghai] , 2020-03-08T10:59:34.102+08:00[Asia/Shanghai]断言Cookie,满足指定的cookie,才能访问路由 示例 配置: 添加 - Cookie=username,liuzhihui #断言Cookie key=username value=liuzhihui 满足这个断言才能访问
spring: cloud: gateway: discovery: locator: enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由 routes: - id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名 # uri: http://localhost:8001 #匹配后提供服务的路由地址 uri: lb://cloud-payment-service #需要注意的是uri的协议为lb,表示启用Gateway的负载均衡功能。 predicates: - Path=/payment/get/** #断言,路径相匹配的进行路由 - After=2020-07-05T12:19:02.779+08:00[Asia/Shanghai] #将当前时间,意思要在当前时间之后,访问path中的请求才有效 - id: payment_routh2 # uri: http://localhost:8001 uri: lb://cloud-payment-service #需要注意的是uri的协议为lb,表示启用Gateway的负载均衡功能。 predicates: - Path=/payment/lb/** #断言,路径相匹配的进行路由 - After=2020-07-05T12:19:02.779+08:00[Asia/Shanghai] #将当前时间,意思要在当前时间之后,访问path中的请求才有效 - Cookie=username,liuzhihui #断言Cookie key=username value=liuzhihui 满足这个断言,才能访问路由测试:接口http://localhost:9527/payment/lb,现在这个接口要能成功访问必须满足两个条件。 第一在指定的时间之后访问; 第二要带上指定的Cookie;
使用curl来测试,curl就是postman底层操作,postman只不过是图形界面操作。 首先,不带cookie访问:curl http://localhost:9527/payment/lb 直接报错了。注意这个属于get请求 然后,带上cookie访问:curl http://localhost:9527/payment/lb --cookie "username=liuzhihui"
加入curl返回中文乱码:https://blog.csdn.net/leedee/article/details/82685636
请求头断言,满足指定的请求头,才能访问路由。 示例:
- Header=X-Request-Id, \d+ #请求头中要有X-Request-Id属性并且值为整数的正则表达式重启网关。 测试:curl http://localhost:9527/payment/lb --cookie "username=liuzhihui" -H "X-Request-Id:123"
host断言 示例: 只要请求头host满足: - Host=**.atguigu.com,**.liuzhihui.com,即可转发路由
spring: cloud: gateway: discovery: locator: enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由 routes: - id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名 # uri: http://localhost:8001 #匹配后提供服务的路由地址 uri: lb://cloud-payment-service #需要注意的是uri的协议为lb,表示启用Gateway的负载均衡功能。 predicates: - Path=/payment/get/** #断言,路径相匹配的进行路由 - After=2020-07-05T12:19:02.779+08:00[Asia/Shanghai] #将当前时间,意思要在当前时间之后,访问path中的请求才有效 - id: payment_routh2 # uri: http://localhost:8001 uri: lb://cloud-payment-service #需要注意的是uri的协议为lb,表示启用Gateway的负载均衡功能。 predicates: - Path=/payment/lb/** #断言,路径相匹配的进行路由 - After=2020-07-05T12:19:02.779+08:00[Asia/Shanghai] #将当前时间,意思要在当前时间之后,访问path中的请求才有效 - Cookie=username,liuzhihui #断言Cookie key=username value=liuzhihui 满足这个断言才能访问 - Header=X-Request-Id, \d+ #请求头中要有X-Request-Id属性并且值为整数的正则表达式 - Host=**.atguigu.com,**.liuzhihui.com测试:
curl http://localhost:9527/payment/lb --cookie "username=liuzhihui" -H "X-Request-Id:1" -H "Host: aaa.liuzhihui.com" curl http://localhost:9527/payment/lb --cookie "username=liuzhihui" -H "X-Request-Id:1" -H "Host: www.liuzhihui.com" curl http://localhost:9527/payment/lb --cookie "username=liuzhihui" -H "X-Request-Id:1" -H "Host: www.atguigu.com"接口的请求方式:Get,Post 示例:是get请求,才能进行路由转发
- Method=GET #接口的请求方式测试:
curl http://localhost:9527/payment/lb -X GET --cookie "username=liuzhihui" -H "X-Request-Id:1" -H "Host: www.atguigu.com" curl http://localhost:9527/payment/lb -X POST --cookie "username=liuzhihui" -H "X-Request-Id:1" -H "Host: www.atguigu.com"请求路径正则匹配指定值。没啥好说的一直都在用。
- Path=/payment/get/** #断言,路径相匹配的进行路由查询参数断言
- Query=username, \d+ #要有参数名称并且是正整数才能路由测试:
predicates: - Path=/payment/lb/** #断言,路径相匹配的进行路由 - After=2020-07-05T12:19:02.779+08:00[Asia/Shanghai] #将当前时间,意思要在当前时间之后,访问path中的请求才有效 - Cookie=username,liuzhihui #断言Cookie key=username value=liuzhihui 满足这个断言才能访问 - Header=X-Request-Id, \d+ #请求头中要有X-Request-Id属性并且值为整数的正则表达式 - Host=**.atguigu.com,**.liuzhihui.com - Method=GET #接口的请求方式 - Query=username, \d+ #要有参数名称并且是正整数才能路由 - Query=age, \d+ #要有参数名称并且是正整数才能路由 curl "http://localhost:9527/payment/lb?username=1&age=1" -X GET --cookie "username=liuzhihui" -H "X-Request-Id:1" -H "Host: www.atguigu.com"说白了,Predicate就是为了实现一组匹配规则,让请求过来找到对应的Route进行处理。 更多的路由请参考官网:
https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.3.RELEASE/reference/html/#gateway-request-predicates-factories路由过滤器可用于修改进入Http的请求和返回的Http响应,路由过滤器只能指定路由进行使用。spring Cloud Gateway内置了多种路由过滤器,他们都由GatewayFilter的工厂类来产生。
官网:
https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.3.RELEASE/reference/html/#gatewayfilter-factories生命周期:在业务逻辑之pre;在业务逻辑之后post 种类:GatewayFilter单一过滤器 GlobalFilter全局过滤器
例如: 该路由的意思是,前端发送请过来,如果路径包含/api/xxx,则符合断言规则,然后经过过滤器,将路径改为/renren-fast/xxx
- id: admin_route uri: lb://renren-fast predicates: - Path=/api/** filters: ##路径重写 - RewritePath=/api/(?<segment>.*),/renren-fast/$\{segment}实际工作中内置的过滤器用的并不多,用的更多的是自定义过滤器。
首先要实现两接口impiemerts GlobalFilter ,Ordered 能干嘛? 全局日志记录 统一网关鉴权 。。。。
案例: 网关子模块里,新建一个过滤器包
@Component @Slf4j public class MyLogGateWayFilter implements GlobalFilter,Ordered { @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { log.info("*********come in MyLogGateWayFilter: "+new Date()); String username = exchange.getRequest().getQueryParams().getFirst("username"); if(StringUtils.isEmpty(username)){ log.info("*****用户名为Null 非法用户,(┬_┬)"); exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);//给人家一个回应 return exchange.getResponse().setComplete(); } return chain.filter(exchange); } @Override public int getOrder() { return 0; } }配置文件: 将之前的断言注释了,只留- Path 避免干扰
predicates: - Path=/payment/lb/** #断言,路径相匹配的进行路由 # - After=2020-07-05T12:19:02.779+08:00[Asia/Shanghai] #将当前时间,意思要在当前时间之后,访问path中的请求才有效 # - Cookie=username,liuzhihui #断言Cookie key=username value=liuzhihui 满足这个断言才能访问 # - Header=X-Request-Id, \d+ #请求头中要有X-Request-Id属性并且值为整数的正则表达式 # - Host=**.atguigu.com,**.liuzhihui.com # - Method=GET #接口的请求方式 # - Query=username, \d+ #要有参数名称并且是正整数才能路由 # - Query=age, \d+ #要有参数名称并且是正整数才能路由重启网关。 正确的访问:http://localhost:9527/payment/lb?username=z3 错误的访问:
http://localhost:9527/payment/lb什么是跨域? 跨域:指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对javascript施加的安全限制。 同源策略:是指协议,域名,端口都要相同,其中有一个不同都会产生跨域; 跨域流程? 非简单请求(put,delete)等,需要先发送预检请求,询问是否允许跨域。
跨域问题:如下图所示
解决跨域的方式一: 跨域的根本原因,目标网站跟想要发远程请求的网站不在同一域; 用nginx把它们转化为同一域; 具体做法:比如有一台nginx服务器,将前端项目也部署到里面,后台的网关也让nginx代理 能行,但是太麻烦了 解决跨域的方式二: 网关统一配置跨域,在网关处添加一个过滤器,所有请求过来,我都给它配置允许跨域。
@Configuration public class GulimallCorsConfiguration { /** * springboot提供了跨域的过滤器CorsWebFilter * @return */ @Bean public CorsWebFilter corsWebFilter(){ UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource(); CorsConfiguration corsConfiguration = new CorsConfiguration(); //配置跨域 //允许哪些都跨域 corsConfiguration.addAllowedHeader("*"); //允许哪些请求方式跨域 corsConfiguration.addAllowedMethod("*"); //允许哪些请求来源跨域 corsConfiguration.addAllowedOrigin("*"); //是否允许携带cookies跨域 corsConfiguration.setAllowCredentials(true); //注册跨域配置 urlBasedCorsConfigurationSource.registerCorsConfiguration("/**",corsConfiguration); return new CorsWebFilter(urlBasedCorsConfigurationSource); } }再次测试需要跨域的接口: 发现会有两次请求,其中一次是预检请求,并且发现请求头添加上了配置的跨域信息,但是是不携带数据的; 另一个就是真实的请求了,携带有真实的数据