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

mendersoftware / mender / 2518387343

12 May 2026 08:01AM UTC coverage: 81.316% (+5.5%) from 75.855%
2518387343

push

gitlab-ci

web-flow
Merge pull request #1948 from vpodzime/5.0.x-ci_improvements

[Cherry 5.0.x]: Miscellaneous CI improvements

8465 of 10410 relevant lines covered (81.32%)

21693.48 hits per line

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

95.74
/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

42
namespace mender {
43
namespace common {
44
namespace http {
45

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

52
using namespace std;
53

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

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

69
class Client;
70
class ClientInterface;
71

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

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

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

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

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

108
        StatusSwitchingProtocols = 101,
109

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

114
        StatusBadRequest = 400,
115
        StatusUnauthorized = 401,
116
        StatusNotFound = 404,
117
        StatusConflict = 409,
118
        StatusRequestBodyTooLarge = 413,
119
        StatusTooManyRequests = 429,
120

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

125
string MethodToString(Method method);
126

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

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

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

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

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

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

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

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

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

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

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

176
protected:
177
        HeaderMap headers_;
178

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

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

186
class Request : public Transaction {
187
public:
188
        Request() {
472✔
189
        }
472✔
190

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

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

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

207
class Response : public Transaction {
208
public:
209
        Response() {
529✔
210
        }
529✔
211

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

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

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

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

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

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

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

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

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

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

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

271
        friend class Client;
272
};
273

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

283

284
class Stream;
285

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

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

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

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

306
        void Cancel() override;
307

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

396
template <typename StreamType>
397
class BodyAsyncReader;
398

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

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

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

417
enum class TransactionStatus {
418
        None,
419
        HeaderHandlerCalled,
420
        ReaderCreated,
421
        BodyReadingInProgress,
422
        BodyReadingFinished,
423
        BodyHandlerCalled, // Only used by server.
424
        Replying,          // Only used by server.
425
        SwitchingProtocol,
426
        Done,
427
};
428
static inline bool AtLeast(TransactionStatus status, TransactionStatus expected_status) {
429
        return static_cast<int>(status) >= static_cast<int>(expected_status);
430
}
431

432
// Interface which manages one connection, and its requests and responses (one at a time).
433
class ClientInterface {
128✔
434
public:
435
        virtual ~ClientInterface() {};
436

437
        // `header_handler` is called when header has arrived, `body_handler` is called when the
438
        // whole body has arrived.
439
        virtual error::Error AsyncCall(
440
                OutgoingRequestPtr req, ResponseHandler header_handler, ResponseHandler body_handler) = 0;
441
        virtual void Cancel() = 0;
442

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

447
        // Returns the real HTTP client.
448
        virtual Client &GetHttpClient() = 0;
449
};
450

451
class Client :
452
        virtual public ClientInterface,
453
        public events::EventLoopObject,
454
        virtual public io::Canceller {
455
public:
456
        Client(
457
                const ClientConfig &client,
458
                events::EventLoop &event_loop,
459
                const string &logger_name = "http_client");
460
        virtual ~Client();
461

462
        Client(Client &&) = default;
463

464
        error::Error AsyncCall(
465
                OutgoingRequestPtr req,
466
                ResponseHandler header_handler,
467
                ResponseHandler body_handler) override;
468
        void Cancel() override;
469

470
        io::ExpectedAsyncReaderPtr MakeBodyAsyncReader(IncomingResponsePtr resp) override;
471

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

476
        Client &GetHttpClient() override {
81✔
477
                return *this;
81✔
478
        };
479

480
protected:
481
        events::EventLoop &event_loop_;
482
        string logger_name_;
483
        log::Logger logger_ {logger_name_};
484
        ClientConfig client_config_;
485

486
        string http_proxy_;
487
        string https_proxy_;
488
        string no_proxy_;
489

490
private:
491
        enum class SocketMode {
492
                Plain,
493
                Tls,
494
                TlsTls,
495
        };
496
        SocketMode socket_mode_;
497

498
        // Used during connections. Must remain valid due to async nature.
499
        OutgoingRequestPtr request_;
500
        IncomingResponsePtr response_;
501
        ResponseHandler header_handler_;
502
        ResponseHandler body_handler_;
503

504
        vector<uint8_t>::iterator reader_buf_start_;
505
        vector<uint8_t>::iterator reader_buf_end_;
506
        io::AsyncIoHandler reader_handler_;
507

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

513
#ifdef MENDER_USE_BOOST_BEAST
514

515
        bool initialized_ {false};
516

517
#define MENDER_BOOST_BEAST_SSL_CTX_COUNT 2
518

519
        ssl::context ssl_ctx_[MENDER_BOOST_BEAST_SSL_CTX_COUNT] = {
520
                ssl::context {ssl::context::tls_client},
521
                ssl::context {ssl::context::tls_client},
522
        };
523

524
        boost::asio::ip::tcp::resolver resolver_;
525
        shared_ptr<ssl::stream<ssl::stream<beast::tcp_stream>>> stream_;
526

527
        vector<uint8_t> body_buffer_;
528

529
        asio::ip::tcp::resolver::results_type resolver_results_;
530

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

554
        // See `Client::request_data_` for why this is a struct.
555
        struct {
556
                shared_ptr<beast::flat_buffer> response_buffer_;
557
                shared_ptr<http::response_parser<http::buffer_body>> http_response_parser_;
558
                size_t last_buffer_size_;
559
        } response_data_;
560
        TransactionStatus status_ {TransactionStatus::None};
561

562
        // Only used for HTTPS proxy requests, because we need two requests, one to CONNECT, and one
563
        // for the original request. HTTP doesn't need it because it only modifies the original
564
        // request.
565
        OutgoingRequestPtr secondary_req_;
566

567
        error::Error Initialize();
568
        void DoCancel();
569

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

594
        friend class IncomingResponse;
595
        friend class BodyAsyncReader<Client>;
596
};
597
using ClientPtr = shared_ptr<Client>;
598

599
// Master object that servers are made from.
600
struct ServerConfig {
601
        // Empty for now, but will probably contain configuration options later.
602
};
603

604
class Server;
605

606
class Stream : public enable_shared_from_this<Stream> {
607
public:
608
        Stream(const Stream &) = delete;
609
        ~Stream();
610

611
        void Cancel();
612

613
private:
614
        Stream(Server &server);
615

616
private:
617
        Server &server_;
618
        friend class Server;
619

620
        log::Logger logger_;
621

622
        IncomingRequestPtr request_;
623

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

636
        friend class IncomingRequest;
637
        friend class OutgoingResponse;
638
        friend class BodyAsyncReader<Stream>;
639

640
        ReplyFinishedHandler reply_finished_handler_;
641
        SwitchProtocolHandler switch_protocol_handler_;
642

643
        vector<uint8_t>::iterator reader_buf_start_;
644
        vector<uint8_t>::iterator reader_buf_end_;
645
        io::AsyncIoHandler reader_handler_;
646

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

652
#ifdef MENDER_USE_BOOST_BEAST
653
        asio::ip::tcp::socket socket_;
654

655
        // See `Client::request_data_` for why this is a struct.
656
        struct {
657
                shared_ptr<beast::flat_buffer> request_buffer_;
658
                shared_ptr<http::request_parser<http::buffer_body>> http_request_parser_;
659
                size_t last_buffer_size_;
660
        } request_data_;
661
        vector<uint8_t> body_buffer_;
662
        TransactionStatus status_ {TransactionStatus::None};
663

664
        // See `Client::request_data_` for why this is a struct.
665
        struct {
666
                shared_ptr<http::response<http::buffer_body>> http_response_;
667
                shared_ptr<http::response_serializer<http::buffer_body>> http_response_serializer_;
668
        } response_data_;
669

670
        void DoCancel();
671

672
        void CallErrorHandler(const error_code &ec, const RequestPtr &req, RequestHandler handler);
673
        void CallErrorHandler(const error::Error &err, const RequestPtr &req, RequestHandler handler);
674
        void CallErrorHandler(
675
                const error_code &ec, const IncomingRequestPtr &req, IdentifiedRequestHandler handler);
676
        void CallErrorHandler(
677
                const error::Error &err, const IncomingRequestPtr &req, IdentifiedRequestHandler handler);
678
        void CallErrorHandler(
679
                const error_code &ec, const RequestPtr &req, ReplyFinishedHandler handler);
680
        void CallErrorHandler(
681
                const error::Error &err, const RequestPtr &req, ReplyFinishedHandler handler);
682
        void CallErrorHandler(
683
                const error_code &ec, const RequestPtr &req, SwitchProtocolHandler handler);
684
        void CallErrorHandler(
685
                const error::Error &err, const RequestPtr &req, SwitchProtocolHandler handler);
686

687
        void AcceptHandler(const error_code &ec);
688
        void ReadHeader();
689
        void ReadHeaderHandler(const error_code &ec, size_t num_read);
690
        void AsyncReadNextBodyPart(
691
                vector<uint8_t>::iterator start, vector<uint8_t>::iterator end, io::AsyncIoHandler handler);
692
        void ReadBodyHandler(error_code ec, size_t num_read);
693
        void AsyncReply(ReplyFinishedHandler reply_finished_handler);
694
        void SetupResponse();
695
        void WriteHeaderHandler(const error_code &ec, size_t num_written);
696
        void PrepareAndWriteNewBodyBuffer();
697
        void WriteNewBodyBuffer(size_t size);
698
        void WriteBody();
699
        void WriteBodyHandler(const error_code &ec, size_t num_written);
700
        void CallBodyHandler();
701
        void FinishReply();
702
        error::Error AsyncSwitchProtocol(SwitchProtocolHandler handler);
703
        void SwitchingProtocolHandler(error_code ec, size_t num_written);
704
#endif // MENDER_USE_BOOST_BEAST
705
};
706

707
class Server : public events::EventLoopObject, virtual public io::Canceller {
708
public:
709
        Server(const ServerConfig &server, events::EventLoop &event_loop);
710
        ~Server();
711

712
        Server(Server &&) = default;
713

714
        error::Error AsyncServeUrl(
715
                const string &url, RequestHandler header_handler, RequestHandler body_handler);
716
        // Same as the above, except that the body handler has the `IncomingRequestPtr` included
717
        // even when there is an error, so that the request can be matched with the request which
718
        // was received in the header handler.
719
        error::Error AsyncServeUrl(
720
                const string &url, RequestHandler header_handler, IdentifiedRequestHandler body_handler);
721
        void Cancel() override;
722

723
        uint16_t GetPort() const;
724
        // Can differ from the passed in URL if a 0 (random) port number was used.
725
        string GetUrl() const;
726

727
        // Use this to get a response that can be used to reply to the request. Due to the
728
        // asynchronous nature, this can be done immediately or some time later.
729
        virtual ExpectedOutgoingResponsePtr MakeResponse(IncomingRequestPtr req);
730
        virtual error::Error AsyncReply(
731
                OutgoingResponsePtr resp, ReplyFinishedHandler reply_finished_handler);
732

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

737
        // An alternative to AsyncReply. `resp` should already contain the correct status and
738
        // headers to perform the switch, and the handler will be called after the HTTP headers have
739
        // been written.
740
        virtual error::Error AsyncSwitchProtocol(
741
                OutgoingResponsePtr resp, SwitchProtocolHandler handler);
742

743
private:
744
        events::EventLoop &event_loop_;
745

746
        BrokenDownUrl address_;
747

748
        RequestHandler header_handler_;
749
        IdentifiedRequestHandler body_handler_;
750

751
        friend class IncomingRequest;
752
        friend class Stream;
753
        friend class OutgoingResponse;
754

755
        using StreamPtr = shared_ptr<Stream>;
756

757
        friend class TestInspector;
758

759
#ifdef MENDER_USE_BOOST_BEAST
760
        asio::ip::tcp::acceptor acceptor_;
761

762
        unordered_set<StreamPtr> streams_;
763

764
        void DoCancel();
765

766
        void PrepareNewStream();
767
        void AsyncAccept(StreamPtr stream);
768
        void RemoveStream(StreamPtr stream);
769
#endif // MENDER_USE_BOOST_BEAST
770
};
771

772
class ExponentialBackoff {
773
public:
774
        ExponentialBackoff(chrono::milliseconds max_interval, int try_count = -1) :
519✔
775
                try_count_ {try_count} {
519✔
776
                SetMaxInterval(max_interval);
519✔
777
        }
519✔
778

779
        void Reset() {
780
                SetIteration(0);
781
        }
115✔
782

783
        int TryCount() {
784
                return try_count_;
785
        }
786
        void SetTryCount(int count) {
787
                try_count_ = count;
1✔
788
        }
789

790
        chrono::milliseconds SmallestInterval() {
791
                return smallest_interval_;
×
792
        }
793
        void SetSmallestInterval(chrono::milliseconds interval) {
212✔
794
                smallest_interval_ = interval;
212✔
795
                if (max_interval_ < smallest_interval_) {
212✔
796
                        max_interval_ = smallest_interval_;
×
797
                }
798
        }
212✔
799

800
        chrono::milliseconds MaxInterval() {
801
                return max_interval_;
802
        }
803
        void SetMaxInterval(chrono::milliseconds interval) {
519✔
804
                max_interval_ = interval;
519✔
805
                if (max_interval_ < smallest_interval_) {
519✔
806
                        max_interval_ = smallest_interval_;
385✔
807
                }
808
        }
519✔
809

810
        using ExpectedInterval = expected::expected<chrono::milliseconds, error::Error>;
811
        ExpectedInterval NextInterval();
812

813
        // Set which iteration we're at. Mainly for use in tests.
814
        void SetIteration(int iteration) {
815
                iteration_ = iteration;
324✔
816
        }
817

818
private:
819
        chrono::milliseconds smallest_interval_ {chrono::minutes(1)};
820
        chrono::milliseconds max_interval_;
821
        int try_count_;
822

823
        int iteration_ {0};
824
};
825

826
expected::ExpectedString GetHttpProxyStringFromEnvironment();
827
expected::ExpectedString GetHttpsProxyStringFromEnvironment();
828
expected::ExpectedString GetNoProxyStringFromEnvironment();
829

830
bool HostNameMatchesNoProxy(const string &host, const string &no_proxy);
831

832
} // namespace http
833
} // namespace common
834
} // namespace mender
835

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