[llvm] 8366e21 - [llvm] [Debuginfod] Add HTTP Server to Debuginfod library.

Noah Shutty via llvm-commits llvm-commits at lists.llvm.org
Wed Jul 6 11:56:59 PDT 2022


Author: Noah Shutty
Date: 2022-07-06T18:56:54Z
New Revision: 8366e21ef176033e8d41af87eacf94f1971dc5dd

URL: https://github.com/llvm/llvm-project/commit/8366e21ef176033e8d41af87eacf94f1971dc5dd
DIFF: https://github.com/llvm/llvm-project/commit/8366e21ef176033e8d41af87eacf94f1971dc5dd.diff

LOG: [llvm] [Debuginfod] Add HTTP Server to Debuginfod library.

This provides a minimal HTTP server interface and an implementation wrapping [[ https://github.com/yhirose/cpp-httplib | cpp-httplib ]] in the Debuginfod library. If the Curl HTTP client is available (D112753) the server is tested by pinging it with the client.

Reviewed By: dblaikie

Differential Revision: https://reviews.llvm.org/D114415

Added: 
    llvm/include/llvm/Debuginfod/HTTPServer.h
    llvm/lib/Debuginfod/HTTPServer.cpp
    llvm/unittests/Debuginfod/HTTPServerTests.cpp

Modified: 
    llvm/lib/Debuginfod/CMakeLists.txt
    llvm/unittests/Debuginfod/CMakeLists.txt

Removed: 
    


################################################################################
diff  --git a/llvm/include/llvm/Debuginfod/HTTPServer.h b/llvm/include/llvm/Debuginfod/HTTPServer.h
new file mode 100644
index 0000000000000..410ba32b3f2ed
--- /dev/null
+++ b/llvm/include/llvm/Debuginfod/HTTPServer.h
@@ -0,0 +1,123 @@
+//===-- llvm/Debuginfod/HTTPServer.h - HTTP server library ------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// This file contains the declarations of the HTTPServer and HTTPServerRequest
+/// classes, the HTTPResponse, and StreamingHTTPResponse structs, and the
+/// streamFile function.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_SUPPORT_HTTP_SERVER_H
+#define LLVM_SUPPORT_HTTP_SERVER_H
+
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+#ifdef LLVM_ENABLE_HTTPLIB
+// forward declarations
+namespace httplib {
+class Request;
+class Response;
+class Server;
+} // namespace httplib
+#endif
+
+namespace llvm {
+
+struct HTTPResponse;
+struct StreamingHTTPResponse;
+class HTTPServer;
+
+class HTTPServerRequest {
+  friend HTTPServer;
+
+#ifdef LLVM_ENABLE_HTTPLIB
+private:
+  HTTPServerRequest(const httplib::Request &HTTPLibRequest,
+                    httplib::Response &HTTPLibResponse);
+  httplib::Response &HTTPLibResponse;
+#endif
+
+public:
+  std::string UrlPath;
+  /// The elements correspond to match groups in the url path matching regex.
+  SmallVector<std::string, 1> UrlPathMatches;
+
+  // TODO bring in HTTP headers
+
+  void setResponse(StreamingHTTPResponse Response);
+  void setResponse(HTTPResponse Response);
+};
+
+struct HTTPResponse {
+  unsigned Code;
+  const char *ContentType;
+  StringRef Body;
+};
+
+typedef std::function<void(HTTPServerRequest &)> HTTPRequestHandler;
+
+/// An HTTPContentProvider is called by the HTTPServer to obtain chunks of the
+/// streaming response body. The returned chunk should be located at Offset
+/// bytes and have Length bytes.
+typedef std::function<StringRef(size_t /*Offset*/, size_t /*Length*/)>
+    HTTPContentProvider;
+
+/// Wraps the content provider with HTTP Status code and headers.
+struct StreamingHTTPResponse {
+  unsigned Code;
+  const char *ContentType;
+  size_t ContentLength;
+  HTTPContentProvider Provider;
+  /// Called after the response transfer is complete with the success value of
+  /// the transfer.
+  std::function<void(bool)> CompletionHandler = [](bool Success) {};
+};
+
+/// Sets the response to stream the file at FilePath, if available, and
+/// otherwise an HTTP 404 error response.
+bool streamFile(HTTPServerRequest &Request, StringRef FilePath);
+
+/// An HTTP server which can listen on a single TCP/IP port for HTTP
+/// requests and delgate them to the appropriate registered handler.
+class HTTPServer {
+#ifdef LLVM_ENABLE_HTTPLIB
+  std::unique_ptr<httplib::Server> Server;
+  unsigned Port = 0;
+#endif
+public:
+  HTTPServer();
+  ~HTTPServer();
+
+  /// Returns true only if LLVM has been compiled with a working HTTPServer.
+  static bool isAvailable();
+
+  /// Registers a URL pattern routing rule. When the server is listening, each
+  /// request is dispatched to the first registered handler whose UrlPathPattern
+  /// matches the UrlPath.
+  Error get(StringRef UrlPathPattern, HTTPRequestHandler Handler);
+
+  /// Attempts to assign the requested port and interface, returning an Error
+  /// upon failure.
+  Error bind(unsigned Port, const char *HostInterface = "0.0.0.0");
+
+  /// Attempts to assign any available port and interface, returning either the
+  /// port number or an Error upon failure.
+  Expected<unsigned> bind(const char *HostInterface = "0.0.0.0");
+
+  /// Attempts to listen for requests on the bound port. Returns an Error if
+  /// called before binding a port.
+  Error listen();
+
+  /// If the server is listening, stop and unbind the socket.
+  void stop();
+};
+} // end namespace llvm
+
+#endif // LLVM_SUPPORT_HTTP_SERVER_H

diff  --git a/llvm/lib/Debuginfod/CMakeLists.txt b/llvm/lib/Debuginfod/CMakeLists.txt
index be8965c9b2e43..06f7441280a91 100644
--- a/llvm/lib/Debuginfod/CMakeLists.txt
+++ b/llvm/lib/Debuginfod/CMakeLists.txt
@@ -3,12 +3,18 @@ if (LLVM_ENABLE_CURL)
   set(imported_libs CURL::libcurl)
 endif()
 
+# Link cpp-httplib if the user wants it
+if (LLVM_ENABLE_HTTPLIB)
+  set(imported_libs ${imported_libs} httplib::httplib)
+endif()
+
 # Note: This isn't a component, since that could potentially add a libcurl
 # dependency to libLLVM.
 add_llvm_library(LLVMDebuginfod
   Debuginfod.cpp
   DIFetcher.cpp
   HTTPClient.cpp
+  HTTPServer.cpp
 
   ADDITIONAL_HEADER_DIRS
   ${LLVM_MAIN_INCLUDE_DIR}/llvm/Debuginfod

diff  --git a/llvm/lib/Debuginfod/HTTPServer.cpp b/llvm/lib/Debuginfod/HTTPServer.cpp
new file mode 100644
index 0000000000000..2ea923d5a734c
--- /dev/null
+++ b/llvm/lib/Debuginfod/HTTPServer.cpp
@@ -0,0 +1,189 @@
+//===-- llvm/Debuginfod/HTTPServer.cpp - HTTP server library -----*- C++-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+///
+/// This file defines the methods of the HTTPServer class and the streamFile
+/// function.
+///
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Debuginfod/HTTPServer.h"
+#include "llvm/ADT/StringExtras.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Errc.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/Regex.h"
+
+#ifdef LLVM_ENABLE_HTTPLIB
+#include "httplib.h"
+#endif
+
+using namespace llvm;
+
+bool llvm::streamFile(HTTPServerRequest &Request, StringRef FilePath) {
+  Expected<sys::fs::file_t> FDOrErr = sys::fs::openNativeFileForRead(FilePath);
+  if (Error Err = FDOrErr.takeError()) {
+    consumeError(std::move(Err));
+    Request.setResponse({404u, "text/plain", "Could not open file to read.\n"});
+    return false;
+  }
+  ErrorOr<std::unique_ptr<MemoryBuffer>> MBOrErr =
+      MemoryBuffer::getOpenFile(*FDOrErr, FilePath,
+                                /*FileSize=*/-1,
+                                /*RequiresNullTerminator=*/false);
+  sys::fs::closeFile(*FDOrErr);
+  if (Error Err = errorCodeToError(MBOrErr.getError())) {
+    consumeError(std::move(Err));
+    Request.setResponse({404u, "text/plain", "Could not memory-map file.\n"});
+    return false;
+  }
+  // Lambdas are copied on conversion to to std::function, preventing use of
+  // smart pointers.
+  MemoryBuffer *MB = MBOrErr->release();
+  Request.setResponse({200u, "application/octet-stream", MB->getBufferSize(),
+                       [=](size_t Offset, size_t Length) -> StringRef {
+                         return MB->getBuffer().substr(Offset, Length);
+                       },
+                       [=](bool Success) { delete MB; }});
+  return true;
+}
+
+#ifdef LLVM_ENABLE_HTTPLIB
+
+bool HTTPServer::isAvailable() { return true; }
+
+HTTPServer::HTTPServer() { Server = std::make_unique<httplib::Server>(); }
+
+HTTPServer::~HTTPServer() { stop(); }
+
+static void expandUrlPathMatches(const std::smatch &Matches,
+                                 HTTPServerRequest &Request) {
+  bool UrlPathSet = false;
+  for (const auto &it : Matches) {
+    if (UrlPathSet)
+      Request.UrlPathMatches.push_back(it);
+    else {
+      Request.UrlPath = it;
+      UrlPathSet = true;
+    }
+  }
+}
+
+HTTPServerRequest::HTTPServerRequest(const httplib::Request &HTTPLibRequest,
+                                     httplib::Response &HTTPLibResponse)
+    : HTTPLibResponse(HTTPLibResponse) {
+  expandUrlPathMatches(HTTPLibRequest.matches, *this);
+}
+
+void HTTPServerRequest::setResponse(HTTPResponse Response) {
+  HTTPLibResponse.set_content(Response.Body.begin(), Response.Body.size(),
+                              Response.ContentType);
+  HTTPLibResponse.status = Response.Code;
+}
+
+void HTTPServerRequest::setResponse(StreamingHTTPResponse Response) {
+  HTTPLibResponse.set_content_provider(
+      Response.ContentLength, Response.ContentType,
+      [=](size_t Offset, size_t Length, httplib::DataSink &Sink) {
+        if (Offset < Response.ContentLength) {
+          StringRef Chunk = Response.Provider(Offset, Length);
+          Sink.write(Chunk.begin(), Chunk.size());
+        }
+        return true;
+      },
+      [=](bool Success) { Response.CompletionHandler(Success); });
+
+  HTTPLibResponse.status = Response.Code;
+}
+
+Error HTTPServer::get(StringRef UrlPathPattern, HTTPRequestHandler Handler) {
+  std::string ErrorMessage;
+  if (!Regex(UrlPathPattern).isValid(ErrorMessage))
+    return createStringError(errc::argument_out_of_domain, ErrorMessage);
+  Server->Get(std::string(UrlPathPattern),
+              [Handler](const httplib::Request &HTTPLibRequest,
+                        httplib::Response &HTTPLibResponse) {
+                HTTPServerRequest Request(HTTPLibRequest, HTTPLibResponse);
+                Handler(Request);
+              });
+  return Error::success();
+}
+
+Error HTTPServer::bind(unsigned ListenPort, const char *HostInterface) {
+  if (!Server->bind_to_port(HostInterface, ListenPort))
+    return createStringError(errc::io_error,
+                             "Could not assign requested address.");
+  Port = ListenPort;
+  return Error::success();
+}
+
+Expected<unsigned> HTTPServer::bind(const char *HostInterface) {
+  int ListenPort = Server->bind_to_any_port(HostInterface);
+  if (ListenPort < 0)
+    return createStringError(errc::io_error,
+                             "Could not assign any port on requested address.");
+  return Port = ListenPort;
+}
+
+Error HTTPServer::listen() {
+  if (!Port)
+    return createStringError(errc::io_error,
+                             "Cannot listen without first binding to a port.");
+  if (!Server->listen_after_bind())
+    return createStringError(
+        errc::io_error,
+        "An unknown error occurred when cpp-httplib attempted to listen.");
+  return Error::success();
+}
+
+void HTTPServer::stop() {
+  Server->stop();
+  Port = 0;
+}
+
+#else
+
+// TODO: Implement barebones standalone HTTP server implementation.
+bool HTTPServer::isAvailable() { return false; }
+
+HTTPServer::HTTPServer() = default;
+
+HTTPServer::~HTTPServer() = default;
+
+void HTTPServerRequest::setResponse(HTTPResponse Response) {
+  llvm_unreachable("No HTTP server implementation available");
+}
+
+void HTTPServerRequest::setResponse(StreamingHTTPResponse Response) {
+  llvm_unreachable("No HTTP server implementation available");
+}
+
+Error HTTPServer::get(StringRef UrlPathPattern, HTTPRequestHandler Handler) {
+  llvm_unreachable("No HTTP server implementation available");
+}
+
+Error HTTPServer::bind(unsigned ListenPort, const char *HostInterface) {
+  llvm_unreachable("No HTTP server implementation available");
+}
+
+Expected<unsigned> HTTPServer::bind(const char *HostInterface) {
+  llvm_unreachable("No HTTP server implementation available");
+}
+
+Error HTTPServer::listen() {
+  llvm_unreachable("No HTTP server implementation available");
+}
+
+void HTTPServer::stop() {
+  llvm_unreachable("No HTTP server implementation available");
+}
+
+#endif // LLVM_ENABLE_HTTPLIB

diff  --git a/llvm/unittests/Debuginfod/CMakeLists.txt b/llvm/unittests/Debuginfod/CMakeLists.txt
index cb800872e10e8..9b084ff33f3b7 100644
--- a/llvm/unittests/Debuginfod/CMakeLists.txt
+++ b/llvm/unittests/Debuginfod/CMakeLists.txt
@@ -1,4 +1,5 @@
 add_llvm_unittest(DebuginfodTests
+  HTTPServerTests.cpp
   DebuginfodTests.cpp
   )
 

diff  --git a/llvm/unittests/Debuginfod/HTTPServerTests.cpp b/llvm/unittests/Debuginfod/HTTPServerTests.cpp
new file mode 100644
index 0000000000000..3b2951a900ab2
--- /dev/null
+++ b/llvm/unittests/Debuginfod/HTTPServerTests.cpp
@@ -0,0 +1,309 @@
+//===-- llvm/unittest/Support/HTTPServer.cpp - unit tests -------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Debuginfod/HTTPClient.h"
+#include "llvm/Debuginfod/HTTPServer.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/ThreadPool.h"
+#include "llvm/Testing/Support/Error.h"
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+
+using namespace llvm;
+
+#ifdef LLVM_ENABLE_HTTPLIB
+
+TEST(HTTPServer, IsAvailable) { EXPECT_TRUE(HTTPServer::isAvailable()); }
+
+HTTPResponse Response = {200u, "text/plain", "hello, world\n"};
+std::string UrlPathPattern = R"(/(.*))";
+std::string InvalidUrlPathPattern = R"(/(.*)";
+
+HTTPRequestHandler Handler = [](HTTPServerRequest &Request) {
+  Request.setResponse(Response);
+};
+
+HTTPRequestHandler DelayHandler = [](HTTPServerRequest &Request) {
+  std::this_thread::sleep_for(std::chrono::milliseconds(50));
+  Request.setResponse(Response);
+};
+
+HTTPRequestHandler StreamingHandler = [](HTTPServerRequest &Request) {
+  Request.setResponse({200, "text/plain", Response.Body.size(),
+                       [=](size_t Offset, size_t Length) -> StringRef {
+                         return Response.Body.substr(Offset, Length);
+                       }});
+};
+
+TEST(HTTPServer, InvalidUrlPath) {
+  // test that we can bind to any address
+  HTTPServer Server;
+  EXPECT_THAT_ERROR(Server.get(InvalidUrlPathPattern, Handler),
+                    Failed<StringError>());
+  EXPECT_THAT_EXPECTED(Server.bind(), Succeeded());
+}
+
+TEST(HTTPServer, bind) {
+  // test that we can bind to any address
+  HTTPServer Server;
+  EXPECT_THAT_ERROR(Server.get(UrlPathPattern, Handler), Succeeded());
+  EXPECT_THAT_EXPECTED(Server.bind(), Succeeded());
+}
+
+TEST(HTTPServer, ListenBeforeBind) {
+  // test that we can bind to any address
+  HTTPServer Server;
+  EXPECT_THAT_ERROR(Server.get(UrlPathPattern, Handler), Succeeded());
+  EXPECT_THAT_ERROR(Server.listen(), Failed<StringError>());
+}
+
+#ifdef LLVM_ENABLE_CURL
+// Test the client and server against each other.
+
+// Test fixture to initialize and teardown the HTTP client for each
+// client-server test
+class HTTPClientServerTest : public ::testing::Test {
+protected:
+  void SetUp() override { HTTPClient::initialize(); }
+  void TearDown() override { HTTPClient::cleanup(); }
+};
+
+/// A simple handler which writes returned data to a string.
+struct StringHTTPResponseHandler final : public HTTPResponseHandler {
+  std::string ResponseBody = "";
+  /// These callbacks store the body and status code in an HTTPResponseBuffer
+  /// allocated based on Content-Length. The Content-Length header must be
+  /// handled by handleHeaderLine before any calls to handleBodyChunk.
+  Error handleBodyChunk(StringRef BodyChunk) override {
+    ResponseBody = ResponseBody + BodyChunk.str();
+    return Error::success();
+  }
+};
+
+TEST_F(HTTPClientServerTest, Hello) {
+  HTTPServer Server;
+  EXPECT_THAT_ERROR(Server.get(UrlPathPattern, Handler), Succeeded());
+  Expected<unsigned> PortOrErr = Server.bind();
+  EXPECT_THAT_EXPECTED(PortOrErr, Succeeded());
+  unsigned Port = *PortOrErr;
+  ThreadPool Pool(hardware_concurrency(1));
+  Pool.async([&]() { EXPECT_THAT_ERROR(Server.listen(), Succeeded()); });
+  std::string Url = "http://localhost:" + utostr(Port);
+  HTTPRequest Request(Url);
+  StringHTTPResponseHandler Handler;
+  HTTPClient Client;
+  EXPECT_THAT_ERROR(Client.perform(Request, Handler), Succeeded());
+  EXPECT_EQ(Handler.ResponseBody, Response.Body);
+  EXPECT_EQ(Client.responseCode(), Response.Code);
+  Server.stop();
+}
+
+TEST_F(HTTPClientServerTest, LambdaHandlerHello) {
+  HTTPServer Server;
+  HTTPResponse LambdaResponse = {200u, "text/plain",
+                                 "hello, world from a lambda\n"};
+  EXPECT_THAT_ERROR(Server.get(UrlPathPattern,
+                               [LambdaResponse](HTTPServerRequest &Request) {
+                                 Request.setResponse(LambdaResponse);
+                               }),
+                    Succeeded());
+  Expected<unsigned> PortOrErr = Server.bind();
+  EXPECT_THAT_EXPECTED(PortOrErr, Succeeded());
+  unsigned Port = *PortOrErr;
+  ThreadPool Pool(hardware_concurrency(1));
+  Pool.async([&]() { EXPECT_THAT_ERROR(Server.listen(), Succeeded()); });
+  std::string Url = "http://localhost:" + utostr(Port);
+  HTTPRequest Request(Url);
+  StringHTTPResponseHandler Handler;
+  HTTPClient Client;
+  EXPECT_THAT_ERROR(Client.perform(Request, Handler), Succeeded());
+  EXPECT_EQ(Handler.ResponseBody, LambdaResponse.Body);
+  EXPECT_EQ(Client.responseCode(), LambdaResponse.Code);
+  Server.stop();
+}
+
+// Test the streaming response.
+TEST_F(HTTPClientServerTest, StreamingHello) {
+  HTTPServer Server;
+  EXPECT_THAT_ERROR(Server.get(UrlPathPattern, StreamingHandler), Succeeded());
+  Expected<unsigned> PortOrErr = Server.bind();
+  EXPECT_THAT_EXPECTED(PortOrErr, Succeeded());
+  unsigned Port = *PortOrErr;
+  ThreadPool Pool(hardware_concurrency(1));
+  Pool.async([&]() { EXPECT_THAT_ERROR(Server.listen(), Succeeded()); });
+  std::string Url = "http://localhost:" + utostr(Port);
+  HTTPRequest Request(Url);
+  StringHTTPResponseHandler Handler;
+  HTTPClient Client;
+  EXPECT_THAT_ERROR(Client.perform(Request, Handler), Succeeded());
+  EXPECT_EQ(Handler.ResponseBody, Response.Body);
+  EXPECT_EQ(Client.responseCode(), Response.Code);
+  Server.stop();
+}
+
+// Writes a temporary file and streams it back using streamFile.
+HTTPRequestHandler TempFileStreamingHandler = [](HTTPServerRequest Request) {
+  int FD;
+  SmallString<64> TempFilePath;
+  sys::fs::createTemporaryFile("http-stream-file-test", "temp", FD,
+                               TempFilePath);
+  raw_fd_ostream OS(FD, true, /*unbuffered=*/true);
+  OS << Response.Body;
+  OS.close();
+  streamFile(Request, TempFilePath);
+};
+
+// Test streaming back chunks of a file.
+TEST_F(HTTPClientServerTest, StreamingFileResponse) {
+  HTTPServer Server;
+  EXPECT_THAT_ERROR(Server.get(UrlPathPattern, TempFileStreamingHandler),
+                    Succeeded());
+  Expected<unsigned> PortOrErr = Server.bind();
+  EXPECT_THAT_EXPECTED(PortOrErr, Succeeded());
+  unsigned Port = *PortOrErr;
+  ThreadPool Pool(hardware_concurrency(1));
+  Pool.async([&]() { EXPECT_THAT_ERROR(Server.listen(), Succeeded()); });
+  std::string Url = "http://localhost:" + utostr(Port);
+  HTTPRequest Request(Url);
+  StringHTTPResponseHandler Handler;
+  HTTPClient Client;
+  EXPECT_THAT_ERROR(Client.perform(Request, Handler), Succeeded());
+  EXPECT_EQ(Handler.ResponseBody, Response.Body);
+  EXPECT_EQ(Client.responseCode(), Response.Code);
+  Server.stop();
+}
+
+// Deletes the temporary file before streaming it back, should give a 404 not
+// found status code.
+HTTPRequestHandler MissingTempFileStreamingHandler =
+    [](HTTPServerRequest Request) {
+      int FD;
+      SmallString<64> TempFilePath;
+      sys::fs::createTemporaryFile("http-stream-file-test", "temp", FD,
+                                   TempFilePath);
+      raw_fd_ostream OS(FD, true, /*unbuffered=*/true);
+      OS << Response.Body;
+      OS.close();
+      // delete the file
+      sys::fs::remove(TempFilePath);
+      streamFile(Request, TempFilePath);
+    };
+
+// Streaming a missing file should give a 404.
+TEST_F(HTTPClientServerTest, StreamingMissingFileResponse) {
+  HTTPServer Server;
+  EXPECT_THAT_ERROR(Server.get(UrlPathPattern, MissingTempFileStreamingHandler),
+                    Succeeded());
+  Expected<unsigned> PortOrErr = Server.bind();
+  EXPECT_THAT_EXPECTED(PortOrErr, Succeeded());
+  unsigned Port = *PortOrErr;
+  ThreadPool Pool(hardware_concurrency(1));
+  Pool.async([&]() { EXPECT_THAT_ERROR(Server.listen(), Succeeded()); });
+  std::string Url = "http://localhost:" + utostr(Port);
+  HTTPRequest Request(Url);
+  StringHTTPResponseHandler Handler;
+  HTTPClient Client;
+  EXPECT_THAT_ERROR(Client.perform(Request, Handler), Succeeded());
+  EXPECT_EQ(Client.responseCode(), 404u);
+  Server.stop();
+}
+
+TEST_F(HTTPClientServerTest, ClientTimeout) {
+  HTTPServer Server;
+  EXPECT_THAT_ERROR(Server.get(UrlPathPattern, DelayHandler), Succeeded());
+  Expected<unsigned> PortOrErr = Server.bind();
+  EXPECT_THAT_EXPECTED(PortOrErr, Succeeded());
+  unsigned Port = *PortOrErr;
+  ThreadPool Pool(hardware_concurrency(1));
+  Pool.async([&]() { EXPECT_THAT_ERROR(Server.listen(), Succeeded()); });
+  std::string Url = "http://localhost:" + utostr(Port);
+  HTTPClient Client;
+  // Timeout below 50ms, request should fail
+  Client.setTimeout(std::chrono::milliseconds(40));
+  HTTPRequest Request(Url);
+  StringHTTPResponseHandler Handler;
+  EXPECT_THAT_ERROR(Client.perform(Request, Handler), Failed<StringError>());
+  Server.stop();
+}
+
+// Check that Url paths are dispatched to the first matching handler and provide
+// the correct path pattern match components.
+TEST_F(HTTPClientServerTest, PathMatching) {
+  HTTPServer Server;
+
+  EXPECT_THAT_ERROR(
+      Server.get(R"(/abc/(.*)/(.*))",
+                 [&](HTTPServerRequest &Request) {
+                   EXPECT_EQ(Request.UrlPath, "/abc/1/2");
+                   ASSERT_THAT(Request.UrlPathMatches,
+                               testing::ElementsAre("1", "2"));
+                   Request.setResponse({200u, "text/plain", Request.UrlPath});
+                 }),
+      Succeeded());
+  EXPECT_THAT_ERROR(Server.get(UrlPathPattern,
+                               [&](HTTPServerRequest &Request) {
+                                 llvm_unreachable(
+                                     "Should not reach this handler");
+                                 Handler(Request);
+                               }),
+                    Succeeded());
+
+  Expected<unsigned> PortOrErr = Server.bind();
+  EXPECT_THAT_EXPECTED(PortOrErr, Succeeded());
+  unsigned Port = *PortOrErr;
+  ThreadPool Pool(hardware_concurrency(1));
+  Pool.async([&]() { EXPECT_THAT_ERROR(Server.listen(), Succeeded()); });
+  std::string Url = "http://localhost:" + utostr(Port) + "/abc/1/2";
+  HTTPRequest Request(Url);
+  StringHTTPResponseHandler Handler;
+  HTTPClient Client;
+  EXPECT_THAT_ERROR(Client.perform(Request, Handler), Succeeded());
+  EXPECT_EQ(Handler.ResponseBody, "/abc/1/2");
+  EXPECT_EQ(Client.responseCode(), 200u);
+  Server.stop();
+}
+
+TEST_F(HTTPClientServerTest, FirstPathMatched) {
+  HTTPServer Server;
+
+  EXPECT_THAT_ERROR(
+      Server.get(UrlPathPattern,
+                 [&](HTTPServerRequest Request) { Handler(Request); }),
+      Succeeded());
+
+  EXPECT_THAT_ERROR(
+      Server.get(R"(/abc/(.*)/(.*))",
+                 [&](HTTPServerRequest Request) {
+                   EXPECT_EQ(Request.UrlPathMatches.size(), 2u);
+                   llvm_unreachable("Should not reach this handler");
+                   Request.setResponse({200u, "text/plain", Request.UrlPath});
+                 }),
+      Succeeded());
+
+  Expected<unsigned> PortOrErr = Server.bind();
+  EXPECT_THAT_EXPECTED(PortOrErr, Succeeded());
+  unsigned Port = *PortOrErr;
+  ThreadPool Pool(hardware_concurrency(1));
+  Pool.async([&]() { EXPECT_THAT_ERROR(Server.listen(), Succeeded()); });
+  std::string Url = "http://localhost:" + utostr(Port) + "/abc/1/2";
+  HTTPRequest Request(Url);
+  StringHTTPResponseHandler Handler;
+  HTTPClient Client;
+  EXPECT_THAT_ERROR(Client.perform(Request, Handler), Succeeded());
+  EXPECT_EQ(Handler.ResponseBody, Response.Body);
+  EXPECT_EQ(Client.responseCode(), Response.Code);
+  Server.stop();
+}
+
+#endif
+
+#else
+
+TEST(HTTPServer, IsAvailable) { EXPECT_FALSE(HTTPServer::isAvailable()); }
+
+#endif // LLVM_ENABLE_HTTPLIB


        


More information about the llvm-commits mailing list