当前位置:   article > 正文

Spring Cloud-网关 Spring-Cloud-Gateway,eureka配置_gateway和eureka的使用

gateway和eureka的使用

简介

Spring Cloud Gateway 是 Spring Cloud 的一个全新项目,该项目是基于 Spring 5.0,Spring Boot 2.0 和 Project Reactor 等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。

Spring Cloud Gateway 作为 Spring Cloud 生态系统中的网关,目标是替代 Netflix Zuul,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控/指标,和限流。

概念

  • Route(路由):Route 是网关的基础元素,由 ID、目标 URI、断言、过滤器组成。当请求到达网关时,由 Gateway Handler Mapping 通过断言进行路由匹配,当断言为真时,匹配到路由。
  • Predicate(断言):Predicate 是 Java 8 中提供的一个函数。允许开发人员匹配来自 HTTP 的请求,例如请求头或者请求参数。简单来说它就是匹配条件。
  • Filter(过滤器):Filter 是 Gateway 中的过滤器,可以在请求发出前后进行一些业务上的处理。

工作原理

image

当客户端请求到达 Spring Cloud Gateway 后,Gateway Handler Mapping 会将其拦截,根据 predicates 确定请求与哪个路由匹配。如果匹配成功,则会将请求发送至 Gateway web handler。Gateway web handler 处理请求会经过一系列 “pre” 类型的过滤器,然后执行代理请求。执行完之后再经过一系列的 “post” 类型的过滤器,最后返回给客户端。

快速开始

新建一个子工程,命名为 spring-cloud-gateway

引入依赖

  1. <dependency>
  2. <groupId>org.springframework.cloud</groupId>
  3. <artifactId>spring-cloud-starter-gateway</artifactId>
  4. </dependency>

application.properties 配置路由

  1. spring.application.name=spring-cloud-gateway
  2. server.port=8074
  3. ############ 定义了一个 router(注意是数组的形式) ############
  4. # 路由 ID,保持唯一
  5. spring.cloud.gateway.routes[0].id=my-gateway
  6. # 目标服务地址
  7. spring.cloud.gateway.routes[0].uri=http://httpbin.org
  8. # 路由条件
  9. spring.cloud.gateway.routes[0].predicates[0]=Path=/get

上面这段配置的意思是,配置了一个 id 为 my-gateway 的路由规则,当访问地址为 /get 时会自动转发到 http://httpbin.org/get

还可以通过代码的形式配置路由

  1. /**
  2. * 通过代码的形式配置路由
  3. * @param builder
  4. * @return
  5. */
  6. @Bean
  7. public RouteLocator myRoutes(RouteLocatorBuilder builder) {
  8. return builder.routes()
  9. .route(
  10. p -> p.path("/get").uri("http://httpbin.org")
  11. )
  12. .build();
  13. }

application.propertise 配置路由和代码配置路由选择其中一个就好了,个人推荐 application.propertise 的形式配置。

启动服务,访问 http://localhost:8074/get

应该会输出以下内容:

  1. {
  2. "args": {},
  3. "headers": {
  4. "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
  5. "Accept-Encoding": "gzip, deflate, br",
  6. "Accept-Language": "zh-CN,zh;q=0.9",
  7. "Cache-Control": "max-age=0",
  8. "Cookie": "Webstorm-2f8f75da=e0c5ee46-9276-490c-b32b-d5dc1483ca18; acw_tc=2760828015735472938194099e940a3c3ebc07316bcb1096abc6fefde61bf8; BD_UPN=12314353; H_PS_645EC=8749qnWwXCzugp%2FwPJDVeB7bqBisqx6VKFthj5OZOsWBAz1JPX2YkatsizA; BD_HOME=0",
  9. "Forwarded": "proto=http;host=\"localhost:8074\";for=\"0:0:0:0:0:0:0:1:51881\"",
  10. "Host": "httpbin.org",
  11. "Sec-Fetch-Mode": "navigate",
  12. "Sec-Fetch-Site": "none",
  13. "Sec-Fetch-User": "?1",
  14. "Upgrade-Insecure-Requests": "1",
  15. "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36",
  16. "X-Forwarded-Host": "localhost:8074"
  17. },
  18. "origin": "0:0:0:0:0:0:0:1, 183.14.135.71, ::1",
  19. "url": "https://localhost:8074/get"
  20. }

整合 Eureka

添加 Eureka Client 的依赖

  1. <dependency>
  2. <groupId>org.springframework.cloud</groupId>
  3. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  4. </dependency>

配置基于 Eureka 的路由

  1. spring.application.name=spring-cloud-gateway
  2. server.port=8074
  3. ########### 配置注册中心 ###########
  4. # 获取注册实例列表
  5. eureka.client.fetch-registry=true
  6. # 注册到 Eureka 的注册中心
  7. eureka.client.register-with-eureka=true
  8. # 配置注册中心地址
  9. eureka.client.service-url.defaultZone=http://localhost:8070/eureka
  10. ############ 定义了一个基于 Eureka 的 router(注意是数组的形式) ############
  11. # 路由 ID,保持唯一
  12. spring.cloud.gateway.routes[0].id=my-gateway
  13. # 目标服务地址
  14. spring.cloud.gateway.routes[0].uri=lb://spring-cloud-provider
  15. # 路由条件
  16. spring.cloud.gateway.routes[0].predicates[0]=Path=/user-service/**

uri 以 lb:// 开头(lb 代表从注册中心获取服务),后面接的就是你需要转发到的服务名称,这个服务名称必须跟 Eureka 中的对应,否则会找不到服务。

spring-cloud-provider 服务提供的接口如下:

  1. @RestController
  2. @RequestMapping("/user-service")
  3. public class UserController {
  4. @Value("${spring.application.name}")
  5. private String applicationName;
  6. @Value("${server.port}")
  7. private String post;
  8. @GetMapping("/users/{name}")
  9. public String users(@PathVariable("name") String name) {
  10. return String.format("hello %s,from server %s,post: %s", name, applicationName, post);
  11. }
  12. }

启动 spring-cloud-eureka-server(注册中心)、spring-cloud-provider 和 spring-cloud-gateway

访问 http://localhost:8074/user-service/users/zhangsan,输出如下:

image

配置默认路由

Spring Cloud Gateway 提供了类似于 Zuul 那种为所有服务转发的功能

配置如下:

  1. spring.application.name=spring-cloud-gateway
  2. server.port=8074
  3. ########### 配置注册中心 ###########
  4. # 获取注册实例列表
  5. eureka.client.fetch-registry=true
  6. # 注册到 Eureka 的注册中心
  7. eureka.client.register-with-eureka=true
  8. # 配置注册中心地址
  9. eureka.client.service-url.defaultZone=http://localhost:8070/eureka
  10. # 配置默认路由
  11. spring.cloud.gateway.discovery.locator.enabled=true

开启之后我们需要通过地址去访问服务了,格式如下:

http://网关地址/服务名称(大写)/**

例如:http://localhost:8074/SPRING-CLOUD-PROVIDER/user-service/users/zhangsan

结果如图:

image

 

服务名称也可以配置成小写的格式,只需要增加一条配置即可:

  1. # 配置服务名称小写
  2. spring.cloud.gateway.discovery.locator.lowerCaseServiceId=true

路由断言工厂

官方提供了很多个常用的路由断言工厂,如图所示:

image

 

1. Path 路由断言工厂

Path 路由断言工厂接收一个参数,根据 Path 定义好的规则来判断访问的 URI 是否匹配

固定的 Path

# spring.cloud.gateway.routes[0].predicates[0]=Path=/users/zhangsan

带有前缀的 Path

# spring.cloud.gateway.routes[0].predicates[0]=Path=/users/{segment}

使用通配符的 Path

# spring.cloud.gateway.routes[0].predicates[0]=Path=/users/**

2. Query 路由断言工厂

Query 路由断言工厂接收两个参数,一个必需的参数和一个可选的正则表达式

# spring.cloud.gateway.routes[0].predicates[0]=Query=foo, ba.

如果请求包含 foo 查询参数,则此路由将匹配。bar 和 baz 也会匹配,因为第二个参数是正则表达式(注意 ba 后面有个 .)

测试链接:

http://localhost:8074/users/zhangsan?foo=ba

http://localhost:8074/users/zhangsan?foo=bar

http://localhost:8074/users/zhangsan?foo=baz

3. Method 路由断言工厂

Method 路由断言工厂接收一个参数,即要匹配的 HTTP 方法。

# spring.cloud.gateway.routes[0].predicates[0]=Method=GET

4. Header 路由断言工厂

Header 路由断言工厂接收两个参数,分别是请求头名称和正则表达式。

# spring.cloud.gateway.routes[0].predicates[0]=Header=X-Request-Id, \d+

如果请求中带有请求头名为 x-request-id,其值与 \d+ 正则表达式匹配(值为一个或多个数字),则此路由匹配。

具体的可以看一下官方文档,写的很清楚。

https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.0.0.RELEASE/single/spring-cloud-gateway.html

自定义路由断言工厂

自定义路由断言工厂需要继承 AbstractRoutePredicateFactory 类,重写 apply 方法的逻辑。

在 apply 方法中可以通过 exchange.getRequest() 拿到 ServerHttpRequest 对象,从而可以获取到请求的参数、请求方式、请求头等信息。

apply 方法的参数是自定义的配置类,也就是静态内部类 Config,在使用的时候配置参数,就可以在 apply 方法中直接获取使用。

我们自己写一个 Query 路由断言工厂吧,名字就叫 MyQueryRoutePredicateFactory(命名需要以 RoutePredicateFactory 结尾)

代码如下:

  1. @Component
  2. public class MyQueryRoutePredicateFactory extends AbstractRoutePredicateFactory<MyQueryRoutePredicateFactory.Config> {
  3. public MyQueryRoutePredicateFactory() {
  4. super(Config.class);
  5. }
  6. /**
  7. * 返回有关 args 数量和快捷方式分析顺序的提示。
  8. * <p>必须要重写这个方法,否则 Config 设置不了参数。
  9. */
  10. @Override
  11. public List<String> shortcutFieldOrder() {
  12. return Arrays.asList("param", "regexp");
  13. }
  14. /**
  15. * 自己实现 Query 路由断言工厂
  16. * <p>在这个方法里写逻辑
  17. */
  18. @Override
  19. public Predicate<ServerWebExchange> apply(Config config) {
  20. return exchange -> {
  21. System.out.println(config.toString());
  22. if (config.getRegexp() == null || "".equals(config.getRegexp())) {
  23. return exchange.getRequest().getQueryParams().containsKey(config.getParam());
  24. }
  25. List<String> values = exchange.getRequest().getQueryParams().get(config.getParam());
  26. if (values == null) {
  27. return false;
  28. }
  29. for (String value : values) {
  30. if (value != null && value.matches(config.getRegexp())) {
  31. return true;
  32. }
  33. }
  34. return false;
  35. };
  36. }
  37. /**
  38. * Config 静态内部类用来保存配置信息
  39. * @author miansen.wang
  40. * @date 2019-11-14
  41. */
  42. public static class Config {
  43. // 请求参数名
  44. private String param;
  45. // 请求参数值的正则
  46. private String regexp;
  47. public String getParam() {
  48. return param;
  49. }
  50. public void setParam(String param) {
  51. this.param = param;
  52. }
  53. public String getRegexp() {
  54. return regexp;
  55. }
  56. public void setRegexp(String regexp) {
  57. this.regexp = regexp;
  58. }
  59. @Override
  60. public String toString() {
  61. return "Config {param=" + param + ", regexp=" + regexp + "}";
  62. }
  63. }
  64. }

在配置文件中使用

spring.cloud.gateway.routes[0].predicates[0]=MyQuery=foo, ba.

重启服务,在 apply 方法处断点,访问 http://localhost:8074/user-service/users/zhangsan?foo=bar 进入到 apply 方法,说明我们自定义路由断言工厂起作用了。

过滤器工厂

Spring Cloud Gateway 根据作用范围划分为 GatewayFilter 和 GlobalFilter,二者区别如下:

  • GatewayFilter:需要通过 spring.cloud.routes.filters 配置在具体路由下,只作用在当前路由上
  • GlobalFilter:全局过滤器,不需要在配置文件中配置,作用在所有的路由上

官方提供了很多 GatewayFilter 和 GlobalFilter,可以在这里查看: https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.0.0.RELEASE/single/spring-cloud-gateway.html

GatewayFilter

先看看官方提供的 GatewayFilter

image

 

下面我们写一个例子,这个例子需要做到在请求到达服务之前添加一个请求头,服务响应之后添加一个响应头。

由图可知,添加请求头的 GatewayFilter 为 AddRequestHeaderGatewayFilterFactory(约定写成 AddRequestHeader)

添加响应头的 GatewayFilter 为 AddResponseHeaderGatewayFilterFactory(约定写成 AddResponseHeader)

所以,配置文件可以这样配置

  1. spring.application.name=spring-cloud-gateway
  2. server.port=8074
  3. ########### 通过配置的形式配置路由 ###########
  4. # 自定义的路由 ID,保持唯一
  5. spring.cloud.gateway.routes[0].id=my-gateway
  6. # 目标服务地址
  7. spring.cloud.gateway.routes[0].uri=http://httpbin.org
  8. # 路由条件
  9. spring.cloud.gateway.routes[0].predicates[0]=Path=/get
  10. # GatewayFilter(添加请求头)
  11. spring.cloud.gateway.routes[0].filters[0]=AddRequestHeader=X-Request-Foo, Bar
  12. # GatewayFilter(添加响应头)
  13. spring.cloud.gateway.routes[0].filters[1]=AddResponseHeader=X-Response-Foo, Bar

也可以通过代码配置

  1. /**
  2. * 通过代码的形式配置路由
  3. * @param builder
  4. * @return
  5. */
  6. @Bean
  7. public RouteLocator myRoutes(RouteLocatorBuilder builder) {
  8. return builder.routes()
  9. .route(
  10. p -> p.path("/get")
  11. .filters(f -> f.addRequestHeader("X-Request-Foo", "Bar").addResponseHeader("X-Response-Foo", "Bar"))
  12. .uri("http://httpbin.org")
  13. )
  14. .build();
  15. }

启动服务,访问 http://localhost:8074/get

得到以下响应信息:

  1. {
  2. "args": {},
  3. "headers": {
  4. "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3",
  5. "Accept-Encoding": "gzip, deflate, br",
  6. "Accept-Language": "zh-CN,zh;q=0.9",
  7. "Cache-Control": "max-age=0",
  8. "Cookie": "Webstorm-2f8f75da=e0c5ee46-9276-490c-b32b-d5dc1483ca18; acw_tc=2760828015735472938194099e940a3c3ebc07316bcb1096abc6fefde61bf8; BD_UPN=12314353; BD_HOME=0",
  9. "Forwarded": "proto=http;host=\"localhost:8074\";for=\"0:0:0:0:0:0:0:1:55395\"",
  10. "Host": "httpbin.org",
  11. "Sec-Fetch-Mode": "navigate",
  12. "Sec-Fetch-Site": "none",
  13. "Sec-Fetch-User": "?1",
  14. "Upgrade-Insecure-Requests": "1",
  15. "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.97 Safari/537.36",
  16. "X-Forwarded-Host": "localhost:8074",
  17. "X-Request-Foo": "Bar"
  18. },
  19. "origin": "0:0:0:0:0:0:0:1, 183.14.135.71, ::1",
  20. "url": "https://localhost:8074/get"
  21. }

注意看添加了一个请求头:”X-Request-Foo”: “Bar”

响应头也添加了

image

 

通过这个例子可以知道,当 Gateway Handler Mapping 确定请求与哪个路由匹配之后,会将请求发送至 Gateway web handler 进行 GatewayFilter 拦截。 GatewayFilter 分为请求 filter 和 响应 filter,前者可以对请求信息过滤,后者可以对响应信息过滤。

自定义 GatewayFilter

自定义 Spring Cloud Gateway 过滤器工厂需要继承 AbstractGatewayFilterFactory 类,重写 apply 方法的逻辑。命名需要以 GatewayFilterFactory 结尾。

下面我们自己实现一个 AddRequestHeaderGatewayFilterFactory,就命名为 MyAddRequestHeaderGatewayFilterFactory 吧。在使用的时候 MyAddRequestHeader 就是这个过滤器工厂的名称。

代码如下:

  1. @Component
  2. public class MyAddRequestHeaderGatewayFilterFactory
  3. extends AbstractGatewayFilterFactory<MyAddRequestHeaderGatewayFilterFactory.Config> {
  4. // 必须要显示调用父类的构造方法,指定 configClass,否则报类型转换异常
  5. public MyAddRequestHeaderGatewayFilterFactory() {
  6. super(Config.class);
  7. }
  8. // 指定 args 数量和顺序,否则无法初始化 Config
  9. @Override
  10. public List<String> shortcutFieldOrder() {
  11. return Arrays.asList("headerName", "headerValue");
  12. }
  13. // 在这里面写逻辑
  14. @Override
  15. public GatewayFilter apply(Config config) {
  16. return (exchange, chain) -> {
  17. ServerHttpRequest request = exchange.getRequest().mutate()
  18. .header(config.getHeaderName(), config.getHeaderValue()).build();
  19. return chain.filter(exchange.mutate().request(request).build());
  20. };
  21. }
  22. // 配置类
  23. public static class Config {
  24. // 请求头的名称
  25. private String headerName;
  26. // 请求头的值
  27. private String headerValue;
  28. public String getHeaderName() {
  29. return headerName;
  30. }
  31. public void setHeaderName(String headerName) {
  32. this.headerName = headerName;
  33. }
  34. public String getHeaderValue() {
  35. return headerValue;
  36. }
  37. public void setHeaderValue(String headerValue) {
  38. this.headerValue = headerValue;
  39. }
  40. }
  41. }

配置文件:

spring.cloud.gateway.routes[0].filters[0]=MyAddRequestHeader=X-Request-Foo, Bar

如果你不想通过配置文件的形式使用过滤器工厂,那么你可以在 myRoutes 方法中直接实现 GatewayFilter 接口,然后写上你的过滤逻辑。

代码如下:

  1. @Bean
  2. public RouteLocator myRoutes(RouteLocatorBuilder builder) {
  3. return builder.routes().route(p -> p.path("/get").filters(f -> f.filter((exchange, chain) -> {
  4. ServerHttpRequest request = exchange.getRequest().mutate().header("X-Request-Foo", "Bar").build();
  5. return chain.filter(exchange.mutate().request(request).build());
  6. }).addResponseHeader("X-Response-Foo", "Bar")).uri("http://httpbin.org")).build();
  7. }

这段代码调用链比较复杂,但是只要自己动手敲一遍就能理清楚其中的逻辑。

启动服务,访问 http://localhost:8074/get,如果能看到添加了一个名为 X-Request-Foo,值为 Bar 的请求头,就说明我们自定义的过滤器工厂生效了。

GlobalFilter

自定义一个 GlobalFilter,实现对 IP 地址的限制。

代码如下:

  1. @Component
  2. public class IPCheckFilter implements GlobalFilter, Ordered {
  3. @Override
  4. public int getOrder() {
  5. return 0;
  6. }
  7. @Override
  8. public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
  9. HttpHeaders headers = exchange.getRequest().getHeaders();
  10. InetSocketAddress host = headers.getHost();
  11. String hostName = host.getHostName();
  12. // 此处的 IP 地址是写死的,实际中需要采取配置的方式
  13. if ("localhost".equals(hostName)) {
  14. ServerHttpResponse response = exchange.getResponse();
  15. byte[] datas = "{\"code\": 401,\"message\": \"非法请求\"}".getBytes(StandardCharsets.UTF_8);
  16. DataBuffer buffer = response.bufferFactory().wrap(datas);
  17. response.setStatusCode(HttpStatus.UNAUTHORIZED);
  18. response.getHeaders().add("Content-Type", "application/json;charset=UTF-8");
  19. return response.writeWith(Mono.just(buffer));
  20. }
  21. return chain.filter(exchange);
  22. }
  23. }

源码下载:https://github.com/miansen/SpringCloud-Learn

原文链接:https://miansen.wang/2019/10/21/spring-cloud-gateway/

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/544977
推荐阅读
相关标签
  

闽ICP备14008679号