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

mendersoftware / mender / 992981781

05 Sep 2023 08:55AM UTC coverage: 79.541% (+0.3%) from 79.264%
992981781

push

gitlab-ci

lluiscampos
chore: Clean-up `gmock` use in tests

Many tests had the header and include path while only using GTest.

Completely unnecessary clean-up...

Signed-off-by: Lluis Campos <lluis.campos@northern.tech>

5719 of 7190 relevant lines covered (79.54%)

290.46 hits per line

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

78.78
/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 UnsupportedMethodError:
×
47
                return "Unsupported HTTP method";
×
48
        case StreamCancelledError:
×
49
                return "Stream has been cancelled/destroyed";
×
50
        case UnsupportedBodyType:
×
51
                return "HTTP stream has a body type we don't understand";
×
52
        case MaxRetryError:
1✔
53
                return "Tried maximum number of times";
1✔
54
        }
55
        // Don't use "default" case. This should generate a warning if we ever add any enums. But
56
        // still assert here for safety.
57
        assert(false);
×
58
        return "Unknown";
59
}
60

61
error::Error MakeError(ErrorCode code, const string &msg) {
127✔
62
        return error::Error(error_condition(code, HttpErrorCategory), msg);
254✔
63
}
64

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

88
error::Error BreakDownUrl(const string &url, BrokenDownUrl &address) {
210✔
89
        const string url_split {"://"};
420✔
90

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

99
        address.protocol = url.substr(0, split_index);
209✔
100

101
        auto tmp = url.substr(split_index + url_split.size());
418✔
102
        split_index = tmp.find("/");
209✔
103
        if (split_index == string::npos) {
209✔
104
                address.host = tmp;
116✔
105
                address.path = "/";
116✔
106
        } else {
107
                address.host = tmp.substr(0, split_index);
93✔
108
                address.path = tmp.substr(split_index);
93✔
109
        }
110

111
        split_index = address.host.find(":");
209✔
112
        if (split_index != string::npos) {
209✔
113
                tmp = std::move(address.host);
206✔
114
                address.host = tmp.substr(0, split_index);
206✔
115

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

134
        log::Trace(
208✔
135
                "URL broken down into (protocol: " + address.protocol + "), (host: " + address.host
416✔
136
                + "), (port: " + to_string(address.port) + "), (path: " + address.path + ")");
832✔
137

138
        return error::NoError;
208✔
139
}
140

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

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

157
        return escaped.str();
30✔
158
}
159

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

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

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

174
size_t CaseInsensitiveHasher::operator()(const string &str) const {
1,103✔
175
        string lower_str(str.length(), ' ');
1,103✔
176
        transform(
177
                str.begin(), str.end(), lower_str.begin(), [](unsigned char c) { return std::tolower(c); });
13,969✔
178
        return hash<string>()(lower_str);
2,204✔
179
}
180

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

185
expected::ExpectedString Transaction::GetHeader(const string &name) const {
276✔
186
        if (headers_.find(name) == headers_.end()) {
276✔
187
                return expected::unexpected(MakeError(NoSuchHeaderError, "No such header: " + name));
194✔
188
        }
189
        return headers_.at(name);
179✔
190
}
191

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

196
string Request::GetPath() const {
102✔
197
        return address_.path;
102✔
198
}
199

200
unsigned Response::GetStatusCode() const {
187✔
201
        return status_code_;
187✔
202
}
203

204
string Response::GetStatusMessage() const {
116✔
205
        return status_message_;
116✔
206
}
207

208
void OutgoingRequest::SetMethod(Method method) {
111✔
209
        method_ = method;
111✔
210
}
111✔
211

212
void OutgoingRequest::SetHeader(const string &name, const string &value) {
242✔
213
        headers_[name] = value;
242✔
214
}
242✔
215

216
error::Error OutgoingRequest::SetAddress(const string &address) {
111✔
217
        orig_address_ = address;
111✔
218

219
        return BreakDownUrl(address, address_);
111✔
220
}
221

222
void OutgoingRequest::SetBodyGenerator(BodyGenerator body_gen) {
34✔
223
        async_body_gen_ = nullptr;
34✔
224
        async_body_reader_ = nullptr;
34✔
225
        body_gen_ = body_gen;
34✔
226
}
34✔
227

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

234
IncomingResponse::IncomingResponse(weak_ptr<Client> client) :
95✔
235
        client_ {client} {
95✔
236
}
95✔
237

238
void IncomingResponse::SetBodyWriter(io::WriterPtr body_writer) {
40✔
239
        body_writer_ = body_writer;
40✔
240
}
40✔
241

242
io::AsyncReaderPtr IncomingResponse::MakeBodyAsyncReader() {
32✔
243
        body_async_reader_ = shared_ptr<BodyAsyncReader>(new BodyAsyncReader(client_));
32✔
244
        return body_async_reader_;
32✔
245
}
246

247
IncomingResponse::BodyAsyncReader::BodyAsyncReader(weak_ptr<Client> client) :
×
248
        client_ {client} {
×
249
}
×
250

251
IncomingResponse::BodyAsyncReader::~BodyAsyncReader() {
×
252
        Cancel();
×
253
}
×
254

255
error::Error IncomingResponse::BodyAsyncReader::AsyncRead(
2,143✔
256
        vector<uint8_t>::iterator start, vector<uint8_t>::iterator end, io::AsyncIoHandler handler) {
257
        if (done_) {
2,143✔
258
                handler(0);
×
259
        }
260

261
        auto client = client_.lock();
4,286✔
262
        if (!client) {
2,143✔
263
                return error::MakeError(
264
                        error::ProgrammingError, "BodyAsyncReader::AsyncRead called after Client is destroyed");
×
265
        }
266
        client->AsyncReadNextBodyPart(start, end, handler);
2,143✔
267
        return error::NoError;
2,143✔
268
}
269

270
void IncomingResponse::BodyAsyncReader::Cancel() {
32✔
271
        auto client = client_.lock();
64✔
272
        if (client) {
32✔
273
                client->Cancel();
×
274
        }
275
}
32✔
276

277
void IncomingRequest::SetBodyWriter(io::WriterPtr body_writer) {
16✔
278
        body_writer_ = body_writer;
16✔
279
}
16✔
280

281
ExpectedOutgoingResponsePtr IncomingRequest::MakeResponse() {
95✔
282
        auto stream = stream_.lock();
190✔
283
        if (!stream) {
95✔
284
                return expected::unexpected(MakeError(StreamCancelledError, "Cannot make response"));
×
285
        }
286
        OutgoingResponsePtr response {new OutgoingResponse};
190✔
287
        response->stream_ = stream_;
95✔
288
        stream->maybe_response_ = response;
95✔
289
        return response;
95✔
290
}
291

292
io::AsyncReaderPtr IncomingRequest::MakeBodyAsyncReader() {
2✔
293
        body_async_reader_ = shared_ptr<BodyAsyncReader>(new BodyAsyncReader(stream_));
2✔
294
        return body_async_reader_;
2✔
295
}
296

297
IncomingRequest::BodyAsyncReader::BodyAsyncReader(weak_ptr<Stream> stream) :
×
298
        stream_ {stream} {
×
299
}
×
300

301
IncomingRequest::BodyAsyncReader::~BodyAsyncReader() {
×
302
        Cancel();
×
303
}
×
304

305
error::Error IncomingRequest::BodyAsyncReader::AsyncRead(
2,112✔
306
        vector<uint8_t>::iterator start, vector<uint8_t>::iterator end, io::AsyncIoHandler handler) {
307
        if (done_) {
2,112✔
308
                handler(0);
×
309
        }
310

311
        auto stream = stream_.lock();
4,224✔
312
        if (!stream) {
2,112✔
313
                return error::MakeError(
314
                        error::ProgrammingError, "BodyAsyncReader::AsyncRead called after Stream is destroyed");
×
315
        }
316
        stream->AsyncReadNextBodyPart(start, end, handler);
2,112✔
317
        return error::NoError;
2,112✔
318
}
319

320
void IncomingRequest::BodyAsyncReader::Cancel() {
2✔
321
        auto stream = stream_.lock();
4✔
322
        if (stream) {
2✔
323
                stream->Cancel();
×
324
        }
325
}
2✔
326

327
void IncomingRequest::Cancel() {
2✔
328
        auto stream = stream_.lock();
4✔
329
        if (stream) {
2✔
330
                stream->Cancel();
2✔
331
        }
332
}
2✔
333

334
OutgoingResponse::~OutgoingResponse() {
377✔
335
        Cancel();
95✔
336

337
        auto stream = stream_.lock();
95✔
338
        if (!stream) {
95✔
339
                // It was probably cancelled. Destroying the response is normal then.
340
                return;
94✔
341
        }
342

343
        if (!has_replied_) {
1✔
344
                stream->logger_.Error(
2✔
345
                        "Response was destroyed before sending anything. Closing stream prematurely");
2✔
346
        }
347

348
        // Remove the stream from the server.
349
        stream->server_.RemoveStream(stream);
1✔
350
}
95✔
351

352
void OutgoingResponse::Cancel() {
95✔
353
        auto stream = stream_.lock();
190✔
354
        if (stream) {
95✔
355
                stream->Cancel();
1✔
356
        }
357
}
95✔
358

359
void OutgoingResponse::SetStatusCodeAndMessage(unsigned code, const string &message) {
94✔
360
        status_code_ = code;
94✔
361
        status_message_ = message;
94✔
362
}
94✔
363

364
void OutgoingResponse::SetHeader(const string &name, const string &value) {
82✔
365
        headers_[name] = value;
82✔
366
}
82✔
367

368
void OutgoingResponse::SetBodyReader(io::ReaderPtr body_reader) {
73✔
369
        async_body_reader_ = nullptr;
73✔
370
        body_reader_ = body_reader;
73✔
371
}
73✔
372

373
void OutgoingResponse::SetAsyncBodyReader(io::AsyncReaderPtr body_reader) {
1✔
374
        body_reader_ = nullptr;
1✔
375
        async_body_reader_ = body_reader;
1✔
376
}
1✔
377

378
error::Error OutgoingResponse::AsyncReply(ReplyFinishedHandler reply_finished_handler) {
94✔
379
        auto stream = stream_.lock();
188✔
380
        if (!stream) {
94✔
381
                return MakeError(StreamCancelledError, "Cannot send response");
1✔
382
        }
383

384
        stream->AsyncReply(reply_finished_handler);
93✔
385
        has_replied_ = true;
93✔
386
        return error::NoError;
93✔
387
}
388

389
ExponentialBackoff::ExpectedInterval ExponentialBackoff::NextInterval() {
63✔
390
        iteration_++;
63✔
391

392
        if (try_count_ > 0 && iteration_ > try_count_) {
63✔
393
                return expected::unexpected(MakeError(MaxRetryError, "Exponential backoff"));
8✔
394
        }
395

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

414
        return current_interval;
54✔
415
}
416

417
} // namespace http
418
} // 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