mirror of
https://github.com/scottlamb/moonfire-nvr.git
synced 2025-02-04 10:26:01 -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::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;
|
||||
|
22
src/http.cc
22
src/http.cc
@ -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,
|
||||
|
37
src/http.h
37
src/http.h
@ -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.)
|
||||
//
|
||||
|
Loading…
x
Reference in New Issue
Block a user