Skip to content

Commit 08a8edc

Browse files
authored
Merge pull request #104 from whwjiang/main
Support streaming writes against HTTP backends
2 parents d8c59a8 + 48d6cf3 commit 08a8edc

File tree

7 files changed

+397
-57
lines changed

7 files changed

+397
-57
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
build/
2+
.vscode/

src/HTTPCommands.cc

Lines changed: 73 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/***************************************************************
22
*
3-
* Copyright (C) 2024, Pelican Project, Morgridge Institute for Research
3+
* Copyright (C) 2025, Pelican Project, Morgridge Institute for Research
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License"); you
66
* may not use this file except in compliance with the License. You may
@@ -277,6 +277,15 @@ size_t HTTPRequest::ReadCallback(char *buffer, size_t size, size_t n, void *v) {
277277
return CURL_READFUNC_ABORT;
278278
}
279279

280+
if (payload->m_parent.m_log.getMsgMask() & LogMask::Dump) {
281+
payload->m_parent.m_log.Log(
282+
LogMask::Dump, "ReadCallback",
283+
("sentSoFar=" + std::to_string(payload->sentSoFar) +
284+
" data.size=" + std::to_string(payload->data.size()) +
285+
" final=" + std::to_string(payload->final))
286+
.c_str());
287+
}
288+
280289
if (payload->sentSoFar == static_cast<off_t>(payload->data.size())) {
281290
payload->sentSoFar = 0;
282291
if (payload->final) {
@@ -778,19 +787,30 @@ void HTTPRequest::ProcessCurlResult(CURL *curl, CURLcode rv) {
778787

779788
HTTPUpload::~HTTPUpload() {}
780789

781-
bool HTTPUpload::SendRequest(const std::string &payload, off_t offset,
782-
size_t size) {
783-
if (offset != 0 || size != 0) {
784-
std::string range;
785-
formatstr(range, "bytes=%lld-%lld", static_cast<long long int>(offset),
786-
static_cast<long long int>(offset + size - 1));
787-
headers["Range"] = range.c_str();
788-
}
789-
790+
bool HTTPUpload::SendRequest(const std::string &payload) {
790791
httpVerb = "PUT";
792+
expectedResponseCode = 201;
791793
return SendHTTPRequest(payload);
792794
}
793795

796+
bool HTTPUpload::StartStreamingRequest(const std::string_view payload,
797+
off_t object_size) {
798+
httpVerb = "PUT";
799+
expectedResponseCode = 201;
800+
headers["Content-Type"] = "binary/octet-stream";
801+
return sendPreparedRequest(hostUrl, payload, object_size, false);
802+
}
803+
804+
bool HTTPUpload::ContinueStreamingRequest(const std::string_view payload,
805+
off_t object_size, bool final) {
806+
// Note that despite the fact that final gets passed through here,
807+
// in reality the way that curl determines whether the data transfer is
808+
// done is by seeing if the total amount of data sent is equal to the
809+
// expected size of the entire payload, stored in m_object_size.
810+
// See HTTPRequest::ReadCallback for more info
811+
return sendPreparedRequest(hostUrl, payload, object_size, final);
812+
}
813+
794814
void HTTPRequest::Init(XrdSysError &log) {
795815
if (!m_workers_initialized) {
796816
for (unsigned idx = 0; idx < CurlWorker::GetPollThreads(); idx++) {
@@ -837,3 +857,46 @@ bool HTTPHead::SendRequest() {
837857
}
838858

839859
// ---------------------------------------------------------------------------
860+
861+
int HTTPRequest::HandleHTTPError(const HTTPRequest &request, XrdSysError &log,
862+
const char *operation, const char *context) {
863+
auto httpCode = request.getResponseCode();
864+
if (httpCode) {
865+
std::stringstream ss;
866+
ss << operation << " failed: " << request.getResponseCode() << ": "
867+
<< request.getResultString();
868+
if (context) {
869+
ss << " (context: " << context << ")";
870+
}
871+
log.Log(LogMask::Warning, "HTTPRequest::HandleHTTPError",
872+
ss.str().c_str());
873+
874+
switch (httpCode) {
875+
case 404:
876+
return -ENOENT;
877+
case 500:
878+
return -EIO;
879+
case 403:
880+
return -EPERM;
881+
case 401:
882+
return -EACCES;
883+
case 400:
884+
return -EINVAL;
885+
case 503:
886+
return -EAGAIN;
887+
default:
888+
return -EIO;
889+
}
890+
} else {
891+
std::stringstream ss;
892+
ss << "Failed to send " << operation
893+
<< " command: " << request.getErrorCode() << ": "
894+
<< request.getErrorMessage();
895+
if (context) {
896+
ss << " (context: " << context << ")";
897+
}
898+
log.Log(LogMask::Warning, "HTTPRequest::HandleHTTPError",
899+
ss.str().c_str());
900+
return -EIO;
901+
}
902+
}

src/HTTPCommands.hh

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/***************************************************************
22
*
3-
* Copyright (C) 2024, Pelican Project, Morgridge Institute for Research
3+
* Copyright (C) 2025, Pelican Project, Morgridge Institute for Research
44
*
55
* Licensed under the Apache License, Version 2.0 (the "License"); you
66
* may not use this file except in compliance with the License. You may
@@ -95,6 +95,19 @@ class HTTPRequest {
9595
return m_timeout_duration;
9696
}
9797

98+
// Handle HTTP request errors and convert them to appropriate POSIX error codes.
99+
// This function can be used anywhere HTTP requests are made to provide
100+
// consistent error handling.
101+
//
102+
// - request: The HTTPRequest object that was used for the request
103+
// - log: The logger instance for error reporting
104+
// - operation: A string describing the operation being performed (for logging)
105+
// - context: Additional context information (for logging)
106+
//
107+
// Returns: A POSIX error code (-ENOENT, -EIO, -EPERM, etc.) or 0 if no error
108+
static int HandleHTTPError(const HTTPRequest &request, XrdSysError &log,
109+
const char *operation, const char *context = nullptr);
110+
98111
protected:
99112
// Send the request to the HTTP server.
100113
// Blocks until the request has completed.
@@ -295,8 +308,23 @@ class HTTPUpload : public HTTPRequest {
295308

296309
virtual ~HTTPUpload();
297310

298-
virtual bool SendRequest(const std::string &payload, off_t offset,
299-
size_t size);
311+
virtual bool SendRequest(const std::string &payload);
312+
313+
// Start a streaming request.
314+
//
315+
// - payload: The payload contents when uploading.
316+
// - object_size: Size of the entire upload payload.
317+
bool StartStreamingRequest(const std::string_view payload,
318+
off_t object_size);
319+
320+
// Continue a streaming request.
321+
//
322+
// - payload: The payload contents when uploading.
323+
// - object_size: Size of the entire upload payload.
324+
// - final: True if this is the last or only payload for the request. False
325+
// otherwise.
326+
bool ContinueStreamingRequest(const std::string_view payload,
327+
off_t object_size, bool final);
300328

301329
protected:
302330
std::string object;

0 commit comments

Comments
 (0)