Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions include/uvhttp_server.h
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,13 @@ uvhttp_error_t uvhttp_server_stop(uvhttp_server_t* server);
* @note Added in v2.10. The five-step sequence remains available for callers
* that need to keep the router handle - prefer this for everything else.
*
* @note This entry builds a bare server: no `uvhttp_context_t` and no
* `uvhttp_config_t` are attached. Every config-backed setting on the
* result therefore falls back to its compile-time constant (backlog,
* tcp_keepalive_timeout). If you need those tunable, use the five-step
* sequence with a context instead - wiring a config into this path is
* tracked separately.
*
* @example
* uvhttp_server_t* server = NULL;
* uvhttp_route_t routes[] = {
Expand Down Expand Up @@ -331,6 +338,12 @@ uvhttp_error_t uvhttp_server_set_handler(uvhttp_server_t* server,
* uvhttp_server_free. A NULL router is only a no-op when the server
* holds none already; once a router has been taken, NULL is rejected
* like any other pointer.
*
* @note The guard above is per-server, so it does NOT stop the same router
* from being handed to two different servers. Do that and both
* uvhttp_server_free calls release it: the second one is a double free
* (ASan reports it at free time, far from the assignment). One router
* belongs to exactly one server.
*/
uvhttp_error_t uvhttp_server_take_router(uvhttp_server_t* server,
uvhttp_router_t* router);
Expand All @@ -348,6 +361,10 @@ uvhttp_error_t uvhttp_server_take_router(uvhttp_server_t* server,
* as uvhttp_server_take_router. No detach operation exists: a NULL
* context is only a no-op when the server holds none already, and is
* rejected once one has been taken.
*
* @note Like take_router, the guard is per-server: handing the same context
* to two servers makes both destroy it - a double free at free time.
* One context belongs to exactly one server.
*/
uvhttp_error_t uvhttp_server_take_context(uvhttp_server_t* server,
struct uvhttp_context* context);
Expand Down
14 changes: 14 additions & 0 deletions src/uvhttp_lru_cache.c
Original file line number Diff line number Diff line change
Expand Up @@ -677,12 +677,26 @@ void uvhttp_lru_cache_reset_stats(cache_manager_t* cache) {

/**
* set maximum file size for cache
*
* The write point is the ONLY guard for this field: uvhttp_lru_cache_create
* takes no max_file_size argument (it hardcodes a 1GB default), and put()
* applies it as a hard cutoff (content_length > max_file_size, see the
* "check if file size exceeds limit" branch). A zero written here therefore
* bricks the cache - every later put returns UVHTTP_ERROR_INVALID_PARAM with
* only a WARN as explanation - so it is rejected like the two setters above
* instead of being written silently.
*/
void uvhttp_lru_cache_set_max_file_size(cache_manager_t* cache,
size_t max_file_size) {
if (!cache)
return;

if (max_file_size == 0) {
UVHTTP_LOG_WARN(
"Ignoring max_file_size=0 (put() would reject every entry)");
return;
}

cache->max_file_size = max_file_size;
}

Expand Down
11 changes: 9 additions & 2 deletions src/uvhttp_router.c
Original file line number Diff line number Diff line change
Expand Up @@ -352,8 +352,15 @@ static uvhttp_error_t migrate_to_trie(uvhttp_router_t* router) {
* already had; only the route that triggered migration fails.
*
* The trie nodes built so far stay in the pool unreferenced
* (use_trie is 0, so no lookup reaches them) and are reused by
* the next attempt, which walks the array again from the root.
* (use_trie is 0, so no lookup reaches them); they are neither
* freed nor reachable, so the pool grows monotonically until a
* migration succeeds or the router is freed. A retry re-walks
* the array from the root and find_or_create_child matches by
* segment name, so it reuses the nodes already there (traced
* by reading, not by a test - the failure here is the 12-child
* cap, which no route removal can relieve, so the
* "fail then succeed" transition has no test).
*
* Freeing or detaching old_routes here would destroy every
* previously registered route. */
return UVHTTP_ERROR_OUT_OF_MEMORY;
Expand Down
46 changes: 46 additions & 0 deletions test/unit/test_lru_cache_capacity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -196,4 +196,50 @@ TEST_F(LruCacheCapacity, SetMaxEntriesAcceptsPositiveValue) {
EXPECT_EQ(cache_->max_entries, 3);
}

/* ========== set_max_file_size:写点不变量 ========== */

/*
* put() 在「check if file size exceeds limit」分支用
* if (content_length > cache->max_file_size) → INVALID_PARAM
* 对内容长度做硬截断。而 create() 根本不接收 max_file_size(默认 1GB 写死在
* lru_cache.c:82),所以 setter 是这个字段唯一的守卫。
*
* 评审探针实证:set_max_file_size(cache, 0) 之后,每次 put 都以 -1 失败 ——
* 缓存被砖化,调用方只看到一串 "File size exceeds limit" 的 WARN。
* 本次补上与另两个 setter 同范式的下限校验。
*/

TEST_F(LruCacheCapacity, SetMaxFileSizeRejectsZero) {
Create(kBudget);

uvhttp_lru_cache_set_max_file_size(cache_, 0);

EXPECT_EQ(cache_->max_file_size, 1024u * 1024 * 1024)
<< "create 把默认值写死为 1GB;setter 不得把 0 写进去";
}

/* 核心回归:0 不得砖化缓存。砖化的表现是 put 永久 INVALID_PARAM。 */
TEST_F(LruCacheCapacity, ZeroMaxFileSizeDoesNotBrickTheCache) {
Create(kBudget);

uvhttp_lru_cache_set_max_file_size(cache_, 0);

EXPECT_EQ(Put("/after.bin", 64, 's'), UVHTTP_OK)
<< "写入 0 之后 put 必须仍能成功(评审探针在此处实测返回 -1)";
EXPECT_EQ(cache_->entry_count, 1);
}

/* 合法值仍然立即生效:小于新上限的进得去,大于的被拒 */
TEST_F(LruCacheCapacity, SetMaxFileSizeAcceptsPositiveValue) {
Create(kBudget);

uvhttp_lru_cache_set_max_file_size(cache_, 1024);
EXPECT_EQ(cache_->max_file_size, 1024u);

EXPECT_EQ(Put("/small.bin", 512, 's'), UVHTTP_OK)
<< "小于新上限的内容应能放入";
EXPECT_EQ(Put("/big.bin", 4096, 'b'), UVHTTP_ERROR_INVALID_PARAM)
<< "大于新上限的内容应被拒(文件大小判定先于内存预算判定)";
}

#endif /* UVHTTP_FEATURE_STATIC_FILES */
Loading