7#include <packager/file/http_file.h>
9#include <absl/flags/declare.h>
10#include <absl/flags/flag.h>
11#include <absl/log/check.h>
12#include <absl/log/log.h>
13#include <absl/log/vlog_is_on.h>
14#include <absl/strings/escaping.h>
15#include <absl/strings/str_format.h>
18#include <packager/file/file_closer.h>
19#include <packager/file/thread_pool.h>
20#include <packager/macros/compiler.h>
21#include <packager/macros/logging.h>
22#include <packager/version/version.h>
27 "Set a custom User-Agent string for HTTP requests.");
31 "Absolute path to the Certificate Authority file for the "
32 "server cert. PEM format");
36 "Absolute path to client certificate file.");
38 client_cert_private_key_file,
40 "Absolute path to the Private Key file.");
42 client_cert_private_key_password,
44 "Password to the private key file.");
46 disable_peer_verification,
48 "Disable peer verification. This is needed to talk to servers "
49 "without valid certificates.");
51 ignore_http_output_failures,
53 "Ignore HTTP output failures. Can help recover from live stream "
56ABSL_DECLARE_FLAG(uint64_t, io_cache_size);
62constexpr const char* kBinaryContentType =
"application/octet-stream";
63constexpr const int kMinLogLevelForCurlDebugFunction = 2;
65size_t CurlWriteCallback(
char* buffer,
size_t size,
size_t nmemb,
void* user) {
66 IoCache* cache =
reinterpret_cast<IoCache*
>(user);
67 size_t length = size * nmemb;
69 length = cache->Write(buffer, length);
70 VLOG(3) <<
"CurlWriteCallback length=" << length;
78size_t CurlReadCallback(
char* buffer,
size_t size,
size_t nitems,
void* user) {
79 IoCache* cache =
reinterpret_cast<IoCache*
>(user);
80 size_t length = cache->Read(buffer, size * nitems);
81 VLOG(3) <<
"CurlRead length=" << length;
85int CurlDebugCallback(CURL* ,
90 const char* type_text;
95 type_text =
"== Info";
96 log_level = kMinLogLevelForCurlDebugFunction + 1;
99 case CURLINFO_HEADER_IN:
100 type_text =
"<= Recv header";
101 log_level = kMinLogLevelForCurlDebugFunction;
104 case CURLINFO_HEADER_OUT:
105 type_text =
"=> Send header";
106 log_level = kMinLogLevelForCurlDebugFunction;
109 case CURLINFO_DATA_IN:
110 type_text =
"<= Recv data";
111 log_level = kMinLogLevelForCurlDebugFunction + 1;
114 case CURLINFO_DATA_OUT:
115 type_text =
"=> Send data";
116 log_level = kMinLogLevelForCurlDebugFunction + 1;
119 case CURLINFO_SSL_DATA_IN:
120 type_text =
"<= Recv SSL data";
121 log_level = kMinLogLevelForCurlDebugFunction + 2;
124 case CURLINFO_SSL_DATA_OUT:
125 type_text =
"=> Send SSL data";
126 log_level = kMinLogLevelForCurlDebugFunction + 2;
134 const std::string data_string(data, size);
135 VLOG(log_level) <<
"\n\n"
136 << type_text <<
" (0x" << std::hex << size << std::dec
138 << (in_hex ? absl::BytesToHexString(data_string)
143class LibCurlInitializer {
145 LibCurlInitializer() { curl_global_init(CURL_GLOBAL_DEFAULT); }
147 ~LibCurlInitializer() { curl_global_cleanup(); }
149 LibCurlInitializer(
const LibCurlInitializer&) =
delete;
150 LibCurlInitializer& operator=(
const LibCurlInitializer&) =
delete;
153template <
typename List>
154bool AppendHeader(
const std::string& header, List* list) {
155 auto* temp = curl_slist_append(list->get(), header.c_str());
167HttpFile::HttpFile(HttpMethod method,
const std::string& url)
168 : HttpFile(method, url, kBinaryContentType, {}, 0) {}
170HttpFile::HttpFile(HttpMethod method,
171 const std::string& url,
172 const std::string& upload_content_type,
173 const std::vector<std::string>& headers,
174 int32_t timeout_in_seconds)
177 upload_content_type_(upload_content_type),
178 timeout_in_seconds_(timeout_in_seconds),
180 isUpload_(method == HttpMethod::kPut || method == HttpMethod::kPost),
181 download_cache_(absl::GetFlag(FLAGS_io_cache_size)),
182 upload_cache_(absl::GetFlag(FLAGS_io_cache_size)),
183 curl_(curl_easy_init()),
185 user_agent_(absl::GetFlag(FLAGS_user_agent)),
186 ca_file_(absl::GetFlag(FLAGS_ca_file)),
187 client_cert_file_(absl::GetFlag(FLAGS_client_cert_file)),
188 client_cert_private_key_file_(
189 absl::GetFlag(FLAGS_client_cert_private_key_file)),
190 client_cert_private_key_password_(
191 absl::GetFlag(FLAGS_client_cert_private_key_password)) {
192 static LibCurlInitializer lib_curl_initializer;
193 if (user_agent_.empty()) {
194 user_agent_ +=
"ShakaPackager/" + GetPackagerVersion();
201 std::unique_ptr<curl_slist, CurlDelete> temp_headers;
202 if (!AppendHeader(
"Expect:", &temp_headers))
204 if (!upload_content_type.empty() &&
205 !AppendHeader(
"Content-Type: " + upload_content_type_, &temp_headers)) {
208 if (isUpload_ && !AppendHeader(
"Transfer-Encoding: chunked", &temp_headers)) {
211 for (
const auto& item : headers) {
212 if (!AppendHeader(item, &temp_headers)) {
216 request_headers_ = std::move(temp_headers);
219HttpFile::~HttpFile() {}
222bool HttpFile::Delete(
const std::string& url) {
223 std::unique_ptr<HttpFile, FileCloser> file(
224 new HttpFile(HttpMethod::kDelete, url));
228 return file.release()->Close();
231bool HttpFile::Open() {
232 VLOG(2) <<
"Opening " << url_;
234 if (!curl_ || !request_headers_) {
235 LOG(ERROR) <<
"curl_easy_init() failed.";
243 ThreadPool::instance.PostTask(std::bind(&HttpFile::ThreadMain,
this));
248Status HttpFile::CloseWithStatus() {
249 VLOG(2) <<
"Closing " << url_;
256 upload_cache_.Close();
257 task_exit_event_.WaitForNotification();
259 const Status result = status_;
260 LOG_IF(ERROR, !result.ok()) <<
"HttpFile request failed: " << result;
262 return absl::GetFlag(FLAGS_ignore_http_output_failures) ? Status::OK : result;
265bool HttpFile::Close() {
266 return CloseWithStatus().ok();
269int64_t HttpFile::Read(
void* buffer, uint64_t length) {
270 VLOG(2) <<
"Reading from " << url_ <<
", length=" << length;
271 return download_cache_.Read(buffer, length);
274int64_t HttpFile::Write(
const void* buffer, uint64_t length) {
275 DCHECK(!upload_cache_.closed());
276 VLOG(2) <<
"Writing to " << url_ <<
", length=" << length;
277 return upload_cache_.Write(buffer, length);
280void HttpFile::CloseForWriting() {
281 VLOG(2) <<
"Closing further writes to " << url_;
282 upload_cache_.Close();
285int64_t HttpFile::Size() {
286 VLOG(1) <<
"HttpFile does not support Size().";
290bool HttpFile::Flush() {
292 upload_cache_.WaitUntilEmptyOrClosed();
296bool HttpFile::Seek(uint64_t position) {
298 LOG(ERROR) <<
"HttpFile does not support Seek().";
302bool HttpFile::Tell(uint64_t* position) {
304 LOG(ERROR) <<
"HttpFile does not support Tell().";
308void HttpFile::CurlDelete::operator()(CURL* curl) {
309 curl_easy_cleanup(curl);
312void HttpFile::CurlDelete::operator()(curl_slist* headers) {
313 curl_slist_free_all(headers);
316void HttpFile::SetupRequest() {
317 auto* curl = curl_.get();
320 case HttpMethod::kGet:
321 curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
323 case HttpMethod::kPost:
324 curl_easy_setopt(curl, CURLOPT_POST, 1L);
326 case HttpMethod::kPut:
327 curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L);
329 case HttpMethod::kDelete:
330 curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST,
"DELETE");
334 curl_easy_setopt(curl, CURLOPT_URL, url_.c_str());
335 curl_easy_setopt(curl, CURLOPT_USERAGENT, user_agent_.c_str());
336 curl_easy_setopt(curl, CURLOPT_TIMEOUT, timeout_in_seconds_);
337 curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
338 curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
339 curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &CurlWriteCallback);
340 curl_easy_setopt(curl, CURLOPT_WRITEDATA, &download_cache_);
342 curl_easy_setopt(curl, CURLOPT_READFUNCTION, &CurlReadCallback);
343 curl_easy_setopt(curl, CURLOPT_READDATA, &upload_cache_);
346 curl_easy_setopt(curl, CURLOPT_HTTPHEADER, request_headers_.get());
348 if (absl::GetFlag(FLAGS_disable_peer_verification))
349 curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
352 if (!client_cert_private_key_file_.empty() && !client_cert_file_.empty()) {
353 curl_easy_setopt(curl, CURLOPT_SSLKEY,
354 client_cert_private_key_file_.data());
355 curl_easy_setopt(curl, CURLOPT_SSLCERT, client_cert_file_.data());
356 curl_easy_setopt(curl, CURLOPT_SSLKEYTYPE,
"PEM");
357 curl_easy_setopt(curl, CURLOPT_SSLCERTTYPE,
"PEM");
359 if (!client_cert_private_key_password_.empty()) {
360 curl_easy_setopt(curl, CURLOPT_KEYPASSWD,
361 client_cert_private_key_password_.data());
364 if (!ca_file_.empty()) {
365 curl_easy_setopt(curl, CURLOPT_CAINFO, ca_file_.data());
368 if (VLOG_IS_ON(kMinLogLevelForCurlDebugFunction)) {
369 curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, CurlDebugCallback);
370 curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
374void HttpFile::ThreadMain() {
377 CURLcode res = curl_easy_perform(curl_.get());
378 if (res != CURLE_OK) {
379 std::string error_message = curl_easy_strerror(res);
380 if (res == CURLE_HTTP_RETURNED_ERROR) {
381 long response_code = 0;
382 curl_easy_getinfo(curl_.get(), CURLINFO_RESPONSE_CODE, &response_code);
383 error_message += absl::StrFormat(
", response code: %ld.", response_code);
387 res == CURLE_OPERATION_TIMEDOUT ? error::TIME_OUT : error::HTTP_FAILURE,
396 upload_cache_.Close();
397 download_cache_.Close();
398 task_exit_event_.Notify();
All the methods that are virtual are virtual for mocking.