微服务网关是整个微服务API请求的入口,可以实现过滤Api接口。
作用:可以实现用户的验证登录、解决跨域、日志拦截、权限控制、限流、熔断、负载均衡、黑名单与白名单机制等。
微服务中的架构模式采用前后端分离,前端调用接口地址都能够被抓包分析到。
在微服务中,我们所有的企业入口必须先经过API网关,经过API网关转发到真实的服务器中。
如何添加验证信息:
过滤器适合于单个服务实现过滤请求;
网关拦截整个的微服务实现过滤请求,能够解决整个微服务中冗余代码。
过滤器是局部拦截,网关实现全局拦截。
Zuul网关属于netfix公司开源的产品,属于第一代微服务网关。
Gateway属于SpringCloud自研发的网关框架,属于第二代微服务网关。
Nacos全程是启动的
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.0.2.RELEASEversion>
parent>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
<version>2.0.0.RELEASEversion>
dependency>
dependencies>
server:
port: 80
####服务网关名称
spring:
application:
name: gateway
cloud:
gateway:
###路由策略
routes:
###路由id
- id: provider
## 根据我们的服务名称查找地址是实现调用
uri: https://www.baidu.com/
###匹配规则
predicates:
- Path=/provider/**
### 127.0.0.1/provider 帮助我们转发到 https://www.baidu.com/
## 留个心眼:配置不会写死
@SpringBootApplication
public class AppGateWay {
public static void main(String[] args) {
SpringApplication.run(AppGateWay.class, args);
}
}
启动项目访问 http://localhost/provider 即可跳到百度页面
先添加依赖下先
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
<version>2.0.0.RELEASEversion>
dependency>
我演示的时候还有一个项目也启动了:服务提供者,里面有这样一个接口,方便观察:
@RequestMapping("/")
public String provider(HttpServletRequest request) {
String serverPort = request.getHeader("serverPort");
return "提供者端口号:" + this.serverPort + "网关端口号:" + serverPort;
}
server:
port: 80
#服务网关名称
spring:
application:
name: gateway
cloud:
### 服务注册地址
nacos:
discovery:
server-addr: 127.0.0.1:8848
gateway:
discovery:
locator:
#开启以服务id去注册中心上获取转发地址
enabled: true
###路由策略
routes:
###路由id
- id: provider
## 根据我们的服务名称查找地址是实现调用
# uri: https://www.baidu.com/
# 修改uri 由于我们是从服务注册中心拿的服务名,所以可能是一个集群,所以用lb
uri: lb://provider/
# 配置以下过滤,不然报错
filters:
- StripPrefix=1
###匹配规则
predicates:
- Path=/provider/**
### 127.0.0.1/provider 帮助我们转发到 http://127.0.0.1:8080/ (8080是我服务提供者的端口号,暂时只启动了一个)
启动项目即可: http://localhost/provider
其实也很简单,实现一个接口即可GlobalFilter
,下面写一个全局过滤器:必须传token才能访问
@Component
public class TokenGlobalFilter implements GlobalFilter {
@Value("${server.port}")
private String serverPort;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//如何去获取参数
String token = exchange.getRequest().getQueryParams().getFirst("token");
if (StringUtils.isEmpty(token)) {
ServerHttpResponse response = exchange.getResponse();
response.setStatusCode(HttpStatus.INTERNAL_SERVER_ERROR);
String msg = "token not is null ";
DataBuffer buffer = response.bufferFactory().wrap(msg.getBytes());
return response.writeWith(Mono.just(buffer));
}
// 在请求头中存放serverPort serverPort
ServerHttpRequest request = exchange.getRequest().mutate().header("serverPort", serverPort).build();
//说明你的token不为空,直接转发到我们的真实服务
return chain.filter(exchange.mutate().request(request).build());
}
}
首先在Nginx的配置文件中修改配置
worker_processes 1;
events {
worker_connections 1024;
}
http{
include mime.types;
default_type application/octet-stream;
# 监听的网关地址,默认为轮询算法
upstream mygatewayaddress {
server 127.0.0.1:81;
server 127.0.0.1:82;
}
server {
listen 80;
# 我的www.wu.com是本地的域名映射
server_name www.wu.com;
location / {
proxy_pass http://mygatewayaddress/;
}
}
}
把上面的那个全局过滤的方法给注释了,免得访问还要带参数
然后启动网关分别为 81、82,测试即可 http://127.0.0.1/provider
既然实现动态配置,那么可以把aplication.yml里面的路由注释掉
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
dependency>
<dependency>
<groupId>org.mybatis.spring.bootgroupId>
<artifactId>mybatis-spring-boot-starterartifactId>
<version>2.1.4version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-jdbcartifactId>
dependency>
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.21version>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>druidartifactId>
<version>1.1.23version>
dependency>
create database gateway;
use gateway;
CREATE TABLE `gateway`
(
`id` int(11) NOT NULL AUTO_INCREMENT primary key,
`route_id` varchar(11) DEFAULT NULL,
`route_name` varchar(255) DEFAULT NULL,
`route_pattern` varchar(255) DEFAULT NULL,
`route_type` varchar(255) DEFAULT NULL,
`route_url` varchar(255) DEFAULT NULL
) ENGINE = InnoDB
AUTO_INCREMENT = 2;
select *
from gateway;
@Repository
@Data
@AllArgsConstructor
@NoArgsConstructor
public class GateWay {
private int id;
private String routeId;
private String routeName;
private String routePattern;
private String routeType;
private String routeUrl;
}
@Mapper
@Component
public interface GateWayMapper {
@Select("select * from gateway")
List<GateWay> getAll();
}
datasource:
username: root
password: Wl123456
url: jdbc:mysql://127.0.0.1:3306/gateway?useUnicode=true&characterEncoding=UTF-8&useSSL=true&serverTimezone=UTC
driver-class-name: com.mysql.cj.jdbc.Driver
#整合mybatis
mybatis:
type-aliases-package: com.wu.pojo
configuration:
map-underscore-to-camel-case: true
//可以说是写死的
@Service
public class GateWayService implements ApplicationEventPublisherAware {
private ApplicationEventPublisher publisher;
@Autowired
private GateWayMapper gateWayMapper;
@Autowired
private RouteDefinitionWriter routeDefinitionWriter;
@Autowired
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.publisher = applicationEventPublisher;
}
public String initAllRoute() {
// 从数据库查询配置的网关配置
List<GateWay> gateWays = gateWayMapper.getAll();
for (GateWay gw : gateWays) {
System.out.println(gw);
loadRoute(gw);
}
return "success";
}
//设置路由
public String loadRoute(GateWay gateWay) {
RouteDefinition definition = new RouteDefinition();
Map<String, String> predicateParams = new HashMap<>(8);
PredicateDefinition predicate = new PredicateDefinition();
FilterDefinition filterDefinition = new FilterDefinition();
Map<String, String> filterParams = new HashMap<>(8);
// 如果配置路由type为0的话 则从注册中心获取服务
URI uri = null;
if ("0".equals(gateWay.getRouteType())) {
uri = UriComponentsBuilder.fromUriString("lb://" + gateWay.getRouteUrl() + "/").build().toUri();
} else {
uri = UriComponentsBuilder.fromHttpUrl(gateWay.getRouteUrl()).build().toUri();
}
// 定义的路由唯一的id
definition.setId(gateWay.getRouteId());
predicate.setName("Path");
//路由转发地址
predicateParams.put("pattern", gateWay.getRoutePattern());
predicate.setArgs(predicateParams);
// 名称是固定的, 路径去前缀
filterDefinition.setName("StripPrefix");
filterParams.put("_genkey_0", "1");
filterDefinition.setArgs(filterParams);
definition.setPredicates(Arrays.asList(predicate));
definition.setFilters(Arrays.asList(filterDefinition));
definition.setUri(uri);
routeDefinitionWriter.save(Mono.just(definition)).subscribe();
this.publisher.publishEvent(new RefreshRoutesEvent(this));
return "success";
}
}
@RestController
public class GateWayController {
@Autowired
private GateWayService gateWayService;
/**
* 同步网关配置
*
* @return
*/
@RequestMapping("/synGateWayConfig")
public String synGateWayConfig() {
return gateWayService.initAllRoute();
}
}
启动项目访问: http://127.0.0.1/provider
好的,现在我们调用自己的接口来动态配置http://127.0.0.1/synGateWayConfig
调用完后再次访问http://127.0.0.1/provider
路由:是网关基本的模块,分别为id(唯一)、目标uri(真实服务地址)、一组谓词+过滤器一起组合而成,如果谓词匹配成功,则路由匹配成功。
谓词: 匹配Http请求参数
过滤器:对下游的服务器之前和之后实现处理。
- id: provider
uri: http://www.baidu.com/
###匹配规则
predicates:
- After=2017-01-20T17:42:47.789-07:00[America/Denver]
# 此路由与 2017 年 1 月 20 日 17:42 MountainTime(Denver)之后的所有请求相匹配。
- id: provider
uri: http://www.baidu.com/
###匹配规则
predicates:
- Before=2017-01-20T17:42:47.789-07:00[America/Denver]
# 此路由与 2017 年 1 月 20 日 17:42 MountainTime(Denver)之前的所有请求相匹配。
- id: provider
uri: http://www.baidu.com/
###匹配规则
predicates:
- Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2018-01-21T17:42:47.789-07:00[America/Denver]
# 此路由与 2017年--2018年之间的所有请求相匹配。
- id: provider
uri: http://www.baidu.com/
###匹配规则
predicates:
- Cookie=chocolate, ch.p
# 此路由匹配具有名称为chocolate与ch.p匹配的cookie的请求。
- id: provider
uri: http://www.baidu.com/
###匹配规则
predicates:
- Host=**.somehost.org,**.anotherhost.org
# www.**.somehost.org ...
- id: provider
uri: http://www.baidu.com/
###匹配规则
predicates:
- Method=GET,POST
# get post请求的都行
- id: provider
uri: http://www.baidu.com/
###匹配规则
predicates:
- Path=/red/{segment},/blue/{segment}
# /red/1 , /red/1/ ...
spring:
cloud:
gateway:
routes:
- id: weight_high
uri: https://www.baidu.com
predicates:
# 分组要相同才有效果
- Weight=group1, 8
- id: weight_low
uri: https://www.taobao.com
predicates:
- Weight=group1, 2
# 这里的 8 2 不是没访问10次会出现 8 次或 2 次,而是没访问一次出现的概率!
解决跨域的问题
HttpClient转发
使用过滤器允许接口可以跨域 响应头设置
Jsonp 不支持我们的post 属于前端解决
Nginx解决跨域的问题保持我们域名和端口号一致性
Nginx也是通过配置文件解决跨域的问题
基于微服务网关解决跨域问题,需要保持域名和端口一致性
使用网关代码允许所有的服务可以跨域的问题
使用SpringBoot注解形式@CrossOrigin
/**
* 解决跨域问题
*/
@Component
public class CrossOriginFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
HttpHeaders headers = response.getHeaders();
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, "POST, GET, PUT, OPTIONS, DELETE, PATCH");
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "*");
headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*");
return chain.filter(exchange);
}
}
1、客户端向网关发送Http请求,会到达DispatcherHandler接受请求,匹配到
RoutePredicateHandlerMapping。
2、根据RoutePredicateHandlerMapping匹配到具体的路由策略。
3、FilteringWebHandler获取的路由的GatewayFilter数组,创建 GatewayFilterChain 处理过滤请求
GatewayClassPathWarningAutoConfiguration--------->作用检查是否配置我们webfux依赖。
GatewayAutoConfiguration--------->加载了我们Gateway需要的注入的类。
GatewayLoadBalancerClientAutoConfiguration --------->网关需要使用的负载均衡
GatewayRedisAutoConfiguration --------->网关整合Redis整合Lua实现限流
GatewayDiscoveryClientAutoConfiguration --------->服务注册与发现功能