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

mendersoftware / mender / 2291262117

28 Jan 2026 10:00AM UTC coverage: 81.518% (+0.04%) from 81.475%
2291262117

push

gitlab-ci

web-flow
Merge pull request #1892 from lluiscampos/fix-build

chore: Fix build after bad merge

2 of 2 new or added lines in 1 file covered. (100.0%)

169 existing lines in 5 files now uncovered.

8874 of 10886 relevant lines covered (81.52%)

20156.53 hits per line

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

98.0
/src/common/http.hpp
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
#ifndef MENDER_COMMON_HTTP_HPP
16
#define MENDER_COMMON_HTTP_HPP
17

18
#include <functional>
19
#include <string>
20
#include <memory>
21
#include <unordered_map>
22
#include <unordered_set>
23
#include <vector>
24

25
#include <common/config.h>
26

27
#ifdef MENDER_USE_BOOST_BEAST
28
#include <boost/asio.hpp>
29
#include <boost/beast.hpp>
30
#include <boost/asio/ssl.hpp>
31
#include <boost/asio/ssl/error.hpp>
32
#include <boost/asio/ssl/stream.hpp>
33
#endif // MENDER_USE_BOOST_BEAST
34

35
#include <common/common.hpp>
36
#include <common/error.hpp>
37
#include <common/events.hpp>
38
#include <common/expected.hpp>
39
#include <common/io.hpp>
40
#include <common/log.hpp>
41
#include <client_shared/config_parser.hpp>
42

43
// For friend declaration below, used in tests.
44
class DeploymentsTests;
45
namespace mender {
46
namespace common {
47
namespace http {
48

49
namespace resumer {
50
class DownloadResumerClient;
51
class HeaderHandlerFunctor;
52
class BodyHandlerFunctor;
53
} // namespace resumer
54

55
using namespace std;
56

57
#ifdef MENDER_USE_BOOST_BEAST
58
namespace asio = boost::asio;
59
namespace beast = boost::beast;
60
namespace http = beast::http;
61
namespace ssl = asio::ssl;
62
using tcp = asio::ip::tcp;
63
#endif // MENDER_USE_BOOST_BEAST
64

65
namespace common = mender::common;
66
namespace error = mender::common::error;
67
namespace events = mender::common::events;
68
namespace expected = mender::common::expected;
69
namespace io = mender::common::io;
70
namespace log = mender::common::log;
71

72
class Client;
73
class ClientInterface;
74

75
class HttpErrorCategoryClass : public std::error_category {
76
public:
77
        const char *name() const noexcept override;
78
        string message(int code) const override;
79
};
80
extern const HttpErrorCategoryClass HttpErrorCategory;
81

82
enum ErrorCode {
83
        NoError = 0,
84
        NoSuchHeaderError,
85
        InvalidUrlError,
86
        BodyMissingError,
87
        BodyIgnoredError,
88
        HTTPInitError,
89
        UnsupportedMethodError,
90
        StreamCancelledError,
91
        MaxRetryError,
92
        DownloadResumerError,
93
        ProxyError,
94
        InvalidDateFormatError
95
};
96

97
error::Error MakeError(ErrorCode code, const string &msg);
98

99
enum class Method {
100
        Invalid,
101
        GET,
102
        HEAD,
103
        POST,
104
        PUT,
105
        PATCH,
106
        CONNECT,
107
};
108

109
enum StatusCode {
110
        // Not a complete enum, we define only the ones we use.
111

112
        StatusSwitchingProtocols = 101,
113

114
        StatusOK = 200,
115
        StatusNoContent = 204,
116
        StatusPartialContent = 206,
117

118
        StatusBadRequest = 400,
119
        StatusUnauthorized = 401,
120
        StatusNotFound = 404,
121
        StatusConflict = 409,
122
        StatusTooManyRequests = 429,
123

124
        StatusInternalServerError = 500,
125
        StatusNotImplemented = 501,
126
};
127

128
string MethodToString(Method method);
129

130
struct BrokenDownUrl {
131
        string protocol;
132
        string host;
133
        uint16_t port;
134
        string path;
135
        string username;
136
        string password;
137
};
138

139
error::Error BreakDownUrl(const string &url, BrokenDownUrl &address, bool with_auth = false);
140

141
string URLEncode(const string &value);
142
expected::ExpectedString URLDecode(const string &value);
143

144
string JoinOneUrl(const string &prefix, const string &url);
145

146
template <typename... Urls>
147
string JoinUrl(const string &prefix, const Urls &...urls) {
167✔
148
        string final_url {prefix};
167✔
149
        for (const auto &url : {urls...}) {
807✔
150
                final_url = JoinOneUrl(final_url, url);
348✔
151
        }
152
        return final_url;
167✔
153
}
154

155
// HTTP standard defines some headers, e.g. Retry-After to contain either the number of seconds
156
// to wait, or an HTTP-date after which something should happen.
157
// Based on either a number of seconds or a HTTP-date in the string calculate the number of seconds
158
// remaining from current time.
159
// HTTP-date format is defined in https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.1
160
// example: Sun, 06 Nov 1994 08:49:37 GMT
161
expected::Expected<chrono::seconds> GetRemainingTime(const string &date);
162

163
class CaseInsensitiveHasher {
164
public:
165
        size_t operator()(const string &str) const;
166
};
167

168
class CaseInsensitiveComparator {
169
public:
170
        bool operator()(const string &str1, const string &str2) const;
171
};
172

173
class Transaction {
174
public:
175
        virtual ~Transaction() {
1,324✔
176
        }
1,324✔
177

178
        expected::ExpectedString GetHeader(const string &name) const;
179

180
        using HeaderMap =
181
                unordered_map<string, string, CaseInsensitiveHasher, CaseInsensitiveComparator>;
182

183
        const HeaderMap &GetHeaders() const {
184
                return headers_;
3✔
185
        }
186

187
protected:
188
        HeaderMap headers_;
189

190
        friend class Client;
191
};
192
using TransactionPtr = shared_ptr<Transaction>;
193

194
using BodyGenerator = function<io::ExpectedReaderPtr()>;
195
using AsyncBodyGenerator = function<io::ExpectedAsyncReaderPtr()>;
196

197
class Request : public Transaction {
198
public:
199
        Request() {
535✔
200
        }
535✔
201

202
        string GetHost() const;
203
        string GetProtocol() const;
204
        int GetPort() const;
205
        Method GetMethod() const;
206
        string GetPath() const;
207

208
protected:
209
        Method method_ {Method::Invalid};
210
        BrokenDownUrl address_;
211

212
        friend class Client;
213
        friend class Stream;
214
};
215
using RequestPtr = shared_ptr<Request>;
216
using ExpectedRequestPtr = expected::expected<RequestPtr, error::Error>;
217

218
class Response : public Transaction {
219
public:
220
        Response() {
648✔
221
        }
648✔
222

223
        unsigned GetStatusCode() const;
224
        string GetStatusMessage() const;
225

226
protected:
227
        unsigned status_code_ {StatusInternalServerError};
228
        string status_message_;
229

230
        friend class Client;
231
        friend class Stream;
232
};
233
using ResponsePtr = shared_ptr<Response>;
234
using ExpectedResponsePtr = expected::expected<ResponsePtr, error::Error>;
235

236
class OutgoingRequest;
237
using OutgoingRequestPtr = shared_ptr<OutgoingRequest>;
238
using ExpectedOutgoingRequestPtr = expected::expected<OutgoingRequestPtr, error::Error>;
239
class IncomingRequest;
240
using IncomingRequestPtr = shared_ptr<IncomingRequest>;
241
using ExpectedIncomingRequestPtr = expected::expected<IncomingRequestPtr, error::Error>;
242
class IncomingResponse;
243
using IncomingResponsePtr = shared_ptr<IncomingResponse>;
244
using ExpectedIncomingResponsePtr = expected::expected<IncomingResponsePtr, error::Error>;
245
class OutgoingResponse;
246
using OutgoingResponsePtr = shared_ptr<OutgoingResponse>;
247
using ExpectedOutgoingResponsePtr = expected::expected<OutgoingResponsePtr, error::Error>;
248

249
using RequestHandler = function<void(ExpectedIncomingRequestPtr)>;
250
using IdentifiedRequestHandler = function<void(IncomingRequestPtr, error::Error)>;
251
using ResponseHandler = function<void(ExpectedIncomingResponsePtr)>;
252

253
using ReplyFinishedHandler = function<void(error::Error)>;
254
using SwitchProtocolHandler = function<void(io::ExpectedAsyncReadWriterPtr)>;
255

256
class BaseOutgoingRequest : public Request {
257
public:
258
        BaseOutgoingRequest() {
250✔
259
        }
250✔
260
        BaseOutgoingRequest(const BaseOutgoingRequest &other) = default;
423✔
261

262
        void SetMethod(Method method);
263
        void SetHeader(const string &name, const string &value);
264

265
        // Set to a function which will generate the body. Make sure that the Content-Length set in
266
        // the headers matches the length of the body. Using a generator instead of a direct reader
267
        // is needed in case of redirects. Note that it is not possible to set both; setting one
268
        // unsets the other.
269
        void SetBodyGenerator(BodyGenerator body_gen);
270
        void SetAsyncBodyGenerator(AsyncBodyGenerator body_gen);
271

272
protected:
273
        // Original address.
274
        string orig_address_;
275

276
private:
277
        BodyGenerator body_gen_;
278
        io::ReaderPtr body_reader_;
279
        AsyncBodyGenerator async_body_gen_;
280
        io::AsyncReaderPtr async_body_reader_;
281

282
        friend class Client;
283
};
284

285
class OutgoingRequest : public BaseOutgoingRequest {
286
public:
287
        OutgoingRequest() {
217✔
288
        }
217✔
289
        OutgoingRequest(const BaseOutgoingRequest &req) :
30✔
290
                BaseOutgoingRequest(req) {};
30✔
291
        error::Error SetAddress(const string &address);
292
};
293

294

295
class Stream;
296

297
class IncomingRequest :
298
        public Request,
299
        virtual public io::Canceller,
300
        public enable_shared_from_this<IncomingRequest> {
301
public:
302
        ~IncomingRequest();
303

304
        // Set this after receiving the headers to automatically write the body. If there is no
305
        // body, nothing will be written. Mutually exclusive with `MakeBodyAsyncReader()`.
306
        void SetBodyWriter(io::WriterPtr body_writer);
307

308
        // Use this to get an async reader for the body. If there is no body, it returns a
309
        // `BodyMissingError`; it's safe to continue afterwards, but without a reader. Mutually
310
        // exclusive with `SetBodyWriter()`.
311
        io::ExpectedAsyncReaderPtr MakeBodyAsyncReader();
312

313
        // Use this to get a response that can be used to reply to the request. Due to the
314
        // asynchronous nature, this can be done immediately or some time later.
315
        ExpectedOutgoingResponsePtr MakeResponse();
316

317
        void Cancel() override;
318

319
private:
320
        IncomingRequest(Stream &stream, shared_ptr<bool> cancelled) :
570✔
321
                stream_(stream),
285✔
322
                cancelled_(cancelled) {
285✔
323
        }
570✔
324

325
        Stream &stream_;
326
        shared_ptr<bool> cancelled_;
327

328
        friend class Server;
329
        friend class Stream;
330
};
331

332
class IncomingResponse :
333
        public Response,
334
        virtual public io::Canceller,
335
        public enable_shared_from_this<IncomingResponse> {
336
public:
337
        void Cancel() override;
338

339
        // Set this after receiving the headers to automatically write the body. If there is no
340
        // body, nothing will be written. Mutually exclusive with `MakeBodyAsyncReader()`.
341
        void SetBodyWriter(io::WriterPtr body_writer);
342

343
        // Use this to get an async reader for the body. If there is no body, it returns a
344
        // `BodyMissingError`; it's safe to continue afterwards, but without a reader. Mutually
345
        // exclusive with `SetBodyWriter()`.
346
        io::ExpectedAsyncReaderPtr MakeBodyAsyncReader();
347

348
        // Gets the underlying socket after a 101 Switching Protocols response. This detaches the
349
        // socket from `Client`, and both can be used independently from then on.
350
        io::ExpectedAsyncReadWriterPtr SwitchProtocol();
351

352
private:
353
        IncomingResponse(ClientInterface &client, shared_ptr<bool> cancelled);
354

355
private:
356
        ClientInterface &client_;
357
        shared_ptr<bool> cancelled_;
358

359
        friend class Client;
360
        friend class resumer::DownloadResumerClient;
361
        // The DownloadResumer's handlers needs to manipulate internals of IncomingResponse
362
        friend class resumer::HeaderHandlerFunctor;
363
        friend class resumer::BodyHandlerFunctor;
364
        friend class ::DeploymentsTests;
365
};
366

367
class OutgoingResponse :
368
        public Response,
369
        virtual public io::Canceller,
370
        public enable_shared_from_this<OutgoingResponse> {
371
public:
372
        ~OutgoingResponse();
373

374
        error::Error AsyncReply(ReplyFinishedHandler reply_finished_handler);
375
        void Cancel() override;
376

377
        void SetStatusCodeAndMessage(unsigned code, const string &message);
378
        void SetHeader(const string &name, const string &value);
379

380
        // Set to a Reader which contains the body. Make sure that the Content-Length set in the
381
        // headers matches the length of the body. Note that it is not possible to set both; setting
382
        // one unsets the other.
383
        void SetBodyReader(io::ReaderPtr body_reader);
384
        void SetAsyncBodyReader(io::AsyncReaderPtr body_reader);
385

386
        // An alternative to AsyncReply. `resp` should already contain the correct status and
387
        // headers to perform the switch, and the handler will be called after the HTTP headers have
388
        // been written.
389
        error::Error AsyncSwitchProtocol(SwitchProtocolHandler handler);
390

391
private:
392
        OutgoingResponse(Stream &stream, shared_ptr<bool> cancelled) :
552✔
393
                stream_ {stream},
276✔
394
                cancelled_ {cancelled} {
276✔
395
        }
552✔
396

397
        io::ReaderPtr body_reader_;
398
        io::AsyncReaderPtr async_body_reader_;
399

400
        Stream &stream_;
401
        shared_ptr<bool> cancelled_;
402

403
        friend class Server;
404
        friend class Stream;
405
        friend class IncomingRequest;
406
};
407

408
template <typename StreamType>
409
class BodyAsyncReader;
410

411

412

413
// Master object that connections are made from. Configure TLS options on this object before making
414
// connections.
415
struct ClientConfig {
416
        string server_cert_path;
417
        string client_cert_path;
418
        string client_cert_key_path;
419

420
        // C++11 cannot mix default member initializers with designated initializers
421
        // (named parameters). However, bool doesn't have a guaranteed initial value
422
        // so we need to use our custom type that defaults to false.
423
        common::def_bool skip_verify;
424

425
        string http_proxy;
426
        string https_proxy;
427
        string no_proxy;
428
        string ssl_engine;
429

430
        // Similar to skip_verify, provide default value, while keeping ClientConfig
431
        // a POD type, allowing named initalizer lists in C++11
432
        common::def_value<
433
                int,
434
                mender::client_shared::config_parser::MenderConfigFromFile::kRetry_download_count_default>
435
                retry_download_count;
436
};
437

438
enum class TransactionStatus {
439
        None,
440
        HeaderHandlerCalled,
441
        ReaderCreated,
442
        BodyReadingInProgress,
443
        BodyReadingFinished,
444
        BodyHandlerCalled, // Only used by server.
445
        Replying,          // Only used by server.
446
        SwitchingProtocol,
447
        Done,
448
};
449
static inline bool AtLeast(TransactionStatus status, TransactionStatus expected_status) {
450
        return static_cast<int>(status) >= static_cast<int>(expected_status);
451
}
452

453
// Interface which manages one connection, and its requests and responses (one at a time).
454
class ClientInterface {
135✔
455
public:
456
        virtual ~ClientInterface() {};
457

458
        // `header_handler` is called when header has arrived, `body_handler` is called when the
459
        // whole body has arrived.
460
        virtual error::Error AsyncCall(
461
                OutgoingRequestPtr req, ResponseHandler header_handler, ResponseHandler body_handler) = 0;
462
        virtual void Cancel() = 0;
463

464
        // Use this to get an async reader for the body. If there is no body, it returns a
465
        // `BodyMissingError`; it's safe to continue afterwards, but without a reader.
466
        virtual io::ExpectedAsyncReaderPtr MakeBodyAsyncReader(IncomingResponsePtr resp) = 0;
467

468
        // Returns the real HTTP client.
469
        virtual Client &GetHttpClient() = 0;
470
};
471

472
class Client :
473
        virtual public ClientInterface,
474
        public events::EventLoopObject,
475
        virtual public io::Canceller {
476
public:
477
        Client(
478
                const ClientConfig &client,
479
                events::EventLoop &event_loop,
480
                const string &logger_name = "http_client");
481
        virtual ~Client();
482

483
        Client(Client &&) = default;
484

485
        error::Error AsyncCall(
486
                OutgoingRequestPtr req,
487
                ResponseHandler header_handler,
488
                ResponseHandler body_handler) override;
489
        void Cancel() override;
490

491
        io::ExpectedAsyncReaderPtr MakeBodyAsyncReader(IncomingResponsePtr resp) override;
492

493
        // Gets the underlying socket after a 101 Switching Protocols response. This detaches the
494
        // socket from `Client`, and both can be used independently from then on.
495
        virtual io::ExpectedAsyncReadWriterPtr SwitchProtocol(IncomingResponsePtr req);
496

497
        Client &GetHttpClient() override {
132✔
498
                return *this;
132✔
499
        };
500

501
protected:
502
        events::EventLoop &event_loop_;
503
        string logger_name_;
504
        log::Logger logger_ {logger_name_};
505
        ClientConfig client_config_;
506

507
        string http_proxy_;
508
        string https_proxy_;
509
        string no_proxy_;
510

511
private:
512
        enum class SocketMode {
513
                Plain,
514
                Tls,
515
                TlsTls,
516
        };
517
        SocketMode socket_mode_;
518

519
        // Used during connections. Must remain valid due to async nature.
520
        OutgoingRequestPtr request_;
521
        IncomingResponsePtr response_;
522
        ResponseHandler header_handler_;
523
        ResponseHandler body_handler_;
524

525
        vector<uint8_t>::iterator reader_buf_start_;
526
        vector<uint8_t>::iterator reader_buf_end_;
527
        io::AsyncIoHandler reader_handler_;
528

529
        // Each time we cancel something, we set this to true, and then make a new one. This ensures
530
        // that for everyone who has a copy, it will stay true even after a new request is made, or
531
        // after things have been destroyed.
532
        shared_ptr<bool> cancelled_;
533

534
#ifdef MENDER_USE_BOOST_BEAST
535

536
        bool initialized_ {false};
537

538
#define MENDER_BOOST_BEAST_SSL_CTX_COUNT 2
539

540
        ssl::context ssl_ctx_[MENDER_BOOST_BEAST_SSL_CTX_COUNT] = {
541
                ssl::context {ssl::context::tls_client},
542
                ssl::context {ssl::context::tls_client},
543
        };
544

545
        boost::asio::ip::tcp::resolver resolver_;
546
        shared_ptr<ssl::stream<ssl::stream<beast::tcp_stream>>> stream_;
547

548
        vector<uint8_t> body_buffer_;
549

550
        asio::ip::tcp::resolver::results_type resolver_results_;
551

552
        // The reason that these are inside a struct is a bit complicated. We need to deal with what
553
        // may be a bug in Boost Beast: Parsers and serializers can access the corresponding request
554
        // and response structures even after they have been cancelled. This means two things:
555
        //
556
        // 1. We need to make sure that the response/request and the parser/serializer both survive
557
        //    until the handler is called, even if they are not used in the handler, and even if
558
        //    the handler returns `operation_aborted` (cancelled).
559
        //
560
        // 2. We need to make sure that the parser/serializer is destroyed before the
561
        //    response/request, since the former accesses the latter.
562
        //
563
        // For point number 1, it is enough to simply make a copy of the shared pointers in the
564
        // handler function, which will keep them alive long enough.
565
        //
566
        // For point 2 however, even though it may seem logical that a lambda would destroy its
567
        // captured variables in the reverse order they are captured, the order is in fact
568
        // unspecified. That means we need to enforce the order, and that's what the struct is
569
        // for: Struct members are always destroyed in reverse declaration order.
570
        struct {
571
                shared_ptr<http::request<http::buffer_body>> http_request_;
572
                shared_ptr<http::request_serializer<http::buffer_body>> http_request_serializer_;
573
        } request_data_;
574

575
        // See `Client::request_data_` for why this is a struct.
576
        struct {
577
                shared_ptr<beast::flat_buffer> response_buffer_;
578
                shared_ptr<http::response_parser<http::buffer_body>> http_response_parser_;
579
                size_t last_buffer_size_;
580
        } response_data_;
581
        TransactionStatus status_ {TransactionStatus::None};
582

583
        // Only used for HTTPS proxy requests, because we need two requests, one to CONNECT, and one
584
        // for the original request. HTTP doesn't need it because it only modifies the original
585
        // request.
586
        OutgoingRequestPtr secondary_req_;
587

588
        error::Error Initialize();
589
        void DoCancel();
590

591
        void CallHandler(ResponseHandler handler);
592
        void CallErrorHandler(
593
                const error_code &ec, const OutgoingRequestPtr &req, ResponseHandler handler);
594
        void CallErrorHandler(
595
                const error::Error &err, const OutgoingRequestPtr &req, ResponseHandler handler);
596
        error::Error HandleProxySetup();
597
        void ResolveHandler(const error_code &ec, const asio::ip::tcp::resolver::results_type &results);
598
        void ConnectHandler(const error_code &ec, const asio::ip::tcp::endpoint &endpoint);
599
        template <typename StreamType>
600
        void HandshakeHandler(
601
                StreamType &stream, const error_code &ec, const asio::ip::tcp::endpoint &endpoint);
602
        void WriteHeaderHandler(const error_code &ec, size_t num_written);
603
        void WriteBodyHandler(const error_code &ec, size_t num_written);
604
        void PrepareAndWriteNewBodyBuffer();
605
        void WriteNewBodyBuffer(size_t size);
606
        void WriteBody();
607
        void ReadHeaderHandler(const error_code &ec, size_t num_read);
608
        void HandleSecondaryRequest();
609
        void ReadHeader();
610
        void AsyncReadNextBodyPart(
611
                vector<uint8_t>::iterator start, vector<uint8_t>::iterator end, io::AsyncIoHandler handler);
612
        void ReadBodyHandler(error_code ec, size_t num_read);
613
#endif // MENDER_USE_BOOST_BEAST
614

615
        friend class IncomingResponse;
616
        friend class BodyAsyncReader<Client>;
617
};
618
using ClientPtr = shared_ptr<Client>;
619

620
// Master object that servers are made from.
621
struct ServerConfig {
622
        // Empty for now, but will probably contain configuration options later.
623
};
624

625
class Server;
626

627
class Stream : public enable_shared_from_this<Stream> {
628
public:
629
        Stream(const Stream &) = delete;
630
        ~Stream();
631

632
        void Cancel();
633

634
private:
635
        Stream(Server &server);
636

637
private:
638
        Server &server_;
639
        friend class Server;
640

641
        log::Logger logger_;
642

643
        IncomingRequestPtr request_;
644

645
        // The reason we have two pointers is this: Between receiving a request, and producing a
646
        // reply, an arbitrary amount of time may pass, and it is the caller's responsibility to
647
        // first call MakeResponse(), and then at some point later, call AsyncReply(). However, if
648
        // the caller never does this, and destroys the response instead, we still have ownership to
649
        // the response here, which means it will never be destroyed, and we will leak memory. So we
650
        // use a weak_ptr to bridge the gap. As long as AsyncReply() has not been called yet, we use
651
        // a weak pointer so if the response goes out of scope, it will be properly destroyed. After
652
        // AsyncReply is called, we know that a handler will eventually be called, so we take
653
        // ownership of the response object from that point onwards.
654
        OutgoingResponsePtr response_;
655
        weak_ptr<OutgoingResponse> maybe_response_;
656

657
        friend class IncomingRequest;
658
        friend class OutgoingResponse;
659
        friend class BodyAsyncReader<Stream>;
660

661
        ReplyFinishedHandler reply_finished_handler_;
662
        SwitchProtocolHandler switch_protocol_handler_;
663

664
        vector<uint8_t>::iterator reader_buf_start_;
665
        vector<uint8_t>::iterator reader_buf_end_;
666
        io::AsyncIoHandler reader_handler_;
667

668
        // Each time we cancel something, we set this to true, and then make a new one. This ensures
669
        // that for everyone who has a copy, it will stay true even after a new request is made, or
670
        // after things have been destroyed.
671
        shared_ptr<bool> cancelled_;
672

673
#ifdef MENDER_USE_BOOST_BEAST
674
        asio::ip::tcp::socket socket_;
675

676
        // See `Client::request_data_` for why this is a struct.
677
        struct {
678
                shared_ptr<beast::flat_buffer> request_buffer_;
679
                shared_ptr<http::request_parser<http::buffer_body>> http_request_parser_;
680
                size_t last_buffer_size_;
681
        } request_data_;
682
        vector<uint8_t> body_buffer_;
683
        TransactionStatus status_ {TransactionStatus::None};
684

685
        // See `Client::request_data_` for why this is a struct.
686
        struct {
687
                shared_ptr<http::response<http::buffer_body>> http_response_;
688
                shared_ptr<http::response_serializer<http::buffer_body>> http_response_serializer_;
689
        } response_data_;
690

691
        void DoCancel();
692

693
        void CallErrorHandler(const error_code &ec, const RequestPtr &req, RequestHandler handler);
694
        void CallErrorHandler(const error::Error &err, const RequestPtr &req, RequestHandler handler);
695
        void CallErrorHandler(
696
                const error_code &ec, const IncomingRequestPtr &req, IdentifiedRequestHandler handler);
697
        void CallErrorHandler(
698
                const error::Error &err, const IncomingRequestPtr &req, IdentifiedRequestHandler handler);
699
        void CallErrorHandler(
700
                const error_code &ec, const RequestPtr &req, ReplyFinishedHandler handler);
701
        void CallErrorHandler(
702
                const error::Error &err, const RequestPtr &req, ReplyFinishedHandler handler);
703
        void CallErrorHandler(
704
                const error_code &ec, const RequestPtr &req, SwitchProtocolHandler handler);
705
        void CallErrorHandler(
706
                const error::Error &err, const RequestPtr &req, SwitchProtocolHandler handler);
707

708
        void AcceptHandler(const error_code &ec);
709
        void ReadHeader();
710
        void ReadHeaderHandler(const error_code &ec, size_t num_read);
711
        void AsyncReadNextBodyPart(
712
                vector<uint8_t>::iterator start, vector<uint8_t>::iterator end, io::AsyncIoHandler handler);
713
        void ReadBodyHandler(error_code ec, size_t num_read);
714
        void AsyncReply(ReplyFinishedHandler reply_finished_handler);
715
        void SetupResponse();
716
        void WriteHeaderHandler(const error_code &ec, size_t num_written);
717
        void PrepareAndWriteNewBodyBuffer();
718
        void WriteNewBodyBuffer(size_t size);
719
        void WriteBody();
720
        void WriteBodyHandler(const error_code &ec, size_t num_written);
721
        void CallBodyHandler();
722
        void FinishReply();
723
        error::Error AsyncSwitchProtocol(SwitchProtocolHandler handler);
724
        void SwitchingProtocolHandler(error_code ec, size_t num_written);
725
#endif // MENDER_USE_BOOST_BEAST
726
};
727

728
class Server : public events::EventLoopObject, virtual public io::Canceller {
729
public:
730
        Server(const ServerConfig &server, events::EventLoop &event_loop);
731
        ~Server();
732

733
        Server(Server &&) = default;
734

735
        error::Error AsyncServeUrl(
736
                const string &url, RequestHandler header_handler, RequestHandler body_handler);
737
        // Same as the above, except that the body handler has the `IncomingRequestPtr` included
738
        // even when there is an error, so that the request can be matched with the request which
739
        // was received in the header handler.
740
        error::Error AsyncServeUrl(
741
                const string &url, RequestHandler header_handler, IdentifiedRequestHandler body_handler);
742
        void Cancel() override;
743

744
        uint16_t GetPort() const;
745
        // Can differ from the passed in URL if a 0 (random) port number was used.
746
        string GetUrl() const;
747

748
        // Use this to get a response that can be used to reply to the request. Due to the
749
        // asynchronous nature, this can be done immediately or some time later.
750
        virtual ExpectedOutgoingResponsePtr MakeResponse(IncomingRequestPtr req);
751
        virtual error::Error AsyncReply(
752
                OutgoingResponsePtr resp, ReplyFinishedHandler reply_finished_handler);
753

754
        // Use this to get an async reader for the body. If there is no body, it returns a
755
        // `BodyMissingError`; it's safe to continue afterwards, but without a reader.
756
        virtual io::ExpectedAsyncReaderPtr MakeBodyAsyncReader(IncomingRequestPtr req);
757

758
        // An alternative to AsyncReply. `resp` should already contain the correct status and
759
        // headers to perform the switch, and the handler will be called after the HTTP headers have
760
        // been written.
761
        virtual error::Error AsyncSwitchProtocol(
762
                OutgoingResponsePtr resp, SwitchProtocolHandler handler);
763

764
private:
765
        events::EventLoop &event_loop_;
766

767
        BrokenDownUrl address_;
768

769
        RequestHandler header_handler_;
770
        IdentifiedRequestHandler body_handler_;
771

772
        friend class IncomingRequest;
773
        friend class Stream;
774
        friend class OutgoingResponse;
775

776
        using StreamPtr = shared_ptr<Stream>;
777

778
        friend class TestInspector;
779

780
#ifdef MENDER_USE_BOOST_BEAST
781
        asio::ip::tcp::acceptor acceptor_;
782

783
        unordered_set<StreamPtr> streams_;
784

785
        void DoCancel();
786

787
        void PrepareNewStream();
788
        void AsyncAccept(StreamPtr stream);
789
        void RemoveStream(StreamPtr stream);
790
#endif // MENDER_USE_BOOST_BEAST
791
};
792

793
class ExponentialBackoff {
794
public:
795
        ExponentialBackoff(chrono::milliseconds max_interval, int try_count = -1) :
529✔
796
                try_count_ {try_count} {
529✔
797
                SetMaxInterval(max_interval);
529✔
798
        }
529✔
799

800
        void Reset() {
801
                SetIteration(0);
802
        }
115✔
803

804
        int TryCount() {
805
                return try_count_;
102✔
806
        }
807
        void SetTryCount(int count) {
808
                try_count_ = count;
1✔
809
        }
810

811
        chrono::milliseconds SmallestInterval() {
812
                return smallest_interval_;
18✔
813
        }
814
        void SetSmallestInterval(chrono::milliseconds interval) {
217✔
815
                smallest_interval_ = interval;
217✔
816
                if (max_interval_ < smallest_interval_) {
217✔
UNCOV
817
                        max_interval_ = smallest_interval_;
×
818
                }
819
        }
217✔
820

821
        chrono::milliseconds MaxInterval() {
822
                return max_interval_;
823
        }
824
        void SetMaxInterval(chrono::milliseconds interval) {
555✔
825
                max_interval_ = interval;
555✔
826
                if (max_interval_ < smallest_interval_) {
555✔
827
                        max_interval_ = smallest_interval_;
385✔
828
                }
829
        }
555✔
830

831
        using ExpectedInterval = expected::expected<chrono::milliseconds, error::Error>;
832
        ExpectedInterval NextInterval();
833

834
        // Set which iteration we're at. Mainly for use in tests.
835
        void SetIteration(int iteration) {
836
                iteration_ = iteration;
328✔
837
        }
838

839
        int CurrentIteration() const {
840
                return iteration_;
102✔
841
        }
842

843
private:
844
        chrono::milliseconds smallest_interval_ {chrono::minutes(1)};
845
        chrono::milliseconds max_interval_;
846
        int try_count_;
847

848
        int iteration_ {0};
849
};
850

851
expected::ExpectedString GetHttpProxyStringFromEnvironment();
852
expected::ExpectedString GetHttpsProxyStringFromEnvironment();
853
expected::ExpectedString GetNoProxyStringFromEnvironment();
854

855
bool HostNameMatchesNoProxy(const string &host, const string &no_proxy);
856

857
} // namespace http
858
} // namespace common
859
} // namespace mender
860

861
#endif // MENDER_COMMON_HTTP_HPP
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

© 2026 Coveralls, Inc