Skip to content

Commit 56a9b65

Browse files
committed
[curl] Week 1: Add SendPutReq to RCurlConnection
1 parent 9fdfd5c commit 56a9b65

3 files changed

Lines changed: 121 additions & 0 deletions

File tree

net/curl/inc/ROOT/RCurlConnection.hxx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ public:
116116
/// a valid batching of requests into multiple multi-range requests takes place automatically.
117117
/// The fNBytesRecv member of the ranges is only well-defined on success.
118118
RStatus SendRangesReq(std::size_t N, RUserRange *ranges);
119+
/// Uploads data to the URL using an HTTP PUT request.
120+
RStatus SendPutReq(const unsigned char *data, std::size_t length);
119121

120122
const std::string &GetEscapedUrl() const { return fEscapedUrl; }
121123

net/curl/src/RCurlConnection.cxx

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -552,6 +552,22 @@ void ReverseDisplacements(std::vector<std::size_t> &displacements, ROOT::Interna
552552
}
553553
}
554554

555+
struct RPutReadState {
556+
const unsigned char *fData;
557+
std::size_t fLength;
558+
std::size_t fOffset = 0;
559+
};
560+
561+
std::size_t CallbackPutRead(char *buffer, std::size_t size, std::size_t nmemb, void *userdata)
562+
{
563+
auto *state = static_cast<RPutReadState *>(userdata);
564+
std::size_t remaining = state->fLength - state->fOffset;
565+
std::size_t toCopy = std::min(size * nmemb, remaining);
566+
memcpy(buffer, state->fData + state->fOffset, toCopy);
567+
state->fOffset += toCopy;
568+
return toCopy;
569+
}
570+
555571
std::string GetCurlErrorString(CURLcode code)
556572
{
557573
return std::string(curl_easy_strerror(code)) + " (" + std::to_string(code) + ")";
@@ -849,6 +865,42 @@ ROOT::Internal::RCurlConnection::SendRangesReq(std::size_t N, RUserRange *ranges
849865
return status;
850866
}
851867

868+
ROOT::Internal::RCurlConnection::RStatus
869+
ROOT::Internal::RCurlConnection::SendPutReq(const unsigned char *data, std::size_t length)
870+
{
871+
auto rc = curl_easy_setopt(fHandle, CURLOPT_UPLOAD, 1L);
872+
R__ASSERT(rc == CURLE_OK);
873+
rc = curl_easy_setopt(fHandle, CURLOPT_INFILESIZE_LARGE, static_cast<curl_off_t>(length));
874+
R__ASSERT(rc == CURLE_OK);
875+
876+
// Reset sticky options that may have been set by previous HEAD or GET calls on this handle
877+
rc = curl_easy_setopt(fHandle, CURLOPT_NOBODY, 0L);
878+
R__ASSERT(rc == CURLE_OK);
879+
rc = curl_easy_setopt(fHandle, CURLOPT_HTTPGET, 0L);
880+
R__ASSERT(rc == CURLE_OK);
881+
rc = curl_easy_setopt(fHandle, CURLOPT_RANGE, NULL);
882+
R__ASSERT(rc == CURLE_OK);
883+
884+
RPutReadState readState{data, length, 0};
885+
rc = curl_easy_setopt(fHandle, CURLOPT_READFUNCTION, CallbackPutRead);
886+
R__ASSERT(rc == CURLE_OK);
887+
rc = curl_easy_setopt(fHandle, CURLOPT_READDATA, &readState);
888+
R__ASSERT(rc == CURLE_OK);
889+
890+
RStatus status;
891+
Perform(status);
892+
893+
// Reset upload options so that subsequent GET/HEAD calls on this handle are not affected
894+
rc = curl_easy_setopt(fHandle, CURLOPT_UPLOAD, 0L);
895+
R__ASSERT(rc == CURLE_OK);
896+
rc = curl_easy_setopt(fHandle, CURLOPT_READFUNCTION, NULL);
897+
R__ASSERT(rc == CURLE_OK);
898+
rc = curl_easy_setopt(fHandle, CURLOPT_READDATA, NULL);
899+
R__ASSERT(rc == CURLE_OK);
900+
901+
return status;
902+
}
903+
852904
void ROOT::Internal::RCurlConnection::SetCredentials(const RS3Credentials &credentials)
853905
{
854906
ClearCredentials();

net/curl/test/curl_connection.cxx

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,50 @@
99
#include <string>
1010
#include <thread>
1111

12+
static void TaskRecvPut(TServerSocket *serverSocket, std::string *requestHeaders, std::string *requestBody)
13+
{
14+
requestHeaders->clear();
15+
requestBody->clear();
16+
auto sock = serverSocket->Accept();
17+
18+
const char *eof = "\r\n\r\n";
19+
const std::size_t eofLen = strlen(eof);
20+
std::size_t nextInEof = 0;
21+
char c;
22+
while (sock->RecvRaw(&c, 1)) {
23+
requestHeaders->push_back(c);
24+
if (c == eof[nextInEof]) {
25+
if (++nextInEof == eofLen)
26+
break;
27+
} else {
28+
nextInEof = 0;
29+
}
30+
}
31+
32+
// Parse Content-Length from headers
33+
std::size_t contentLength = 0;
34+
std::string clHeader = "Content-Length: ";
35+
auto pos = requestHeaders->find(clHeader);
36+
if (pos == std::string::npos) {
37+
clHeader = "content-length: ";
38+
pos = requestHeaders->find(clHeader);
39+
}
40+
if (pos != std::string::npos) {
41+
auto valStart = pos + clHeader.size();
42+
auto valEnd = requestHeaders->find("\r\n", valStart);
43+
contentLength = std::stoul(requestHeaders->substr(valStart, valEnd - valStart));
44+
}
45+
46+
if (contentLength > 0) {
47+
requestBody->resize(contentLength);
48+
sock->RecvRaw(&(*requestBody)[0], contentLength);
49+
}
50+
51+
const char *response = "HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n";
52+
sock->SendRaw(response, strlen(response));
53+
sock->Close();
54+
}
55+
1256
static void TaskRecv(TServerSocket *serverSocket, std::string *request)
1357
{
1458
request->clear();
@@ -63,3 +107,26 @@ TEST(RCurlConnection, Cred)
63107
threadRecv.join();
64108
EXPECT_EQ(std::string::npos, request.find("\r\nAuthorization: "));
65109
}
110+
111+
TEST(RCurlConnection, Put)
112+
{
113+
TServerSocket sock(0, false, TServerSocket::kDefaultBacklog, -1, ESocketBindOption::kInaddrLoopback);
114+
const std::string url =
115+
std::string("http://") + sock.GetLocalInetAddress().GetHostAddress() + ":" + std::to_string(sock.GetLocalPort());
116+
117+
const unsigned char payload[] = "Hello, S3!";
118+
const std::size_t payloadLen = sizeof(payload) - 1; // exclude null terminator
119+
120+
std::string headers;
121+
std::string body;
122+
std::thread threadRecv(TaskRecvPut, &sock, &headers, &body);
123+
124+
ROOT::Internal::RCurlConnection conn(url);
125+
auto status = conn.SendPutReq(payload, payloadLen);
126+
127+
threadRecv.join();
128+
EXPECT_TRUE(static_cast<bool>(status));
129+
EXPECT_EQ(0u, headers.find("PUT "));
130+
EXPECT_NE(std::string::npos, headers.find("Content-Length: " + std::to_string(payloadLen)));
131+
EXPECT_EQ(std::string(reinterpret_cast<const char *>(payload), payloadLen), body);
132+
}

0 commit comments

Comments
 (0)