|
8 | 8 | # include <array> |
9 | 9 | #endif // ENABLE_OTLP_RETRY_PREVIEW |
10 | 10 |
|
| 11 | +#include <stdint.h> |
11 | 12 | #include <algorithm> |
12 | 13 | #include <atomic> |
| 14 | +#include <cctype> |
13 | 15 | #include <chrono> |
14 | 16 | #include <cmath> |
15 | 17 | #include <cstring> |
| 18 | +#include <ctime> |
16 | 19 | #include <functional> |
17 | 20 | #include <future> |
| 21 | +#include <iomanip> |
| 22 | +#include <limits> |
18 | 23 | #include <memory> |
19 | 24 | #include <random> |
20 | 25 | #include <sstream> |
|
23 | 28 | #include <utility> |
24 | 29 | #include <vector> |
25 | 30 |
|
| 31 | +#include "opentelemetry/common/string_util.h" |
26 | 32 | #include "opentelemetry/ext/http/client/curl/http_client_curl.h" |
27 | 33 | #include "opentelemetry/ext/http/client/curl/http_operation_curl.h" |
28 | 34 | #include "opentelemetry/ext/http/client/http_client.h" |
|
35 | 41 | # define CURL_VERSION_BITS(x, y, z) ((x) << 16 | (y) << 8 | (z)) |
36 | 42 | #endif |
37 | 43 |
|
| 44 | +namespace |
| 45 | +{ |
| 46 | + |
| 47 | +std::time_t PortableTimegm(std::tm *tm) |
| 48 | +{ |
| 49 | + int year = tm->tm_year + 1900; |
| 50 | + int month = tm->tm_mon + 1; |
| 51 | + |
| 52 | + if (month <= 2) |
| 53 | + { |
| 54 | + year -= 1; |
| 55 | + month += 12; |
| 56 | + } |
| 57 | + |
| 58 | + int day = tm->tm_mday; |
| 59 | + int days = 365 * year + year / 4 - year / 100 + year / 400 + 367 * month / 12 - 30 + day - 719530; |
| 60 | + |
| 61 | + return static_cast<std::time_t>(days) * 86400 + tm->tm_hour * 3600 + tm->tm_min * 60 + tm->tm_sec; |
| 62 | +} |
| 63 | + |
| 64 | +bool ParseRetryAfterDelay(opentelemetry::nostd::string_view value, |
| 65 | + std::chrono::seconds &delay) noexcept |
| 66 | +{ |
| 67 | + value = opentelemetry::common::StringUtil::Trim(value); |
| 68 | + |
| 69 | + if (value.empty()) |
| 70 | + { |
| 71 | + return false; |
| 72 | + } |
| 73 | + |
| 74 | + std::chrono::seconds::rep result = 0; |
| 75 | + |
| 76 | + for (const char c : value) |
| 77 | + { |
| 78 | + if (!std::isdigit(static_cast<unsigned char>(c))) |
| 79 | + { |
| 80 | + return false; |
| 81 | + } |
| 82 | + |
| 83 | + auto digit = c - '0'; |
| 84 | + |
| 85 | + if (result > (std::numeric_limits<std::chrono::seconds::rep>::max() - digit) / 10) |
| 86 | + { |
| 87 | + return false; |
| 88 | + } |
| 89 | + |
| 90 | + result = result * 10 + digit; |
| 91 | + } |
| 92 | + |
| 93 | + if (result == 0) |
| 94 | + { |
| 95 | + return false; |
| 96 | + } |
| 97 | + |
| 98 | + delay = std::chrono::seconds(result); |
| 99 | + return true; |
| 100 | +} |
| 101 | + |
| 102 | +bool ParseRetryAfterDate(opentelemetry::nostd::string_view value, |
| 103 | + std::chrono::system_clock::time_point &date) |
| 104 | +{ |
| 105 | + value = opentelemetry::common::StringUtil::Trim(value); |
| 106 | + |
| 107 | + std::string str(value.data(), value.size()); |
| 108 | + std::tm tm = {}; |
| 109 | + std::istringstream ss(str); |
| 110 | + |
| 111 | + ss >> std::get_time(&tm, "%a, %d %b %Y %H:%M:%S"); |
| 112 | + if (!ss.fail()) |
| 113 | + { |
| 114 | + date = std::chrono::system_clock::from_time_t(PortableTimegm(&tm)); |
| 115 | + return true; |
| 116 | + } |
| 117 | + |
| 118 | + ss.clear(); |
| 119 | + ss.str(str); |
| 120 | + tm = {}; |
| 121 | + ss >> std::get_time(&tm, "%A, %d-%b-%y %H:%M:%S"); |
| 122 | + if (!ss.fail()) |
| 123 | + { |
| 124 | + std::time_t now_t = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); |
| 125 | + int current_year = 1970 + static_cast<int>(now_t / 31556952); |
| 126 | + int full_year = 1900 + tm.tm_year; |
| 127 | + if (full_year - current_year > 50) |
| 128 | + { |
| 129 | + full_year -= 100; |
| 130 | + } |
| 131 | + tm.tm_year = full_year - 1900; |
| 132 | + date = std::chrono::system_clock::from_time_t(PortableTimegm(&tm)); |
| 133 | + return true; |
| 134 | + } |
| 135 | + |
| 136 | + ss.clear(); |
| 137 | + ss.str(str); |
| 138 | + tm = {}; |
| 139 | + ss >> std::get_time(&tm, "%a %b %d %H:%M:%S %Y"); |
| 140 | + if (!ss.fail()) |
| 141 | + { |
| 142 | + date = std::chrono::system_clock::from_time_t(PortableTimegm(&tm)); |
| 143 | + return true; |
| 144 | + } |
| 145 | + |
| 146 | + return false; |
| 147 | +} |
| 148 | + |
| 149 | +bool FindRetryAfterValue(const std::vector<uint8_t> &raw_headers, std::string &value) |
| 150 | +{ |
| 151 | + if (raw_headers.empty()) |
| 152 | + { |
| 153 | + return false; |
| 154 | + } |
| 155 | + |
| 156 | + const char *data = reinterpret_cast<const char *>(raw_headers.data()); |
| 157 | + const char *end = data + raw_headers.size(); |
| 158 | + const char *line = data; |
| 159 | + |
| 160 | + while (line < end) |
| 161 | + { |
| 162 | + const char *line_end = line; |
| 163 | + while (line_end < end && *line_end != '\n') |
| 164 | + { |
| 165 | + ++line_end; |
| 166 | + } |
| 167 | + |
| 168 | + static const char kRetryAfterHeader[] = "retry-after:"; |
| 169 | + static const size_t kRetryAfterLen = sizeof(kRetryAfterHeader) - 1; |
| 170 | + |
| 171 | + size_t line_len = static_cast<size_t>(line_end - line); |
| 172 | + if (line_len > kRetryAfterLen) |
| 173 | + { |
| 174 | + bool match = true; |
| 175 | + for (size_t i = 0; i < kRetryAfterLen && match; ++i) |
| 176 | + { |
| 177 | + match = (std::tolower(static_cast<unsigned char>(line[i])) == kRetryAfterHeader[i]); |
| 178 | + } |
| 179 | + |
| 180 | + if (match) |
| 181 | + { |
| 182 | + value = std::string(line + kRetryAfterLen, line_end); |
| 183 | + return true; |
| 184 | + } |
| 185 | + } |
| 186 | + |
| 187 | + line = (line_end < end) ? line_end + 1 : end; |
| 188 | + } |
| 189 | + |
| 190 | + return false; |
| 191 | +} |
| 192 | + |
| 193 | +} // namespace |
| 194 | + |
38 | 195 | OPENTELEMETRY_BEGIN_NAMESPACE |
39 | 196 | namespace ext |
40 | 197 | { |
@@ -560,6 +717,11 @@ bool HttpOperation::IsRetryable() |
560 | 717 |
|
561 | 718 | std::chrono::system_clock::time_point HttpOperation::NextRetryTime() |
562 | 719 | { |
| 720 | + if (retry_after_time_point_ != std::chrono::system_clock::time_point{}) |
| 721 | + { |
| 722 | + return retry_after_time_point_; |
| 723 | + } |
| 724 | + |
563 | 725 | static std::random_device rd; |
564 | 726 | static std::mt19937 gen(rd()); |
565 | 727 | static std::uniform_real_distribution<float> dis(0.8f, 1.2f); |
@@ -1463,8 +1625,9 @@ void HttpOperation::Abort() |
1463 | 1625 | void HttpOperation::PerformCurlMessage(CURLcode code) |
1464 | 1626 | { |
1465 | 1627 | ++retry_attempts_; |
1466 | | - last_attempt_time_ = std::chrono::system_clock::now(); |
1467 | | - last_curl_result_ = code; |
| 1628 | + last_attempt_time_ = std::chrono::system_clock::now(); |
| 1629 | + last_curl_result_ = code; |
| 1630 | + retry_after_time_point_ = std::chrono::system_clock::time_point{}; |
1468 | 1631 |
|
1469 | 1632 | if (code != CURLE_OK) |
1470 | 1633 | { |
@@ -1513,6 +1676,27 @@ void HttpOperation::PerformCurlMessage(CURLcode code) |
1513 | 1676 |
|
1514 | 1677 | if (IsRetryable()) |
1515 | 1678 | { |
| 1679 | + |
| 1680 | + std::string retry_after; |
| 1681 | + if (FindRetryAfterValue(response_headers_, retry_after)) |
| 1682 | + { |
| 1683 | + std::chrono::seconds delay; |
| 1684 | + |
| 1685 | + if (ParseRetryAfterDelay(retry_after, delay)) |
| 1686 | + { |
| 1687 | + retry_after_time_point_ = std::chrono::system_clock::now() + delay; |
| 1688 | + } |
| 1689 | + else |
| 1690 | + { |
| 1691 | + std::chrono::system_clock::time_point date; |
| 1692 | + if (ParseRetryAfterDate(retry_after, date)) |
| 1693 | + { |
| 1694 | + auto now = std::chrono::system_clock::now(); |
| 1695 | + retry_after_time_point_ = (date > now) ? date : now; |
| 1696 | + } |
| 1697 | + } |
| 1698 | + } |
| 1699 | + |
1516 | 1700 | // Clear any response data received in previous attempt |
1517 | 1701 | ReleaseResponse(); |
1518 | 1702 | // Rewind request data so that read callback can re-transfer the payload |
|
0 commit comments