服务网关: Spring Cloud Gateway
前面已经介绍了基于Spring Cloud搭建微服务框架所需要的必需组件,利用这些组件再配合客户端就可以构建出一个完整的系统。但在实际应用场景中,每一个微服务都会部署到内网服务器中,或者禁止外部访问这些端口,这是对应用的一种安全保护机制。因此,我们如果想通过互联网来访问这些服务,需要一个统一的入口,这就是本章将介绍的微服务的又一大组件——服务网关。
我们需要服务网关,还有一些很重要的因素,比如服务网关会对接口进行统一拦截并做合法性校验,一个服务可以启动多个端口,利用服务网关进行负载均衡处理等。
目前市面上有很多产品可以实现服务网关这一功能,如 Nginx、Apache、Zuul以及 Spring CloudGateway等。Spring Cloud集成了Zuul和Gateway,我们可以很方便地实现服务网关这一功能。
Gateway简介
关于Gateway,其官网是这样描述的:
This project provides a library for building an API Gateway on top of Spring MVC.Spring CloudGateway aims to provide a simple, yet effective way to route to APIs and provide cross cuttingconcerns to them such as: security, monitoring/metrics, and resiliency.
这个项目提供了一个在Spring MVC之上构建的API网关库,Spring Cloud Gateway致力于提供一个简单而有效的方法来由路由到API,并为它们提供跨领域的关注点,如安全、监控/度量和弹性。
Gateway是由Spring Cloud官方开发的一套基于WebFlux实现的网关组件,它的出现是为了替代Zuul。Gateway 不仅提供统一的路由方式,还基于Filter Chain供了网关的基本功能,例如安全、监控、埋点和限流等。
创建服务网关
本节中,我们将开始创建服务网关,进一步优化我们的微服务架构。
(1)创建一个子工程,命名为gateway并添加以下依赖:
代码语言:javascript复制<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>< / dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</ artifactId></ dependency>
<dependency>
<artifactId>common</artifactId><groupId>com.lynn.blog</groupId>
<version>1.e-SNAPSHOT</version>
< / dependency>
前面提到,Spring Cloud Gateway基于WebFlux,因此需要添加 WebFlux依赖,注意不能引入Web依赖,否则无法正常启动gateway工程;此外,为了启用服务网关功能,还需要添加 spring-cloud-starter-gateway依赖。
(2)在Git仓库创建一个配置文件gateway.yml,并添加以下内容:
代码语言:javascript复制server:
port: 8088spring:
application:
name: gatewaycloud:
#Spring Cloud Gateway路由配置方式gateway:
#是否与服务发现组件进行结合,通过serviceId(必须设置成大写)转发到具体的服务实例。默认为false,设为true便开启通过服务中心的自动根据serviceId创建路由的功能discovery:
#路由访问方式:http://Gateway_HOST:Gateway_PORT/大写的serviceId/**,其中微服务应用名默认大写访问
locator:
enabled: true
logging:
#配置网关日志策略level:
org.springframework.cloud.gateway : trace
org.springframework.http.server.reactive: debug
org.springframework.web.reactive: debug
reactor.ipc.netty: debug
feign:
hystrix:
#开启熔断器
enabled: true
在上述配置中 spring.cloud.gateway.discovery.locator.enabled默认为false,设置为true,我们就可以通过注册中心的 serviceId 请求路由地址,路由访问格式为http://Gateway_HOST:Gateway_PORT/serviceId/**,其中微服务应用名默认大写;logging.level配置的是服务网关日志策略。
(3)在gateway工程下创建bootstrap.yml配置文件,并添加以下内容:
代码语言:javascript复制spring:
cloud :
config:
name: eurekaclient,gatewaylabel: master
discovery :
enabled: trueserviceId: configusername: admin
password: admin
eureka:
client:
serviceUrl:
defaultzone: http: // admin :admin123@localhost:8101/eureka/
上述配置通过spring.cloud.config.name指定要拉取的文件,除了gateway还多了eurekaclient文件。eurekaclient 用于配置Eureka客户端。由于每个微服务都应注册到Eureka服务端中,所以每个服务都需要拉取eurekaclient配置,该配置内容如下:
代码语言:javascript复制spring:
cloud:
inetutils:
preferred-networks : 127.0.e.1
eureka:
instance:
prefer-ip-address: trueclient:
register-with-eureka: truefetch-registry: true
#开启熔断器
feign:
hystrix:
enabled: true
这些配置前面已经讲解,这里不再重复。
(4)创建应用启动类GatewayApplication,代码和前面讲的人口程序类似,此处省略具体的代码。
这样一个最简单的服务网关组件就搭建完成了。
接下来,分别启动register、config、test、 gateway工程并访问localhost:8080/TEST/test,如果出现如图9-1所示的内容,说明服务网关搭建成功。
在以上地址中,8080为网关启动端口,TEST为服务注册名 (Spring Cloud默认为大写),test为服务的restapi3地址。
利用过滤器拦截API请求
使用服务网关还有一个很重要的原因是我们需要对外提供统一的HTTP入口,便于我们管理各个服务接口,尤其是在鉴权R方面。假如没有服务网关进行拦截,就需要在每个服务下都实现拦截代码,而微服务系统的鉴权逻辑往往是一样的,代码也是一样的,所以这样做不利于维护和扩展。因此,我们可以利用Spring Cloud Gateway统一过滤外来请求。
服务网关提供了多种过滤器( filter )供大家选择,如GatewayFilter和 GlobalFilter等,不同过滤器的作用是不一样的,GatewayFilter处理单个路由的请求,而GlobalFilter根据名字就能知道,它是一个全局过滤器,可以过滤所有路由请求。本文以全局过滤器GlobalFilter为例,讲解如何通过过滤器过滤API请求,达到鉴权的目的。
(1)创建ApiGlobalFilter类,并实现GlobalFilter:
代码语言:javascript复制@Component
public class ApiGlobalFilter implements GlobalFilter {
@Override
public Mono<Void>filter(ServerwebExchange exchange,GatewayFilterChain chain){
String token = exchange.getRequest().getQueryParams().getFirst("token");
if ( StringUtils.isBlank( token)) i
ServerHttpResponse response = exchange.getResponse();3SONObject message = new JSONObject();
message.put( ""status", -1);
message.put( "data","鉴权失败");
byte[] bits = message.toJSONString().getBytes(StandardCharsets.UTF_8);DataBuffer buffer = response.bufferFactory( ).wrap(bits);
response. setStatuscode(HttpStatus. UNAUTHORIZED);
response.getHeaders().add("content-Type", "text/json;charset=UTF-8");return response.writewith(Mono.just(buffer));
}
return chain.filter( exchange);
}
}
上述代码的意思是过滤所有请求路由,从参数中提取token并通过chain.filter方法执行目标路由,如果没有token,则提示鉴权失败,并通过writewith方法返回。
Spring Cloud Gateway依赖WebFlux,而WebFlux通过Mono对象返回数据,因此上述过滤器也返回了Mono对象。我们注意到,filter方法返回的是 Mono<Void>,读者可以将Void类理解为同Java的void关键字一样的功能,它其实就是void关键字的包装类,同int和 Integer的区别一样。该Mono并不返回任何数据,我们如果将它想象成普通的定义方法,就应该是void filter()。
(2)启动gateway 工程,访问localhost:8080/TEST/test,得到以下结果,如图9-2所示。说明全局过滤器对路由做了过滤处理。将地址加上 token参数后,将会得到如图9-1所示的结果。
请求失败处理
如果要调用的服务出现异常或者宕机了,那么Gateway请求失败,必然会返回错误。这时停止 test工程并访问网关地址,可以看到如图9-3所示的界面。
这种500 错误对用户是不友好的,需要对服务网关进行统一的异常处理并给客户端返回统一的JSON数据,让客户端具有友好的体验,具体步骤如下。
(1)创建异常处理类JsonExceptionHandler,它继承自 DefaultErrorwebExceptionHandler:
代码语言:javascript复制public class JsonExceptionHandler extends DefaultErrorwebExceptionHandler{
public JsonExceptionHandler(ErrorAttributes errorAttributes,ResourceProperties
resourceProperties, ErrorProperties errorProperties,ApplicationContext
applicationcontext) {
super(errorAttributes,resourceProperties,errorProperties, applicationContext);
)
@override
protected Map<String, 0bject> getErrorAttributes(ServerRequest request,boolean
includeStackTrace) i
int code = 508;
Throwable error = super.getError(request);
if (error instanceof org.springframework.cloud.gateway. support.NotFoundException){
code = 404;
}
return response(code,this.buildMessage(request,error));
}
@override
protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes
errorAttributes) {
return RouterFunctions.route(RequestPredicates.all(), this:: renderErrorResponse);
}
@override
protected HttpStatus getHttpStatus(Map<String,object> errorAttributes){
int statuscode =(int) errorAttributes.get( "code" );
return Httpstatus.valueOf(statuscode);
}
private String buildMessage(ServerRequest request,Throwable ex){
StringBuilder message = new StringBuilder("Failed to handle request [");message.append(request.methodName());
message. append(" ");
message. append(request.uri());message. append("]");
if (ex != null){
message. append(": ");
message.append(ex.getMessage();
}
return message.toString();
}
public static Map<String,0bject> response(int status,String errorMessage){
Map<String,object> map = new HashMap<>();
map.put( "code", status);
map.put( "message" , errorMessage);map.put( "data" , null);
return map;
}
}
SpringBoot提供了默认的异常处理类 DefaultErrorwebExceptionHandler,显示效果如图9-3所示,这显然不符合我们的预期。因此,需要重写此类,并返回JSON格式。
Spring Cloud Gateway进行异常处理的原理是,当出现请求服务失败(可以是服务不可用,也可以是路由地址404等)的情况,首先会调用getRoutingFunction方法,该方法接收ErrorAttributes对象,即接收具体的错误信息,然后调用getErrorAttributes方法获得异常属性,通过该方法判断具体的错误码,最终将错误信息放到Map 并返回客户端。
(2)覆盖默认异常,具体的实现如下:
代码语言:javascript复制@SpringBootConfiguration
@EnableConfigurationProperties({ServerProperties.class,ResourceProperties.class})
public class ErrorHandlerConfiguration {
private final ServerProperties serverProperties;private final ApplicationContext applicationContext;private final ResourceProperties resourceProperties;private final List<ViewResolver> viewResolvers;
private final ServerCodecConfigurer serverCodecconfigurer;
public ErrorHandlerconfiguration(ServerProperties serverProperties,
ResourceProperties resourceProperties,0bjectProvider<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 3sonExceptionHandler(
errorAttributes,
this.resourceProperties,
this.serverProperties.getError(),this.applicationContext);
exceptionHandler.setViewResolvers(this.viewResolvers);
exceptionHandler.setMessagewriters(this.serverCodecConfigurer.getwriters());exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());return exceptionHandler;
}
}
以上代码最核心的部分是errorlwebExceptionHandler方法,因此上述类添加了@SpringBoot-Configuration注解,并且 errorWwebExceptionHandler声明了@Bean。gateway工程启动时就会执行errorwebExceptionHandler方法且需要返回ErrorWwebExceptionHandler对象,方法内可以实例化sonExceptionHandler对象并返回。这样gateway在发生异常时就会自动执行JsonExceptionHandler而不会执行其默认类了。
(3)重新启动gateway并停止 test,访问地址 localhost:8080/TEST/test就可以得到如图9-4所示的结果。
小结
本文介绍了Spring Cloud的另一大组件:服务网关,它是外部通信的唯一入口。在实际项目中,我们需要对接口进行安全性校验,而一套微服务架构可能存在成千上万个服务,不可能对每个服务都单独实现安全机制,而应通过服务网关统一拦截。Spring Cloud Gateway默认实现了负载均衡,一个服务可以部署到多台服务器,通过其负载均衡机制,.可以有效地提升系统的并发处理能力。
本文给大家讲解的内容是springcloud实战:服务网关,SpringCloudGateway
- 下篇文章给大家讲解的是springcloud实战:网站功能开发;
- 觉得文章不错的朋友可以转发此文关注小编;
- 感谢大家的支持!
本文就是愿天堂没有BUG给大家分享的内容,大家有收获的话可以分享下,想学习更多的话可以到微信公众号里找我,我等你哦。