You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Gateway MVC转发请求时遇ClosedChannelException问题求助

问题分析:Spring Cloud Gateway MVC转发请求到WireMock出现通道异常

场景说明

需要完成以下操作:

  • 向Gateway MVC服务器发送POST请求
  • 让网关将请求转发至同一主机上的WireMock模拟端点

但执行时出现ClosedChannelException异常,改为GET请求测试后又出现GOAWAY错误(端口异常)。已引入Spring Cloud Gateway MVC和WireMock依赖,相关代码及错误日志如下:

测试代码

import java.net.URI;

import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.junit5.WireMockTest;
import org.junit.jupiter.api.Test;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.function.HandlerFilterFunction;
import org.springframework.web.servlet.function.RouterFunction;
import org.springframework.web.servlet.function.ServerRequest;
import org.springframework.web.servlet.function.ServerResponse;
import org.springframework.web.util.UriComponentsBuilder;

import static org.springframework.cloud.gateway.server.mvc.handler.GatewayRouterFunctions.route;
import static org.springframework.cloud.gateway.server.mvc.handler.HandlerFunctions.http;
import static org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.method;
import static org.springframework.cloud.gateway.server.mvc.predicate.GatewayRequestPredicates.path;

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT,
        properties = "server.port=8080")
@WireMockTest(httpPort = 8090)
public class GenericTest {
    private static final String BODY = "body";

    @Autowired
    private RestTemplate restTemplate;

    @Test
    void test() {

        WireMock.stubFor(WireMock.post("/post-to-service")
                .willReturn(WireMock.ok().withBody("response-body")));

        ResponseEntity<String> response = restTemplate.postForEntity(
                "http://localhost:8080/post-to-gateway",
                BODY, String.class);
    }

    @Configuration
    @EnableAutoConfiguration
    static class ComplimentingConfig {
        @Bean
        public RestTemplate restTemplate() {
            return new RestTemplate();
        }

        @Bean
        public RouterFunction<ServerResponse> postRouterFunction() {
            return route("post-route")
                    .route(path("/post-to-gateway")
                                    .and(method(HttpMethod.POST)),
                            http("http://localhost:8090")
                    )
                    .filter(rewritePathHandlerFunction("/post-to-service"))
                    .build();
        }
        
        private HandlerFilterFunction<ServerResponse, ServerResponse> rewritePathHandlerFunction(String newPath) {
            return (request, next) -> {
                URI newUri = UriComponentsBuilder.fromUri(request.uri())
                        .replacePath(newPath)
                        .build()
                        .toUri();
                ServerRequest newRequest = ServerRequest.from(request)
                        .uri(newUri)
                        .build();
                return next.handle(newRequest);
            };
        }
    }
}

POST请求错误日志

ERROR 8036 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://localhost:8090/post-to-service": null] with root cause

java.nio.channels.ClosedChannelException: null

GET请求测试代码修改部分

WireMock.stubFor(WireMock.get("/post-to-service")
                .willReturn(WireMock.ok()));

        ResponseEntity<String> response = restTemplate.getForEntity(
                "http://localhost:8080/post-to-gateway",
                String.class);
// ...
            return route("post-route")
                    .route(path("/post-to-gateway"),
                            http("http://localhost:8090"))
                    .filter(rewritePathHandlerFunction("/post-to-service"))
                    .build();

GET请求错误日志

ERROR 10880 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.springframework.web.client.ResourceAccessException: I/O error on GET request for "http://localhost:8090/post-to-service": Can't get stream 1: java.io.IOException: /127.0.0.1:51610: GOAWAY received] with root cause

java.io.IOException: /127.0.0.1:51610: GOAWAY received
    at java.net.http/jdk.internal.net.http.Http2Connection.handleGoAway(Http2Connection.java:1008) ~[java.net.http:na]

问题根源与解决方案

你的问题出在Gateway MVC的http() handler默认使用HTTP/2协议,但WireMock默认只支持HTTP/1.1,协议不兼容导致通道关闭或GOAWAY错误,和Tomcat无关。同时路由配置中路径重写过滤器的顺序错误,导致重写逻辑未生效。

修复步骤:

  1. 显式指定HTTP客户端使用HTTP/1.1
    修改http() handler的配置,强制使用HTTP/1.1与WireMock通信:

    http("http://localhost:8090")
        .httpClient(client -> client.version(HttpClient.Version.HTTP_1_1))
    
  2. 调整路由配置顺序
    路径重写过滤器必须在http()转发逻辑之前执行,否则原始路径已经被用来发起请求,重写不会生效。修改后的路由Bean如下:

    @Bean
    public RouterFunction<ServerResponse> postRouterFunction() {
        return route("post-route")
                .route(path("/post-to-gateway").and(method(HttpMethod.POST)),
                        rewritePathHandlerFunction("/post-to-service")
                                .andThen(http("http://localhost:8090")
                                        .httpClient(client -> client.version(HttpClient.Version.HTTP_1_1)))
                )
                .build();
    }
    
  3. 验证端点匹配
    确保重写后的路径和WireMock的stub路径完全一致,避免因404引发间接异常。

修改后,Gateway会用HTTP/1.1和WireMock正常通信,路径重写逻辑也会正确生效,解决通道关闭和GOAWAY错误。

内容的提问来源于stack exchange,提问作者Powet

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 00:45:14