forked from OSchip/llvm-project
[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
This commit is contained in:
parent
6703fe25b7
commit
170783f991
|
@ -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
|
|
@ -155,6 +155,7 @@ add_llvm_component_library(LLVMSupport
|
|||
GlobPattern.cpp
|
||||
GraphWriter.cpp
|
||||
Hashing.cpp
|
||||
HTTPClient.cpp
|
||||
InitLLVM.cpp
|
||||
InstructionCost.cpp
|
||||
IntEqClasses.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.");
|
||||
}
|
|
@ -41,6 +41,7 @@ add_llvm_unittest(SupportTests
|
|||
GlobPatternTest.cpp
|
||||
HashBuilderTest.cpp
|
||||
Host.cpp
|
||||
HTTPClient.cpp
|
||||
IndexedAccessorTest.cpp
|
||||
InstructionCostTest.cpp
|
||||
ItaniumManglingCanonicalizerTest.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>());
|
||||
}
|
Loading…
Reference in New Issue