Skip to content

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 等域名。先说解决方案,这是浏览器发起的跟踪请求,可以忽视这些错误日志。

以下是具体的排查过程:

  1. 增加容器日志记录

开启 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.
  1. 复现异常

为了复现以上异常,创建一个 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();
}
  1. 消除异常

那么如何禁止外部尝试用我们服务做代理呢?我们想到的是当然是禁止 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)
编程洪同学服务平台是一个广泛收集编程相关内容和资源,旨在满足编程爱好者和专业开发人员的需求的网站。无论您是初学者还是经验丰富的开发者,都可以在这里找到有用的信息和资料,我们将助您提升编程技能和知识。
专业开发
高端定制
售后无忧
站内资源均为本站制作或收集于互联网等平台,如有侵权,请第一时间联系本站,敬请谅解!本站资源仅限于学习与参考,严禁用于各种非法活动,否则后果自行负责,本站概不承担!