模式切换
UT005071: Undertow request failed HttpServerExchange
【问题】
在部署 Spring Boot 项目到服务器上时,启动成功但是日志中频繁出现如下错误:
bash
2024-09-24 11:32:15 [XNIO-1 I/O-8] ERROR io.undertow.request
- UT005071: Undertow request failed HttpServerExchange{ CONNECT google.com:443}
java.lang.IllegalArgumentException: UT000068: Servlet path match failed
at io.undertow.servlet.handlers.ServletPathMatchesData.getServletHandlerByPath(ServletPathMatchesData.java:83)
at io.undertow.servlet.handlers.ServletPathMatches.getServletHandlerByPath(ServletPathMatches.java:133)
at io.undertow.servlet.handlers.ServletInitialHandler.handleRequest(ServletInitialHandler.java:148)
at io.undertow.server.handlers.HttpContinueReadHandler.handleRequest(HttpContinueReadHandler.java:69)
at org.springframework.boot.web.embedded.undertow.DeploymentManagerHttpHandlerFactory$DeploymentManagerHandler.handleRequest(DeploymentManagerHttpHandlerFactory.java:74)
at io.undertow.server.Connectors.executeRootHandler(Connectors.java:393)
at io.undertow.server.protocol.http.HttpReadListener.handleEventWithNoRunningRequest(HttpReadListener.java:265)
at io.undertow.server.protocol.http.HttpReadListener.handleEvent(HttpReadListener.java:136)
at io.undertow.server.protocol.http.HttpReadListener.handleEvent(HttpReadListener.java:59)
at org.xnio.ChannelListeners.invokeChannelListener(ChannelListeners.java:92)
at org.xnio.conduits.ReadReadyHandler$ChannelListenerHandler.readReady(ReadReadyHandler.java:66)
at org.xnio.nio.NioSocketConduit.handleReady(NioSocketConduit.java:89)
at org.xnio.nio.WorkerThread.run(WorkerThread.java:591)
2024-09-24 11:53:00 [XNIO-1 I/O-7] ERROR io.undertow.request
- UT005071: Undertow request failed HttpServerExchange{ CONNECT example.com:443}
java.lang.IllegalArgumentException: UT000068: Servlet path match failed
at io.undertow.servlet.handlers.ServletPathMatchesData.getServletHandlerByPath(ServletPathMatchesData.java:83)
at io.undertow.servlet.handlers.ServletPathMatches.getServletHandlerByPath(ServletPathMatches.java:133)
at io.undertow.servlet.handlers.ServletInitialHandler.handleRequest(ServletInitialHandler.java:148)
at io.undertow.server.handlers.HttpContinueReadHandler.handleRequest(HttpContinueReadHandler.java:69)
at org.springframework.boot.web.embedded.undertow.DeploymentManagerHttpHandlerFactory$DeploymentManagerHandler.handleRequest(DeploymentManagerHttpHandlerFactory.java:74)
at io.undertow.server.Connectors.executeRootHandler(Connectors.java:393)
at io.undertow.server.protocol.http.HttpReadListener.handleEventWithNoRunningRequest(HttpReadListener.java:265)
at io.undertow.server.protocol.http.HttpReadListener.handleEvent(HttpReadListener.java:136)
at io.undertow.server.protocol.http.HttpOpenListener.handleEvent(HttpOpenListener.java:162)
at io.undertow.server.protocol.http.HttpOpenListener.handleEvent(HttpOpenListener.java:100)
at io.undertow.server.protocol.http.HttpOpenListener.handleEvent(HttpOpenListener.java:57)
at org.xnio.ChannelListeners.invokeChannelListener(ChannelListeners.java:92)
at org.xnio.ChannelListeners$10.handleEvent(ChannelListeners.java:291)
at org.xnio.ChannelListeners$10.handleEvent(ChannelListeners.java:286)
at org.xnio.ChannelListeners.invokeChannelListener(ChannelListeners.java:92)
at org.xnio.nio.QueuedNioTcpServer2.acceptTask(QueuedNioTcpServer2.java:178)
at org.xnio.nio.WorkerThread.safeRun(WorkerThread.java:612)
at org.xnio.nio.WorkerThread.run(WorkerThread.java:479)
【解决】
排查过后发现项目中并没有使用到错误中提到的诸如 google.com 和 example.com 等域名。先说解决方案,这是浏览器发起的跟踪请求,可以忽视这些错误日志。
以下是具体的排查过程:
- 增加容器日志记录
开启 Undertow 的 Access 日志:
yml
server:
port: 8080
undertow:
accesslog:
enabled: true
dir: /var/log/undertow
观察到以下记录:
bash
45.61.188.13 - - [11/Oct/2021:22:54:29 +0800] "GET /config/getuser?index=0 HTTP/1.0" 404 295
45.61.188.13 - - [11/Oct/2021:23:30:01 +0800] "POST /boaform/admin/formLogin HTTP/1.1" 404 295
89.248.165.52 - - [11/Oct/2021:19:53:54 +0800] "CONNECT hotmail-com.olc.protection.outlook.com:25 HTTP/1.1" 500 -
89.248.165.52 - - [11/Oct/2021:21:17:17 +0800] "CONNECT 85.206.160.115:80 HTTP/1.1" 500 -
原来这些域名信息都是外部请求带过来的,查阅资料发现 CONNECT 是 HTTP 代理方法的内容,和 GET、POST 是同一个级别的关键词,CONNECT 的作用就是将服务器作为代理。
CONNECT 代理完整的报文:
bash
CONNECT hotmail-com.olc.protection.outlook.com:25 HTTP/1.1\r\n
Host: hotmail-com.olc.protection.outlook.com:25\r\n
Proxy-Connection: Keep-Alive\r\n
Content-Length: 0\r\n
\r\n
参考:HTTP (HyperText Transfer Protocol)
- GET: A client can use the GET request to get a web resource from the server.
- HEAD: A client can use the HEAD request to get the header that a GET request would have obtained. Since the header contains the last-modified date of the data, this can be used to check against the local cache copy.
- POST: Used to post data up to the web server.
- PUT: Ask the server to store the data.
- DELETE: Ask the server to delete the data.
- TRACE: Ask the server to return a diagnostic trace of the actions it takes.
- OPTIONS: Ask the server to return the list of request methods it supports.
- CONNECT: Used to tell a proxy to make a connection to another host and simply reply the content, without attempting to parse or cache it. This is often used to make SSL connection through the proxy.
- Other extension methods.
- 复现异常
为了复现以上异常,创建一个 TCP Socket 向 Undertow 服务发送报文即可。
java
public static void main(String[] args) throws IOException, InterruptedException {
Socket socket = new Socket();
SocketAddress address = new InetSocketAddress("localhost", 8080);
socket.connect(address, 5000);
socket.setSoTimeout(10000);
OutputStream outputStream = socket.getOutputStream();
outputStream.write(("CONNECT hotmail-com.olc.protection.outlook.com:25 HTTP/1.1\r\n" +
"Host: hotmail-com.olc.protection.outlook.com\r\n" +
"Proxy-Connection: Keep-Alive\r\n\r\n").getBytes());
outputStream.flush();
outputStream.close();
socket.close();
}
- 消除异常
那么如何禁止外部尝试用我们服务做代理呢?我们想到的是当然是禁止 CONNECT 这个方法,Spring Boot 提供了一个方法,我们只需要加一个配置即可:
java
@Configuration
public class UndertowWebServerCustomizerConfig implements WebServerFactoryCustomizer<UndertowServletWebServerFactory> {
@Override
public void customize(UndertowServletWebServerFactory factory) {
factory.addDeploymentInfoCustomizers(deploymentInfo -> {
deploymentInfo.addInitialHandlerChainWrapper(new HandlerWrapper() {
@Override
public HttpHandler wrap(HttpHandler handler) {
//禁止三个方法TRACE也是不安全的
System.out.println("disable HTTP methods: CONNECT/TRACE/TRACK");
HttpString[] disallowedHttpMethods = {
HttpString.tryFromString("CONNECT"),
HttpString.tryFromString("TRACE"),
HttpString.tryFromString("TRACK")
};
return new DisallowedMethodsHandler(handler, disallowedHttpMethods);
}
});
});
}
}
再观察 access 日志:
bash
127.0.0.1 - - [12/Oct/2021:14:38:06 +0800] "CONNECT hotmail-com.olc.protection.outlook.com:25 HTTP/1.1" 405 -
127.0.0.1 - - [12/Oct/2021:14:38:24 +0800] "CONNECT hotmail-com.olc.protection.outlook.com:25 HTTP/1.1" 405 -
加上 UndertowWebServerCustomizerConfig,有以下两点变化:
- 项目 Java 中不会抛异常
- HTTP 响应状态码由 500 变为 405(Method not allowed)