使用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无关。同时路由配置中路径重写过滤器的顺序错误,导致重写逻辑未生效。
修复步骤:
显式指定HTTP客户端使用HTTP/1.1
修改http()handler的配置,强制使用HTTP/1.1与WireMock通信:http("http://localhost:8090") .httpClient(client -> client.version(HttpClient.Version.HTTP_1_1))调整路由配置顺序
路径重写过滤器必须在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(); }验证端点匹配
确保重写后的路径和WireMock的stub路径完全一致,避免因404引发间接异常。
修改后,Gateway会用HTTP/1.1和WireMock正常通信,路径重写逻辑也会正确生效,解决通道关闭和GOAWAY错误。
内容的提问来源于stack exchange,提问作者Powet
相关产品推荐
相关产品推荐

