Skip to content

Commit e0b259f

Browse files
committed
[llvm] [Support] Add CURL HTTP Client.
Provides an implementation of `HTTPClient` that wraps libcurl. Reviewed By: dblaikie Differential Revision: https://reviews.llvm.org/D112753
1 parent ef8e9be commit e0b259f

File tree

7 files changed

+141
-1
lines changed

7 files changed

+141
-1
lines changed

llvm/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,8 @@ endif()
392392

393393
set(LLVM_ENABLE_ZLIB "ON" CACHE STRING "Use zlib for compression/decompression if available. Can be ON, OFF, or FORCE_ON")
394394

395+
set(LLVM_ENABLE_CURL "ON" CACHE STRING "Use libcurl for the HTTP client if available. Can be ON, OFF, or FORCE_ON")
396+
395397
set(LLVM_Z3_INSTALL_DIR "" CACHE STRING "Install directory of the Z3 solver.")
396398

397399
option(LLVM_ENABLE_Z3_SOLVER

llvm/include/llvm/Config/llvm-config.h.cmake

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@
8585
/* Define if we have z3 and want to build it */
8686
#cmakedefine LLVM_WITH_Z3 ${LLVM_WITH_Z3}
8787

88+
/* Define if we have curl and want to use it */
89+
#cmakedefine LLVM_ENABLE_CURL ${LLVM_ENABLE_CURL}
90+
8891
/* Define if LLVM was built with a dependency to the libtensorflow dynamic library */
8992
#cmakedefine LLVM_HAVE_TF_API
9093

llvm/include/llvm/Support/HTTPClient.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ class BufferedHTTPResponseHandler final : public HTTPResponseHandler {
7777

7878
/// A reusable client that can perform HTTPRequests through a network socket.
7979
class HTTPClient {
80+
#ifdef LLVM_ENABLE_CURL
81+
void *Curl = nullptr;
82+
static bool IsInitialized;
83+
#endif
84+
8085
public:
8186
HTTPClient();
8287
~HTTPClient();

llvm/lib/Support/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,11 @@ if(LLVM_WITH_Z3)
7474
set(system_libs ${system_libs} ${Z3_LIBRARIES})
7575
endif()
7676

77+
# Link LibCURL if the user wants it
78+
if (LLVM_ENABLE_CURL)
79+
set(system_libs ${system_libs} ${CURL_LIBRARIES})
80+
endif()
81+
7782
# Override the C runtime allocator on Windows and embed it into LLVM tools & libraries
7883
if(LLVM_INTEGRATED_CRT_ALLOC)
7984
if (CMAKE_BUILD_TYPE AND NOT ${LLVM_USE_CRT_${uppercase_CMAKE_BUILD_TYPE}} MATCHES "^(MT|MTd)$")

llvm/lib/Support/HTTPClient.cpp

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
#include "llvm/Support/Errc.h"
2020
#include "llvm/Support/Error.h"
2121
#include "llvm/Support/MemoryBuffer.h"
22+
#ifdef LLVM_ENABLE_CURL
23+
#include <curl/curl.h>
24+
#endif
2225

2326
using namespace llvm;
2427

@@ -81,12 +84,120 @@ Expected<HTTPResponseBuffer> HTTPClient::get(StringRef Url) {
8184
return perform(Request);
8285
}
8386

87+
#ifdef LLVM_ENABLE_CURL
88+
89+
bool HTTPClient::isAvailable() { return true; }
90+
91+
bool HTTPClient::IsInitialized = false;
92+
93+
void HTTPClient::initialize() {
94+
if (!IsInitialized) {
95+
curl_global_init(CURL_GLOBAL_ALL);
96+
IsInitialized = true;
97+
}
98+
}
99+
100+
void HTTPClient::cleanup() {
101+
if (IsInitialized) {
102+
curl_global_cleanup();
103+
IsInitialized = false;
104+
}
105+
}
106+
107+
void HTTPClient::setTimeout(std::chrono::milliseconds Timeout) {
108+
if (Timeout < std::chrono::milliseconds(0))
109+
Timeout = std::chrono::milliseconds(0);
110+
curl_easy_setopt(Curl, CURLOPT_TIMEOUT_MS, Timeout.count());
111+
}
112+
113+
/// CurlHTTPRequest and the curl{Header,Write}Function are implementation
114+
/// details used to work with Curl. Curl makes callbacks with a single
115+
/// customizable pointer parameter.
116+
struct CurlHTTPRequest {
117+
CurlHTTPRequest(HTTPResponseHandler &Handler) : Handler(Handler) {}
118+
void storeError(Error Err) {
119+
ErrorState = joinErrors(std::move(Err), std::move(ErrorState));
120+
}
121+
HTTPResponseHandler &Handler;
122+
llvm::Error ErrorState = Error::success();
123+
};
124+
125+
static size_t curlHeaderFunction(char *Contents, size_t Size, size_t NMemb,
126+
CurlHTTPRequest *CurlRequest) {
127+
assert(Size == 1 && "The Size passed by libCURL to CURLOPT_HEADERFUNCTION "
128+
"should always be 1.");
129+
if (Error Err =
130+
CurlRequest->Handler.handleHeaderLine(StringRef(Contents, NMemb))) {
131+
CurlRequest->storeError(std::move(Err));
132+
return 0;
133+
}
134+
return NMemb;
135+
}
136+
137+
static size_t curlWriteFunction(char *Contents, size_t Size, size_t NMemb,
138+
CurlHTTPRequest *CurlRequest) {
139+
Size *= NMemb;
140+
if (Error Err =
141+
CurlRequest->Handler.handleBodyChunk(StringRef(Contents, Size))) {
142+
CurlRequest->storeError(std::move(Err));
143+
return 0;
144+
}
145+
return Size;
146+
}
147+
148+
HTTPClient::HTTPClient() {
149+
assert(IsInitialized &&
150+
"Must call HTTPClient::initialize() at the beginning of main().");
151+
if (Curl)
152+
return;
153+
assert((Curl = curl_easy_init()) && "Curl could not be initialized.");
154+
// Set the callback hooks.
155+
curl_easy_setopt(Curl, CURLOPT_WRITEFUNCTION, curlWriteFunction);
156+
curl_easy_setopt(Curl, CURLOPT_HEADERFUNCTION, curlHeaderFunction);
157+
}
158+
159+
HTTPClient::~HTTPClient() { curl_easy_cleanup(Curl); }
160+
161+
Error HTTPClient::perform(const HTTPRequest &Request,
162+
HTTPResponseHandler &Handler) {
163+
if (Request.Method != HTTPMethod::GET)
164+
return createStringError(errc::invalid_argument,
165+
"Unsupported CURL request method.");
166+
167+
SmallString<128> Url = Request.Url;
168+
curl_easy_setopt(Curl, CURLOPT_URL, Url.c_str());
169+
curl_easy_setopt(Curl, CURLOPT_FOLLOWLOCATION, Request.FollowRedirects);
170+
171+
CurlHTTPRequest CurlRequest(Handler);
172+
curl_easy_setopt(Curl, CURLOPT_WRITEDATA, &CurlRequest);
173+
curl_easy_setopt(Curl, CURLOPT_HEADERDATA, &CurlRequest);
174+
CURLcode CurlRes = curl_easy_perform(Curl);
175+
if (CurlRes != CURLE_OK)
176+
return joinErrors(std::move(CurlRequest.ErrorState),
177+
createStringError(errc::io_error,
178+
"curl_easy_perform() failed: %s\n",
179+
curl_easy_strerror(CurlRes)));
180+
if (CurlRequest.ErrorState)
181+
return std::move(CurlRequest.ErrorState);
182+
183+
unsigned Code;
184+
curl_easy_getinfo(Curl, CURLINFO_RESPONSE_CODE, &Code);
185+
if (Error Err = Handler.handleStatusCode(Code))
186+
return joinErrors(std::move(CurlRequest.ErrorState), std::move(Err));
187+
188+
return std::move(CurlRequest.ErrorState);
189+
}
190+
191+
#else
192+
84193
HTTPClient::HTTPClient() = default;
85194

86195
HTTPClient::~HTTPClient() = default;
87196

88197
bool HTTPClient::isAvailable() { return false; }
89198

199+
void HTTPClient::initialize() {}
200+
90201
void HTTPClient::cleanup() {}
91202

92203
void HTTPClient::setTimeout(std::chrono::milliseconds Timeout) {}
@@ -95,3 +206,5 @@ Error HTTPClient::perform(const HTTPRequest &Request,
95206
HTTPResponseHandler &Handler) {
96207
llvm_unreachable("No HTTP Client implementation available.");
97208
}
209+
210+
#endif

llvm/lib/Support/InitLLVM.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include "llvm/Support/InitLLVM.h"
1010
#include "llvm/Support/Error.h"
11+
#include "llvm/Support/HTTPClient.h"
1112
#include "llvm/Support/ManagedStatic.h"
1213
#include "llvm/Support/PrettyStackTrace.h"
1314
#include "llvm/Support/Process.h"
@@ -58,6 +59,11 @@ InitLLVM::InitLLVM(int &Argc, const char **&Argv,
5859
Argc = Args.size() - 1;
5960
Argv = Args.data();
6061
#endif
62+
63+
HTTPClient::initialize();
6164
}
6265

63-
InitLLVM::~InitLLVM() { llvm_shutdown(); }
66+
InitLLVM::~InitLLVM() {
67+
HTTPClient::cleanup();
68+
llvm_shutdown();
69+
}

llvm/unittests/Support/HTTPClient.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,9 @@ TEST(BufferedHTTPResponseHandler, MalformedContentLength) {
8686
EXPECT_THAT_ERROR(Handler.handleBodyChunk("non-empty body content"),
8787
Failed<llvm::StringError>());
8888
}
89+
90+
#ifdef LLVM_ENABLE_CURL
91+
92+
TEST(HTTPClient, isAvailable) { EXPECT_TRUE(HTTPClient::isAvailable()); }
93+
94+
#endif

0 commit comments

Comments
 (0)