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

mendersoftware / mender / 1019717806

28 Sep 2023 01:24PM UTC coverage: 78.05% (-0.1%) from 78.153%
1019717806

push

gitlab-ci

kacf
test: Drain event queue before checking number of connections.

One http instance was moved because Boost accesses its members even
after they are destroyed. It shouldn't, but this is unlikely to happen
in production where the loop is continuously running.

Signed-off-by: Kristian Amlie <kristian.amlie@northern.tech>

6749 of 8647 relevant lines covered (78.05%)

10652.94 hits per line

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

78.15
/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) {
228✔
64
        return error::Error(error_condition(code, HttpErrorCategory), msg);
456✔
65
}
66

67
string MethodToString(Method method) {
97✔
68
        switch (method) {
97✔
69
        case Method::Invalid:
×
70
                return "Invalid";
×
71
        case Method::GET:
82✔
72
                return "GET";
82✔
73
        case Method::HEAD:
×
74
                return "HEAD";
×
75
        case Method::POST:
×
76
                return "POST";
×
77
        case Method::PUT:
15✔
78
                return "PUT";
15✔
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) {
339✔
91
        const string url_split {"://"};
678✔
92

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

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

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

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

118
                tmp = tmp.substr(split_index + 1);
335✔
119
                auto port = common::StringToLongLong(tmp);
335✔
120
                if (!port) {
335✔
121
                        return error::Error(port.error().code, url + " contains invalid port number");
×
122
                }
123
                address.port = port.value();
335✔
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(
337✔
137
                "URL broken down into (protocol: " + address.protocol + "), (host: " + address.host
674✔
138
                + "), (port: " + to_string(address.port) + "), (path: " + address.path + ")");
1,348✔
139

140
        return error::NoError;
337✔
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) {
161✔
163
        auto prefix_end = prefix.cend();
161✔
164
        while (prefix_end != prefix.cbegin() && prefix_end[-1] == '/') {
173✔
165
                prefix_end--;
12✔
166
        }
167

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

236
IncomingRequest::~IncomingRequest() {
×
237
        if (!*cancelled_) {
×
238
                stream_.server_.RemoveStream(stream_.shared_from_this());
×
239
        }
240
}
×
241

242
void IncomingRequest::Cancel() {
2✔
243
        if (!*cancelled_) {
2✔
244
                stream_.Cancel();
2✔
245
                stream_.server_.RemoveStream(stream_.shared_from_this());
2✔
246
        }
247
}
2✔
248

249
io::ExpectedAsyncReaderPtr IncomingRequest::MakeBodyAsyncReader() {
57✔
250
        if (*cancelled_) {
57✔
251
                return expected::unexpected(MakeError(
×
252
                        StreamCancelledError, "Cannot make reader for a request that doesn't exist anymore"));
×
253
        }
254
        return stream_.server_.MakeBodyAsyncReader(shared_from_this());
114✔
255
}
256

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

267
        io::AsyncCopy(writer, reader, [reader, mode](error::Error err) {
74✔
268
                if (err != error::NoError) {
37✔
269
                        log::Error("Could not copy HTTP stream: " + err.String());
3✔
270
                        if (mode == BodyWriterErrorMode::Cancel) {
3✔
271
                                reader->Cancel();
2✔
272
                        }
273
                }
274
        });
111✔
275
}
276

277
ExpectedOutgoingResponsePtr IncomingRequest::MakeResponse() {
136✔
278
        if (*cancelled_) {
136✔
279
                return expected::unexpected(MakeError(
×
280
                        StreamCancelledError, "Cannot make response for a request that doesn't exist anymore"));
×
281
        }
282
        return stream_.server_.MakeResponse(shared_from_this());
272✔
283
}
284

285
IncomingResponse::IncomingResponse(Client &client, shared_ptr<bool> cancelled) :
×
286
        client_ {client},
287
        cancelled_ {cancelled} {
×
288
}
×
289

290
void IncomingResponse::Cancel() {
1✔
291
        if (!*cancelled_) {
1✔
292
                client_.Cancel();
1✔
293
        }
294
}
1✔
295

296
io::ExpectedAsyncReaderPtr IncomingResponse::MakeBodyAsyncReader() {
107✔
297
        if (*cancelled_) {
107✔
298
                return expected::unexpected(MakeError(
×
299
                        StreamCancelledError, "Cannot make reader for a response that doesn't exist anymore"));
×
300
        }
301
        return client_.MakeBodyAsyncReader(shared_from_this());
214✔
302
}
303

304
void IncomingResponse::SetBodyWriter(io::WriterPtr writer, BodyWriterErrorMode mode) {
45✔
305
        auto exp_reader = MakeBodyAsyncReader();
45✔
306
        if (!exp_reader) {
45✔
307
                if (exp_reader.error().code != MakeError(BodyMissingError, "").code) {
11✔
308
                        log::Error(exp_reader.error().String());
×
309
                }
310
                return;
11✔
311
        }
312
        auto &reader = exp_reader.value();
34✔
313

314
        io::AsyncCopy(writer, reader, [reader, mode](error::Error err) {
68✔
315
                if (err != error::NoError) {
34✔
316
                        log::Error("Could not copy HTTP stream: " + err.String());
5✔
317
                        if (mode == BodyWriterErrorMode::Cancel) {
5✔
318
                                reader->Cancel();
4✔
319
                        }
320
                }
321
        });
102✔
322
}
323

324
io::ExpectedAsyncReadWriterPtr IncomingResponse::SwitchProtocol() {
8✔
325
        if (*cancelled_) {
8✔
326
                return expected::unexpected(MakeError(
1✔
327
                        StreamCancelledError, "Cannot switch protocol when the stream doesn't exist anymore"));
2✔
328
        }
329
        return client_.SwitchProtocol(shared_from_this());
14✔
330
}
331

332
OutgoingResponse::~OutgoingResponse() {
×
333
        if (!*cancelled_) {
×
334
                stream_.server_.RemoveStream(stream_.shared_from_this());
×
335
        }
336
}
×
337

338
void OutgoingResponse::Cancel() {
1✔
339
        if (!*cancelled_) {
1✔
340
                stream_.Cancel();
1✔
341
                stream_.server_.RemoveStream(stream_.shared_from_this());
1✔
342
        }
343
}
1✔
344

345
void OutgoingResponse::SetStatusCodeAndMessage(unsigned code, const string &message) {
134✔
346
        status_code_ = code;
134✔
347
        status_message_ = message;
134✔
348
}
134✔
349

350
void OutgoingResponse::SetHeader(const string &name, const string &value) {
113✔
351
        headers_[name] = value;
113✔
352
}
113✔
353

354
void OutgoingResponse::SetBodyReader(io::ReaderPtr body_reader) {
100✔
355
        async_body_reader_ = nullptr;
100✔
356
        body_reader_ = body_reader;
100✔
357
}
100✔
358

359
void OutgoingResponse::SetAsyncBodyReader(io::AsyncReaderPtr body_reader) {
4✔
360
        body_reader_ = nullptr;
4✔
361
        async_body_reader_ = body_reader;
4✔
362
}
4✔
363

364
error::Error OutgoingResponse::AsyncReply(ReplyFinishedHandler reply_finished_handler) {
125✔
365
        if (*cancelled_) {
125✔
366
                return MakeError(StreamCancelledError, "Cannot reply when response doesn't exist anymore");
1✔
367
        }
368
        return stream_.server_.AsyncReply(shared_from_this(), reply_finished_handler);
248✔
369
}
370

371
error::Error OutgoingResponse::AsyncSwitchProtocol(SwitchProtocolHandler handler) {
9✔
372
        if (*cancelled_) {
9✔
373
                return MakeError(
374
                        StreamCancelledError, "Cannot switch protocol when response doesn't exist anymore");
×
375
        }
376
        return stream_.server_.AsyncSwitchProtocol(shared_from_this(), handler);
18✔
377
}
378

379
ExponentialBackoff::ExpectedInterval ExponentialBackoff::NextInterval() {
63✔
380
        iteration_++;
63✔
381

382
        if (try_count_ > 0 && iteration_ > try_count_) {
63✔
383
                return expected::unexpected(MakeError(MaxRetryError, "Exponential backoff"));
8✔
384
        }
385

386
        chrono::milliseconds current_interval = smallest_interval_;
59✔
387
        // Backoff algorithm: Each interval is returned three times, then it's doubled, and then
388
        // that is returned three times, and so on. But if interval is ever higher than the max
389
        // interval, then return the max interval instead, and once that is returned three times,
390
        // produce MaxRetryError. If try_count_ is set, then that controls the total number of
391
        // retries, but the rest is the same, so then it simply "gets stuck" at max interval for
392
        // many iterations.
393
        for (int count = 3; count < iteration_; count += 3) {
124✔
394
                auto new_interval = current_interval * 2;
70✔
395
                if (new_interval > max_interval_) {
70✔
396
                        new_interval = max_interval_;
17✔
397
                }
398
                if (try_count_ <= 0 && new_interval == current_interval) {
70✔
399
                        return expected::unexpected(MakeError(MaxRetryError, "Exponential backoff"));
10✔
400
                }
401
                current_interval = new_interval;
65✔
402
        }
403

404
        return current_interval;
54✔
405
}
406

407
} // namespace http
408
} // 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