Helper for composing a VirtualFile from "slices".

This commit is contained in:
Scott Lamb 2016-01-09 22:15:22 -08:00
parent c89907d785
commit bb7fb95b57
3 changed files with 135 additions and 2 deletions

View File

@ -49,9 +49,21 @@ DECLARE_bool(alsologtostderr);
using moonfire_nvr::internal::ParseRangeHeader;
using moonfire_nvr::internal::RangeHeaderType;
using testing::_;
using testing::AnyNumber;
using testing::DoAll;
using testing::Return;
using testing::SetArgPointee;
namespace moonfire_nvr {
namespace {
class MockFileSlice : public FileSlice {
public:
MOCK_CONST_METHOD0(size, int64_t());
MOCK_CONST_METHOD3(AddRange, bool(ByteRange, EvBuffer *, std::string *));
};
TEST(EvBufferTest, AddFileTest) {
std::string dir = PrepareTempDirOrDie("http");
std::string foo_filename = StrCat(dir, "/foo");
@ -72,6 +84,72 @@ TEST(EvBufferTest, AddFileTest) {
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(RangeHeaderTest, Rfc_2616_Section_14_35_1) {
std::vector<ByteRange> ranges;

View File

@ -205,6 +205,28 @@ bool FillerFileSlice::AddRange(ByteRange range, EvBuffer *buf,
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,
int posix_err) {
evhttp_send_error(req, http_err,

View File

@ -48,6 +48,8 @@
#include <glog/logging.h>
#include <re2/stringpiece.h>
#include "string.h"
namespace moonfire_nvr {
// Wrapped version of libevent's "struct evbuffer" which uses RAII and simply
@ -102,11 +104,11 @@ struct ByteRange {
bool operator==(const ByteRange &o) const {
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) {
out << "[" << range.begin << ", " << range.end << ")";
return out;
return out << range.DebugString();
}
// Helper for sending HTTP errors based on POSIX error returns.
@ -154,6 +156,37 @@ class FillerFileSlice : public FileSlice {
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
// conditional serving. (Similar to golang's http.ServeContent.)
//