• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mendersoftware / mender / 1001210836

12 Sep 2023 01:20PM UTC coverage: 79.201% (+0.09%) from 79.112%
1001210836

push

gitlab-ci

vpodzime
feat: Add support for signals delivering two strings

Ticket: MEN-6651
Changelog: none
Signed-off-by: Vratislav Podzimek <v.podzimek@mykolab.com>

5731 of 7236 relevant lines covered (79.2%)

256.26 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.0
/common/http/http.cpp
1
// Copyright 2023 Northern.tech AS
2
//
3
//    Licensed under the Apache License, Version 2.0 (the "License");
4
//    you may not use this file except in compliance with the License.
5
//    You may obtain a copy of the License at
6
//
7
//        http://www.apache.org/licenses/LICENSE-2.0
8
//
9
//    Unless required by applicable law or agreed to in writing, software
10
//    distributed under the License is distributed on an "AS IS" BASIS,
11
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
//    See the License for the specific language governing permissions and
13
//    limitations under the License.
14

15
#include <common/http.hpp>
16

17
#include <algorithm>
18
#include <cctype>
19
#include <cstdlib>
20
#include <iomanip>
21
#include <string>
22

23
#include <common/common.hpp>
24

25
namespace mender {
26
namespace http {
27

28
namespace common = mender::common;
29

30
const HttpErrorCategoryClass HttpErrorCategory;
31

32
const char *HttpErrorCategoryClass::name() const noexcept {
×
33
        return "HttpErrorCategory";
×
34
}
35

36
string HttpErrorCategoryClass::message(int code) const {
1✔
37
        switch (code) {
1✔
38
        case NoError:
×
39
                return "Success";
×
40
        case NoSuchHeaderError:
×
41
                return "No such header";
×
42
        case InvalidUrlError:
×
43
                return "Malformed URL";
×
44
        case BodyMissingError:
×
45
                return "Body is missing";
×
46
        case BodyIgnoredError:
×
47
                return "HTTP stream contains a body, but a reader has not been created for it";
×
48
        case UnsupportedMethodError:
×
49
                return "Unsupported HTTP method";
×
50
        case StreamCancelledError:
×
51
                return "Stream has been cancelled/destroyed";
×
52
        case UnsupportedBodyType:
×
53
                return "HTTP stream has a body type we don't understand";
×
54
        case MaxRetryError:
1✔
55
                return "Tried maximum number of times";
1✔
56
        }
57
        // Don't use "default" case. This should generate a warning if we ever add any enums. But
58
        // still assert here for safety.
59
        assert(false);
×
60
        return "Unknown";
61
}
62

63
error::Error MakeError(ErrorCode code, const string &msg) {
171✔
64
        return error::Error(error_condition(code, HttpErrorCategory), msg);
342✔
65
}
66

67
string MethodToString(Method method) {
21✔
68
        switch (method) {
21✔
69
        case Method::Invalid:
×
70
                return "Invalid";
×
71
        case Method::GET:
21✔
72
                return "GET";
21✔
73
        case Method::HEAD:
×
74
                return "HEAD";
×
75
        case Method::POST:
×
76
                return "POST";
×
77
        case Method::PUT:
×
78
                return "PUT";
×
79
        case Method::PATCH:
×
80
                return "PATCH";
×
81
        case Method::CONNECT:
×
82
                return "CONNECT";
×
83
        }
84
        // Don't use "default" case. This should generate a warning if we ever add any methods. But
85
        // still assert here for safety.
86
        assert(false);
×
87
        return "INVALID_METHOD";
88
}
89

90
error::Error BreakDownUrl(const string &url, BrokenDownUrl &address) {
214✔
91
        const string url_split {"://"};
428✔
92

93
        auto split_index = url.find(url_split);
214✔
94
        if (split_index == string::npos) {
214✔
95
                return MakeError(InvalidUrlError, url + " is not a valid URL.");
2✔
96
        }
97
        if (split_index == 0) {
213✔
98
                return MakeError(InvalidUrlError, url + ": missing hostname");
×
99
        }
100

101
        address.protocol = url.substr(0, split_index);
213✔
102

103
        auto tmp = url.substr(split_index + url_split.size());
426✔
104
        split_index = tmp.find("/");
213✔
105
        if (split_index == string::npos) {
213✔
106
                address.host = tmp;
120✔
107
                address.path = "/";
120✔
108
        } else {
109
                address.host = tmp.substr(0, split_index);
93✔
110
                address.path = tmp.substr(split_index);
93✔
111
        }
112

113
        split_index = address.host.find(":");
213✔
114
        if (split_index != string::npos) {
213✔
115
                tmp = std::move(address.host);
210✔
116
                address.host = tmp.substr(0, split_index);
210✔
117

118
                tmp = tmp.substr(split_index + 1);
210✔
119
                auto port = common::StringToLongLong(tmp);
210✔
120
                if (!port) {
210✔
121
                        return error::Error(port.error().code, url + " contains invalid port number");
×
122
                }
123
                address.port = port.value();
210✔
124
        } else {
125
                if (address.protocol == "http") {
3✔
126
                        address.port = 80;
1✔
127
                } else if (address.protocol == "https") {
2✔
128
                        address.port = 443;
1✔
129
                } else {
130
                        return error::Error(
131
                                make_error_condition(errc::protocol_not_supported),
1✔
132
                                "Cannot deduce port number from protocol " + address.protocol);
3✔
133
                }
134
        }
135

136
        log::Trace(
212✔
137
                "URL broken down into (protocol: " + address.protocol + "), (host: " + address.host
424✔
138
                + "), (port: " + to_string(address.port) + "), (path: " + address.path + ")");
848✔
139

140
        return error::NoError;
212✔
141
}
142

143
string URLEncode(const string &value) {
15✔
144
        stringstream escaped;
30✔
145
        escaped << hex;
15✔
146

147
        for (auto c : value) {
288✔
148
                // Keep alphanumeric and other accepted characters intact
149
                if (isalnum(c) || c == '-' || c == '_' || c == '.' || c == '~') {
273✔
150
                        escaped << c;
251✔
151
                } else {
152
                        // Any other characters are percent-encoded
153
                        escaped << uppercase;
22✔
154
                        escaped << '%' << setw(2) << int((unsigned char) c);
22✔
155
                        escaped << nouppercase;
22✔
156
                }
157
        }
158

159
        return escaped.str();
30✔
160
}
161

162
string JoinOneUrl(const string &prefix, const string &suffix) {
94✔
163
        auto prefix_end = prefix.cend();
94✔
164
        while (prefix_end != prefix.cbegin() && prefix_end[-1] == '/') {
94✔
165
                prefix_end--;
×
166
        }
167

168
        auto suffix_start = suffix.cbegin();
94✔
169
        while (suffix_start != suffix.cend() && *suffix_start == '/') {
136✔
170
                suffix_start++;
42✔
171
        }
172

173
        return string(prefix.cbegin(), prefix_end) + "/" + string(suffix_start, suffix.cend());
282✔
174
}
175

176
size_t CaseInsensitiveHasher::operator()(const string &str) const {
1,121✔
177
        string lower_str(str.length(), ' ');
1,121✔
178
        transform(
179
                str.begin(), str.end(), lower_str.begin(), [](unsigned char c) { return std::tolower(c); });
14,182✔
180
        return hash<string>()(lower_str);
2,240✔
181
}
182

183
bool CaseInsensitiveComparator::operator()(const string &str1, const string &str2) const {
364✔
184
        return strcasecmp(str1.c_str(), str2.c_str()) == 0;
364✔
185
}
186

187
expected::ExpectedString Transaction::GetHeader(const string &name) const {
280✔
188
        if (headers_.find(name) == headers_.end()) {
280✔
189
                return expected::unexpected(MakeError(NoSuchHeaderError, "No such header: " + name));
196✔
190
        }
191
        return headers_.at(name);
182✔
192
}
193

194
Method Request::GetMethod() const {
49✔
195
        return method_;
49✔
196
}
197

198
string Request::GetPath() const {
105✔
199
        return address_.path;
105✔
200
}
201

202
unsigned Response::GetStatusCode() const {
188✔
203
        return status_code_;
188✔
204
}
205

206
string Response::GetStatusMessage() const {
117✔
207
        return status_message_;
117✔
208
}
209

210
void OutgoingRequest::SetMethod(Method method) {
113✔
211
        method_ = method;
113✔
212
}
113✔
213

214
void OutgoingRequest::SetHeader(const string &name, const string &value) {
245✔
215
        headers_[name] = value;
245✔
216
}
245✔
217

218
error::Error OutgoingRequest::SetAddress(const string &address) {
113✔
219
        orig_address_ = address;
113✔
220

221
        return BreakDownUrl(address, address_);
113✔
222
}
223

224
void OutgoingRequest::SetBodyGenerator(BodyGenerator body_gen) {
35✔
225
        async_body_gen_ = nullptr;
35✔
226
        async_body_reader_ = nullptr;
35✔
227
        body_gen_ = body_gen;
35✔
228
}
35✔
229

230
void OutgoingRequest::SetAsyncBodyGenerator(AsyncBodyGenerator body_gen) {
1✔
231
        body_gen_ = nullptr;
1✔
232
        body_reader_ = nullptr;
1✔
233
        async_body_gen_ = body_gen;
1✔
234
}
1✔
235

236
IncomingRequest::~IncomingRequest() {
×
237
        Cancel();
×
238
}
×
239

240
void IncomingRequest::Cancel() {
104✔
241
        auto stream = stream_.lock();
208✔
242
        if (stream) {
104✔
243
                stream->Cancel();
2✔
244
                stream->server_.RemoveStream(stream);
2✔
245
        }
246
}
104✔
247

248
io::ExpectedAsyncReaderPtr IncomingRequest::MakeBodyAsyncReader() {
40✔
249
        auto stream = stream_.lock();
80✔
250
        if (!stream) {
40✔
251
                return expected::unexpected(MakeError(
×
252
                        StreamCancelledError, "Cannot make reader for a server that doesn't exist anymore"));
×
253
        }
254
        return stream->server_.MakeBodyAsyncReader(shared_from_this());
80✔
255
}
256

257
void IncomingRequest::SetBodyWriter(io::WriterPtr writer) {
40✔
258
        auto exp_reader = MakeBodyAsyncReader();
40✔
259
        if (!exp_reader) {
40✔
260
                if (exp_reader.error().code != MakeError(BodyMissingError, "").code) {
8✔
261
                        log::Error(exp_reader.error().String());
×
262
                }
263
                return;
8✔
264
        }
265

266
        io::AsyncCopy(writer, exp_reader.value(), [](error::Error err) {
64✔
267
                if (err != error::NoError) {
32✔
268
                        log::Error("Could not copy HTTP stream: " + err.String());
1✔
269
                }
270
        });
128✔
271
}
272

273
ExpectedOutgoingResponsePtr IncomingRequest::MakeResponse() {
96✔
274
        auto stream = stream_.lock();
192✔
275
        if (!stream) {
96✔
276
                return expected::unexpected(MakeError(
×
277
                        StreamCancelledError, "Cannot make response for a server that doesn't exist anymore"));
×
278
        }
279
        return stream->server_.MakeResponse(shared_from_this());
192✔
280
}
281

282
IncomingResponse::IncomingResponse(weak_ptr<Client> client) :
×
283
        client_ {client} {
×
284
}
×
285

286
void IncomingResponse::Cancel() {
×
287
        auto client = client_.lock();
×
288
        if (client) {
×
289
                client->Cancel();
×
290
        }
291
}
×
292

293
io::ExpectedAsyncReaderPtr IncomingResponse::MakeBodyAsyncReader() {
72✔
294
        auto client = client_.lock();
144✔
295
        if (!client) {
72✔
296
                return expected::unexpected(MakeError(
×
297
                        StreamCancelledError, "Cannot make reader for a client that doesn't exist anymore"));
×
298
        }
299
        return client->MakeBodyAsyncReader(shared_from_this());
144✔
300
}
301

302
void IncomingResponse::SetBodyWriter(io::WriterPtr writer) {
42✔
303
        auto exp_reader = MakeBodyAsyncReader();
42✔
304
        if (!exp_reader) {
42✔
305
                if (exp_reader.error().code != MakeError(BodyMissingError, "").code) {
11✔
306
                        log::Error(exp_reader.error().String());
×
307
                }
308
                return;
11✔
309
        }
310

311
        io::AsyncCopy(writer, exp_reader.value(), [](error::Error err) {
62✔
312
                if (err != error::NoError) {
31✔
313
                        log::Error("Could not copy HTTP stream: " + err.String());
3✔
314
                }
315
        });
124✔
316
}
317

318
OutgoingResponse::~OutgoingResponse() {
×
319
        Cancel();
×
320
}
×
321

322
void OutgoingResponse::Cancel() {
96✔
323
        auto stream = stream_.lock();
192✔
324
        if (stream) {
96✔
325
                stream->Cancel();
1✔
326
                stream->server_.RemoveStream(stream);
1✔
327
        }
328
}
96✔
329

330
void OutgoingResponse::SetStatusCodeAndMessage(unsigned code, const string &message) {
94✔
331
        status_code_ = code;
94✔
332
        status_message_ = message;
94✔
333
}
94✔
334

335
void OutgoingResponse::SetHeader(const string &name, const string &value) {
83✔
336
        headers_[name] = value;
83✔
337
}
83✔
338

339
void OutgoingResponse::SetBodyReader(io::ReaderPtr body_reader) {
74✔
340
        async_body_reader_ = nullptr;
74✔
341
        body_reader_ = body_reader;
74✔
342
}
74✔
343

344
void OutgoingResponse::SetAsyncBodyReader(io::AsyncReaderPtr body_reader) {
1✔
345
        body_reader_ = nullptr;
1✔
346
        async_body_reader_ = body_reader;
1✔
347
}
1✔
348

349
error::Error OutgoingResponse::AsyncReply(ReplyFinishedHandler reply_finished_handler) {
95✔
350
        auto stream = stream_.lock();
190✔
351
        if (!stream) {
95✔
352
                return MakeError(StreamCancelledError, "Cannot reply when server doesn't exist anymore");
1✔
353
        }
354
        return stream->server_.AsyncReply(shared_from_this(), reply_finished_handler);
188✔
355
}
356

357
ExponentialBackoff::ExpectedInterval ExponentialBackoff::NextInterval() {
63✔
358
        iteration_++;
63✔
359

360
        if (try_count_ > 0 && iteration_ > try_count_) {
63✔
361
                return expected::unexpected(MakeError(MaxRetryError, "Exponential backoff"));
8✔
362
        }
363

364
        chrono::milliseconds current_interval = smallest_interval_;
59✔
365
        // Backoff algorithm: Each interval is returned three times, then it's doubled, and then
366
        // that is returned three times, and so on. But if interval is ever higher than the max
367
        // interval, then return the max interval instead, and once that is returned three times,
368
        // produce MaxRetryError. If try_count_ is set, then that controls the total number of
369
        // retries, but the rest is the same, so then it simply "gets stuck" at max interval for
370
        // many iterations.
371
        for (int count = 3; count < iteration_; count += 3) {
124✔
372
                auto new_interval = current_interval * 2;
70✔
373
                if (new_interval > max_interval_) {
70✔
374
                        new_interval = max_interval_;
17✔
375
                }
376
                if (try_count_ <= 0 && new_interval == current_interval) {
70✔
377
                        return expected::unexpected(MakeError(MaxRetryError, "Exponential backoff"));
10✔
378
                }
379
                current_interval = new_interval;
65✔
380
        }
381

382
        return current_interval;
54✔
383
}
384

385
} // namespace http
386
} // namespace mender
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc