一、GateWay基本概念
1、GateWay说明
gateway之所以性能号,因为底层使用WebFlux,而webFlux底层使用netty通信(NIO)
GateWay能干啥
反向代理
鉴权
流量控制
熔断
日志监控
2、微服务架构中的网关在哪里
3、GateWay的特性
4、GateWay和Zuul区别
5、什么是webflux:
是一个非阻塞的web框架,类似springmvc这样的
6、Gate的一些基本概念
(1)路由
路由是构建网关的基本模块,它由ID,目标URI,一系列的断言和过滤器组成,如断言为true则匹配该路由
(2)断言
参考的是Java8的java.util.function.Predicate
开发人员可以匹配HTTP请求中的所有内容(例如请求头或请求参数),如果请求与断言相匹配则进行路由
(3)过滤
指的是Spring框架中GatewayFilter的实例,使用过滤器,可以在请求被路由前或者之后对请求进行修改.
7、GateWay工作原理
二、模块搭建
1、pom
不可以加spring-boot-starter-web
和spring-boot-starter-actuator
<dependencies>
<!--gateway-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!--eureka-client-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
<dependency>
<groupId>com.atguigu.springcloud</groupId>
<artifactId>cloud-api-commons</artifactId>
<version>${project.version}</version>
</dependency>
<!--一般基础配置类-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
2、application.yml配置文件配置网关
server:
port: 9527
spring:
application:
name: cloud-gateway
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
# cors 配置
globalcors:
cors-configurations:
'[/**]':
allowedOrigins: "*"
allowedMethods: "*"
allowedHeaders: "*"
routes:
- id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址(动态路由)
predicates:
- Path=/payment/get/** # 断言,路径相匹配的进行路由
- id: payment_routh2 #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/payment/lb/** # 断言,路径相匹配的进行路由
#- After=2020-02-21T15:51:37.485+08:00[Asia/Shanghai]
#- Cookie=username,zzyy
#- Header=X-Request-Id, \d+ # 请求头要有X-Request-Id属性并且值为整数的正则表达式
#使用nacos作为注册中心
nacos:
discovery:
server-addr: localhost:8848 #配置Nacos地址
namespace: 38efc944-744e-45bb-a814-e356fbd883c3
#使用euraka最为注册中心
eureka:
instance:
hostname: cloud-gateway-service
client: #服务提供者provider注册进eureka服务列表内
service-url:
register-with-eureka: true
fetch-registry: true
defaultZone: http://eureka7001.com:7001/eureka
3、配置类配置网关
也可不采用yml配置,而采用配置类进行配置
@Configuration
public class GateWayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder)
{
RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes();
//将localhost:9527/guonei 路由映射到 百度http://news.baidu.com/guonei
routes.route("path_route_atguigu",
r -> r.path("/guonei")
.uri("http://news.baidu.com/guonei")).build();
return routes.build();
}
}
4、主启动类
@SpringBootApplication
@EnableEurekaClient //euraka最为注册中心
@EnableDiscoveryClient //Nacos作为注册中心
public class MainGateWay9527 {
public static void main(String[] args) {
SpringApplication.run(MainGateWay9527.class,args);
}
}
5、测试
可在localhost:7001的Eureka图形化界面中看到9527的服务网关微服务
我们目前不想暴露8001端口,希望在8001外面套一层9527
三、常用的Route Predicate(断言)
routes:
- id: payment_routh #payment_route #路由的ID,没有固定规则但要求唯一,建议配合服务名
#uri: http://localhost:8001 #匹配后提供服务的路由地址
uri: lb://cloud-payment-service #匹配后提供服务的路由地址
predicates:
- Path=/payment/get/** # 断言,路径相匹配的进行路由
- After=2020-02-21T15:51:37.485+08:00[Asia/Shanghai] #在指定时间后执行
- Cookie=username,zzyy #含有响应的cookie键值对
- Header=X-Request-Id, \d+ # 请求头有X-Request-Id属性并且值为整数的正则表达式
- Host=**.com,**.org.com
- Method=GET #必须是get请求
- Query=username,\d+ #有参数名并且只是整数
有先后 顺序,当前面的匹配到了,就不会匹配到后面的
1、Path(路径)
这个断言表示,如果外部访问路径是指定路径,就路由到指定微服务上
只有访问指定路径,才进行路由,比如访问,/abc才路由
2、After、Before、Between(时间)
- After:可以指定,只有在指定时间后,才可以路由到指定微服务
- Before:可以指定,只有在指定时间前,才可以路由到指定微服务
- Between:可以指定,只有在指定时间内,才可以路由到指定微服务(需指定2个时区,用
,
隔开)
时区时间串的获得方法:
ZonedDateTime time = ZonedDateTime.now();
System.out.println(time); //2021-01-05T22:19:46.948+08:00[Asia/Shanghai]
3、Cookie
两个参数,一个是Cookie name,一个是正则表达式,路由规则会根据对应的Cookie name和正则表达式进行匹配,匹配成功执行路由,否则不执行。
只有包含某些指定cookie(key,value),的请求才可以路由。
配置后直接通过路径无法访问。
需要有指定的Cookie(使用curl测试)
如果加入curl返回中文乱码,解决方法https://blog.csdn.net/leedee/article/details/82685636
4、 Header
只有包含指定请求头的请求,才可以路由。(与Cookie相似)(区别键和值用:
隔开)
curl http://localhost:9527/payment/lb --cookie "username=zzyy" -H "X-Request-Id:123"
5、Host(防盗链)
只有指定主机的才可以访问,
比如我们当前的网站的域名是www.aa.com
那么这里就可以设置,只有用户是www.aa.com的请求,才进行路由
6、Method
只有指定请求才可以路由,比如get请求…
7、Query
必须带有请求参数才可以访问
四、Filter过滤器
1、基本概念
路由过滤器可用于修改进入的HTTP请求
和返回的HTTP响应
。
springcloud GateWay内置多种路由过滤器,都由GateWayFilter
的工厂类产生
2、声明周期
- 在请求进入路由之前
- 处理请求完成,再次到达路由之前
3、种类
- GateWayFilter(单一过滤器)(31种)
- GlobalFilter(全局过滤器)(10种)
4、自定义过滤器
(1)作用
- 全局日志记录
- 统一网关鉴权
(2)在filter包下创建MyLogGateWayFilter
//该过滤器直接通过@Component加入到容器中,故不需要其他配置
@Component
@Slf4j
//必须实现2个接口
public class MyLogGateWayFilter implements GlobalFilter,Ordered
{
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain)
{
log.info("***********come in MyLogGateWayFilter: "+new Date());
//获取请求参数uname
String uname = exchange.getRequest().getQueryParams().getFirst("uname");
//如果name为空,直接过滤掉,不走路由
if(uname == null)
{
log.info("*******用户名为null,非法用户,o(╥﹏╥)o");
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
//该过滤器放行,调用下一个过滤器
return chain.filter(exchange);
}
@Override
public int getOrder()
{
return 0;
}
}
(3)测试
http://localhost:9527/payment/lb无法访问
http://localhost:9527/payment/lb?uname=123可以访问
五、解决跨域问题
gateway网关微服务配置类
/**
* 用于解决跨域问题
* 用了此类后不要在在cobtroller中加 @CrossOrign 注解了,加了会出问题
*/
@Configuration
public class CorsConfig {
@Bean
public CorsWebFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedMethod("*");
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
}
六、权限控制(鉴权)
gateway网关微服务过滤器
/**
* 全局Filter,统一处理会员登录与外部不允许访问的服务
*/
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
private AntPathMatcher antPathMatcher = new AntPathMatcher();
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
String path = request.getURI().getPath();
//谷粒学院api接口,校验用户必须登录
if(antPathMatcher.match("/api/**/auth/**", path)) {
List<String> tokenList = request.getHeaders().get("token");
if(null == tokenList) {
ServerHttpResponse response = exchange.getResponse();
return out(response);
} else {
// Boolean isCheck = JwtUtils.checkToken(tokenList.get(0));
// if(!isCheck) {
ServerHttpResponse response = exchange.getResponse();
return out(response);
// }
}
}
//内部服务接口,不允许外部访问
if(antPathMatcher.match("/**/inner/**", path)) {
ServerHttpResponse response = exchange.getResponse();
return out(response);
}
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0;
}
private Mono<Void> out(ServerHttpResponse response) {
JsonObject message = new JsonObject();
message.addProperty("success", false);
message.addProperty("code", 28004);
message.addProperty("data", "鉴权失败");
byte[] bits = message.toString().getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = response.bufferFactory().wrap(bits);
//response.setStatusCode(HttpStatus.UNAUTHORIZED);
//指定编码,否则在浏览器中会中文乱码
response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
return response.writeWith(Mono.just(buffer));
}
}
七、异常处理
1、覆盖默认的异常处理
/**
* 覆盖默认的异常处理
*/
@Configuration
@EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class})
public class ErrorHandlerConfig {
private final ServerProperties serverProperties;
private final ApplicationContext applicationContext;
private final ResourceProperties resourceProperties;
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public ErrorHandlerConfig(ServerProperties serverProperties,
ResourceProperties resourceProperties,
ObjectProvider<List<ViewResolver>> viewResolversProvider,
ServerCodecConfigurer serverCodecConfigurer,
ApplicationContext applicationContext) {
this.serverProperties = serverProperties;
this.applicationContext = applicationContext;
this.resourceProperties = resourceProperties;
this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
this.serverCodecConfigurer = serverCodecConfigurer;
}
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {
JsonExceptionHandler exceptionHandler = new JsonExceptionHandler(
errorAttributes,
this.resourceProperties,
this.serverProperties.getError(),
this.applicationContext);
exceptionHandler.setViewResolvers(this.viewResolvers);
exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
return exceptionHandler;
}
}
2、自定义异常处理
/**
* 自定义异常处理
*
* <p>异常时用JSON代替HTML异常信息<p>
*/
public class JsonExceptionHandler extends DefaultErrorWebExceptionHandler {
public JsonExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties,
ErrorProperties errorProperties, ApplicationContext applicationContext) {
super(errorAttributes, resourceProperties, errorProperties, applicationContext);
}
/**
* 获取异常属性
*/
@Override
protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
Map<String, Object> map = new HashMap<>();
map.put("success", false);
map.put("code", 20005);
map.put("message", "网关失败");
map.put("data", null);
return map;
}
/**
* 指定响应处理方法为JSON处理的方法
* @param errorAttributes
*/
@Override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
}
/**
* 根据code获取对应的HttpStatus
* @param errorAttributes
*/
@Override
protected int getHttpStatus(Map<String, Object> errorAttributes) {
return 200;
}
}
八、全局熔断降级Hystrix
1、依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
2、添加配置
spring:
application:
name: cloud-gateway-hystrix
cloud:
gateway:
discovery:
locator:
enabled: true #开启从注册中心动态创建路由的功能,利用微服务名进行路由
# 超时设置,配合熔断降级使用
httpclient:
# 链接超时,单位毫秒,默认45秒
connect-timeout: 30000
# 响应超时,30秒
response-timeout: 30s
# 全局熔断降级配置
default-filters:
- name: Hystrix
args:
name: default
fallbackUri: 'forward:/fallback'
3、降级路由配置
import com.rewind.gateway.handler.HystrixFallbackHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RequestPredicates;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
/**
* 路由配置信息
*/
@Configuration
public class RouterFunctionConfiguration {
@Autowired
private HystrixFallbackHandler hystrixFallbackHandler;
@Bean
public RouterFunction routerFunction() {
return RouterFunctions.route(RequestPredicates.path("/fallback").and(RequestPredicates.accept(MediaType.TEXT_PLAIN)), hystrixFallbackHandler);
}
}
4、降级方法
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.HandlerFunction;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.Optional;
import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_ORIGINAL_REQUEST_URL_ATTR;
/**
* Hystrix 降级处理
*/
@Component
public class HystrixFallbackHandler implements HandlerFunction<ServerResponse> {
private final Logger logger = LoggerFactory.getLogger(HystrixFallbackHandler.class);
@Override
public Mono<ServerResponse> handle(ServerRequest serverRequest) {
Optional<Object> originalUris = serverRequest.attribute(GATEWAY_ORIGINAL_REQUEST_URL_ATTR);
originalUris.ifPresent(originalUri -> logger.error("Gateway execution request: {} fail, Hystrix service downgrades", originalUri));
HashMap<String, Object> result = new HashMap<>();
result.put("success", false);
result.put("code", HttpStatus.SERVICE_UNAVAILABLE.value());
result.put("message", "服务繁忙,请稍后重试");
result.put("data", null);
return ServerResponse.status(HttpStatus.INTERNAL_SERVER_ERROR.value())
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(result));
}
}