瀏覽代碼

Merge pull request #2504 from emreay-/skip-drain-on-closing-connection

Skip request body drain when connection will close
yhirose 2 月之前
父節點
當前提交
c64bf21a5e
共有 2 個文件被更改,包括 60 次插入 和 5 次删除
  1. 11 5
      httplib.h
  2. 49 0
      test/test.cc

+ 11 - 5
httplib.h

@@ -12772,13 +12772,19 @@ Server::process_request(Stream &strm, const std::string &remote_addr,
 
   // Drain any unconsumed framed body to prevent request smuggling on
   // keep-alive. Without framing there is no body to drain — reading would
-  // consume the next request (issue #2450).
+  // consume the next request (issue #2450). If the response has committed the
+  // connection to close, there is no next request to protect.
   if (!req.body_consumed_ && detail::has_framed_body(req)) {
-    int dummy_status;
-    if (!detail::read_content(
-            strm, req, payload_max_length_, dummy_status, nullptr,
-            [](const char *, size_t, size_t, size_t) { return true; }, false)) {
+    if (res.get_header_value("Connection") == "close") {
       connection_closed = true;
+    } else {
+      int dummy_status;
+      if (!detail::read_content(
+              strm, req, payload_max_length_, dummy_status, nullptr,
+              [](const char *, size_t, size_t, size_t) { return true; },
+              false)) {
+        connection_closed = true;
+      }
     }
   }
 

+ 49 - 0
test/test.cc

@@ -20039,6 +20039,55 @@ TEST(KeepAliveTest, DeleteWithoutContentLengthDoesNotEatNextRequest) {
   EXPECT_EQ(2, delete_count.load());
 }
 
+TEST(KeepAliveTest, UnconsumedChunkedBodyIsNotDrainedWhenResponseCloses) {
+  Server svr;
+  svr.Post("/ingest", [&](const Request &, Response &res,
+                          const ContentReader &content_reader) {
+    auto consumed = content_reader([](const char *, size_t) { return false; });
+    EXPECT_FALSE(consumed);
+    res.status = StatusCode::Conflict_409;
+    res.set_header("Connection", "close");
+  });
+
+  auto port = svr.bind_to_any_port(HOST);
+  thread t = thread([&] { svr.listen_after_bind(); });
+  auto se = detail::scope_exit([&] {
+    svr.stop();
+    t.join();
+  });
+  svr.wait_until_ready();
+
+  auto error = Error::Success;
+  auto sock = detail::create_client_socket(
+      HOST, "", port, AF_UNSPEC, false, false, nullptr,
+      /*connection_timeout_sec=*/2, 0,
+      /*read_timeout_sec=*/1, 0,
+      /*write_timeout_sec=*/2, 0, std::string(), error);
+  ASSERT_NE(INVALID_SOCKET, sock);
+  auto sock_se = detail::scope_exit([&] { detail::close_socket(sock); });
+
+  std::string request = "POST /ingest HTTP/1.1\r\n"
+                        "Host: localhost\r\n"
+                        "Transfer-Encoding: chunked\r\n"
+                        "\r\n"
+                        "4\r\n"
+                        "data\r\n";
+  auto sent = send(sock, request.data(), request.size(), 0);
+  ASSERT_EQ(static_cast<ssize_t>(request.size()), sent);
+
+  std::string response;
+  ssize_t received = 0;
+  do {
+    char buf[4096];
+    received = recv(sock, buf, sizeof(buf), 0);
+    if (received > 0) { response.append(buf, static_cast<size_t>(received)); }
+  } while (received > 0);
+
+  EXPECT_NE(std::string::npos, response.find("HTTP/1.1 409 Conflict"));
+  EXPECT_NE(std::string::npos, response.find("Connection: close"));
+  EXPECT_EQ(0, received);
+}
+
 namespace no_proxy_test {
 
 // Server bound to 127.0.0.1:<dynamic>, listen thread spawned by listen(),