服务网关
1. 微服务gateway网关
参考:
https://javaweixin6.blog.csdn.net/article/details/107735759
谷粒商城17-SpringCloud Gateway网关介绍
https://javaweixin6.blog.csdn.net/article/details/107736472
谷粒商城18-注册“gulimall-gateway”服务到Nacos
https://javaweixin6.blog.csdn.net/article/details/107736535
谷粒商城19-gulimall-gateway网关使用案例
1.1. 不用网关所产生的问题1(请求同一功能的不同微服务需要重新修改请求端口)
后台管理系统(如浏览器)需要给商品服务、订单服务等各个服务发送请求,现在要想对商品做CRUD,而发请求就得知道商品服务等各个服务的地址。假设一开始请求的是1号商品服务的地址,在1号商品服务掉线后,那么要使业务正常运行,就得将该请求改为2号商品服务的地址(即动态切换到一个能用的商品服务中),这样在后台代码中修改各个服务所在的端口太麻烦。
改进:后台管理系统给任何服务发送请求都经过网关,网关帮我们动态地路由到各个服务。且网关也能从注册中心中实时感知每一个服务的上下线,总会能帮我们把请求正确路由到指定位置;
1.2. 不用网关所产生的问题2(每个微服务都开发权限、监控等功能,这样会导致冗余)
每一个请求到微服务后,后期都需要加权限、监控等等。而如果每个请求到每一个微服务都做这个事(在每个服务都重复开发权限、监控等功能),这样太冗余。如:
改进:后台管理系统先请求API网关,在网关层将权限、监控等统一的功能统一处理,再将请求代转给每一个服务。
1.3. Gateway
网关作为流量的入口,常用功能包括路由转发、权限校验、限流控制等。而 springcloud gateway
作为 SpringCloud
官方推出的第二代网关框架,取代了 Zuul
网关。
1.3.1. Gateway的三大概念
https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.3.RELEASE/reference/html/#glossary
- Route: The basic building block of the gateway. It is defined by an ID, a destination URI, a collection of predicates, and a collection of filters. A route is matched if the aggregate predicate is true.
Route为路由,客户端发一个请求个网关,网关把请求路由到指定的服务中去。路由之间的区分采用的是ID。当次请求的路径为URI,只要路由匹配了断言,那么该次请求就能到达指定的路径。 - Predicate: This is a Java 8 Function Predicate. The input type is a Spring Framework ServerWebExchange. This lets you match on anything from the HTTP request, such as headers or parameters.
请求发送给网关,是否需要到达某一个服务,是需要进行条件判断的,该条件就是Predicate断言。让开发者匹配当次请求的任何信息,比如请求头或者请求参数。网关根据请求参数,或者请求头的不同,路由到不同的服务,判断路由到不同的服务,就是进行断言。 - Filter: These are instances of Spring Framework GatewayFilter that have been constructed with a specific factory. Here, you can modify requests and responses before or after sending the downstream request.
Filter为Spring提供的网关过滤器,过滤器就是请求到后台服务的时候,进行一次过滤,后台给前端响应,再次进行一次过滤。在请求到后台之前,或者响应之前都能进行修改过滤。
1.3.2. SpringCloud Gateway工作流程
客户端将请求到达网关,先利用Predicate 断言,判定是否符合某个路由Route规则。
如果符合了,就让路由转发到指定的后台服务中,在到达后台之前,需要进行一系列的filter,进行过滤。
在Gateway的官方文档中,如下的两节,写明了有哪些路由断言的工厂,和哪些过滤器工厂。
1.3.3. 给交易模型配置网关
1.3.3.1. 配置pom
注意,网关也要注册到服务注册/发现(这样它才能发现其他服务所在的位置)以及配置中心,所以要加相关的依赖。
1.3.3.2. 配置application.yml(nacos注册中心、gateway网关)
1.3.3.3. 配置bootstrap.properties(nacos配置中心)
#服务名
spring.application.name=gateway
# 配置中心url
#spring.cloud.nacos.config.server-addr=120.26.160.234:8848
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
#配置命名空间
#spring.cloud.nacos.config.namespace=9cdb5e05-e6fb-43fb-a770-1a571ab04182
配置命名空间的步骤:
- 新建名字空间
- 新建配置
- namespace指定名字空间的唯一ID
1.3.3.4. 启动网关
该网关使用Netty做的:
1.3.3.5. 账户开立服务和转账服务的网关测试
1.3.3.5.1. 账户开立服务
- id: resetpassword-gateway
uri: lb://distributed-accopen #端口地址(请求要去的地址)
predicates:
#匹配规则 *代表所有的字符
- Path=/rest/ResetPassword/*
filters:
#过滤规则 过滤掉第一个字段details。如http://127.0.0.1:25008/details/test/test 转发后变成 http://127.0.0.1:25002/test/test
- StripPrefix=1
则URL路径为/AccLogin/*,都会跳转到lb://distributed-accopen中(lb代表作了负载均衡)
1.3.3.5.2. 转账服务
- id: transfer-gateway
uri: lb://distributed-transfer
predicates:
- Path=/transfer/transfer/*
filters:
- StripPrefix=1
则URL路径为/transfer/*,都会跳转到lb://distributed-transfer中(lb代表作了负载均衡)
2. gateway网关统一配置跨域
参考:
谷粒商城40 - 后端-商品服务-API-三级分类-网关统一配置跨域
https://javaweixin6.blog.csdn.net/article/details/107797387
谷粒商城39 - 前后端-商品服务-API-三级分类-配置网关路由与路径重写
https://javaweixin6.blog.csdn.net/article/details/107772429
2.1. 没有统一配置跨域导致报错的案例
在localhost:8001/
端口的登录页面,点击登录按钮,应跳转到http://localhost:88/api/sys/login
地址,但此时会报错,这是因为由于从8001跨域到88端口,不满足浏览器的同源策略,导致登录失败。(需求检查请求头中,是否有Access-Control-Allow-Origin访问控制允许来源, 来允许跨域。)
2.2. 跨域的概念
端口号以前,稍微不一样,都不满足同源策略。
2.3. 跨域流程
发送非简单请求(PUT、DELETE)等,需要先发送预检请求。
跨源资源共享(CORS)
https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS
2.4. 如何实现跨域请求?
跨域失败的根本原因:目标网站和想要发送远程请求的网站不在同一域(网址的端口号、协议、域名有任何一个发生了变化)。
2.4.1. 使用nginx服务器将目标网站和想要发送远程请求的网站部署为同一个域
- 把前端项目和后端网关项目,都放在nginx服务器中。
- 浏览器要访问前端项目,不直接访问前端项目的地址(
localhost:8001/#/login
),而是访问nginx服务器的地址。 - 登录的时候,只要是静请求都默认代理给前端项目,动态请求由nginx反向代理到网关,网关再转给其他后端微服务。
这样,从头到尾,访问的都是nginx的地址。
但此方法只适合于部署的时候,不适合开发环境。
2.4.2. 后端服务配置当次请求允许跨域(分布式金融交易模型的网关跨域配置)
既然浏览器跨域首先会发一个预检请求给服务器,询问该服务器能不能跨域。那么如果服务器响应浏览器能跨域,就可以了。
写一个fiter过滤器,所有请求(预检请求)一进来后,放行,执行完以后,在给浏览器响应数据之前,给响应添加跨域需要的响应头字段即可。
这个fiter不用写在每一个服务中,只需要写在gateway服务中即可。因为最终每个服务都可能被远程服务(即都需要跨域),我们是使用网关代理浏览器请求给其他服务,在网关统一配置跨域即可解决各个微服务的跨域问题。
当浏览器发送一个预检请求到后端服务,首先会经过网关,网关的fiter会对该请求进行跨域过滤,对于没被过滤的请求,后端会响应允许该请求跨域的信号给浏览器,此时浏览器会发生真实请求给后端服务,后端服务进而响应数据。
gateway中配置了跨域过滤器fiter,其他各个后端服务不需要单独配置跨域过滤器。
2.4.2.1. 使用corsConfiguration.addAllowed**来设置服务器给浏览器返回的响应(gateway服务和转账服务中的做法)
package com.icbc.distributed.gateway.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsConfigurationSource;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.server.ServerWebExchange;
import java.nio.file.Path;
@Configuration
/**
* 网关统一配置跨域
**/
public class DisCorsConfiguration {
// 网关统一配置跨域
@Bean
public CorsWebFilter corsWebFilter(){
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedHeader("*");//允许跨域的请求头
corsConfiguration.addAllowedMethod("*");//允许跨域的请求方式
corsConfiguration.addAllowedOrigin("*");//允许跨域的请求来源
corsConfiguration.setAllowCredentials(true);//允许携带cookie跨域
source.registerCorsConfiguration("/**",corsConfiguration);// 任意路径都进行跨域配置
// source:跨域的配置信息
return new CorsWebFilter(source);
}
}
此时,在某端口跨域访问另一个不同端口,是可以获得响应数据的。
2.4.2.2. 使用httpServletResponse.setHeader来设置服务器给浏览器返回的响应(账户开立服务中的做法)
参考:
前后端交互跨域问题
https://blog.csdn.net/qq_38321137/article/details/113309117?utm_medium=distribute.pc_category.none-task-blog-hot-1.nonecase&depth_1-utm_source=distribute.pc_category.none-task-blog-hot-1.nonecase&request_id=
package com.icbc.distributed.accopen.config;
import org.springframework.stereotype.Component;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
/**
* 网关统一配置跨域方法而
* https://zhuanlan.zhihu.com/p/347924923
**/
public class OpenCorsConfiguration implements Filter{
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
//这里填写你允许进行跨域的主机ip
httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
//允许的访问方法
httpServletResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE, PATCH");
//Access-Control-Max-Age 用于 CORS 相关配置的缓存
httpServletResponse.setHeader("Access-Control-Max-Age", "3600");
httpServletResponse.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
String userId = servletRequest.getParameter("userId");
String token = servletRequest.getParameter("token");
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
两种方法都是使用跨域资源共享CORS来解决浏览器请求跨域的问题的。
3. gateway网关的跨域问题和Feign的远程调用是不一样的
网关针对的是客户端(浏览器)请求路径的路由,Feign是针对后端服务内部代码调用其它后端服务接口的路由。