admin管理员组文章数量:1608828
Spring Cloud Gateway由浅入深
- 1、如何理解微服务网关Gateway
- 2、核心名词讲解
- 3、搭建微服务网关
- 4、异常处理
- 5、服务熔断
- 6、微服务防护
- 7、跨域设置
- 8、服务限流
- 9、禁止访问资源
- 思考?
1、如何理解微服务网关Gateway
Spring Cloud Gateway 旨在提供一种简单而有效的方式来路由到 API,并为它们提供横切关注点,例如:安全性、监控/指标和弹性。
按照官方的说法,Spring Cloud Gateway主要用于路由、安全拦截等。
2、核心名词讲解
Spring Cloud Gateway包含但不仅限于路由、谓词工厂、过滤器这些专业名词。
-
路由:网关的基本构建块。它由 ID、目标 URI、谓词集合和过滤器集合定义。如果聚合谓词为真,则匹配路由。id代表唯一标识,随便填写,uri与服务名称相结合,常用格式lb://微服务名称。
-
谓词:匹配来自 HTTP 请求的任何内容,例如标头或参数。
-
过滤器:在发送下游请求之前或之后修改请求和响应。
3、搭建微服务网关
本章节只讲解Spring Cloud Gateway网关相关、服务注册发现将不会体现
pom文件引入Maven依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
application.yaml文件
server:
port: 8301
spring:
application:
name: Test-Gateway
cloud:
gateway:
routes:
- id: febs-auth
uri: lb://Test-Auth
predicates:
- Path=/auth/**
filters:
- StripPrefix=1
- id: FEBS-Server-System
uri: lb://Test-Server-System
predicates:
- Path=/system/**
filters:
- StripPrefix=1
- id: FEBS-Server-Test
uri: lb://Test-Server-Test
predicates:
- Path=/test/**
filters:
- StripPrefix=1
predicates谓词,用于匹配规则。filters过滤器,StripPrefix=1表示截取请求前一位,例如/auth/token - -> /token。
访问http://127.0.0.1/8301/auth/token接口,在Gateway作用下相当于访问Test-Auth微服务的token接口。
4、异常处理
Spring Cloud Gateway默认使用DefaultErrorWebExceptionHandler构建异常信息对象,ErrorWebFluxAutoConfiguration配置中,采用了
@ConditionalOnMissingBean(value=ErrorWebExceptionHandler.class)注解,所以我们可以继承DefaultErrorWebExceptionHandler,自定义异常处理。
创建IGatewayExceptionHandler类。
/**
* Gateway异常处理
*/
@Slf4j
public class IGatewayExceptionHandler extends DefaultErrorWebExceptionHandler {
public IGatewayExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties, ErrorProperties errorProperties, ApplicationContext applicationContext) {
super(errorAttributes, resourceProperties, errorProperties, applicationContext);
}
/**
* 异常处理,定义返回报文格式
*/
@Override
protected Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
Throwable error = super.getError(request);
log.error(
"请求发生异常,请求URI:{},请求方法:{},异常信息:{}",
request.path(), request.methodName(), error.getMessage()
);
String errorMessage;
if (error instanceof NotFoundException) {
String serverId = StringUtils.substringAfterLast(error.getMessage(), "Unable to find instance for ");
serverId = StringUtils.replace(serverId, "\"", StringUtils.EMPTY);
errorMessage = String.format("无法找到%s服务", serverId);
} else if (StringUtils.containsIgnoreCase(error.getMessage(), "connection refused")) {
errorMessage = "目标服务拒绝连接";
} else if (error instanceof TimeoutException) {
errorMessage = "访问服务超时";
} else if (error instanceof ResponseStatusException
&& StringUtils.containsIgnoreCase(error.getMessage(), HttpStatus.NOT_FOUND.toString())) {
errorMessage = "未找到该资源";
} else {
errorMessage = "网关转发异常";
}
Map<String, Object> errorAttributes = new HashMap<>(3);
errorAttributes.put("code",500);
errorAttributes.put("message", errorMessage);
return errorAttributes;
}
@Override
@SuppressWarnings("all")
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
}
@Override
protected HttpStatus getHttpStatus(Map<String, Object> errorAttributes) {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
}
将IGatewayExceptionHandler注入SpringIOC容器中去。
@Configuration
public class IGatewayExceptionConfiguration {
private final ServerProperties serverProperties;
private final ApplicationContext applicationContext;
private final ResourceProperties resourceProperties;
private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecConfigurer;
public IGatewayExceptionConfiguration(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){
IGatewayExceptionHandler exceptionHandler = new IGatewayExceptionHandler (
errorAttributes,
this.resourceProperties,
this.serverProperties.getError(),
this.applicationContext);
exceptionHandler.setViewResolvers(this.viewResolvers);
exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
return exceptionHandler;
}
}
Spring检测@Configuration注解时,通过有参构造将IGatewayExceptionHandler需要参数注入进来。
测试并查看控制台打印信息:
请求发生异常,请求URI:/system/testOpenFeign,请求方法:GET,异常信息:Connection refused: no further information
[231e9045] 500 Server Error for HTTP GET "/system/testOpenFeign"
5、服务熔断
当网关转发的微服务长时间未响应,该请求将会一直维持无法释放,占用系统资源,我们可以采用网关熔断技术来解决。
yaml文件添加Hystrix
修改yaml文件:
server:
port: 8301
spring:
application:
name: Test-Gateway
cloud:
gateway:
routes:
- id: febs-auth
uri: lb://Test-Auth
predicates:
- Path=/auth/**
filters:
- StripPrefix=1
- id: FEBS-Server-System
uri: lb://Test-Server-System
predicates:
- Path=/system/**
filters:
- StripPrefix=1
- name: Hystrix
args:
name: systemFallback
fallbackUri: forward:/fallback/FEBS-Server-System
- id: FEBS-Server-Test
uri: lb://Test-Server-Test
predicates:
- Path=/test/**
filters:
- StripPrefix=1
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 3000
添加GlobalGatewayFallbackController
@RestController
public class GlobalGatewayFallbackController {
@GetMapping("/fallback/{name}")
public Mono<FebsResponse> fallback(@PathVariable String name){
String message = String.format("服务%s访问超时",name);
return Mono.just(new FebsResponse().code(HttpStatus.INTERNAL_SERVER_ERROR.value()).message(message));
}
}
设置超时时间3s,fallbackUri执行自定义回滚Controller。
测试结果:
6、微服务防护
虽然我们集成了Gateway网关对微服务进行统一的路由,但是我们仍然可以通过资源服务的端口进行访问,通用的解决方案为:采用token进行校验,Gateway携带token请求,资源服务统一拦截请求进行token校验,如果是网关的请求放行,否则进行拦截并返回错误信息。
添加IGlobalRequestFilter:
@Slf4j
@Component
public class IGlobalRequestFilter implements GlobalFilter {
private final String TOKEN_HEADER = "GatewayHeader";
private final String TOKEN_VALUE = "febs:febs";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 打印转发日志
printLog(exchange);
ServerHttpRequest request = exchange.getRequest();
byte[] token = Base64Utils.encode(TOKEN_VALUE.getBytes());
ServerHttpRequest build = request.mutate().header(TOKEN_HEADER, new String(token)).build();
ServerWebExchange newExchange = exchange.mutate().request(build).build();
return chain.filter(newExchange);
}
/**打印转发日志**/
private void printLog(ServerWebExchange exchange){
URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);
Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);
LinkedHashSet<URI> uris = exchange.getAttribute(GATEWAY_ORIGINAL_REQUEST_URL_ATTR);
URI originUri = null;
if (uris != null) {
originUri = uris.stream().findFirst().orElse(null);
}
if (url != null && route != null && originUri != null) {
log.info("转发请求:{}://{}{} --> 目标服务:{},目标地址:{}://{}{},转发时间:{}",
originUri.getScheme(), originUri.getAuthority(), originUri.getPath(),
route.getId(), url.getScheme(), url.getAuthority(), url.getPath(), LocalDateTime.now()
);
}
}
}
资源服务器配置:
public class ServerProtectInterceptor implements HandlerInterceptor {
private final String TOKEN_HEADER = "GatewayHeader";
private final String TOKEN_VALUE = "febs:febs";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
// 从请求头中获取 Token
String token = request.getHeader(TOKEN_HEADER );
String Token = new String(Base64Utils.encode(TOKEN_VALUE .getBytes()));
// 校验 Token的正确性
if (StringUtils.equals(Token, token)) {
return true;
} else {
FebsResponse febsResponse = new FebsResponse();
response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.getWriter().write(JSONObject.toJSONString(febsResponse.message("请通过网关获取资源")));
return false;
}
}
}
public class ServerProtectConfigure implements WebMvcConfigurer {
@Bean
public HandlerInterceptor serverProtectInterceptor() {
return new ServerProtectInterceptor();
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(serverProtectInterceptor());
}
}
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(ServerProtectConfigure.class)
public @interface EnableServerProtect {
}
ServerProtectInterceptor 实现HandlerInterceptor,从请求中获取Token进行校验。
ServerProtectConfigure 配置类将ServerProtectInterceptor注入SpringIOC。
EnableServerProtect 注解表示开启ServerProtectConfigure配置
7、跨域设置
添加跨域配置。
@Configuration
public class IGateWayCorsConfigure {
@Bean
public CorsWebFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
CorsConfiguration cors = new CorsConfiguration();
cors.setAllowCredentials(true);
cors.addAllowedOrigin(CorsConfiguration.ALL);
cors.addAllowedHeader(CorsConfiguration.ALL);
cors.addAllowedMethod(CorsConfiguration.ALL);
source.registerCorsConfiguration("/**", cors);
return new CorsWebFilter(source);
}
}
8、服务限流
9、禁止访问资源
思考?
经常有人问到,Nginx和Gateway都是网关,为什么Gateway不可以替代Nginx?
Nginx主要用来做流量入口、负载均衡、反向代理,属于流量网关。
- 负载均衡:针对于服务器端流量分发,采用不同负载策略,实现流量的分发
- 反向代理:将外部访问地址转向服务器内部资源地址
Gateway将流量分发到不同的微服务上,最要用来做路由、过滤器,属于业务网关
参考资料: mrbird鸟哥的FEBS开源项目、FEBS看云文档。
版权声明:本文标题:Spring Cloud Gateway由浅入深 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://www.elefans.com/dongtai/1728550441a1163361.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论