You've already forked cpp-httplib
Add ETag and Last-Modified handling for If-Range requests
This commit is contained in:
34
httplib.h
34
httplib.h
@@ -1257,7 +1257,7 @@ private:
|
||||
bool listen_internal();
|
||||
|
||||
bool routing(Request &req, Response &res, Stream &strm);
|
||||
bool handle_file_request(const Request &req, Response &res);
|
||||
bool handle_file_request(Request &req, Response &res);
|
||||
bool dispatch_request(Request &req, Response &res,
|
||||
const Handlers &handlers) const;
|
||||
bool dispatch_request_for_content_reader(
|
||||
@@ -3020,6 +3020,12 @@ inline time_t parse_http_date(const std::string &date_str) {
|
||||
#endif
|
||||
}
|
||||
|
||||
// Check if the string is an ETag (starts with '"' or 'W/"')
|
||||
inline bool is_etag(const std::string &s) {
|
||||
return !s.empty() &&
|
||||
(s[0] == '"' || (s.size() > 2 && s[0] == 'W' && s[1] == '/'));
|
||||
}
|
||||
|
||||
inline size_t to_utf8(int code, char *buff) {
|
||||
if (code < 0x0080) {
|
||||
buff[0] = static_cast<char>(code & 0x7F);
|
||||
@@ -8313,7 +8319,7 @@ inline bool Server::read_content_core(
|
||||
return true;
|
||||
}
|
||||
|
||||
inline bool Server::handle_file_request(const Request &req, Response &res) {
|
||||
inline bool Server::handle_file_request(Request &req, Response &res) {
|
||||
for (const auto &entry : base_dirs_) {
|
||||
// Prefix match
|
||||
if (!req.path.compare(0, entry.mount_point.size(), entry.mount_point)) {
|
||||
@@ -8373,6 +8379,30 @@ inline bool Server::handle_file_request(const Request &req, Response &res) {
|
||||
}
|
||||
}
|
||||
|
||||
// Handle If-Range for partial content requests (RFC 9110
|
||||
// Section 13.1.5) If-Range is only evaluated when Range header is
|
||||
// present. If the validator matches, serve partial content; otherwise
|
||||
// serve full content.
|
||||
if (!req.ranges.empty() && req.has_header("If-Range")) {
|
||||
auto if_range = req.get_header_value("If-Range");
|
||||
auto valid = false;
|
||||
|
||||
if (detail::is_etag(if_range)) {
|
||||
// ETag comparison (weak comparison for If-Range per RFC 9110)
|
||||
valid = (!etag.empty() && if_range == etag);
|
||||
} else {
|
||||
// HTTP-date comparison
|
||||
auto if_range_time = detail::parse_http_date(if_range);
|
||||
valid = (if_range_time != static_cast<time_t>(-1) &&
|
||||
mtime <= if_range_time);
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
// Validator doesn't match: ignore Range and serve full content
|
||||
req.ranges.clear();
|
||||
}
|
||||
}
|
||||
|
||||
auto mm = std::make_shared<detail::mmap>(path.c_str());
|
||||
if (!mm->is_open()) {
|
||||
output_error_log(Error::OpenFile, &req);
|
||||
|
||||
100
test/test.cc
100
test/test.cc
@@ -12848,3 +12848,103 @@ TEST(ETagTest, VaryAcceptEncodingWithCompression) {
|
||||
svr.stop();
|
||||
t.join();
|
||||
}
|
||||
|
||||
TEST(ETagTest, IfRangeWithETag) {
|
||||
using namespace httplib;
|
||||
|
||||
// Create a test file with known content
|
||||
const char *fname = "if_range_testfile.txt";
|
||||
const std::string content = "0123456789ABCDEFGHIJ"; // 20 bytes
|
||||
{
|
||||
std::ofstream ofs(fname);
|
||||
ofs << content;
|
||||
}
|
||||
|
||||
Server svr;
|
||||
svr.set_mount_point("/static", ".");
|
||||
auto t = std::thread([&]() { svr.listen("localhost", 8090); });
|
||||
svr.wait_until_ready();
|
||||
|
||||
Client cli("localhost", 8090);
|
||||
|
||||
// First request: get ETag
|
||||
auto res1 = cli.Get("/static/if_range_testfile.txt");
|
||||
ASSERT_TRUE(res1);
|
||||
ASSERT_EQ(200, res1->status);
|
||||
ASSERT_TRUE(res1->has_header("ETag"));
|
||||
std::string etag = res1->get_header_value("ETag");
|
||||
|
||||
// Range request with matching If-Range (ETag): should get 206
|
||||
Headers h2 = {{"Range", "bytes=0-4"}, {"If-Range", etag}};
|
||||
auto res2 = cli.Get("/static/if_range_testfile.txt", h2);
|
||||
ASSERT_TRUE(res2);
|
||||
EXPECT_EQ(206, res2->status);
|
||||
EXPECT_EQ("01234", res2->body);
|
||||
EXPECT_TRUE(res2->has_header("Content-Range"));
|
||||
|
||||
// Range request with non-matching If-Range (ETag): should get 200 (full
|
||||
// content)
|
||||
Headers h3 = {{"Range", "bytes=0-4"}, {"If-Range", "W/\"wrong-etag\""}};
|
||||
auto res3 = cli.Get("/static/if_range_testfile.txt", h3);
|
||||
ASSERT_TRUE(res3);
|
||||
EXPECT_EQ(200, res3->status);
|
||||
EXPECT_EQ(content, res3->body);
|
||||
EXPECT_FALSE(res3->has_header("Content-Range"));
|
||||
|
||||
svr.stop();
|
||||
t.join();
|
||||
std::remove(fname);
|
||||
}
|
||||
|
||||
TEST(ETagTest, IfRangeWithDate) {
|
||||
using namespace httplib;
|
||||
|
||||
// Create a test file
|
||||
const char *fname = "if_range_date_testfile.txt";
|
||||
const std::string content = "ABCDEFGHIJ0123456789"; // 20 bytes
|
||||
{
|
||||
std::ofstream ofs(fname);
|
||||
ofs << content;
|
||||
}
|
||||
|
||||
Server svr;
|
||||
svr.set_mount_point("/static", ".");
|
||||
auto t = std::thread([&]() { svr.listen("localhost", 8091); });
|
||||
svr.wait_until_ready();
|
||||
|
||||
Client cli("localhost", 8091);
|
||||
|
||||
// First request: get Last-Modified
|
||||
auto res1 = cli.Get("/static/if_range_date_testfile.txt");
|
||||
ASSERT_TRUE(res1);
|
||||
ASSERT_EQ(200, res1->status);
|
||||
ASSERT_TRUE(res1->has_header("Last-Modified"));
|
||||
std::string last_modified = res1->get_header_value("Last-Modified");
|
||||
|
||||
// Range request with matching If-Range (date): should get 206
|
||||
Headers h2 = {{"Range", "bytes=5-9"}, {"If-Range", last_modified}};
|
||||
auto res2 = cli.Get("/static/if_range_date_testfile.txt", h2);
|
||||
ASSERT_TRUE(res2);
|
||||
EXPECT_EQ(206, res2->status);
|
||||
EXPECT_EQ("FGHIJ", res2->body);
|
||||
|
||||
// Range request with old If-Range date: should get 200 (full content)
|
||||
Headers h3 = {{"Range", "bytes=5-9"},
|
||||
{"If-Range", "Sun, 01 Jan 2000 00:00:00 GMT"}};
|
||||
auto res3 = cli.Get("/static/if_range_date_testfile.txt", h3);
|
||||
ASSERT_TRUE(res3);
|
||||
EXPECT_EQ(200, res3->status);
|
||||
EXPECT_EQ(content, res3->body);
|
||||
|
||||
// Range request with future If-Range date: should get 206
|
||||
Headers h4 = {{"Range", "bytes=0-4"},
|
||||
{"If-Range", "Sun, 01 Jan 2099 00:00:00 GMT"}};
|
||||
auto res4 = cli.Get("/static/if_range_date_testfile.txt", h4);
|
||||
ASSERT_TRUE(res4);
|
||||
EXPECT_EQ(206, res4->status);
|
||||
EXPECT_EQ("ABCDE", res4->body);
|
||||
|
||||
svr.stop();
|
||||
t.join();
|
||||
std::remove(fname);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user