-
Notifications
You must be signed in to change notification settings - Fork 1.4k
feat: Enhance filecache #823
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
a90085a
bc30edc
f14db65
f9bae97
f5e9a67
7c7d63e
1f83fce
a9790f6
16c24e7
4fd86b4
f1850b9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,90 +1,158 @@ | ||
| #ifndef HV_FILE_CACHE_H_ | ||
| #define HV_FILE_CACHE_H_ | ||
|
|
||
| /* | ||
| * FileCache — Enhanced File Cache for libhv HTTP server | ||
| * | ||
| * Features: | ||
| * 1. Configurable max_header_length (default 4096, tunable per-instance) | ||
| * 2. prepend_header() returns bool to report success/failure | ||
| * 3. Exposes header/buffer metrics via accessors | ||
| * 4. Fixes stat() name collision in is_modified() | ||
| * 5. max_cache_num / max_file_size configurable at runtime | ||
| * 6. Reserved header space can be tuned per-instance | ||
| * 7. Source-level API compatible; struct layout differs from original (no ABI/layout compatibility) | ||
| */ | ||
|
|
||
| #include <memory> | ||
| #include <map> | ||
| #include <string> | ||
| #include <mutex> | ||
|
|
||
| #include "hexport.h" | ||
| #include "hbuf.h" | ||
| #include "hstring.h" | ||
| #include "LRUCache.h" | ||
|
|
||
| #define HTTP_HEADER_MAX_LENGTH 1024 // 1K | ||
| #define FILE_CACHE_MAX_NUM 100 | ||
| #define FILE_CACHE_MAX_SIZE (1 << 22) // 4M | ||
| // Default values — may be overridden at runtime via FileCache setters | ||
| #define FILE_CACHE_DEFAULT_HEADER_LENGTH 4096 // 4K | ||
| #define FILE_CACHE_DEFAULT_MAX_NUM 100 | ||
| #define FILE_CACHE_DEFAULT_MAX_FILE_SIZE (1 << 22) // 4M | ||
|
|
||
| typedef struct file_cache_s { | ||
| mutable std::mutex mutex; // protects all mutable state below | ||
| std::string filepath; | ||
| struct stat st; | ||
| time_t open_time; | ||
| time_t stat_time; | ||
| uint32_t stat_cnt; | ||
| HBuf buf; // http_header + file_content | ||
| hbuf_t filebuf; | ||
| hbuf_t httpbuf; | ||
| HBuf buf; // header_reserve + file_content | ||
| hbuf_t filebuf; // points into buf: file content region | ||
| hbuf_t httpbuf; // points into buf: header + file content after prepend | ||
| char last_modified[64]; | ||
| char etag[64]; | ||
| std::string content_type; | ||
|
|
||
| // --- new: expose header metrics --- | ||
| int header_reserve; // reserved bytes before file content | ||
| int header_used; // actual bytes used by prepend_header | ||
|
|
||
| file_cache_s() { | ||
| stat_cnt = 0; | ||
| header_reserve = FILE_CACHE_DEFAULT_HEADER_LENGTH; | ||
| header_used = 0; | ||
| memset(last_modified, 0, sizeof(last_modified)); | ||
| memset(etag, 0, sizeof(etag)); | ||
| } | ||
|
|
||
| // NOTE: caller must hold mutex. | ||
| // On Windows, Open() uses _wstat() directly instead of calling this. | ||
| bool is_modified() { | ||
| time_t mtime = st.st_mtime; | ||
| stat(filepath.c_str(), &st); | ||
| ::stat(filepath.c_str(), &st); | ||
| return mtime != st.st_mtime; | ||
| } | ||
|
|
||
| // NOTE: caller must hold mutex | ||
| bool is_complete() { | ||
| if(S_ISDIR(st.st_mode)) return filebuf.len > 0; | ||
| return filebuf.len == st.st_size; | ||
| if (S_ISDIR(st.st_mode)) return filebuf.len > 0; | ||
| return filebuf.len == (size_t)st.st_size; | ||
| } | ||
|
|
||
| void resize_buf(int filesize) { | ||
| buf.resize(HTTP_HEADER_MAX_LENGTH + filesize); | ||
| filebuf.base = buf.base + HTTP_HEADER_MAX_LENGTH; | ||
| // NOTE: caller must hold mutex — invalidates filebuf/httpbuf pointers | ||
| void resize_buf(size_t filesize, int reserved) { | ||
| if (reserved < 0) reserved = 0; | ||
| header_reserve = reserved; | ||
| buf.resize((size_t)reserved + filesize); | ||
| filebuf.base = buf.base + reserved; | ||
| filebuf.len = filesize; | ||
| // Invalidate httpbuf since buffer may have been reallocated | ||
| httpbuf.base = NULL; | ||
| httpbuf.len = 0; | ||
| header_used = 0; | ||
| } | ||
|
|
||
| void prepend_header(const char* header, int len) { | ||
| if (len > HTTP_HEADER_MAX_LENGTH) return; | ||
| void resize_buf(size_t filesize) { | ||
| resize_buf(filesize, header_reserve); | ||
| } | ||
|
|
||
| // Thread-safe: prepend header into reserved space. | ||
| // Returns true on success, false if header exceeds reserved space. | ||
| // On failure, httpbuf falls back to filebuf (body only, no header). | ||
| bool prepend_header(const char* header, int len) { | ||
| std::lock_guard<std::mutex> lock(mutex); | ||
| if (len <= 0 || len > header_reserve) { | ||
| // Safe fallback: point httpbuf at filebuf so callers always get valid data | ||
| httpbuf = filebuf; | ||
| header_used = 0; | ||
| return false; | ||
| } | ||
| httpbuf.base = filebuf.base - len; | ||
| httpbuf.len = len + filebuf.len; | ||
| httpbuf.len = (size_t)len + filebuf.len; | ||
| memcpy(httpbuf.base, header, len); | ||
| header_used = len; | ||
| return true; | ||
| } | ||
|
|
||
| // --- thread-safe accessors --- | ||
| int get_header_reserve() const { std::lock_guard<std::mutex> lock(mutex); return header_reserve; } | ||
| int get_header_used() const { std::lock_guard<std::mutex> lock(mutex); return header_used; } | ||
| int get_header_remaining() const { std::lock_guard<std::mutex> lock(mutex); return header_reserve - header_used; } | ||
| bool header_fits(int len) const { std::lock_guard<std::mutex> lock(mutex); return len > 0 && len <= header_reserve; } | ||
| } file_cache_t; | ||
|
|
||
| typedef std::shared_ptr<file_cache_t> file_cache_ptr; | ||
| typedef std::shared_ptr<file_cache_t> file_cache_ptr; | ||
|
|
||
| class FileCache : public hv::LRUCache<std::string, file_cache_ptr> { | ||
| class HV_EXPORT FileCache : public hv::LRUCache<std::string, file_cache_ptr> { | ||
| public: | ||
| int stat_interval; | ||
| int expired_time; | ||
| // --- configurable parameters (were hardcoded macros before) --- | ||
| int stat_interval; // seconds between stat() checks | ||
| int expired_time; // seconds before cache entry expires | ||
| int max_header_length; // reserved header bytes per entry | ||
| int max_file_size; // max cached file size (larger = large-file path) | ||
|
|
||
| FileCache(size_t capacity = FILE_CACHE_MAX_NUM); | ||
| explicit FileCache(size_t capacity = FILE_CACHE_DEFAULT_MAX_NUM); | ||
|
|
||
| struct OpenParam { | ||
| bool need_read; | ||
| int max_read; | ||
| const char* path; | ||
| size_t filesize; | ||
| int error; | ||
| bool need_read; | ||
| int max_read; // per-request override for max file size | ||
| const char* path; // URL path (for directory listing) | ||
| size_t filesize; // [out] actual file size | ||
| int error; // [out] error code if Open returns NULL | ||
|
Comment on lines
+117
to
+130
|
||
|
|
||
| OpenParam() { | ||
| need_read = true; | ||
| max_read = FILE_CACHE_MAX_SIZE; | ||
| max_read = FILE_CACHE_DEFAULT_MAX_FILE_SIZE; | ||
| path = "/"; | ||
| filesize = 0; | ||
| error = 0; | ||
| } | ||
|
Comment on lines
125
to
138
|
||
| }; | ||
|
|
||
| file_cache_ptr Open(const char* filepath, OpenParam* param); | ||
| bool Exists(const char* filepath) const; | ||
| bool Close(const char* filepath); | ||
| void RemoveExpiredFileCache(); | ||
|
|
||
| // --- new: getters --- | ||
| int GetMaxHeaderLength() const { return max_header_length; } | ||
| int GetMaxFileSize() const { return max_file_size; } | ||
| int GetStatInterval() const { return stat_interval; } | ||
| int GetExpiredTime() const { return expired_time; } | ||
|
|
||
| // --- new: setters --- | ||
| void SetMaxHeaderLength(int len) { max_header_length = len < 0 ? 0 : len; } | ||
| void SetMaxFileSize(int size) { max_file_size = size < 1 ? 1 : size; } | ||
|
|
||
| protected: | ||
| file_cache_ptr Get(const char* filepath); | ||
| }; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On
prepend_header()failure (len <= 0 || len > header_reserve),header_usedis not reset. If a previous call succeeded, metrics returned byget_header_used()/get_header_remaining()will be stale/misleading after a failed prepend. Consider settingheader_used = 0in the failure path as well.