Spring Cloud Alibaba:Gateway之路由过滤器工厂(三)

Spring Cloud Alibaba:Gateway之路由过滤器工厂(三),第1张

Spring Cloud Alibaba:Gateway之路由过滤器工厂(三)

前两篇博客已经介绍了十一种路由过滤器工厂:

  • Spring Cloud Alibaba:Gateway之路由过滤器工厂(一)
  • Spring Cloud Alibaba:Gateway之路由过滤器工厂(二)

随着Gateway的不断更新,Gateway提供的路由过滤器工厂种类一直在增加,目前已经有三十多种了(不同版本可能不一样)。

搭建工程

一个父module和两个子module(server module提供服务,gateway module实现网关)。

父module的pom.xml:



    4.0.0

    com.kaven
    alibaba
    pom
    1.0-SNAPSHOT

    Spring Cloud Alibaba
    
        gateway
        server
    

    
        8
        8
        Hoxton.SR9
        2.2.6.RELEASE
    

    
        org.springframework.boot
        spring-boot-starter-parent
        2.3.2.RELEASE
    

    
        
            
                org.springframework.cloud
                spring-cloud-dependencies
                ${spring-cloud-version}
                pom
                import
            
            
                com.alibaba.cloud
                spring-cloud-alibaba-dependencies
                ${spring-cloud-alibaba-version}
                pom
                import
            
        
    

server module

pom.xml:



    
        com.kaven
        alibaba
        1.0-SNAPSHOT
    
    4.0.0

    server

    
        8
        8
    

    
        
            org.springframework.boot
            spring-boot-starter-web
        
    

application.yml:

server:
  port: 8085

接口定义:

package com.kaven.alibaba.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MessageController {

    @GetMapping("/message")
    public String getMessage() {
        return "hello kaven, this is spring cloud alibaba";
    }
}

启动类:

package com.kaven.alibaba;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class);
    }
}
gateway module

pom.xml:



    
        com.kaven
        alibaba
        1.0-SNAPSHOT
    
    4.0.0

    gateway

    
        8
        8
    

    
        
            org.springframework.cloud
            spring-cloud-starter-gateway
        
    

application.yml:

server:
  port: 8086

spring:
  cloud:
    gateway:
      routes:
        - id: server
          uri: http://localhost:8085
          predicates:
            - Path=/message

启动类:

package com.kaven.alibaba;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class);
    }
}
StripPrefix

StripPrefix路由过滤器工厂接受一个参数(parts),parts参数表示在将请求发送到下游服务之前要从请求中剥离的路径数量。

源码相关部分:

增加路由过滤器的配置(全写):

          predicates:
            - Path=/**
          filters:
            - name: StripPrefix
              args:
                parts: 2

或(简写):

          predicates:
            - Path=/**
          filters:
            - StripPrefix=2

访问http://127.0.0.1:8086/1/2/message,会被路由到http://127.0.0.1:8085/message。

Retry

Retry路由过滤器工厂接受以下参数:

  • retries:应该尝试的重试次数。

  • series:要重试的一系列状态码,用HttpStatus.Series表示。

  • statuses:应该重试的HTTP状态码,用HttpStatus表示。

  • methods:应该重试的HTTP方法,用HttpMethod表示。

  • exceptions:应该重试的抛出异常列表。

  • backoff:为重试配置的指数退避,在退避间隔firstBackoff * (factor ^ n)之后执行重试,其中n是重试次数减一(即n的取值范围为[0,retries-1])。如果maxBackoff已配置,则应用的最大退避限制为maxBackoff。如果basedOnPreviousValue为真,则使用prevBackoff * factor计算退避(也不能超过maxBackoff)。

修改路由过滤器的配置:

          predicates:
            - Path=/message
          filters:
            - name: Retry
              args:
                retries: 5
                statuses: Internal_Server_Error
                methods: GET,POST
                backoff:
                  firstBackoff: 300ms
                  maxBackoff: 1300ms
                  factor: 2
                  basedOnPreviousValue: false

Internal_Server_Error对应500状态码(详见HttpStatus类)。

修改接口:

    AtomicLong time = new AtomicLong(0);

    @GetMapping("/message")
    public String getMessage(HttpServletResponse response) {
        if(time.get() != 0) {
            System.out.println(System.currentTimeMillis() - time.get());
        }
        time.set(System.currentTimeMillis());
        response.setStatus(500);
        return "hello kaven, this is spring cloud alibaba";
    }

访问http://127.0.0.1:8086/message,在server module中会输出如下所示的重试间隔毫秒数(接近网关设置的重试间隔时间):

327
623
1216
1308
1307

是符合预期的。

HTTP系列状态码的枚举类:

	public enum Series {
		INFORMATIONAL(1),
		SUCCESSFUL(2),
		REDIRECTION(3),
		CLIENT_ERROR(4),
		SERVER_ERROR(5);
		...
    }

如下所示的配置可以对5XX系列状态码的响应进行请求重试。

          predicates:
            - Path=/message
          filters:
            - name: Retry
              args:
                retries: 5
                series: SERVER_ERROR   # 默认值
                backoff:
                  firstBackoff: 300ms
                  maxBackoff: 1300ms
                  factor: 2
                  basedOnPreviousValue: true

访问http://127.0.0.1:8086/message,结果是类似的。

修改路由过滤器的配置:

          predicates:
            - Path=/message
          filters:
            - name: Retry
              args:
                retries: 5
                exceptions: java.util.concurrent.TimeoutException,java.lang.ClassNotFoundException
                backoff:
                  firstBackoff: 300ms
                  maxBackoff: 1300ms
                  factor: 2
                  basedOnPreviousValue: true

修改接口:

    AtomicLong time = new AtomicLong(0);

    @GetMapping("/message")
    public String getMessage(@RequestParam("isThrow") boolean isThrow) throws ClassNotFoundException {
        if(time.get() != 0) {
            System.out.println(System.currentTimeMillis() - time.get());
        }
        time.set(System.currentTimeMillis());
        if(isThrow) throw new ClassNotFoundException();
        return "hello kaven, this is spring cloud alibaba";
    }

访问http://127.0.0.1:8086/message,在server module中的输出如下图所示:


也是符合预期的。

RequestSize

当请求大小大于允许的限制时,RequestSize路由过滤器工厂可以限制请求到达下游服务。过滤器接受一个maxSize参数,maxSize是一个DataSize类型,所以值可以被定义为一个数字,后跟一个可选的DataUnit后缀,例如KB或MB,字节的默认值为B。它是以字节为单位定义的请求的允许大小限制。

    BYTES("B", DataSize.ofBytes(1L)),
    KILOBYTES("KB", DataSize.ofKilobytes(1L)),
    MEGABYTES("MB", DataSize.ofMegabytes(1L)),
    GIGABYTES("GB", DataSize.ofGigabytes(1L)),
    TERABYTES("TB", DataSize.ofTerabytes(1L));

源码相关部分:

修改路由过滤器的配置:

          predicates:
            - Path=/message
          filters:
            - name: RequestSize
              args:
                maxSize: 5KB

请求负载限制为5KB。

修改接口:

    @GetMapping("/message")
    public String getMessage() {
        return "hello kaven, this is spring cloud alibaba";
    }

访问http://127.0.0.1:8086/message成功(请求负载没有超过限制)。

访问http://127.0.0.1:8086/message失败(请求负载超过限制)。

PAYLOAD_TOO_LARGE和REQUEST_ENTITY_TOO_LARGE(即将被抛弃)表示同一个状态码,而网关响应的是PAYLOAD_TOO_LARGE(上面的源码截图),Postman显示的是REQUEST_ENTITY_TOO_LARGE,可能是博主的Postman版本的原因。


响应头里面的错误信息显示请求负载限制是5.1KB,很显然是换算不一致的问题(过滤器使用1000,而DataSize使用1024):

	private static String getReadableByteCount(long bytes) {
		int unit = 1000;
		if (bytes < unit) {
			return bytes + " B";
		}
		int exp = (int) (Math.log(bytes) / Math.log(unit));
		String pre = Character.toString(PREFIX.charAt(exp - 1));
		return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre);
	}
    private static final long BYTES_PER_KB = 1024L;
    private static final long BYTES_PER_MB = 1048576L;
    private static final long BYTES_PER_GB = 1073741824L;
    private static final long BYTES_PER_TB = 1099511627776L;
SetRequestHostHeader

在某些情况下,可能需要覆盖请求的主机头。在这种情况下,SetRequestHostHeader路由过滤器工厂可以用指定的值替换现有的主机头。过滤器接受一个host参数。

源码相关部分:

由源码可知,请求没有主机头时,也会给请求添加指定值的主机头。

修改路由过滤器的配置:

          predicates:
            - Path=/message
          filters:
            - name: SetRequestHostHeader
              args:
                host: kaven.top

修改接口:

    @GetMapping("/message")
    public String getMessage(HttpServletRequest httpServletRequest) {
        StringBuilder result = new StringBuilder("hello kaven, this is spring cloud alibaban");
        result.append(getKeyAndValue(httpServletRequest));
        return result.toString();
    }

    // 获取header中key和value组成的StringBuilder
    private StringBuilder getKeyAndValue(HttpServletRequest httpServletRequest) {
        StringBuilder result = new StringBuilder();
        Enumeration headerNames = httpServletRequest.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String key = headerNames.nextElement();
            String value = httpServletRequest.getHeader(key);
            result.append(key).append(" : ").append(value).append("n");
        }
        return result;
    }

访问http://127.0.0.1:8086/message,请求的主机头就会被修改成指定的值。

请求没有主机头时,也会给请求添加指定值的主机头。

注释掉路由过滤器,请求的主机头是本地的套接字。

ModifyRequestBody

使用ModifyRequestBody过滤器在网关向下游服务发送请求Body之前修改请求Body。此过滤器目前只能通过使用Java DSL进行配置。

在gateway module中添加一个bean:

    @Bean
    public RouteLocator routes(RouteLocatorBuilder builder) {
        String uri = "http://127.0.0.1:8085";
        return builder.routes()
                .route("server", r -> r.path("/message")
                        .filters(f -> f.modifyRequestBody(String.class, String.class, MediaType.APPLICATION_JSON_VALUE,
                                        (exchange, s) -> Mono.just(s.toUpperCase())))
                        .uri(uri))
                .build();
    }

其实就是用代码的形式来定义路由的处理流程,因此,之前介绍过的路由过滤器都可以通过这种形式添加到路由中。server是路由的id,path方法是给路由添加Path断言,modifyRequestBody方法是给路由添加ModifyRequestBody路由过滤器,这些lambda表达式应该容易看懂,modifyRequestBody方法的第一个参数表示请求Body输入时需要转换的类型,第二个参数表示请求Body输出时需要转换的类型,而第三个参数就是请求发送到下游服务时的新Content-Type 请求头,最后一个参数就是用于修改请求Body的方法(这里就是将字符串全部变成大写),uri方法是给路由指定路由匹配成功后的请求转发路径。

  • Spring Cloud Alibaba:Gateway网关 & 路由断言工厂

修改接口:

    @GetMapping("/message")
    public String getMessage(@RequestBody String message) {
        return message + "nhello kaven, this is spring cloud alibaban";
    }

ModifyResponseBody

使用ModifyResponseBody过滤器在响应Body发送回客户端之前对其进行修改。此过滤器目前也只能通过使用Java DSL进行配置。

在gateway module中添加一个bean(和之前那个bean类似):

    @Bean
    public RouteLocator routes(RouteLocatorBuilder builder) {
        String uri = "http://127.0.0.1:8085";
        return builder.routes()
                .route("server", r -> r.path("/message")
                        .filters(f -> f.modifyResponseBody(String.class, String.class, MediaType.APPLICATION_JSON_VALUE,
                                        (exchange, s) -> Mono.just(s.toUpperCase())))
                        .uri(uri))
                .build();
    }

通过modifyResponseBody方法给路由添加ModifyResponseBody过滤器。


其他的路由过滤器工厂在以后的博客中介绍,如果博主有说错的地方或者大家有不同的见解,欢迎大家评论补充。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5680992.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存