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

mendersoftware / mender / 1583599051

11 Dec 2024 09:04AM UTC coverage: 76.259% (-0.2%) from 76.43%
1583599051

push

gitlab-ci

vpodzime
fix: Cancel the previous request before scheduling a new one in HTTP resumer

The `http::Client()` class is designed to always have only one
HTTP request in progress. Thus, before scheduling a new request
using the same `http::Client` instance, cancel the previous
request to make sure everything is properly reset for the new
one.

Ticket: MEN-7810
Changelog: Fix download resuming to reset the HTTP state and
avoid repeatedly hitting the same error in case of a bad state

Signed-off-by: Vratislav Podzimek <vratislav.podzimek@northern.tech>

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

55 existing lines in 11 files now uncovered.

7375 of 9671 relevant lines covered (76.26%)

11182.97 hits per line

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

80.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

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

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

123
string MethodToString(Method method);
124

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

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

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

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

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

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

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

160
class Transaction {
161
public:
UNCOV
162
        virtual ~Transaction() {
×
UNCOV
163
        }
×
164

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

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

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

174
protected:
175
        HeaderMap headers_;
176

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

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

184
class Request : public Transaction {
185
public:
186
        Request() {
187
        }
188

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

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

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

205
class Response : public Transaction {
206
public:
207
        Response() {
208
        }
209

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

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

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

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

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

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

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

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

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

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

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

269
        friend class Client;
270
};
271

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

281

282
class Stream;
283

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

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

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

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

304
        void Cancel() override;
305

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

394
template <typename StreamType>
395
class BodyAsyncReader;
396

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

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

409
        string http_proxy;
410
        string https_proxy;
411
        string no_proxy;
412
        string ssl_engine;
413
};
414

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

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

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

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

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

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

460
        Client(Client &&) = default;
461

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

468
        io::ExpectedAsyncReaderPtr MakeBodyAsyncReader(IncomingResponsePtr resp) override;
469

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

474
        Client &GetHttpClient() override {
475
                return *this;
476
        };
477

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

484
        string http_proxy_;
485
        string https_proxy_;
486
        string no_proxy_;
487

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

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

502
        vector<uint8_t>::iterator reader_buf_start_;
503
        vector<uint8_t>::iterator reader_buf_end_;
504
        io::AsyncIoHandler reader_handler_;
505

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

511
#ifdef MENDER_USE_BOOST_BEAST
512

513
        bool initialized_ {false};
514

515
#define MENDER_BOOST_BEAST_SSL_CTX_COUNT 2
516

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

522
        boost::asio::ip::tcp::resolver resolver_;
523
        shared_ptr<ssl::stream<ssl::stream<tcp::socket>>> stream_;
524

525
        vector<uint8_t> body_buffer_;
526

527
        asio::ip::tcp::resolver::results_type resolver_results_;
528

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

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

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

565
        error::Error Initialize();
566
        void DoCancel();
567

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

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

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

602
class Server;
603

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

609
        void Cancel();
610

611
private:
612
        Stream(Server &server);
613

614
private:
615
        Server &server_;
616
        friend class Server;
617

618
        log::Logger logger_;
619

620
        IncomingRequestPtr request_;
621

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

634
        friend class IncomingRequest;
635
        friend class OutgoingResponse;
636
        friend class BodyAsyncReader<Stream>;
637

638
        ReplyFinishedHandler reply_finished_handler_;
639
        SwitchProtocolHandler switch_protocol_handler_;
640

641
        vector<uint8_t>::iterator reader_buf_start_;
642
        vector<uint8_t>::iterator reader_buf_end_;
643
        io::AsyncIoHandler reader_handler_;
644

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

650
#ifdef MENDER_USE_BOOST_BEAST
651
        asio::ip::tcp::socket socket_;
652

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

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

668
        void DoCancel();
669

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

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

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

710
        Server(Server &&) = default;
711

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

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

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

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

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

741
private:
742
        events::EventLoop &event_loop_;
743

744
        BrokenDownUrl address_;
745

746
        RequestHandler header_handler_;
747
        IdentifiedRequestHandler body_handler_;
748

749
        friend class IncomingRequest;
750
        friend class Stream;
751
        friend class OutgoingResponse;
752

753
        using StreamPtr = shared_ptr<Stream>;
754

755
        friend class TestInspector;
756

757
#ifdef MENDER_USE_BOOST_BEAST
758
        asio::ip::tcp::acceptor acceptor_;
759

760
        unordered_set<StreamPtr> streams_;
761

762
        void DoCancel();
763

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

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

777
        void Reset() {
778
                SetIteration(0);
779
        }
780

781
        int TryCount() {
782
                return try_count_;
783
        }
784
        void SetTryCount(int count) {
785
                try_count_ = count;
786
        }
787

788
        chrono::milliseconds SmallestInterval() {
789
                return smallest_interval_;
790
        }
791
        void SetSmallestInterval(chrono::milliseconds interval) {
792
                smallest_interval_ = interval;
793
                if (max_interval_ < smallest_interval_) {
794
                        max_interval_ = smallest_interval_;
795
                }
796
        }
797

798
        chrono::milliseconds MaxInterval() {
799
                return max_interval_;
800
        }
801
        void SetMaxInterval(chrono::milliseconds interval) {
802
                max_interval_ = interval;
803
                if (max_interval_ < smallest_interval_) {
804
                        max_interval_ = smallest_interval_;
805
                }
806
        }
807

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

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

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

821
        int iteration_ {0};
822
};
823

824
expected::ExpectedString GetHttpProxyStringFromEnvironment();
825
expected::ExpectedString GetHttpsProxyStringFromEnvironment();
826
expected::ExpectedString GetNoProxyStringFromEnvironment();
827

828
bool HostNameMatchesNoProxy(const string &host, const string &no_proxy);
829

830
} // namespace http
831
} // namespace common
832
} // namespace mender
833

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

© 2025 Coveralls, Inc