Line data Source code
1 : #ifndef THINGER_HTTP_SERVER_BODY_READER_HPP
2 : #define THINGER_HTTP_SERVER_BODY_READER_HPP
3 :
4 : #include <cstdint>
5 : #include <functional>
6 : #include <limits>
7 : #include <string>
8 : #include <vector>
9 : #include "../common/chunked_decoder.hpp"
10 : #include "../../util/types.hpp"
11 :
12 : namespace thinger::http {
13 :
14 : // Why reading a request body failed
15 : enum class body_error {
16 : none,
17 : too_large, // larger than the allowed size
18 : malformed, // invalid chunked framing
19 : incomplete, // the connection ended before the body did
20 : bad_encoding, // the Content-Encoding (gzip, deflate) could not be decoded
21 : timeout // the read deadline of the request passed (see request::set_read_deadline())
22 : };
23 :
24 : // The error of a read its source ended at the read deadline of the request: ETIME, which no
25 : // socket reports, not to take it for a TCP connection that timed out (ETIMEDOUT)
26 : inline constexpr auto read_deadline_expired = boost::system::errc::stream_timeout;
27 :
28 : /**
29 : * Reads a request body framed by Content-Length or by the chunked transfer coding: first
30 : * from the bytes already read past the request headers (read-ahead), then from the
31 : * connection. Reads never go past the end of the body, so any data following it (the
32 : * next pipelined request) is kept in the read-ahead for whoever reads next.
33 : */
34 : class body_reader {
35 : public:
36 : /// Reads up to `size` bytes from the connection: 0 on end of stream or error (see
37 : /// detail::request_source::read_body_some()), read_deadline_expired at the read deadline
38 : using source = std::function<thinger::awaitable<io_result>(uint8_t* buffer, size_t size)>;
39 :
40 : /// Bytes read from the connection at once while decoding chunked framing (never more
41 : /// than the connection read buffer, as the excess is handed to the next request)
42 : static constexpr size_t framing_read_size = 4096;
43 :
44 90361 : body_reader() = default;
45 :
46 : /// Body framing: chunked, or `content_length` bytes
47 : void set_framing(bool chunked, std::uint64_t content_length);
48 :
49 : /// Connection to read from once the read-ahead is exhausted (none: the body is only
50 : /// what is in the read-ahead)
51 27892 : void set_source(source src) { source_ = std::move(src); }
52 :
53 : /// Maximum body size (none by default): reading a larger body fails (too_large), a
54 : /// Content-Length one at its first read, before reading anything, and a chunked one
55 : /// once it exceeds the limit
56 117944 : void set_limit(std::uint64_t limit) { limit_ = limit; }
57 2056 : std::uint64_t limit() const { return limit_; }
58 :
59 : void set_read_ahead(const uint8_t* data, size_t size);
60 :
61 : /// Read-ahead bytes not consumed yet
62 : size_t buffered() const;
63 :
64 : /// Remove and return the unconsumed read-ahead bytes (data following the body)
65 : std::vector<uint8_t> take_read_ahead();
66 :
67 : /// Read exactly `size` body bytes, or less at the end of the body or on error
68 : thinger::awaitable<size_t> read(uint8_t* buffer, size_t size);
69 :
70 : /// Read up to `max_size` body bytes; 0 at the end of the body or on error, a read past
71 : /// the read deadline included (timeout). A read cancelled (through the cancellation
72 : /// slot of its coroutine) returns 0 too, leaving the body pending, without an error
73 : thinger::awaitable<size_t> read_some(uint8_t* buffer, size_t max_size);
74 :
75 : /// Append the rest of the body to `body`, failing (too_large) if `body` would exceed
76 : /// `max_size` bytes. A Content-Length body is checked before reading anything, and
77 : /// `body` grows as its bytes arrive, not to the announced length at once.
78 : thinger::awaitable<bool> read_all(std::string& body, size_t max_size);
79 :
80 : /// Read and drop the rest of the body; false if it exceeds `max_size` bytes or fails
81 : thinger::awaitable<bool> discard(std::uint64_t max_size);
82 :
83 : /// Whether part of the body is still unread (false once reading failed)
84 : bool has_pending() const;
85 :
86 : bool is_chunked() const { return chunked_; }
87 :
88 28336 : body_error error() const { return error_; }
89 30 : void set_error(body_error error) { error_ = error; }
90 :
91 : private:
92 : source source_;
93 : bool chunked_ = false;
94 : std::uint64_t remaining_ = 0; // unread bytes of a Content-Length body
95 : std::uint64_t read_ = 0; // body bytes read
96 : std::uint64_t limit_ = std::numeric_limits<std::uint64_t>::max();
97 : chunked_decoder decoder_;
98 : body_error error_ = body_error::none;
99 :
100 : std::vector<uint8_t> read_ahead_;
101 : size_t read_ahead_offset_ = 0;
102 :
103 : /// Read raw bytes: from the read-ahead first, then from the source
104 : thinger::awaitable<size_t> read_raw(uint8_t* buffer, size_t max_size);
105 :
106 : /// Refill the (empty) read-ahead from the source; false on end of stream
107 : thinger::awaitable<bool> fill_read_ahead();
108 :
109 : /// The bytes a read from the source got: none past the read deadline (timeout)
110 : size_t received(const io_result& read);
111 :
112 : thinger::awaitable<size_t> read_some_length(uint8_t* buffer, size_t max_size);
113 : thinger::awaitable<size_t> read_some_chunked(uint8_t* buffer, size_t max_size);
114 : };
115 :
116 : }
117 :
118 : #endif
|