IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 创建自定义 Spring Cloud Gateway 过滤器 - spring.io -> 正文阅读

[Java知识库]创建自定义 Spring Cloud Gateway 过滤器 - spring.io

在本文中,我们着眼于为 Spring Cloud Gateway 编写自定义扩展。在开始之前,让我们回顾一下 Spring Cloud Gateway 的工作原理:

?

  1. 首先,客户端向网关发出网络请求
  2. 网关定义了许多路由,每个路由都有谓词来匹配请求和路由。例如,您可以匹配 URL 的路径段或请求的 HTTP 方法。
  3. 一旦匹配,网关对应用于路由的每个过滤器执行预请求逻辑。例如,您可能希望将查询参数添加到您的请求中
  4. 代理过滤器将请求路由到代理服务
  5. 服务执行并返回响应
  6. 网关接收响应并在返回响应之前对每个过滤器执行请求后逻辑。例如,您可以在返回客户端之前删除不需要的响应标头。

我们的扩展将对请求正文进行哈希处理,并将该值添加为名为 的请求标头X-Hash。这对应于上图中的步骤 3。注意:当我们读取请求正文时,网关将受到内存限制。

首先,我们在?start.spring.io?中创建一个具有 Gateway 依赖项的项目。在此示例中,我们将在 Java 中使用带有 JDK 17 和 Spring Boot 2.7.3 的 Gradle 项目。下载、解压缩并在您喜欢的 IDE 中打开项目并运行它,以确保您已为本地开发做好准备。

接下来让我们创建 GatewayFilter Factory,它是一个限定于特定路由的过滤器,它允许我们以某种方式修改传入的 HTTP 请求或传出的 HTTP 响应。在我们的例子中,我们将使用附加标头修改传入的 HTTP 请求:

<b>package</b> com.example.demo;

<b>import</b> java.security.MessageDigest;
<b>import</b> java.security.NoSuchAlgorithmException;
<b>import</b> java.util.Collections;
<b>import</b> java.util.List;

<b>import</b> org.bouncycastle.util.encoders.Hex;
<b>import</b> reactor.core.publisher.Mono;

<b>import</b> org.springframework.cloud.gateway.filter.GatewayFilter;
<b>import</b> org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
<b>import</b> org.springframework.cloud.gateway.support.ServerWebExchangeUtils;
<b>import</b> org.springframework.http.codec.HttpMessageReader;
<b>import</b> org.springframework.http.server.reactive.ServerHttpRequest;
<b>import</b> org.springframework.stereotype.Component;
<b>import</b> org.springframework.util.Assert;
<b>import</b> org.springframework.web.reactive.function.server.HandlerStrategies;
<b>import</b> org.springframework.web.reactive.function.server.ServerRequest;

<b>import</b> <b>static</b> org.springframework.cloud.gateway.support.ServerWebExchangeUtils.CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR;

<font><i>/**
 * This filter hashes the request body, placing the value in the X-Hash header.
 * Note: This causes the gateway to be memory constrained.
 * Sample usage: RequestHashing=SHA-256
 */</i></font><font>
@Component
<b>public</b> <b>class</b> RequestHashingGatewayFilterFactory <b>extends</b>
        AbstractGatewayFilterFactory<RequestHashingGatewayFilterFactory.Config> {

    <b>private</b> <b>static</b> <b>final</b> String HASH_ATTR = </font><font>"hash"</font><font>;
    <b>private</b> <b>static</b> <b>final</b> String HASH_HEADER = </font><font>"X-Hash"</font><font>;
    <b>private</b> <b>final</b> List<HttpMessageReader<?>> messageReaders =
            HandlerStrategies.withDefaults().messageReaders();

    <b>public</b> RequestHashingGatewayFilterFactory() {
        <b>super</b>(Config.<b>class</b>);
    }

    @Override
    <b>public</b> GatewayFilter apply(Config config) {
        MessageDigest digest = config.getMessageDigest();
        <b>return</b> (exchange, chain) -> ServerWebExchangeUtils
                .cacheRequestBodyAndRequest(exchange, (httpRequest) -> ServerRequest
                    .create(exchange.mutate().request(httpRequest).build(),
                            messageReaders)
                    .bodyToMono(String.<b>class</b>)
                    .doOnNext(requestPayload -> exchange
                            .getAttributes()
                            .put(HASH_ATTR, computeHash(digest, requestPayload)))
                    .then(Mono.defer(() -> {
                        ServerHttpRequest cachedRequest = exchange.getAttribute(
                                CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR);
                        Assert.notNull(cachedRequest, 
                                </font><font>"cache request shouldn't be null"</font><font>);
                        exchange.getAttributes()
                                .remove(CACHED_SERVER_HTTP_REQUEST_DECORATOR_ATTR);

                        String hash = exchange.getAttribute(HASH_ATTR);
                        cachedRequest = cachedRequest.mutate()
                                .header(HASH_HEADER, hash)
                                .build();
                        <b>return</b> chain.filter(exchange.mutate()
                                .request(cachedRequest)
                                .build());
                    })));
    }

    @Override
    <b>public</b> List<String> shortcutFieldOrder() {
        <b>return</b> Collections.singletonList(</font><font>"algorithm"</font><font>);
    }

    <b>private</b> String computeHash(MessageDigest messageDigest, String requestPayload) {
        <b>return</b> Hex.toHexString(messageDigest.digest(requestPayload.getBytes()));
    }

    <b>static</b> <b>class</b> Config {

        <b>private</b> MessageDigest messageDigest;

        <b>public</b> MessageDigest getMessageDigest() {
            <b>return</b> messageDigest;
        }

        <b>public</b> <b>void</b> setAlgorithm(String algorithm) throws NoSuchAlgorithmException {
            messageDigest = MessageDigest.getInstance(algorithm);
        }
    }
}
</font>

让我们更详细地看一下代码:

  • 我们为该类添加了@Component注解。Spring Cloud Gateway需要能够检测到这个类,以便使用它。另外,我们也可以用@Bean定义一个实例。
  • 在我们的类名中,我们使用GatewayFilterFactory作为后缀。在application.yaml中添加这个过滤器时,我们不包括后缀,只包括RequestHashing。这是一个Spring Cloud Gateway过滤器的命名惯例。
  • 我们的类还扩展了AbstractGatewayFilterFactory,与所有其他Spring Cloud Gateway过滤器类似。我们还指定了一个类来配置我们的过滤器,一个名为Config的嵌套静态类有助于保持简单。这个配置类允许我们设置使用哪种散列算法。
  • 重载的apply方法是所有工作发生的地方。在参数中,我们得到了一个配置类的实例,在那里我们可以访问MessageDigest实例进行散列。接下来,我们看到(exchange, chain),一个GatewayFilter接口类的lambda被返回。Exchange是ServerWebExchange的一个实例,为Gateway过滤器提供对HTTP请求和响应的访问。对于我们的案例,我们想修改HTTP请求,这就要求我们对交换进行变异。
  • 我们需要读取请求体来产生哈希值,然而,由于请求体被存储在一个字节缓冲区中,它在过滤器中只能被读取一次。通过使用ServerWebExchangeUtils,我们把请求作为交换中的一个属性进行缓存。属性提供了一种在过滤器链中共享特定请求数据的方式。我们也将存储请求主体的计算哈希值。
  • 我们使用交换的属性来获取缓存的请求和计算的哈希值。然后我们通过添加哈希头来突变交换,最后将其发送到链上的下一个过滤器。
  • shortcutFieldOrder方法有助于将参数的数量和顺序映射到过滤器中。该算法字符串与配置类中的setter相匹配。

为了测试代码,我们将使用 WireMock。将依赖项添加到您的build.gradle文件中:

testImplementation 'com.github.tomakehurst:wiremock:2.27.2'

在这里,我们有一个测试检查头的存在和价值,如果没有请求正 ,文另一个测试检查头是否不存在。

<b>package</b> com.example.demo;

<b>import</b> java.security.MessageDigest;
<b>import</b> java.security.NoSuchAlgorithmException;

<b>import</b> com.github.tomakehurst.wiremock.WireMockServer;
<b>import</b> com.github.tomakehurst.wiremock.client.WireMock;
<b>import</b> com.github.tomakehurst.wiremock.core.WireMockConfiguration;
<b>import</b> org.bouncycastle.jcajce.provider.digest.SHA512;
<b>import</b> org.bouncycastle.util.encoders.Hex;
<b>import</b> org.junit.jupiter.api.AfterEach;
<b>import</b> org.junit.jupiter.api.Test;

<b>import</b> org.springframework.beans.factory.annotation.Autowired;
<b>import</b> org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
<b>import</b> org.springframework.boot.test.context.SpringBootTest;
<b>import</b> org.springframework.boot.test.context.TestConfiguration;
<b>import</b> org.springframework.cloud.gateway.filter.GatewayFilter;
<b>import</b> org.springframework.cloud.gateway.route.RouteLocator;
<b>import</b> org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
<b>import</b> org.springframework.context.annotation.Bean;
<b>import</b> org.springframework.http.HttpStatus;
<b>import</b> org.springframework.test.web.reactive.server.WebTestClient;

<b>import</b> <b>static</b> com.example.demo.RequestHashingGatewayFilterFactory.*;
<b>import</b> <b>static</b> com.example.demo.RequestHashingGatewayFilterFactoryTest.*;
<b>import</b> <b>static</b> com.github.tomakehurst.wiremock.client.WireMock.equalTo;
<b>import</b> <b>static</b> com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor;
<b>import</b> <b>static</b> com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
<b>import</b> <b>static</b> com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
<b>import</b> <b>static</b> org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;

@SpringBootTest(
        webEnvironment = RANDOM_PORT,
        <b>class</b>es = RequestHashingFilterTestConfig.<b>class</b>)
@AutoConfigureWebTestClient
<b>class</b> RequestHashingGatewayFilterFactoryTest {

    @TestConfiguration
    <b>static</b> <b>class</b> RequestHashingFilterTestConfig {

        @Autowired
        RequestHashingGatewayFilterFactory requestHashingGatewayFilter;

        @Bean(destroyMethod = <font>"stop"</font><font>)
        WireMockServer wireMockServer() {
            WireMockConfiguration options = wireMockConfig().dynamicPort();
            WireMockServer wireMock = <b>new</b> WireMockServer(options);
            wireMock.start();
            <b>return</b> wireMock;
        }

        @Bean
        RouteLocator testRoutes(RouteLocatorBuilder builder, WireMockServer wireMock)
                throws NoSuchAlgorithmException {
            Config config = <b>new</b> Config();
            config.setAlgorithm(</font><font>"SHA-512"</font><font>);

            GatewayFilter gatewayFilter = requestHashingGatewayFilter.apply(config);
            <b>return</b> builder
                    .routes()
                    .route(predicateSpec -> predicateSpec
                            .path(</font><font>"/post"</font><font>)
                            .filters(spec -> spec.filter(gatewayFilter))
                            .uri(wireMock.baseUrl()))
                    .build();
        }
    }

    @Autowired
    WebTestClient webTestClient;

    @Autowired
    WireMockServer wireMockServer;

    @AfterEach
    <b>void</b> afterEach() {
        wireMockServer.resetAll();
    }

    @Test
    <b>void</b> shouldAddHeaderWithComputedHash() {
        MessageDigest messageDigest = <b>new</b> SHA512.Digest();
        String body = </font><font>"hello world"</font><font>;
        String expectedHash = Hex.toHexString(messageDigest.digest(body.getBytes()));

        wireMockServer.stubFor(WireMock.post(</font><font>"/post"</font><font>).willReturn(WireMock.ok()));

        webTestClient.post().uri(</font><font>"/post"</font><font>)
                .bodyValue(body)
                .exchange()
                .expectStatus()
                .isEqualTo(HttpStatus.OK);

        wireMockServer.verify(postRequestedFor(urlEqualTo(</font><font>"/post"</font><font>))
                .withHeader(</font><font>"X-Hash"</font><font>, equalTo(expectedHash)));
    }

    @Test
    <b>void</b> shouldNotAddHeaderIfNoBody() {
        wireMockServer.stubFor(WireMock.post(</font><font>"/post"</font><font>).willReturn(WireMock.ok()));

        webTestClient.post().uri(</font><font>"/post"</font><font>)
                .exchange()
                .expectStatus()
                .isEqualTo(HttpStatus.OK);

        wireMockServer.verify(postRequestedFor(urlEqualTo(</font><font>"/post"</font><font>))
                .withoutHeader(</font><font>"X-Hash"</font><font>));
    }
}
</font>

为了在我们的网关中使用该过滤器,我们在application.yaml的路由中添加RequestHashing过滤器,使用SHA-256作为算法。

spring:
  cloud:
    gateway:
      routes:
        - id: demo
          uri: https:<font><i>//httpbin.org</i></font><font>
          predicates:
            - Path=/post</font><font><i>/**
          filters:
            - RequestHashing=SHA-256
</i></font>

我们使用https://httpbin.org,因为它在其返回的响应中显示了我们的请求头信息。运行应用程序,并进行curl请求以查看结果。

$> curl --request POST 'http://localhost:8080/post' \
--header 'Content-Type: application/json' \
--data-raw '{
    "data": {
        "hello": "world"
    }
}'

{
  ...
  "data": "{\n    \&#34data\": {\n        \"hello\": \"world\"\n    }\n}",
  "headers": {
        "Accept": "*/*",
        "Accept-Encoding": "gzip, deflate, br",
        "Content-Length": "48",
        "Content-Type": "application/json",
        "Forwarded": "proto=http;host=\"localhost:8080\"for=\"[0:0:0:0:0:0:0:1]:55647\"",
        "Host": "httpbin.org",
        "User-Agent": "PostmanRuntime/7.29.0",
        "X-Forwarded-Host": "localhost:8080",
        "X-Hash": "1bd93d38735501b5aec7a822f8bc8136d9f1f71a30c2020511bdd5df379772b8"
    },
  ...
}

综上所述,我们看到了如何为Spring Cloud Gateway编写一个自定义扩展。我们的过滤器读取了请求的主体,产生了一个哈希值,我们将其作为请求头添加。我们还使用WireMock为该过滤器编写了测试,以检查头的值。最后,我们用该过滤器运行了一个网关来验证结果。

如果你打算在Kubernetes集群上部署Spring Cloud Gateway,一定要查看VMware Spring Cloud Gateway for Kubernetes。除了支持开源的Spring Cloud Gateway过滤器和自定义过滤器(比如我们上面写的那个),它还配有更多的内置过滤器来处理你的请求和响应。Spring Cloud Gateway for Kubernetes代表API开发团队处理跨领域的问题,例如。单点登录(SSO)、访问控制、速率限制、弹性、安全等等。

?

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-09-04 00:56:31  更:2022-09-04 00:56:46 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 12:53:28-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码