[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