mirror of
https://github.com/scottlamb/moonfire-nvr.git
synced 2025-02-09 12:48:08 -05:00
Helper for composing a VirtualFile from "slices".
This commit is contained in:
parent
c89907d785
commit
bb7fb95b57
@ -49,9 +49,21 @@ DECLARE_bool(alsologtostderr);
|
|||||||
using moonfire_nvr::internal::ParseRangeHeader;
|
using moonfire_nvr::internal::ParseRangeHeader;
|
||||||
using moonfire_nvr::internal::RangeHeaderType;
|
using moonfire_nvr::internal::RangeHeaderType;
|
||||||
|
|
||||||
|
using testing::_;
|
||||||
|
using testing::AnyNumber;
|
||||||
|
using testing::DoAll;
|
||||||
|
using testing::Return;
|
||||||
|
using testing::SetArgPointee;
|
||||||
|
|
||||||
namespace moonfire_nvr {
|
namespace moonfire_nvr {
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
class MockFileSlice : public FileSlice {
|
||||||
|
public:
|
||||||
|
MOCK_CONST_METHOD0(size, int64_t());
|
||||||
|
MOCK_CONST_METHOD3(AddRange, bool(ByteRange, EvBuffer *, std::string *));
|
||||||
|
};
|
||||||
|
|
||||||
TEST(EvBufferTest, AddFileTest) {
|
TEST(EvBufferTest, AddFileTest) {
|
||||||
std::string dir = PrepareTempDirOrDie("http");
|
std::string dir = PrepareTempDirOrDie("http");
|
||||||
std::string foo_filename = StrCat(dir, "/foo");
|
std::string foo_filename = StrCat(dir, "/foo");
|
||||||
@ -72,6 +84,72 @@ TEST(EvBufferTest, AddFileTest) {
|
|||||||
EXPECT_EQ(0u, evbuffer_get_length(buf2.get()));
|
EXPECT_EQ(0u, evbuffer_get_length(buf2.get()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class FileSlicesTest : public testing::Test {
|
||||||
|
protected:
|
||||||
|
FileSlicesTest() {
|
||||||
|
EXPECT_CALL(a_, size()).Times(AnyNumber()).WillRepeatedly(Return(5));
|
||||||
|
EXPECT_CALL(b_, size()).Times(AnyNumber()).WillRepeatedly(Return(13));
|
||||||
|
EXPECT_CALL(c_, size()).Times(AnyNumber()).WillRepeatedly(Return(7));
|
||||||
|
EXPECT_CALL(d_, size()).Times(AnyNumber()).WillRepeatedly(Return(17));
|
||||||
|
EXPECT_CALL(e_, size()).Times(AnyNumber()).WillRepeatedly(Return(19));
|
||||||
|
|
||||||
|
slices_.Append(&a_);
|
||||||
|
slices_.Append(&b_);
|
||||||
|
slices_.Append(&c_);
|
||||||
|
slices_.Append(&d_);
|
||||||
|
slices_.Append(&e_);
|
||||||
|
}
|
||||||
|
|
||||||
|
FileSlices slices_;
|
||||||
|
testing::StrictMock<MockFileSlice> a_;
|
||||||
|
testing::StrictMock<MockFileSlice> b_;
|
||||||
|
testing::StrictMock<MockFileSlice> c_;
|
||||||
|
testing::StrictMock<MockFileSlice> d_;
|
||||||
|
testing::StrictMock<MockFileSlice> e_;
|
||||||
|
};
|
||||||
|
|
||||||
|
TEST_F(FileSlicesTest, Size) {
|
||||||
|
EXPECT_EQ(5 + 13 + 7 + 17 + 19, slices_.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(FileSlicesTest, ExactSlice) {
|
||||||
|
// Exactly slice b.
|
||||||
|
std::string error_message;
|
||||||
|
EXPECT_CALL(b_, AddRange(ByteRange(0, 13), _, _)).WillOnce(Return(true));
|
||||||
|
EXPECT_TRUE(slices_.AddRange(ByteRange(5, 18), nullptr, &error_message))
|
||||||
|
<< error_message;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(FileSlicesTest, Offset) {
|
||||||
|
// Part of slice b, all of slice c, and part of slice d.
|
||||||
|
std::string error_message;
|
||||||
|
EXPECT_CALL(b_, AddRange(ByteRange(12, 13), _, _)).WillOnce(Return(true));
|
||||||
|
EXPECT_CALL(c_, AddRange(ByteRange(0, 7), _, _)).WillOnce(Return(true));
|
||||||
|
EXPECT_CALL(d_, AddRange(ByteRange(0, 1), _, _)).WillOnce(Return(true));
|
||||||
|
EXPECT_TRUE(slices_.AddRange(ByteRange(17, 26), nullptr, &error_message))
|
||||||
|
<< error_message;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(FileSlicesTest, Everything) {
|
||||||
|
std::string error_message;
|
||||||
|
EXPECT_CALL(a_, AddRange(ByteRange(0, 5), _, _)).WillOnce(Return(true));
|
||||||
|
EXPECT_CALL(b_, AddRange(ByteRange(0, 13), _, _)).WillOnce(Return(true));
|
||||||
|
EXPECT_CALL(c_, AddRange(ByteRange(0, 7), _, _)).WillOnce(Return(true));
|
||||||
|
EXPECT_CALL(d_, AddRange(ByteRange(0, 17), _, _)).WillOnce(Return(true));
|
||||||
|
EXPECT_CALL(e_, AddRange(ByteRange(0, 19), _, _)).WillOnce(Return(true));
|
||||||
|
EXPECT_TRUE(slices_.AddRange(ByteRange(0, 61), nullptr, &error_message))
|
||||||
|
<< error_message;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(FileSlicesTest, PropagateError) {
|
||||||
|
std::string error_message;
|
||||||
|
EXPECT_CALL(a_, AddRange(ByteRange(0, 5), _, _)).WillOnce(Return(true));
|
||||||
|
EXPECT_CALL(b_, AddRange(ByteRange(0, 13), _, _))
|
||||||
|
.WillOnce(DoAll(SetArgPointee<2>("asdf"), Return(false)));
|
||||||
|
EXPECT_FALSE(slices_.AddRange(ByteRange(0, 61), nullptr, &error_message));
|
||||||
|
EXPECT_EQ("asdf", error_message);
|
||||||
|
}
|
||||||
|
|
||||||
// Test the specific examples enumerated in RFC 2616 section 14.35.1.
|
// Test the specific examples enumerated in RFC 2616 section 14.35.1.
|
||||||
TEST(RangeHeaderTest, Rfc_2616_Section_14_35_1) {
|
TEST(RangeHeaderTest, Rfc_2616_Section_14_35_1) {
|
||||||
std::vector<ByteRange> ranges;
|
std::vector<ByteRange> ranges;
|
||||||
|
22
src/http.cc
22
src/http.cc
@ -205,6 +205,28 @@ bool FillerFileSlice::AddRange(ByteRange range, EvBuffer *buf,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool FileSlices::AddRange(ByteRange range, EvBuffer *buf,
|
||||||
|
std::string *error_message) const {
|
||||||
|
if (range.begin < 0 || range.begin > range.end || range.end > size_) {
|
||||||
|
*error_message = StrCat("Range ", range.DebugString(),
|
||||||
|
" not valid for file of size ", size_);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
auto it = std::upper_bound(slices_.begin(), slices_.end(), range.begin,
|
||||||
|
[](int64_t begin, const SliceInfo &info) {
|
||||||
|
return begin < info.range.end;
|
||||||
|
});
|
||||||
|
for (; it != slices_.end() && range.end > it->range.begin; ++it) {
|
||||||
|
ByteRange mapped(
|
||||||
|
std::max(INT64_C(0), range.begin - it->range.begin),
|
||||||
|
std::min(range.end - it->range.begin, it->range.end - it->range.begin));
|
||||||
|
if (!it->slice->AddRange(mapped, buf, error_message)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
void HttpSendError(evhttp_request *req, int http_err, const std::string &prefix,
|
void HttpSendError(evhttp_request *req, int http_err, const std::string &prefix,
|
||||||
int posix_err) {
|
int posix_err) {
|
||||||
evhttp_send_error(req, http_err,
|
evhttp_send_error(req, http_err,
|
||||||
|
37
src/http.h
37
src/http.h
@ -48,6 +48,8 @@
|
|||||||
#include <glog/logging.h>
|
#include <glog/logging.h>
|
||||||
#include <re2/stringpiece.h>
|
#include <re2/stringpiece.h>
|
||||||
|
|
||||||
|
#include "string.h"
|
||||||
|
|
||||||
namespace moonfire_nvr {
|
namespace moonfire_nvr {
|
||||||
|
|
||||||
// Wrapped version of libevent's "struct evbuffer" which uses RAII and simply
|
// Wrapped version of libevent's "struct evbuffer" which uses RAII and simply
|
||||||
@ -102,11 +104,11 @@ struct ByteRange {
|
|||||||
bool operator==(const ByteRange &o) const {
|
bool operator==(const ByteRange &o) const {
|
||||||
return begin == o.begin && end == o.end;
|
return begin == o.begin && end == o.end;
|
||||||
}
|
}
|
||||||
|
std::string DebugString() const { return StrCat("[", begin, ", ", end, ")"); }
|
||||||
};
|
};
|
||||||
|
|
||||||
inline std::ostream &operator<<(std::ostream &out, const ByteRange &range) {
|
inline std::ostream &operator<<(std::ostream &out, const ByteRange &range) {
|
||||||
out << "[" << range.begin << ", " << range.end << ")";
|
return out << range.DebugString();
|
||||||
return out;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper for sending HTTP errors based on POSIX error returns.
|
// Helper for sending HTTP errors based on POSIX error returns.
|
||||||
@ -154,6 +156,37 @@ class FillerFileSlice : public FileSlice {
|
|||||||
size_t size_;
|
size_t size_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// A slice composed of other slices.
|
||||||
|
class FileSlices : public FileSlice {
|
||||||
|
public:
|
||||||
|
FileSlices() {}
|
||||||
|
FileSlices(const FileSlices &) = delete;
|
||||||
|
FileSlices &operator=(const FileSlices &) = delete;
|
||||||
|
|
||||||
|
// |slice| must outlive the FileSlices.
|
||||||
|
// |slice->size()| should not change after this call.
|
||||||
|
void Append(const FileSlice *slice) {
|
||||||
|
int64_t new_size = size_ + slice->size();
|
||||||
|
slices_.emplace_back(ByteRange(size_, new_size), slice);
|
||||||
|
size_ = new_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
int64_t size() const final { return size_; }
|
||||||
|
bool AddRange(ByteRange range, EvBuffer *buf,
|
||||||
|
std::string *error_message) const final;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct SliceInfo {
|
||||||
|
SliceInfo(ByteRange range, const FileSlice *slice)
|
||||||
|
: range(range), slice(slice) {}
|
||||||
|
ByteRange range;
|
||||||
|
const FileSlice *slice = nullptr;
|
||||||
|
};
|
||||||
|
int64_t size_ = 0;
|
||||||
|
|
||||||
|
std::vector<SliceInfo> slices_;
|
||||||
|
};
|
||||||
|
|
||||||
// Serve an HTTP request |req| from |file|, handling byte range and
|
// Serve an HTTP request |req| from |file|, handling byte range and
|
||||||
// conditional serving. (Similar to golang's http.ServeContent.)
|
// conditional serving. (Similar to golang's http.ServeContent.)
|
||||||
//
|
//
|
||||||
|
Loading…
x
Reference in New Issue
Block a user