yhirose / cpp-httplib

A C++ header-only HTTP/HTTPS server and client library

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Request Lost when using SSL

nanjo712 opened this issue · comments

I am trying to build an HTTP server with SSL using httplib, and I have generated an SSL certificate using OpenSSL, something like this: httplib::SSLServer server("server.crt", "server.key");. When I send a large number of requests using Postman for testing, two-thirds of the requests are consistently lost. Strangely, this two-thirds ratio is almost stable, meaning that no matter how many threads I use, each thread will lose two-thirds of its requests. What's even more peculiar is that if I start 10 threads at the same time and only send one request per thread, all requests receive responses.

This phenomenon is not limited to local HTTP requests; remote requests have the same issue, exhibiting the exact same behavior. However, once I disable SSL, all instances of request loss disappear.

server.Get("/username",
               [&](const httplib::Request &req, httplib::Response &res)
               {
                   std::string token = req.get_header_value("Authorization");
                   if (token.find("Bearer ") == 0)
                   {
                       token.erase(0, 7);
                   }
                   else
                   {
                       res.status = 400;
                       res.set_content("Invalid token", "text/plain");
                       return;
                   }
                   auto user = user_manager.get_user(token);
                   if (user)
                   {
                       spdlog::info("Username retrieved: {}", user->get_username());
                       res.status = 200;
                       res.set_content(user->get_username(), "text/plain");
                   }
                   else
                   {
                       spdlog::warn("Invalid token: {}", token);
                       res.status = 400;
                       res.set_content("Invalid token", "text/plain");
                   }
               });

@nanjo712 could you make a unit test that can reproduce the problem in test/test.cc with httplib:: SSLServer and httplib::SSLClient? Then, I'll take a look at it. Thanks!

I tried unit testing but was unable to reproduce the bug. When I attempted to test with Python and other languages, I did not encounter the same issue either. It seems that this problem only occurs with Postman.

TEST(SSLTest, SSLTest1) {
  SSLServer svr(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE);
  ASSERT_TRUE(svr.is_valid());
  svr.Get("/hi", [](const Request & /*req*/, Response &res) {
    res.set_content("Hello World!", "text/plain");
  });

  std::thread t([&]() { svr.listen(HOST, 8443); });
  auto se = detail::scope_exit([&] {
    svr.stop();
    t.join();
    ASSERT_FALSE(svr.is_running());
  });

  svr.wait_until_ready();

  auto func = []() {
    SSLClient cli("localhost", 8443);
    cli.enable_server_certificate_verification(false);

    for (int i = 0; i < 100; i++) {
      auto res = cli.Get("/hi");
      ASSERT_TRUE(res);
      EXPECT_EQ(StatusCode::OK_200, res->status);
      EXPECT_EQ("Hello World!", res->body);
    }
  };

  std::vector<std::thread> threads;
  for (int i = 0; i < 10; i++) {
    threads.emplace_back(func);
  }
  for (auto &t : threads) {
    t.join();
  }
}

it will pass the test.

Perhaps this library has some compatibility issues with Postman

GET https://localhost:8080/username
Error: read ECONNRESET
Request Headers
Content-Type: application/json
Accept: */*
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJkYXRhIjp7ImlkIjoiNzRjNzQ4ZGUtMjlkYy00ZjIxLTkzNzUtMTVmMjc3N2JhMjdiIn0sImV4cCI6MTcxNDQ2NzA4NiwiaWF0IjoxNzE0NDYzNDg2LCJpc3MiOiJ3b3NoaXJlbiIsIm5iZiI6MTcxNDQ2MzQ4Nn0.fykvd3AVKYLE0PqGQ0h5owIXD-OYTkllKqqK9TyDs7c
User-Agent: PostmanRuntime/7.37.3
Host: localhost:8080
Accept-Encoding: gzip, deflate, br
Connection: keep-alive

When Postman get an Error, it shows message like this.
This looks like the server closed the TCP connection and the client sent a new request before receiving the disconnection message

If I remove the "Connection: keep-alive" from the header, this problem will disappear. I think that‘s the problem

I also encountered a similar problem. Removing header Connection: keep-alive can fix the problem. Does that mean removing this header from the postman side?

I also encountered a similar problem. Removing header Connection: keep-alive can fix the problem. Does that mean removing this header from the postman side?

Yes, just remove the header in Postman. That fix the problem.

@yhirose I try the test TEST(KeepAliveTest, SSLClientReconnection) on my machine. The program did not pass the test

[ctest] Test project D:/cpp-httplib/build
[ctest]     Start 219: KeepAliveTest.SSLClientReconnection
[ctest] 1/1 Test #219: KeepAliveTest.SSLClientReconnection ...***Failed    2.12 sec
[ctest] Running main() from D:/cpp-httplib/build/_deps/gtest-src/googletest/src/gtest_main.cc
[ctest] Note: Google Test filter = KeepAliveTest.SSLClientReconnection
[ctest] [==========] Running 1 test from 1 test suite.
[ctest] [----------] Global test environment set-up.
[ctest] [----------] 1 test from KeepAliveTest
[ctest] [ RUN      ] KeepAliveTest.SSLClientReconnection
[ctest] D:/cpp-httplib/test/test.cc(4988): error: Value of: result
[ctest]   Actual: false
[ctest] Expected: true
[ctest] 
[ctest] [  FAILED  ] KeepAliveTest.SSLClientReconnection (2050 ms)
[ctest] [----------] 1 test from KeepAliveTest (2050 ms total)
[ctest] 
[ctest] [----------] Global test environment tear-down
[ctest] [==========] 1 test from 1 test suite ran. (2050 ms total)
[ctest] [  PASSED  ] 0 tests.
[ctest] [  FAILED  ] 1 test, listed below:
[ctest] [  FAILED  ] KeepAliveTest.SSLClientReconnection
[ctest] 
[ctest]  1 FAILED TEST
[ctest] 
[ctest] 
[ctest] 0% tests passed, 1 tests failed out of 1
[ctest] 
[ctest] Total Test time (real) =   2.14 sec
[ctest] 
[ctest] The following tests FAILED:
[ctest] 	219 - KeepAliveTest.SSLClientReconnection (Failed)

That's the log. The code in line 4988 is

  result = cli.Get("/hi");
  ASSERT_TRUE(result); // 4988
  EXPECT_EQ(StatusCode::OK_200, result->status);

  result = cli.Get("/hi");
  ASSERT_TRUE(result);
  EXPECT_EQ(StatusCode::OK_200, result->status);