XRootD
XrdClHttpOpTape.cc
Go to the documentation of this file.
1 /******************************************************************************/
2 /* */
3 /* X r d C l H t t p O p T a p e . c c */
4 /* */
5 /* (c) 2026 by the XRootD Collaboration */
6 /* */
7 /* This file is part of the XRootD software suite. */
8 /* */
9 /* XRootD is free software: you can redistribute it and/or modify it under */
10 /* the terms of the GNU Lesser General Public License as published by the */
11 /* Free Software Foundation, either version 3 of the License, or (at your */
12 /* option) any later version. */
13 /* */
14 /* XRootD is distributed in the hope that it will be useful, but WITHOUT */
15 /* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */
16 /* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public */
17 /* License for more details. */
18 /* */
19 /* You should have received a copy of the GNU Lesser General Public License */
20 /* along with XRootD in a file called COPYING.LESSER (LGPL license) and file */
21 /* COPYING (GPL license). If not, see <http://www.gnu.org/licenses/>. */
22 /* */
23 /******************************************************************************/
24 
25 #include "XrdClHttpOps.hh"
26 #include "XrdClHttpTape.hh"
27 #include "XrdClHttpWorker.hh"
28 
29 #include <XrdCl/XrdClAnyObject.hh>
30 
31 #include <cerrno>
32 #include <exception>
33 #include <stdexcept>
34 
35 using namespace XrdClHttp;
36 
38  const std::string &url, std::unique_ptr<TapeOperation> tape,
39  struct timespec timeout, XrdCl::Log *logger,
40  CreateConnCalloutType callout, HeaderCallout *header_callout)
41 :
42  CurlOperation(handler, url, timeout, logger, callout, header_callout),
43  m_tape(std::move(tape))
44 {
45 }
46 
48  const std::string &url, const std::vector<std::string> &file_list,
49  XrdCl::PrepareFlags::Flags flags, struct timespec timeout,
50  XrdCl::Log *logger, CreateConnCalloutType callout,
51  HeaderCallout *header_callout)
52 :
53  CurlTapeOp(handler, url,
54  std::make_unique<TapeOperation>(url, file_list, flags), timeout,
55  logger, callout, header_callout)
56 {
57 }
58 
60  const std::string &url, XrdCl::QueryCode::Code query_code,
61  const XrdCl::Buffer &arg, struct timespec timeout,
62  XrdCl::Log *logger, CreateConnCalloutType callout,
63  HeaderCallout *header_callout)
64 :
65  CurlTapeOp(handler, url,
66  std::make_unique<TapeOperation>(url, query_code, arg), timeout,
67  logger, callout, header_callout)
68 {
69 }
70 
71 CurlTapeOp::~CurlTapeOp() = default;
72 
73 bool
75 {
76  const auto failure = [&](uint16_t code, uint32_t error,
77  const std::string &message) {
78  if(!m_curl) m_curl.reset(curl);
79  Fail(code, error, message);
80  return false;
81  };
82 
83  try
84  {
85  const auto status = m_tape->Start(m_request);
86  if(!status.IsOK())
87  {
88  return failure(status.code, status.errNo,
89  status.GetErrorMessage());
90  }
91 
92  m_request_url = m_request.url;
93  if(!CurlOperation::Setup(curl, worker))
94  {
95  throw std::runtime_error(
96  "failed to initialize " + RequestDescription());
97  }
98  m_worker = &worker;
99  if(!ConfigureRequest())
100  {
101  throw std::runtime_error(
102  "failed to configure " + RequestDescription());
103  }
104  return true;
105  }
106  catch(const std::exception &ex)
107  {
108  return failure(XrdCl::errInternal, EIO, ex.what());
109  }
110 }
111 
112 bool
113 CurlTapeOp::ConfigureRequest()
114 {
115  if(!m_curl) return false;
116 
117  m_response.clear();
118  m_headers_list.emplace_back("Accept", "application/json");
119 
120  if(curl_easy_setopt(m_curl.get(), CURLOPT_WRITEFUNCTION,
121  CurlTapeOp::WriteCallback) != CURLE_OK
122  || curl_easy_setopt(m_curl.get(), CURLOPT_WRITEDATA, this) != CURLE_OK)
123  {
124  return false;
125  }
126 #if CURL_AT_LEAST_VERSION(7, 85, 0)
127  if(curl_easy_setopt(m_curl.get(), CURLOPT_PROTOCOLS_STR, "https,http")
128  != CURLE_OK
129  || curl_easy_setopt(m_curl.get(), CURLOPT_REDIR_PROTOCOLS_STR,
130  "https,http") != CURLE_OK)
131  {
132  return false;
133  }
134 #else
135  const long protocols = CURLPROTO_HTTP | CURLPROTO_HTTPS;
136  if(curl_easy_setopt(m_curl.get(), CURLOPT_PROTOCOLS, protocols) != CURLE_OK
137  || curl_easy_setopt(m_curl.get(), CURLOPT_REDIR_PROTOCOLS, protocols)
138  != CURLE_OK)
139  {
140  return false;
141  }
142 #endif
143 
144  switch(m_request.method)
145  {
146  case HttpVerb::GET:
147  if(curl_easy_setopt(m_curl.get(), CURLOPT_HTTPGET, 1L) != CURLE_OK)
148  return false;
149  break;
150  case HttpVerb::POST:
151  m_headers_list.emplace_back("Content-Type", "application/json");
152  if(curl_easy_setopt(m_curl.get(), CURLOPT_POST, 1L) != CURLE_OK
153  || curl_easy_setopt(m_curl.get(), CURLOPT_POSTFIELDS,
154  m_request.body.c_str()) != CURLE_OK
155  || curl_easy_setopt(m_curl.get(), CURLOPT_POSTFIELDSIZE_LARGE,
156  static_cast<curl_off_t>(m_request.body.size())) != CURLE_OK)
157  {
158  return false;
159  }
160  break;
161  case HttpVerb::DELETE:
162  if(curl_easy_setopt(m_curl.get(), CURLOPT_CUSTOMREQUEST, "DELETE")
163  != CURLE_OK)
164  return false;
165  break;
166  default:
167  return false;
168  }
169  return true;
170 }
171 
172 void
174 {
175  try
176  {
177  TapeHttpRequest nextRequest;
178  std::string response;
179  bool complete = false;
180  const auto status = m_tape->Advance(
181  m_curl.get(), GetStatusCode(), m_response,
182  nextRequest, response, complete);
183  if(!status.IsOK())
184  {
185  Fail(status.code, status.errNo, status.GetErrorMessage());
186  return;
187  }
188  if(complete)
189  {
190  Complete(response);
191  return;
192  }
193 
194  m_request = std::move(nextRequest);
195  if(!m_worker)
196  {
197  throw std::runtime_error(
198  "worker unavailable before starting " + RequestDescription());
199  }
200  if(!SetupNextRequest(m_request.url, *m_worker))
201  {
202  throw std::runtime_error(
203  "failed to reset the curl handle for " + RequestDescription());
204  }
205  if(!ConfigureRequest())
206  {
207  throw std::runtime_error(
208  "failed to configure " + RequestDescription());
209  }
210  if(!FinishSetup(m_curl.get()))
211  {
212  throw std::runtime_error(
213  "failed to configure headers for " + RequestDescription());
214  }
215  }
216  catch(const std::exception &ex)
217  {
218  Fail(XrdCl::errInternal, EIO, ex.what());
219  }
220 }
221 
222 void
223 CurlTapeOp::Fail(uint16_t errCode, uint32_t errNum,
224  const std::string &message)
225 {
226  std::string detail = message;
227  if(GetStatusCode() >= 400)
228  {
229  detail = RequestDescription() + " failed: "
230  + TapeProblemResponse(GetStatusCode(), m_response);
231  }
232  CurlOperation::Fail(errCode, errNum, detail);
233 }
234 
237 {
238  return m_request.method;
239 }
240 
241 std::string
242 CurlTapeOp::RequestDescription() const
243 {
244  return "Tape REST " + GetVerbString(GetVerb()) + " " + m_request.url;
245 }
246 
247 void
249 {
250  if(m_curl == nullptr) return;
251 
252  curl_easy_setopt(m_curl.get(), CURLOPT_ERRORBUFFER, nullptr);
253  curl_easy_setopt(m_curl.get(), CURLOPT_WRITEFUNCTION, nullptr);
254  curl_easy_setopt(m_curl.get(), CURLOPT_WRITEDATA, nullptr);
255  curl_easy_setopt(m_curl.get(), CURLOPT_HTTPHEADER, nullptr);
256  curl_easy_setopt(m_curl.get(), CURLOPT_POST, 0L);
257  curl_easy_setopt(m_curl.get(), CURLOPT_POSTFIELDS, nullptr);
258  curl_easy_setopt(m_curl.get(), CURLOPT_POSTFIELDSIZE_LARGE,
259  static_cast<curl_off_t>(0));
260  curl_easy_setopt(m_curl.get(), CURLOPT_CUSTOMREQUEST, nullptr);
262 }
263 
264 size_t
265 CurlTapeOp::WriteCallback(char *buffer, size_t size, size_t nitems, void *data)
266 {
267  return static_cast<CurlTapeOp *>(data)->Write(buffer, size * nitems);
268 }
269 
270 size_t
271 CurlTapeOp::Write(char *buffer, size_t size)
272 {
273  try
274  {
275  m_response.append(buffer, size);
276  UpdateBytes(size);
277  return size;
278  }
279  catch(const std::exception &ex)
280  {
282  std::string("while buffering a Tape REST response: ") + ex.what());
283  }
284  catch(...)
285  {
287  "unknown exception while buffering a Tape REST response");
288  }
289 }
290 
291 void
292 CurlTapeOp::Complete(const std::string &response)
293 {
294  if(m_handler == nullptr)
295  {
296  SetDone(false);
297  return;
298  }
299 
300  auto buffer = std::make_unique<XrdCl::Buffer>();
301  buffer->FromString(response);
302 
303  auto obj = std::make_unique<XrdCl::AnyObject>();
304  auto status = std::make_unique<XrdCl::XRootDStatus>();
305  obj->Set(buffer.release());
306 
307  SetDone(false);
308  auto handle = m_handler;
309  m_handler = nullptr;
310  handle->HandleResponse(status.release(), obj.release());
311 }
@ kXR_ServerError
Definition: XProtocol.hh:1044
void CURL
void SetDone(bool has_failed)
int FailCallback(XErrorCode ecode, const std::string &emsg)
bool FinishSetup(CURL *curl)
std::unique_ptr< CURL, void(*)(CURL *)> m_curl
virtual void Fail(uint16_t errCode, uint32_t errNum, const std::string &)
static const std::string GetVerbString(HttpVerb)
virtual void ReleaseHandle()
void UpdateBytes(uint64_t bytes)
bool SetupNextRequest(const std::string &url, CurlWorker &worker)
std::vector< std::pair< std::string, std::string > > m_headers_list
XrdClHttp::HttpVerb HttpVerb
Definition: XrdClHttpOps.hh:63
XrdCl::ResponseHandler * m_handler
virtual bool Setup(CURL *curl, CurlWorker &)
void Success() override
HttpVerb GetVerb() const override
bool Setup(CURL *curl, CurlWorker &worker) override
CurlTapeOp(XrdCl::ResponseHandler *handler, const std::string &url, std::unique_ptr< TapeOperation > tape, struct timespec timeout, XrdCl::Log *logger, CreateConnCalloutType callout, HeaderCallout *header_callout)
void Fail(uint16_t errCode, uint32_t errNum, const std::string &message) override
void ReleaseHandle() override
CurlTapePrepareOp(XrdCl::ResponseHandler *handler, const std::string &url, const std::vector< std::string > &file_list, XrdCl::PrepareFlags::Flags flags, struct timespec timeout, XrdCl::Log *logger, CreateConnCalloutType callout, HeaderCallout *header_callout)
CurlTapeQueryOp(XrdCl::ResponseHandler *handler, const std::string &url, XrdCl::QueryCode::Code query_code, const XrdCl::Buffer &arg, struct timespec timeout, XrdCl::Log *logger, CreateConnCalloutType callout, HeaderCallout *header_callout)
Binary blob representation.
Definition: XrdClBuffer.hh:34
Handle diagnostics.
Definition: XrdClLog.hh:101
Handle an async response.
virtual void HandleResponse(XRootDStatus *status, AnyObject *response)
std::string TapeProblemResponse(long statusCode, const std::string &body)
const uint16_t errInternal
Internal error.
Definition: XrdClStatus.hh:56
ConnectionCallout *(*)(const std::string &, const ResponseInfo &) CreateConnCalloutType
Code
XRootD query request codes.