Skip to content

Commit ec170f3

Browse files
committed
Refactor ETag handling: extract check_if_not_modified and check_if_range methods for improved readability and maintainability
1 parent 42e762d commit ec170f3

File tree

1 file changed

+81
-64
lines changed

1 file changed

+81
-64
lines changed

httplib.h

Lines changed: 81 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1258,6 +1258,10 @@ class Server {
12581258

12591259
bool routing(Request &req, Response &res, Stream &strm);
12601260
bool handle_file_request(Request &req, Response &res);
1261+
bool check_if_not_modified(const Request &req, Response &res,
1262+
const std::string &etag, time_t mtime) const;
1263+
bool check_if_range(Request &req, const std::string &etag,
1264+
time_t mtime) const;
12611265
bool dispatch_request(Request &req, Response &res,
12621266
const Handlers &handlers) const;
12631267
bool dispatch_request_for_content_reader(
@@ -8357,7 +8361,6 @@ inline bool Server::handle_file_request(Request &req, Response &res) {
83578361
res.set_header(kv.first, kv.second);
83588362
}
83598363

8360-
// Compute and set weak ETag based on mtime+size.
83618364
auto etag = detail::compute_etag(stat);
83628365
if (!etag.empty()) { res.set_header("ETag", etag); }
83638366

@@ -8368,70 +8371,9 @@ inline bool Server::handle_file_request(Request &req, Response &res) {
83688371
res.set_header("Last-Modified", last_modified);
83698372
}
83708373

8371-
// Handle conditional GET:
8372-
// 1. If-None-Match takes precedence (RFC 9110 Section 13.1.2)
8373-
// 2. If-Modified-Since is checked only when If-None-Match is absent
8374-
if (req.has_header("If-None-Match")) {
8375-
if (!etag.empty()) {
8376-
auto val = req.get_header_value("If-None-Match");
8377-
auto matched = false;
8378-
8379-
// NOTE: We use exact string matching here. This works correctly
8380-
// because our server always generates weak ETags (W/"..."), and
8381-
// clients typically send back the same ETag they received.
8382-
// RFC 9110 Section 8.8.3.2 allows weak comparison for
8383-
// If-None-Match, where W/"x" and "x" would match, but this
8384-
// simplified implementation requires exact matches.
8385-
detail::split(val.data(), val.data() + val.size(), ',',
8386-
[&](const char *b, const char *e) {
8387-
if (!matched) {
8388-
auto tag = std::string(b, e);
8389-
matched = tag == "*" || tag == etag;
8390-
}
8391-
});
8392-
8393-
if (matched) {
8394-
res.status = StatusCode::NotModified_304;
8395-
return true;
8396-
}
8397-
}
8398-
} else if (req.has_header("If-Modified-Since")) {
8399-
auto val = req.get_header_value("If-Modified-Since");
8400-
auto t = detail::parse_http_date(val);
8401-
8402-
if (t != static_cast<time_t>(-1) && mtime <= t) {
8403-
res.status = StatusCode::NotModified_304;
8404-
return true;
8405-
}
8406-
}
8407-
8408-
// Handle If-Range for partial content requests (RFC 9110
8409-
// Section 13.1.5). If-Range is only evaluated when Range header is
8410-
// present. If the validator matches, serve partial content; otherwise
8411-
// serve full content.
8412-
if (!req.ranges.empty() && req.has_header("If-Range")) {
8413-
auto val = req.get_header_value("If-Range");
8414-
auto valid = false;
8415-
8416-
if (detail::is_strong_etag(val)) {
8417-
// RFC 9110 Section 13.1.5: If-Range requires strong ETag
8418-
// comparison.
8419-
valid = (!etag.empty() && val == etag);
8420-
} else if (detail::is_weak_etag(val)) {
8421-
// Weak ETags are not valid for If-Range (RFC 9110 Section 13.1.5)
8422-
valid = false;
8423-
} else {
8424-
// HTTP-date comparison
8425-
auto if_range_time = detail::parse_http_date(val);
8426-
valid = (if_range_time != static_cast<time_t>(-1) &&
8427-
mtime <= if_range_time);
8428-
}
8374+
if (check_if_not_modified(req, res, etag, mtime)) { return true; }
84298375

8430-
if (!valid) {
8431-
// Validator doesn't match: ignore Range and serve full content
8432-
req.ranges.clear();
8433-
}
8434-
}
8376+
check_if_range(req, etag, mtime);
84358377

84368378
auto mm = std::make_shared<detail::mmap>(path.c_str());
84378379
if (!mm->is_open()) {
@@ -8462,6 +8404,81 @@ inline bool Server::handle_file_request(Request &req, Response &res) {
84628404
return false;
84638405
}
84648406

8407+
inline bool Server::check_if_not_modified(const Request &req, Response &res,
8408+
const std::string &etag,
8409+
time_t mtime) const {
8410+
// Handle conditional GET:
8411+
// 1. If-None-Match takes precedence (RFC 9110 Section 13.1.2)
8412+
// 2. If-Modified-Since is checked only when If-None-Match is absent
8413+
if (req.has_header("If-None-Match")) {
8414+
if (!etag.empty()) {
8415+
auto val = req.get_header_value("If-None-Match");
8416+
auto matched = false;
8417+
8418+
// NOTE: We use exact string matching here. This works correctly
8419+
// because our server always generates weak ETags (W/"..."), and
8420+
// clients typically send back the same ETag they received.
8421+
// RFC 9110 Section 8.8.3.2 allows weak comparison for
8422+
// If-None-Match, where W/"x" and "x" would match, but this
8423+
// simplified implementation requires exact matches.
8424+
detail::split(val.data(), val.data() + val.size(), ',',
8425+
[&](const char *b, const char *e) {
8426+
if (!matched) {
8427+
auto tag = std::string(b, e);
8428+
matched = tag == "*" || tag == etag;
8429+
}
8430+
});
8431+
8432+
if (matched) {
8433+
res.status = StatusCode::NotModified_304;
8434+
return true;
8435+
}
8436+
}
8437+
} else if (req.has_header("If-Modified-Since")) {
8438+
auto val = req.get_header_value("If-Modified-Since");
8439+
auto t = detail::parse_http_date(val);
8440+
8441+
if (t != static_cast<time_t>(-1) && mtime <= t) {
8442+
res.status = StatusCode::NotModified_304;
8443+
return true;
8444+
}
8445+
}
8446+
return false;
8447+
}
8448+
8449+
inline bool Server::check_if_range(Request &req, const std::string &etag,
8450+
time_t mtime) const {
8451+
// Handle If-Range for partial content requests (RFC 9110
8452+
// Section 13.1.5). If-Range is only evaluated when Range header is
8453+
// present. If the validator matches, serve partial content; otherwise
8454+
// serve full content.
8455+
if (!req.ranges.empty() && req.has_header("If-Range")) {
8456+
auto val = req.get_header_value("If-Range");
8457+
auto valid = false;
8458+
8459+
if (detail::is_strong_etag(val)) {
8460+
// RFC 9110 Section 13.1.5: If-Range requires strong ETag
8461+
// comparison.
8462+
valid = (!etag.empty() && val == etag);
8463+
} else if (detail::is_weak_etag(val)) {
8464+
// Weak ETags are not valid for If-Range (RFC 9110 Section 13.1.5)
8465+
valid = false;
8466+
} else {
8467+
// HTTP-date comparison
8468+
auto if_range_time = detail::parse_http_date(val);
8469+
valid =
8470+
(if_range_time != static_cast<time_t>(-1) && mtime <= if_range_time);
8471+
}
8472+
8473+
if (!valid) {
8474+
// Validator doesn't match: ignore Range and serve full content
8475+
req.ranges.clear();
8476+
return false;
8477+
}
8478+
}
8479+
return true;
8480+
}
8481+
84658482
inline socket_t
84668483
Server::create_server_socket(const std::string &host, int port,
84678484
int socket_flags,

0 commit comments

Comments
 (0)