roar
Loading...
Searching...
No Matches
range_file_body.hpp
Go to the documentation of this file.
1#pragma once
2
4#include <boost/beast/http/message.hpp>
6
7#include <cstdint>
8#include <filesystem>
9#include <fstream>
10#include <stdexcept>
11#include <random>
12#include <string_view>
13#include <algorithm>
14
15namespace Roar
16{
17 namespace Detail
18 {
23 {
24 constexpr static unsigned SplitterLength = 16;
25
26 private:
27 struct Sequence
28 {
29
30 Sequence() = default;
32 std::uint64_t start,
33 std::uint64_t end,
34 std::uint64_t totalFile,
35 std::string_view contentType,
36 std::string const& splitter)
37 : start{start}
38 , end{end}
39 , headConsumed{0}
41 std::string{"\r\n"} + splitter + "\r\n" +
42 "Content-Type: " + std::string{contentType} + "\r\n" +
43 "Content-Range: bytes " + std::to_string(start) + "-" +
44 std::to_string(end) + "/" + std::to_string(totalFile) + "\r\n\r\n"}
45 {}
46 Sequence(std::string const& splitter)
47 : start{0}
48 , end{0}
49 , headConsumed{0}
50 , headSection{std::string{"\r\n"} + splitter}
51 {}
52 Sequence(std::uint64_t start, std::uint64_t end)
53 : start{start}
54 , end{end}
55 , headConsumed{0}
56 , headSection{}
57 {}
58 std::uint64_t start;
59 std::uint64_t end;
60 std::uint64_t headConsumed;
61 std::string headSection;
62 };
63
64 public:
66 : file_{}
67 , sequences_{}
68 , splitter_(SplitterLength + 2, '-')
69 , totalSize_{0}
70 , consumed_{0}
71 {
72 constexpr const char* hexCharacters = "0123456789ABCDEF"; // NOLINT
73
74 std::random_device rd;
75 std::mt19937 gen(rd());
76 std::uniform_int_distribution<> distrib(0, 15);
77 std::generate_n(std::next(std::next(std::begin(splitter_))), SplitterLength, [&]() {
78 return static_cast<char>(hexCharacters[distrib(gen)]);
79 });
80 }
81 ~RangeFileBodyImpl() = default;
86
87 std::fstream& file()
88 {
89 return file_;
90 }
91
92 std::uint64_t size() const
93 {
94 return totalSize_;
95 }
96
97 std::uint64_t remaining() const
98 {
99 return totalSize_ - consumed_;
100 }
101
102 bool isMultipart() const
103 {
104 return sequences_.size() > 1;
105 }
106
107 std::pair<std::uint64_t, std::uint64_t> firstRange()
108 {
109 return {sequences_.rbegin()->start, sequences_.rbegin()->end};
110 }
111
112 std::uint64_t fileSize()
113 {
114 auto pos = file_.tellg();
115 file_.seekg(0, std::ios::end);
116 auto size = file_.tellg();
117 file_.seekg(pos);
118 return static_cast<std::uint64_t>(size);
119 }
120
121 bool isOpen() const
122 {
123 return file_.is_open();
124 }
125
126 void close()
127 {
128 file_.close();
129 }
130
138 void open(std::filesystem::path const& filename, std::ios_base::openmode mode, std::error_code& ec)
139 {
140 ec = {};
141 auto excMask = file_.exceptions();
142 file_.exceptions(excMask | std::ios::failbit | std::ios::badbit);
143 try
144 {
145 file_.open(filename, std::ios::binary | mode);
146 }
147 catch (std::system_error& e)
148 {
149 ec = e.code();
150 }
151 file_.exceptions(excMask);
152 }
153
160 void setReadRanges(Ranges const& ranges, std::string_view contentType)
161 {
162 if (ranges.ranges.empty())
163 throw std::invalid_argument("Ranges must not be empty");
164
165 if (ranges.ranges.size() > 1)
166 {
167 std::size_t totalFile = fileSize();
168 sequences_.resize(ranges.ranges.size() + 1);
169
170 if (ranges.ranges.empty())
171 throw std::runtime_error("No ranges specified");
172
173 for (auto const& range : ranges.ranges)
174 {
175 if (range.end < range.start)
176 throw std::invalid_argument("end < start");
177 if (range.end > fileSize())
178 throw std::invalid_argument("range.end > file size");
179 }
180
181 std::transform(
182 ranges.ranges.begin(), ranges.ranges.end(), sequences_.rbegin(), [&, this](auto const& range) {
183 auto seq = Sequence{range.start, range.end, totalFile, contentType, splitter_};
184 totalSize_ += seq.headSection.size() + (range.end - range.start);
185 return seq;
186 });
187 sequences_.rbegin()->headSection =
188 sequences_.rbegin()->headSection.substr(2, sequences_.rbegin()->headSection.size() - 2);
189 *sequences_.begin() = Sequence{splitter_};
190 totalSize_ += splitter_.size();
191 }
192 else
193 {
194 sequences_.resize(1);
195 sequences_.front() = Sequence{ranges.ranges.front().start, ranges.ranges.front().end};
196 totalSize_ = ranges.ranges.front().end - ranges.ranges.front().start;
197 file_.seekg(static_cast<std::streamoff>(ranges.ranges.front().start));
198 }
199 }
200
208 std::size_t read(char* buf, std::size_t amount)
209 {
210 std::uint64_t origAmount = amount;
211 auto& current = sequences_.back();
212
213 if (current.headConsumed != current.headSection.size())
214 {
215 auto copied = current.headSection.copy(
216 buf,
217 std::min(
218 static_cast<std::uint64_t>(amount),
219 static_cast<std::uint64_t>(current.headSection.size()) - current.headConsumed),
220 current.headConsumed);
221 buf += copied;
222 current.headConsumed += copied;
223 consumed_ += copied;
224 amount -= copied;
225 if (amount > 0)
226 file_.seekg(static_cast<std::streamoff>(current.start));
227 }
228 file_.read(
229 buf,
230 static_cast<std::streamoff>(
231 std::min(static_cast<std::uint64_t>(amount), current.end - current.start)));
232 auto gcount = static_cast<std::uint64_t>(file_.gcount());
233 consumed_ += gcount;
234 current.start += gcount;
235 amount -= gcount;
236 if (current.start == current.end)
237 sequences_.pop_back();
238
239 if (sequences_.empty())
240 return (origAmount - amount); // amount should be 0 at this point.
241 if (amount > 0)
242 return (origAmount - amount) + read(buf + gcount, amount);
243 return origAmount;
244 }
245
254 std::streamsize write(char const* buf, std::streamsize amount, bool& error)
255 {
256 file_.write(buf, amount);
257 // totalBytesProcessed_ += amount;
258 if (file_.fail())
259 error = true;
260 return amount;
261 }
262
268 void reset(std::fstream&& file)
269 {
270 if (isOpen())
271 file_.close();
272 file_ = std::move(file);
273 }
274
278 void reset()
279 {
280 file_.clear();
281 file_.seekg(0, std::ios::beg);
282 }
283
284 std::string boundary() const
285 {
286 return splitter_;
287 }
288
289 private:
290 std::fstream file_{};
291 // are inserted in reverse order, so we can pop_back.
292 std::vector<Sequence> sequences_;
293 std::string splitter_;
294 std::uint64_t totalSize_;
295 std::uint64_t consumed_;
296 };
297
298 constexpr static std::uint64_t bufferSize()
299 {
300 using namespace MemoryLiterals;
301 return 1_MemoryPage;
302 }
303
304 template <unsigned Size>
305 struct Buffer
306 {
307 char buf_[Size];
308 };
309 }
310
315 {
316 public:
318 using const_buffers_type = boost::asio::const_buffer;
319
320 class reader
321 {
322 public:
323 template <bool isRequest, class Fields>
324 reader(boost::beast::http::header<isRequest, Fields>&, value_type&);
325 void init(boost::optional<std::uint64_t> const&, boost::beast::error_code& ec);
326 template <class ConstBufferSequence>
327 std::size_t put(ConstBufferSequence const& buffers, boost::beast::error_code& ec);
328 void finish(boost::beast::error_code& ec);
329
330 private:
332 };
333 class writer : private Detail::Buffer<Detail::bufferSize()>
334 {
335 public:
336 using const_buffers_type = boost::asio::const_buffer;
337
338 template <bool isRequest, class Fields>
339 writer(boost::beast::http::header<isRequest, Fields>&, value_type&);
340 void init(boost::beast::error_code& ec);
341 boost::optional<std::pair<const_buffers_type, bool>> get(boost::beast::error_code& ec);
342
343 private:
345 };
346
347 static std::uint64_t size(value_type const& vt)
348 {
349 return vt.size();
350 }
351 };
352
353 template <bool isRequest, class Fields>
354 inline RangeFileBody::reader::reader(boost::beast::http::header<isRequest, Fields>&, value_type& bodyValue)
355 : body_{bodyValue}
356 {}
357 inline void RangeFileBody::reader::init(boost::optional<std::uint64_t> const&, boost::beast::error_code& ec)
358 {
359 BOOST_ASSERT(body_.isOpen());
360 ec = {};
361 }
362 template <class ConstBufferSequence>
363 inline std::size_t RangeFileBody::reader::put(ConstBufferSequence const& buffers, boost::beast::error_code& ec)
364 {
365 std::size_t writtenBytes = 0;
366 for (auto it = boost::asio::buffer_sequence_begin(buffers); it != boost::asio::buffer_sequence_end(buffers);
367 ++it)
368 {
369 // Write this buffer to the file
370 boost::asio::const_buffer buffer = *it;
371 bool fail{false};
372 writtenBytes += static_cast<std::size_t>(body_.write(
373 reinterpret_cast<char const*>(buffer.data()), static_cast<std::streamsize>(buffer.size()), fail));
374 if (fail)
375 {
376 ec = boost::beast::http::error::body_limit;
377 return writtenBytes;
378 }
379 }
380
381 ec = {};
382 return writtenBytes;
383 }
384 inline void RangeFileBody::reader::finish(boost::beast::error_code& ec)
385 {
386 ec = {};
387 }
388
389 template <bool isRequest, class Fields>
390 inline RangeFileBody::writer::writer(boost::beast::http::header<isRequest, Fields>&, value_type& bodyValue)
391 : body_{bodyValue}
392 {
393 if (!body_.isOpen())
394 throw std::invalid_argument{"File must be open"};
395 }
396 inline void RangeFileBody::writer::init(boost::beast::error_code& ec)
397 {
398 ec = {};
399 }
400 inline boost::optional<std::pair<RangeFileBody::const_buffers_type, bool>>
401 RangeFileBody::writer::get(boost::beast::error_code& ec)
402 {
403 const auto remain = body_.remaining();
404 const auto amount = remain > sizeof(buf_) ? sizeof(buf_) : static_cast<std::size_t>(remain);
405
406 if (amount == 0)
407 {
408 ec = {};
409 return boost::none;
410 }
411
412 const auto readCount = body_.read(buf_, amount);
413 if (readCount == 0)
414 {
415 ec = boost::beast::http::error::short_read;
416 return boost::none;
417 }
418
419 BOOST_ASSERT(readCount != 0);
420 BOOST_ASSERT(readCount <= remain);
421
422 ec = {};
423 return {{
424 const_buffers_type{buf_, readCount},
425 remain > 0,
426 }};
427 }
428}
Support range requests for get requests including multipart/byterange.
Definition range_file_body.hpp:23
std::streamsize write(char const *buf, std::streamsize amount, bool &error)
Writes the given buffer to the file.
Definition range_file_body.hpp:254
void reset(std::fstream &&file)
Resets the file stream with a new stream.
Definition range_file_body.hpp:268
RangeFileBodyImpl(RangeFileBodyImpl &&)=default
void reset()
Resets the fstream state and goes back to the start.
Definition range_file_body.hpp:278
RangeFileBodyImpl & operator=(RangeFileBodyImpl const &)=delete
std::fstream & file()
Definition range_file_body.hpp:87
std::uint64_t consumed_
Definition range_file_body.hpp:295
RangeFileBodyImpl(RangeFileBodyImpl const &)=delete
static constexpr unsigned SplitterLength
Definition range_file_body.hpp:24
std::uint64_t fileSize()
Definition range_file_body.hpp:112
std::size_t read(char *buf, std::size_t amount)
Reads some of the multipart data into the buffer.
Definition range_file_body.hpp:208
void close()
Definition range_file_body.hpp:126
bool isMultipart() const
Definition range_file_body.hpp:102
void open(std::filesystem::path const &filename, std::ios_base::openmode mode, std::error_code &ec)
Opens the file.
Definition range_file_body.hpp:138
bool isOpen() const
Definition range_file_body.hpp:121
std::vector< Sequence > sequences_
Definition range_file_body.hpp:292
RangeFileBodyImpl()
Definition range_file_body.hpp:65
std::uint64_t size() const
Definition range_file_body.hpp:92
std::string splitter_
Definition range_file_body.hpp:293
RangeFileBodyImpl & operator=(RangeFileBodyImpl &&)=default
void setReadRanges(Ranges const &ranges, std::string_view contentType)
Set the Read Range.
Definition range_file_body.hpp:160
std::pair< std::uint64_t, std::uint64_t > firstRange()
Definition range_file_body.hpp:107
std::fstream file_
Definition range_file_body.hpp:290
std::uint64_t totalSize_
Definition range_file_body.hpp:294
std::string boundary() const
Definition range_file_body.hpp:284
std::uint64_t remaining() const
Definition range_file_body.hpp:97
Definition range_file_body.hpp:321
void init(boost::optional< std::uint64_t > const &, boost::beast::error_code &ec)
Definition range_file_body.hpp:357
std::size_t put(ConstBufferSequence const &buffers, boost::beast::error_code &ec)
Definition range_file_body.hpp:363
value_type & body_
Definition range_file_body.hpp:331
void finish(boost::beast::error_code &ec)
Definition range_file_body.hpp:384
Definition range_file_body.hpp:334
value_type & body_
Definition range_file_body.hpp:344
writer(boost::beast::http::header< isRequest, Fields > &, value_type &)
Definition range_file_body.hpp:390
boost::optional< std::pair< const_buffers_type, bool > > get(boost::beast::error_code &ec)
Definition range_file_body.hpp:401
void init(boost::beast::error_code &ec)
Definition range_file_body.hpp:396
boost::asio::const_buffer const_buffers_type
Definition range_file_body.hpp:336
This body is a file body, but with range request support.
Definition range_file_body.hpp:315
static std::uint64_t size(value_type const &vt)
Definition range_file_body.hpp:347
boost::asio::const_buffer const_buffers_type
Definition range_file_body.hpp:318
const auto ranges
Definition ranges.cpp:19
static constexpr std::uint64_t bufferSize()
Definition range_file_body.hpp:298
Definition authorization.hpp:10
std::string to_string(AuthorizationScheme scheme)
Definition authorization.cpp:28
Definition range_file_body.hpp:306
Definition range_file_body.hpp:28
std::uint64_t headConsumed
Definition range_file_body.hpp:60
std::uint64_t end
Definition range_file_body.hpp:59
Sequence(std::uint64_t start, std::uint64_t end)
Definition range_file_body.hpp:52
Sequence(std::uint64_t start, std::uint64_t end, std::uint64_t totalFile, std::string_view contentType, std::string const &splitter)
Definition range_file_body.hpp:31
Sequence(std::string const &splitter)
Definition range_file_body.hpp:46
std::string headSection
Definition range_file_body.hpp:61
std::uint64_t start
Definition range_file_body.hpp:58
Definition ranges.hpp:11