[llvm] 170783f - [llvm] [Support] Add HTTP Client Support library.

Noah Shutty via llvm-commits llvm-commits at lists.llvm.org
Wed Dec 1 16:47:52 PST 2021


Author: Noah Shutty
Date: 2021-12-01T23:54:38Z
New Revision: 170783f991fab1e5480a5bdea5e1b26c27b9b452

URL: https://github.com/llvm/llvm-project/commit/170783f991fab1e5480a5bdea5e1b26c27b9b452
DIFF: https://github.com/llvm/llvm-project/commit/170783f991fab1e5480a5bdea5e1b26c27b9b452.diff

LOG: [llvm] [Support] Add HTTP Client Support library.

This patch implements a small HTTP client library consisting primarily of the `HTTPRequest`, `HTTPResponseHandler`, and `BufferedHTTPResponseHandler` classes. Unit tests of the `HTTPResponseHandler` and `BufferedHTTPResponseHandler` are included.

Reviewed By: dblaikie

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

Added: 
    llvm/include/llvm/Support/HTTPClient.h
    llvm/lib/Support/HTTPClient.cpp
    llvm/unittests/Support/HTTPClient.cpp

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

Removed: 
    


################################################################################
diff  --git a/llvm/include/llvm/Support/HTTPClient.h b/llvm/include/llvm/Support/HTTPClient.h
new file mode 100644
index 0000000000000..3172610c2d8b1
--- /dev/null
+++ b/llvm/include/llvm/Support/HTTPClient.h
@@ -0,0 +1,113 @@
+//===-- llvm/Support/HTTPClient.h - HTTP client 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 HTTPClient, HTTPMethod,
+/// HTTPResponseHandler, and BufferedHTTPResponseHandler classes, as well as
+/// the HTTPResponseBuffer and HTTPRequest structs.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_SUPPORT_HTTP_CLIENT_H
+#define LLVM_SUPPORT_HTTP_CLIENT_H
+
+#include "llvm/Support/Error.h"
+#include "llvm/Support/MemoryBuffer.h"
+
+namespace llvm {
+
+enum class HTTPMethod { GET };
+
+/// A stateless description of an outbound HTTP request.
+struct HTTPRequest {
+  SmallString<128> Url;
+  HTTPMethod Method = HTTPMethod::GET;
+  bool FollowRedirects = true;
+  HTTPRequest(StringRef Url);
+};
+
+bool operator==(const HTTPRequest &A, const HTTPRequest &B);
+
+/// A handler for state updates occurring while an HTTPRequest is performed.
+/// Can trigger the client to abort the request by returning an Error from any
+/// of its methods.
+class HTTPResponseHandler {
+public:
+  /// Processes one line of HTTP response headers.
+  virtual Error handleHeaderLine(StringRef HeaderLine) = 0;
+
+  /// Processes an additional chunk of bytes of the HTTP response body.
+  virtual Error handleBodyChunk(StringRef BodyChunk) = 0;
+
+  /// Processes the HTTP response status code.
+  virtual Error handleStatusCode(unsigned Code) = 0;
+
+protected:
+  ~HTTPResponseHandler();
+};
+
+/// An HTTP response status code bundled with a buffer to store the body.
+struct HTTPResponseBuffer {
+  unsigned Code = 0;
+  std::unique_ptr<WritableMemoryBuffer> Body;
+};
+
+/// A simple handler which writes returned data to an HTTPResponseBuffer.
+/// Ignores all headers except the Content-Length, which it uses to
+/// allocate an appropriately-sized Body buffer.
+class BufferedHTTPResponseHandler final : public HTTPResponseHandler {
+  size_t Offset = 0;
+
+public:
+  /// Stores the data received from the HTTP server.
+  HTTPResponseBuffer ResponseBuffer;
+
+  /// 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 handleHeaderLine(StringRef HeaderLine) override;
+  Error handleBodyChunk(StringRef BodyChunk) override;
+  Error handleStatusCode(unsigned Code) override;
+};
+
+/// A reusable client that can perform HTTPRequests through a network socket.
+class HTTPClient {
+public:
+  HTTPClient();
+  ~HTTPClient();
+
+  /// Returns true only if LLVM has been compiled with a working HTTPClient.
+  static bool isAvailable();
+
+  /// Must be called at the beginning of a program, while it is a single thread.
+  static void initialize();
+
+  /// Must be called at the end of a program, while it is a single thread.
+  static void cleanup();
+
+  /// Sets the timeout for the entire request, in milliseconds. A zero or
+  /// negative value means the request never times out.
+  void setTimeout(std::chrono::milliseconds Timeout);
+
+  /// Performs the Request, passing response data to the Handler. Returns all
+  /// errors which occur during the request. Aborts if an error is returned by a
+  /// Handler method.
+  Error perform(const HTTPRequest &Request, HTTPResponseHandler &Handler);
+
+  /// Performs the Request with the default BufferedHTTPResponseHandler, and
+  /// returns its HTTPResponseBuffer or an Error.
+  Expected<HTTPResponseBuffer> perform(const HTTPRequest &Request);
+
+  /// Performs an HTTPRequest with the default configuration to make a GET
+  /// request to the given Url. Returns an HTTPResponseBuffer or an Error.
+  Expected<HTTPResponseBuffer> get(StringRef Url);
+};
+
+} // end namespace llvm
+
+#endif // LLVM_SUPPORT_HTTP_CLIENT_H

diff  --git a/llvm/lib/Support/CMakeLists.txt b/llvm/lib/Support/CMakeLists.txt
index e6ec9684515b0..516dc4959fa52 100644
--- a/llvm/lib/Support/CMakeLists.txt
+++ b/llvm/lib/Support/CMakeLists.txt
@@ -155,6 +155,7 @@ add_llvm_component_library(LLVMSupport
   GlobPattern.cpp
   GraphWriter.cpp
   Hashing.cpp
+  HTTPClient.cpp
   InitLLVM.cpp
   InstructionCost.cpp
   IntEqClasses.cpp

diff  --git a/llvm/lib/Support/HTTPClient.cpp b/llvm/lib/Support/HTTPClient.cpp
new file mode 100644
index 0000000000000..68ba56d1fe502
--- /dev/null
+++ b/llvm/lib/Support/HTTPClient.cpp
@@ -0,0 +1,97 @@
+//===-- llvm/Support/HTTPClient.cpp - HTTP client 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 HTTPRequest, HTTPClient, and
+/// BufferedHTTPResponseHandler classes.
+///
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Support/HTTPClient.h"
+#include "llvm/ADT/APInt.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Errc.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/MemoryBuffer.h"
+
+using namespace llvm;
+
+HTTPRequest::HTTPRequest(StringRef Url) { this->Url = Url.str(); }
+
+bool operator==(const HTTPRequest &A, const HTTPRequest &B) {
+  return A.Url == B.Url && A.Method == B.Method &&
+         A.FollowRedirects == B.FollowRedirects;
+}
+
+HTTPResponseHandler::~HTTPResponseHandler() = default;
+
+static inline bool parseContentLengthHeader(StringRef LineRef,
+                                            size_t &ContentLength) {
+  // Content-Length is a mandatory header, and the only one we handle.
+  return LineRef.consume_front("Content-Length: ") &&
+         to_integer(LineRef.trim(), ContentLength, 10);
+}
+
+Error BufferedHTTPResponseHandler::handleHeaderLine(StringRef HeaderLine) {
+  if (ResponseBuffer.Body)
+    return Error::success();
+
+  size_t ContentLength;
+  if (parseContentLengthHeader(HeaderLine, ContentLength))
+    ResponseBuffer.Body =
+        WritableMemoryBuffer::getNewUninitMemBuffer(ContentLength);
+
+  return Error::success();
+}
+
+Error BufferedHTTPResponseHandler::handleBodyChunk(StringRef BodyChunk) {
+  if (!ResponseBuffer.Body)
+    return createStringError(errc::io_error,
+                             "Unallocated response buffer. HTTP Body data "
+                             "received before Content-Length header.");
+  if (Offset + BodyChunk.size() > ResponseBuffer.Body->getBufferSize())
+    return createStringError(errc::io_error,
+                             "Content size exceeds buffer size.");
+  memcpy(ResponseBuffer.Body->getBufferStart() + Offset, BodyChunk.data(),
+         BodyChunk.size());
+  Offset += BodyChunk.size();
+  return Error::success();
+}
+
+Error BufferedHTTPResponseHandler::handleStatusCode(unsigned Code) {
+  ResponseBuffer.Code = Code;
+  return Error::success();
+}
+
+Expected<HTTPResponseBuffer> HTTPClient::perform(const HTTPRequest &Request) {
+  BufferedHTTPResponseHandler Handler;
+  if (Error Err = perform(Request, Handler))
+    return std::move(Err);
+  return std::move(Handler.ResponseBuffer);
+}
+
+Expected<HTTPResponseBuffer> HTTPClient::get(StringRef Url) {
+  HTTPRequest Request(Url);
+  return perform(Request);
+}
+
+HTTPClient::HTTPClient() = default;
+
+HTTPClient::~HTTPClient() = default;
+
+bool HTTPClient::isAvailable() { return false; }
+
+void HTTPClient::cleanup() {}
+
+void HTTPClient::setTimeout(std::chrono::milliseconds Timeout) {}
+
+Error HTTPClient::perform(const HTTPRequest &Request,
+                          HTTPResponseHandler &Handler) {
+  llvm_unreachable("No HTTP Client implementation available.");
+}

diff  --git a/llvm/unittests/Support/CMakeLists.txt b/llvm/unittests/Support/CMakeLists.txt
index d331d1b4b187a..e3a57c9dbfc7a 100644
--- a/llvm/unittests/Support/CMakeLists.txt
+++ b/llvm/unittests/Support/CMakeLists.txt
@@ -41,6 +41,7 @@ add_llvm_unittest(SupportTests
   GlobPatternTest.cpp
   HashBuilderTest.cpp
   Host.cpp
+  HTTPClient.cpp
   IndexedAccessorTest.cpp
   InstructionCostTest.cpp
   ItaniumManglingCanonicalizerTest.cpp

diff  --git a/llvm/unittests/Support/HTTPClient.cpp b/llvm/unittests/Support/HTTPClient.cpp
new file mode 100644
index 0000000000000..30ae67e43ad83
--- /dev/null
+++ b/llvm/unittests/Support/HTTPClient.cpp
@@ -0,0 +1,88 @@
+//===-- llvm/unittest/Support/HTTPClient.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/Support/HTTPClient.h"
+#include "llvm/Support/Errc.h"
+#include "llvm/Testing/Support/Error.h"
+#include "gtest/gtest.h"
+
+using namespace llvm;
+
+TEST(BufferedHTTPResponseHandler, Lifecycle) {
+  BufferedHTTPResponseHandler Handler;
+  EXPECT_THAT_ERROR(Handler.handleHeaderLine("Content-Length: 36\r\n"),
+                    Succeeded());
+
+  EXPECT_THAT_ERROR(Handler.handleBodyChunk("body:"), Succeeded());
+  EXPECT_THAT_ERROR(Handler.handleBodyChunk("this puts the total at 36 chars"),
+                    Succeeded());
+  EXPECT_EQ(Handler.ResponseBuffer.Body->MemoryBuffer::getBuffer(),
+            "body:this puts the total at 36 chars");
+
+  // Additional content should be rejected by the handler.
+  EXPECT_THAT_ERROR(
+      Handler.handleBodyChunk("extra content past the content-length"),
+      Failed<llvm::StringError>());
+
+  // Test response code is set.
+  EXPECT_THAT_ERROR(Handler.handleStatusCode(200u), Succeeded());
+  EXPECT_EQ(Handler.ResponseBuffer.Code, 200u);
+  EXPECT_THAT_ERROR(Handler.handleStatusCode(400u), Succeeded());
+  EXPECT_EQ(Handler.ResponseBuffer.Code, 400u);
+}
+
+TEST(BufferedHTTPResponseHandler, NoContentLengthLifecycle) {
+  BufferedHTTPResponseHandler Handler;
+  EXPECT_EQ(Handler.ResponseBuffer.Code, 0u);
+  EXPECT_EQ(Handler.ResponseBuffer.Body, nullptr);
+
+  // A body chunk passed before the content-length header is an error.
+  EXPECT_THAT_ERROR(Handler.handleBodyChunk("body"),
+                    Failed<llvm::StringError>());
+  EXPECT_THAT_ERROR(Handler.handleHeaderLine("a header line"), Succeeded());
+  EXPECT_THAT_ERROR(Handler.handleBodyChunk("body"),
+                    Failed<llvm::StringError>());
+}
+
+TEST(BufferedHTTPResponseHandler, ZeroContentLength) {
+  BufferedHTTPResponseHandler Handler;
+  EXPECT_THAT_ERROR(Handler.handleHeaderLine("Content-Length: 0"), Succeeded());
+  EXPECT_NE(Handler.ResponseBuffer.Body, nullptr);
+  EXPECT_EQ(Handler.ResponseBuffer.Body->getBufferSize(), 0u);
+
+  // All content should be rejected by the handler.
+  EXPECT_THAT_ERROR(Handler.handleBodyChunk("non-empty body content"),
+                    Failed<llvm::StringError>());
+}
+
+TEST(BufferedHTTPResponseHandler, MalformedContentLength) {
+  // Check that several invalid content lengths are ignored.
+  BufferedHTTPResponseHandler Handler;
+  EXPECT_EQ(Handler.ResponseBuffer.Body, nullptr);
+  EXPECT_THAT_ERROR(Handler.handleHeaderLine("Content-Length: fff"),
+                    Succeeded());
+  EXPECT_EQ(Handler.ResponseBuffer.Body, nullptr);
+
+  EXPECT_THAT_ERROR(Handler.handleHeaderLine("Content-Length:    "),
+                    Succeeded());
+  EXPECT_EQ(Handler.ResponseBuffer.Body, nullptr);
+
+  using namespace std::string_literals;
+  EXPECT_THAT_ERROR(Handler.handleHeaderLine("Content-Length: \0\0\0"s),
+                    Succeeded());
+  EXPECT_EQ(Handler.ResponseBuffer.Body, nullptr);
+
+  EXPECT_THAT_ERROR(Handler.handleHeaderLine("Content-Length: -11"),
+                    Succeeded());
+  EXPECT_EQ(Handler.ResponseBuffer.Body, nullptr);
+
+  // All content should be rejected by the handler because no valid
+  // Content-Length header has been received.
+  EXPECT_THAT_ERROR(Handler.handleBodyChunk("non-empty body content"),
+                    Failed<llvm::StringError>());
+}


        


More information about the llvm-commits mailing list