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

mendersoftware / mender / 2281564137

23 Jan 2026 10:59AM UTC coverage: 81.48% (+1.7%) from 79.764%
2281564137

push

gitlab-ci

michalkopczan
fix: Schedule next deployment poll if current one failed early causing no handler to be called

Ticket: MEN-9144
Changelog: Fix a hang when polling for deployment failed early causing no handler of API response
to be called. Added handler call for this case, causing the deployment polling
to continue.

Signed-off-by: Michal Kopczan <michal.kopczan@northern.tech>

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

327 existing lines in 44 files now uncovered.

8839 of 10848 relevant lines covered (81.48%)

20226.53 hits per line

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

97.96
/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
namespace mender {
44
namespace common {
45
namespace http {
46

47
namespace resumer {
48
class DownloadResumerClient;
49
class HeaderHandlerFunctor;
50
class BodyHandlerFunctor;
51
} // namespace resumer
52

53
using namespace std;
54

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

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

70
class Client;
71
class ClientInterface;
72

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

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

94
error::Error MakeError(ErrorCode code, const string &msg);
95

96
enum class Method {
97
        Invalid,
98
        GET,
99
        HEAD,
100
        POST,
101
        PUT,
102
        PATCH,
103
        CONNECT,
104
};
105

106
enum StatusCode {
107
        // Not a complete enum, we define only the ones we use.
108

109
        StatusSwitchingProtocols = 101,
110

111
        StatusOK = 200,
112
        StatusNoContent = 204,
113
        StatusPartialContent = 206,
114

115
        StatusBadRequest = 400,
116
        StatusUnauthorized = 401,
117
        StatusNotFound = 404,
118
        StatusConflict = 409,
119

120
        StatusInternalServerError = 500,
121
        StatusNotImplemented = 501,
122
};
123

124
string MethodToString(Method method);
125

126
struct BrokenDownUrl {
127
        string protocol;
128
        string host;
129
        uint16_t port;
130
        string path;
131
        string username;
132
        string password;
133
};
134

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

137
string URLEncode(const string &value);
138
expected::ExpectedString URLDecode(const string &value);
139

140
string JoinOneUrl(const string &prefix, const string &url);
141

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

151
class CaseInsensitiveHasher {
152
public:
153
        size_t operator()(const string &str) const;
154
};
155

156
class CaseInsensitiveComparator {
157
public:
158
        bool operator()(const string &str1, const string &str2) const;
159
};
160

161
class Transaction {
162
public:
163
        virtual ~Transaction() {
1,321✔
164
        }
1,321✔
165

166
        expected::ExpectedString GetHeader(const string &name) const;
167

168
        using HeaderMap =
169
                unordered_map<string, string, CaseInsensitiveHasher, CaseInsensitiveComparator>;
170

171
        const HeaderMap &GetHeaders() const {
172
                return headers_;
173
        }
174

175
protected:
176
        HeaderMap headers_;
177

178
        friend class Client;
179
};
180
using TransactionPtr = shared_ptr<Transaction>;
181

182
using BodyGenerator = function<io::ExpectedReaderPtr()>;
183
using AsyncBodyGenerator = function<io::ExpectedAsyncReaderPtr()>;
184

185
class Request : public Transaction {
186
public:
187
        Request() {
535✔
188
        }
535✔
189

190
        string GetHost() const;
191
        string GetProtocol() const;
192
        int GetPort() const;
193
        Method GetMethod() const;
194
        string GetPath() const;
195

196
protected:
197
        Method method_ {Method::Invalid};
198
        BrokenDownUrl address_;
199

200
        friend class Client;
201
        friend class Stream;
202
};
203
using RequestPtr = shared_ptr<Request>;
204
using ExpectedRequestPtr = expected::expected<RequestPtr, error::Error>;
205

206
class Response : public Transaction {
207
public:
208
        Response() {
645✔
209
        }
645✔
210

211
        unsigned GetStatusCode() const;
212
        string GetStatusMessage() const;
213

214
protected:
215
        unsigned status_code_ {StatusInternalServerError};
216
        string status_message_;
217

218
        friend class Client;
219
        friend class Stream;
220
};
221
using ResponsePtr = shared_ptr<Response>;
222
using ExpectedResponsePtr = expected::expected<ResponsePtr, error::Error>;
223

224
class OutgoingRequest;
225
using OutgoingRequestPtr = shared_ptr<OutgoingRequest>;
226
using ExpectedOutgoingRequestPtr = expected::expected<OutgoingRequestPtr, error::Error>;
227
class IncomingRequest;
228
using IncomingRequestPtr = shared_ptr<IncomingRequest>;
229
using ExpectedIncomingRequestPtr = expected::expected<IncomingRequestPtr, error::Error>;
230
class IncomingResponse;
231
using IncomingResponsePtr = shared_ptr<IncomingResponse>;
232
using ExpectedIncomingResponsePtr = expected::expected<IncomingResponsePtr, error::Error>;
233
class OutgoingResponse;
234
using OutgoingResponsePtr = shared_ptr<OutgoingResponse>;
235
using ExpectedOutgoingResponsePtr = expected::expected<OutgoingResponsePtr, error::Error>;
236

237
using RequestHandler = function<void(ExpectedIncomingRequestPtr)>;
238
using IdentifiedRequestHandler = function<void(IncomingRequestPtr, error::Error)>;
239
using ResponseHandler = function<void(ExpectedIncomingResponsePtr)>;
240

241
using ReplyFinishedHandler = function<void(error::Error)>;
242
using SwitchProtocolHandler = function<void(io::ExpectedAsyncReadWriterPtr)>;
243

244
class BaseOutgoingRequest : public Request {
245
public:
246
        BaseOutgoingRequest() {
250✔
247
        }
250✔
248
        BaseOutgoingRequest(const BaseOutgoingRequest &other) = default;
423✔
249

250
        void SetMethod(Method method);
251
        void SetHeader(const string &name, const string &value);
252

253
        // Set to a function which will generate the body. Make sure that the Content-Length set in
254
        // the headers matches the length of the body. Using a generator instead of a direct reader
255
        // is needed in case of redirects. Note that it is not possible to set both; setting one
256
        // unsets the other.
257
        void SetBodyGenerator(BodyGenerator body_gen);
258
        void SetAsyncBodyGenerator(AsyncBodyGenerator body_gen);
259

260
protected:
261
        // Original address.
262
        string orig_address_;
263

264
private:
265
        BodyGenerator body_gen_;
266
        io::ReaderPtr body_reader_;
267
        AsyncBodyGenerator async_body_gen_;
268
        io::AsyncReaderPtr async_body_reader_;
269

270
        friend class Client;
271
};
272

273
class OutgoingRequest : public BaseOutgoingRequest {
274
public:
275
        OutgoingRequest() {
217✔
276
        }
217✔
277
        OutgoingRequest(const BaseOutgoingRequest &req) :
30✔
278
                BaseOutgoingRequest(req) {};
30✔
279
        error::Error SetAddress(const string &address);
280
};
281

282

283
class Stream;
284

285
class IncomingRequest :
286
        public Request,
287
        virtual public io::Canceller,
288
        public enable_shared_from_this<IncomingRequest> {
289
public:
290
        ~IncomingRequest();
291

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

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

301
        // Use this to get a response that can be used to reply to the request. Due to the
302
        // asynchronous nature, this can be done immediately or some time later.
303
        ExpectedOutgoingResponsePtr MakeResponse();
304

305
        void Cancel() override;
306

307
private:
308
        IncomingRequest(Stream &stream, shared_ptr<bool> cancelled) :
570✔
309
                stream_(stream),
285✔
310
                cancelled_(cancelled) {
285✔
311
        }
570✔
312

313
        Stream &stream_;
314
        shared_ptr<bool> cancelled_;
315

316
        friend class Server;
317
        friend class Stream;
318
};
319

320
class IncomingResponse :
321
        public Response,
322
        virtual public io::Canceller,
323
        public enable_shared_from_this<IncomingResponse> {
324
public:
325
        void Cancel() override;
326

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

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

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

340
private:
341
        IncomingResponse(ClientInterface &client, shared_ptr<bool> cancelled);
342

343
private:
344
        ClientInterface &client_;
345
        shared_ptr<bool> cancelled_;
346

347
        friend class Client;
348
        friend class resumer::DownloadResumerClient;
349
        // The DownloadResumer's handlers needs to manipulate internals of IncomingResponse
350
        friend class resumer::HeaderHandlerFunctor;
351
        friend class resumer::BodyHandlerFunctor;
352
};
353

354
class OutgoingResponse :
355
        public Response,
356
        virtual public io::Canceller,
357
        public enable_shared_from_this<OutgoingResponse> {
358
public:
359
        ~OutgoingResponse();
360

361
        error::Error AsyncReply(ReplyFinishedHandler reply_finished_handler);
362
        void Cancel() override;
363

364
        void SetStatusCodeAndMessage(unsigned code, const string &message);
365
        void SetHeader(const string &name, const string &value);
366

367
        // Set to a Reader which contains the body. Make sure that the Content-Length set in the
368
        // headers matches the length of the body. Note that it is not possible to set both; setting
369
        // one unsets the other.
370
        void SetBodyReader(io::ReaderPtr body_reader);
371
        void SetAsyncBodyReader(io::AsyncReaderPtr body_reader);
372

373
        // An alternative to AsyncReply. `resp` should already contain the correct status and
374
        // headers to perform the switch, and the handler will be called after the HTTP headers have
375
        // been written.
376
        error::Error AsyncSwitchProtocol(SwitchProtocolHandler handler);
377

378
private:
379
        OutgoingResponse(Stream &stream, shared_ptr<bool> cancelled) :
552✔
380
                stream_ {stream},
276✔
381
                cancelled_ {cancelled} {
276✔
382
        }
552✔
383

384
        io::ReaderPtr body_reader_;
385
        io::AsyncReaderPtr async_body_reader_;
386

387
        Stream &stream_;
388
        shared_ptr<bool> cancelled_;
389

390
        friend class Server;
391
        friend class Stream;
392
        friend class IncomingRequest;
393
};
394

395
template <typename StreamType>
396
class BodyAsyncReader;
397

398

399

400
// Master object that connections are made from. Configure TLS options on this object before making
401
// connections.
402
struct ClientConfig {
403
        string server_cert_path;
404
        string client_cert_path;
405
        string client_cert_key_path;
406

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

412
        string http_proxy;
413
        string https_proxy;
414
        string no_proxy;
415
        string ssl_engine;
416

417
        // Similar to skip_verify, provide default value, while keeping ClientConfig
418
        // a POD type, allowing named initalizer lists in C++11
419
        common::def_value<
420
                int,
421
                mender::client_shared::config_parser::MenderConfigFromFile::kRetry_download_count_default>
422
                retry_download_count;
423
};
424

425
enum class TransactionStatus {
426
        None,
427
        HeaderHandlerCalled,
428
        ReaderCreated,
429
        BodyReadingInProgress,
430
        BodyReadingFinished,
431
        BodyHandlerCalled, // Only used by server.
432
        Replying,          // Only used by server.
433
        SwitchingProtocol,
434
        Done,
435
};
436
static inline bool AtLeast(TransactionStatus status, TransactionStatus expected_status) {
437
        return static_cast<int>(status) >= static_cast<int>(expected_status);
438
}
439

440
// Interface which manages one connection, and its requests and responses (one at a time).
441
class ClientInterface {
132✔
442
public:
443
        virtual ~ClientInterface() {};
444

445
        // `header_handler` is called when header has arrived, `body_handler` is called when the
446
        // whole body has arrived.
447
        virtual error::Error AsyncCall(
448
                OutgoingRequestPtr req, ResponseHandler header_handler, ResponseHandler body_handler) = 0;
449
        virtual void Cancel() = 0;
450

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

455
        // Returns the real HTTP client.
456
        virtual Client &GetHttpClient() = 0;
457
};
458

459
class Client :
460
        virtual public ClientInterface,
461
        public events::EventLoopObject,
462
        virtual public io::Canceller {
463
public:
464
        Client(
465
                const ClientConfig &client,
466
                events::EventLoop &event_loop,
467
                const string &logger_name = "http_client");
468
        virtual ~Client();
469

470
        Client(Client &&) = default;
471

472
        error::Error AsyncCall(
473
                OutgoingRequestPtr req,
474
                ResponseHandler header_handler,
475
                ResponseHandler body_handler) override;
476
        void Cancel() override;
477

478
        io::ExpectedAsyncReaderPtr MakeBodyAsyncReader(IncomingResponsePtr resp) override;
479

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

484
        Client &GetHttpClient() override {
132✔
485
                return *this;
132✔
486
        };
487

488
protected:
489
        events::EventLoop &event_loop_;
490
        string logger_name_;
491
        log::Logger logger_ {logger_name_};
492
        ClientConfig client_config_;
493

494
        string http_proxy_;
495
        string https_proxy_;
496
        string no_proxy_;
497

498
private:
499
        enum class SocketMode {
500
                Plain,
501
                Tls,
502
                TlsTls,
503
        };
504
        SocketMode socket_mode_;
505

506
        // Used during connections. Must remain valid due to async nature.
507
        OutgoingRequestPtr request_;
508
        IncomingResponsePtr response_;
509
        ResponseHandler header_handler_;
510
        ResponseHandler body_handler_;
511

512
        vector<uint8_t>::iterator reader_buf_start_;
513
        vector<uint8_t>::iterator reader_buf_end_;
514
        io::AsyncIoHandler reader_handler_;
515

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

521
#ifdef MENDER_USE_BOOST_BEAST
522

523
        bool initialized_ {false};
524

525
#define MENDER_BOOST_BEAST_SSL_CTX_COUNT 2
526

527
        ssl::context ssl_ctx_[MENDER_BOOST_BEAST_SSL_CTX_COUNT] = {
528
                ssl::context {ssl::context::tls_client},
529
                ssl::context {ssl::context::tls_client},
530
        };
531

532
        boost::asio::ip::tcp::resolver resolver_;
533
        shared_ptr<ssl::stream<ssl::stream<beast::tcp_stream>>> stream_;
534

535
        vector<uint8_t> body_buffer_;
536

537
        asio::ip::tcp::resolver::results_type resolver_results_;
538

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

562
        // See `Client::request_data_` for why this is a struct.
563
        struct {
564
                shared_ptr<beast::flat_buffer> response_buffer_;
565
                shared_ptr<http::response_parser<http::buffer_body>> http_response_parser_;
566
                size_t last_buffer_size_;
567
        } response_data_;
568
        TransactionStatus status_ {TransactionStatus::None};
569

570
        // Only used for HTTPS proxy requests, because we need two requests, one to CONNECT, and one
571
        // for the original request. HTTP doesn't need it because it only modifies the original
572
        // request.
573
        OutgoingRequestPtr secondary_req_;
574

575
        error::Error Initialize();
576
        void DoCancel();
577

578
        void CallHandler(ResponseHandler handler);
579
        void CallErrorHandler(
580
                const error_code &ec, const OutgoingRequestPtr &req, ResponseHandler handler);
581
        void CallErrorHandler(
582
                const error::Error &err, const OutgoingRequestPtr &req, ResponseHandler handler);
583
        error::Error HandleProxySetup();
584
        void ResolveHandler(const error_code &ec, const asio::ip::tcp::resolver::results_type &results);
585
        void ConnectHandler(const error_code &ec, const asio::ip::tcp::endpoint &endpoint);
586
        template <typename StreamType>
587
        void HandshakeHandler(
588
                StreamType &stream, const error_code &ec, const asio::ip::tcp::endpoint &endpoint);
589
        void WriteHeaderHandler(const error_code &ec, size_t num_written);
590
        void WriteBodyHandler(const error_code &ec, size_t num_written);
591
        void PrepareAndWriteNewBodyBuffer();
592
        void WriteNewBodyBuffer(size_t size);
593
        void WriteBody();
594
        void ReadHeaderHandler(const error_code &ec, size_t num_read);
595
        void HandleSecondaryRequest();
596
        void ReadHeader();
597
        void AsyncReadNextBodyPart(
598
                vector<uint8_t>::iterator start, vector<uint8_t>::iterator end, io::AsyncIoHandler handler);
599
        void ReadBodyHandler(error_code ec, size_t num_read);
600
#endif // MENDER_USE_BOOST_BEAST
601

602
        friend class IncomingResponse;
603
        friend class BodyAsyncReader<Client>;
604
};
605
using ClientPtr = shared_ptr<Client>;
606

607
// Master object that servers are made from.
608
struct ServerConfig {
609
        // Empty for now, but will probably contain configuration options later.
610
};
611

612
class Server;
613

614
class Stream : public enable_shared_from_this<Stream> {
615
public:
616
        Stream(const Stream &) = delete;
617
        ~Stream();
618

619
        void Cancel();
620

621
private:
622
        Stream(Server &server);
623

624
private:
625
        Server &server_;
626
        friend class Server;
627

628
        log::Logger logger_;
629

630
        IncomingRequestPtr request_;
631

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

644
        friend class IncomingRequest;
645
        friend class OutgoingResponse;
646
        friend class BodyAsyncReader<Stream>;
647

648
        ReplyFinishedHandler reply_finished_handler_;
649
        SwitchProtocolHandler switch_protocol_handler_;
650

651
        vector<uint8_t>::iterator reader_buf_start_;
652
        vector<uint8_t>::iterator reader_buf_end_;
653
        io::AsyncIoHandler reader_handler_;
654

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

660
#ifdef MENDER_USE_BOOST_BEAST
661
        asio::ip::tcp::socket socket_;
662

663
        // See `Client::request_data_` for why this is a struct.
664
        struct {
665
                shared_ptr<beast::flat_buffer> request_buffer_;
666
                shared_ptr<http::request_parser<http::buffer_body>> http_request_parser_;
667
                size_t last_buffer_size_;
668
        } request_data_;
669
        vector<uint8_t> body_buffer_;
670
        TransactionStatus status_ {TransactionStatus::None};
671

672
        // See `Client::request_data_` for why this is a struct.
673
        struct {
674
                shared_ptr<http::response<http::buffer_body>> http_response_;
675
                shared_ptr<http::response_serializer<http::buffer_body>> http_response_serializer_;
676
        } response_data_;
677

678
        void DoCancel();
679

680
        void CallErrorHandler(const error_code &ec, const RequestPtr &req, RequestHandler handler);
681
        void CallErrorHandler(const error::Error &err, const RequestPtr &req, RequestHandler handler);
682
        void CallErrorHandler(
683
                const error_code &ec, const IncomingRequestPtr &req, IdentifiedRequestHandler handler);
684
        void CallErrorHandler(
685
                const error::Error &err, const IncomingRequestPtr &req, IdentifiedRequestHandler handler);
686
        void CallErrorHandler(
687
                const error_code &ec, const RequestPtr &req, ReplyFinishedHandler handler);
688
        void CallErrorHandler(
689
                const error::Error &err, const RequestPtr &req, ReplyFinishedHandler handler);
690
        void CallErrorHandler(
691
                const error_code &ec, const RequestPtr &req, SwitchProtocolHandler handler);
692
        void CallErrorHandler(
693
                const error::Error &err, const RequestPtr &req, SwitchProtocolHandler handler);
694

695
        void AcceptHandler(const error_code &ec);
696
        void ReadHeader();
697
        void ReadHeaderHandler(const error_code &ec, size_t num_read);
698
        void AsyncReadNextBodyPart(
699
                vector<uint8_t>::iterator start, vector<uint8_t>::iterator end, io::AsyncIoHandler handler);
700
        void ReadBodyHandler(error_code ec, size_t num_read);
701
        void AsyncReply(ReplyFinishedHandler reply_finished_handler);
702
        void SetupResponse();
703
        void WriteHeaderHandler(const error_code &ec, size_t num_written);
704
        void PrepareAndWriteNewBodyBuffer();
705
        void WriteNewBodyBuffer(size_t size);
706
        void WriteBody();
707
        void WriteBodyHandler(const error_code &ec, size_t num_written);
708
        void CallBodyHandler();
709
        void FinishReply();
710
        error::Error AsyncSwitchProtocol(SwitchProtocolHandler handler);
711
        void SwitchingProtocolHandler(error_code ec, size_t num_written);
712
#endif // MENDER_USE_BOOST_BEAST
713
};
714

715
class Server : public events::EventLoopObject, virtual public io::Canceller {
716
public:
717
        Server(const ServerConfig &server, events::EventLoop &event_loop);
718
        ~Server();
719

720
        Server(Server &&) = default;
721

722
        error::Error AsyncServeUrl(
723
                const string &url, RequestHandler header_handler, RequestHandler body_handler);
724
        // Same as the above, except that the body handler has the `IncomingRequestPtr` included
725
        // even when there is an error, so that the request can be matched with the request which
726
        // was received in the header handler.
727
        error::Error AsyncServeUrl(
728
                const string &url, RequestHandler header_handler, IdentifiedRequestHandler body_handler);
729
        void Cancel() override;
730

731
        uint16_t GetPort() const;
732
        // Can differ from the passed in URL if a 0 (random) port number was used.
733
        string GetUrl() const;
734

735
        // Use this to get a response that can be used to reply to the request. Due to the
736
        // asynchronous nature, this can be done immediately or some time later.
737
        virtual ExpectedOutgoingResponsePtr MakeResponse(IncomingRequestPtr req);
738
        virtual error::Error AsyncReply(
739
                OutgoingResponsePtr resp, ReplyFinishedHandler reply_finished_handler);
740

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

745
        // An alternative to AsyncReply. `resp` should already contain the correct status and
746
        // headers to perform the switch, and the handler will be called after the HTTP headers have
747
        // been written.
748
        virtual error::Error AsyncSwitchProtocol(
749
                OutgoingResponsePtr resp, SwitchProtocolHandler handler);
750

751
private:
752
        events::EventLoop &event_loop_;
753

754
        BrokenDownUrl address_;
755

756
        RequestHandler header_handler_;
757
        IdentifiedRequestHandler body_handler_;
758

759
        friend class IncomingRequest;
760
        friend class Stream;
761
        friend class OutgoingResponse;
762

763
        using StreamPtr = shared_ptr<Stream>;
764

765
        friend class TestInspector;
766

767
#ifdef MENDER_USE_BOOST_BEAST
768
        asio::ip::tcp::acceptor acceptor_;
769

770
        unordered_set<StreamPtr> streams_;
771

772
        void DoCancel();
773

774
        void PrepareNewStream();
775
        void AsyncAccept(StreamPtr stream);
776
        void RemoveStream(StreamPtr stream);
777
#endif // MENDER_USE_BOOST_BEAST
778
};
779

780
class ExponentialBackoff {
781
public:
782
        ExponentialBackoff(chrono::milliseconds max_interval, int try_count = -1) :
523✔
783
                try_count_ {try_count} {
523✔
784
                SetMaxInterval(max_interval);
523✔
785
        }
523✔
786

787
        void Reset() {
788
                SetIteration(0);
789
        }
115✔
790

791
        int TryCount() {
792
                return try_count_;
102✔
793
        }
794
        void SetTryCount(int count) {
795
                try_count_ = count;
1✔
796
        }
797

798
        chrono::milliseconds SmallestInterval() {
799
                return smallest_interval_;
4✔
800
        }
801
        void SetSmallestInterval(chrono::milliseconds interval) {
217✔
802
                smallest_interval_ = interval;
217✔
803
                if (max_interval_ < smallest_interval_) {
217✔
UNCOV
804
                        max_interval_ = smallest_interval_;
×
805
                }
806
        }
217✔
807

808
        chrono::milliseconds MaxInterval() {
809
                return max_interval_;
810
        }
811
        void SetMaxInterval(chrono::milliseconds interval) {
549✔
812
                max_interval_ = interval;
549✔
813
                if (max_interval_ < smallest_interval_) {
549✔
814
                        max_interval_ = smallest_interval_;
385✔
815
                }
816
        }
549✔
817

818
        using ExpectedInterval = expected::expected<chrono::milliseconds, error::Error>;
819
        ExpectedInterval NextInterval();
820

821
        // Set which iteration we're at. Mainly for use in tests.
822
        void SetIteration(int iteration) {
823
                iteration_ = iteration;
328✔
824
        }
825

826
        int CurrentIteration() const {
827
                return iteration_;
102✔
828
        }
829

830
private:
831
        chrono::milliseconds smallest_interval_ {chrono::minutes(1)};
832
        chrono::milliseconds max_interval_;
833
        int try_count_;
834

835
        int iteration_ {0};
836
};
837

838
expected::ExpectedString GetHttpProxyStringFromEnvironment();
839
expected::ExpectedString GetHttpsProxyStringFromEnvironment();
840
expected::ExpectedString GetNoProxyStringFromEnvironment();
841

842
bool HostNameMatchesNoProxy(const string &host, const string &no_proxy);
843

844
} // namespace http
845
} // namespace common
846
} // namespace mender
847

848
#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