Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
a90085a
feat: add FileCacheEx - enhanced file cache with configurable header …
Yundi339 Mar 26, 2026
bc30edc
fix: address race conditions and safety issues in FileCacheEx
Yundi339 Mar 26, 2026
f14db65
refactor: align Windows compat with original libhv patterns
Yundi339 Mar 26, 2026
f9bae97
docs: add FileCacheEx documentation (en/cn) and fix comment style
Yundi339 Mar 26, 2026
f5e9a67
fix: add bounds check for negative reserved in resize_buf
Yundi339 Mar 26, 2026
7c7d63e
refactor: replace FileCache with FileCacheEx in HttpHandler and HttpS…
Yundi339 Mar 29, 2026
1f83fce
Merge branch 'ithewei:master' into feature/enhanced-filecache
Yundi339 Apr 5, 2026
a9790f6
Refactor FileCache and remove FileCacheEx
Yundi339 Apr 5, 2026
16c24e7
refactor: enhance FileCache with safe fallback in prepend_header and …
Yundi339 Apr 5, 2026
4fd86b4
refactor: improve FileCache Open method and enhance HttpHandler heade…
Yundi339 Apr 5, 2026
f1850b9
refactor: update FileCache to reset header_used on safe fallback and …
Yundi339 Apr 5, 2026
8cd32ab
refactor: update FileCache to change nread type from ssize_t to int i…
Yundi339 Apr 7, 2026
4c1d30c
rm deprecated comment
ithewei Apr 10, 2026
731a0a8
Header too large for reserved space: send header first, then continue…
ithewei Apr 10, 2026
05248d0
fix: prevent postprocessor/errorHandler from overriding HTTP_STATUS_U…
Copilot Apr 20, 2026
15809f4
fix: thread unsafe call to localtime on linux (#835)
aleksisch May 21, 2026
0763810
Optimize http router using trie (#833) (#836)
ithewei May 21, 2026
19f3e28
chore: add AGENTS.md for Coding Agents (#826)
ithewei May 21, 2026
9c13599
Apply suggestions from ai code review (#837)
ithewei May 23, 2026
33ce9fc
Apply suggestions for evpp dir from ai code review (#838)
ithewei May 23, 2026
51089c6
Apply suggestions for http dir from ai code review (#839)
ithewei May 23, 2026
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
119 changes: 119 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# AGENTS.md

This file provides guidance to agents when working with code in this repository.

## Project Overview

libhv is a cross-platform C/C++ network library providing event-loop with non-blocking IO and timer. Core is C99, high-level wrappers are C++11. Compatible with gcc4.8+, MSVC2015+, clang. Supports Linux, Windows, macOS, Android, iOS, BSD, Solaris.

## Build Commands

### Makefile (primary, Unix)
```bash
./configure --with-openssl --with-http --with-mqtt --with-kcp # configure options
make libhv # build library only (shared + static)
make # build library + examples
make examples # build all example programs
make unittest # compile unit tests
make evpp # build C++ evpp tests (requires libhv built first)
make clean # clean build artifacts
sudo make install # install to /usr/local/include/hv and /usr/local/lib
```

### CMake (cross-platform)
```bash
mkdir build && cd build
cmake .. -DWITH_OPENSSL=ON -DWITH_HTTP=ON -DBUILD_EXAMPLES=ON
cmake --build .
# Windows: cmake .. -G "Visual Studio 17 2022" -A x64
```

### Bazel
```bash
bazel build libhv
```

### Package Managers
```bash
vcpkg install libhv # vcpkg
xrepo install libhv # xmake
```

## Testing

```bash
make unittest # compile all unit tests
make run-unittest # compile and run unit tests (calls scripts/unittest.sh)
bash scripts/unittest.sh # run pre-built unit tests
make check # integration test: builds httpd, runs HTTP checks (scripts/check.sh)
```

Run a single unit test directly:
```bash
bin/rbtree_test # or any test binary in bin/
```

Run evpp C++ tests (link against libhv):
```bash
make evpp
bin/TcpServer_test
bin/EventLoop_test
```

## Key Configuration Options

Build flags via `./configure` or CMake `-D` options (see `config.ini` for defaults):

| Makefile flag | CMake flag | Purpose |
|---|---|---|
| `--with-openssl` | `-DWITH_OPENSSL=ON` | SSL/TLS via OpenSSL |
| `--with-gnutls` | `-DWITH_GNUTLS=ON` | SSL/TLS via GnuTLS |
| `--with-mbedtls` | `-DWITH_MBEDTLS=ON` | SSL/TLS via mbedTLS |
| `--with-nghttp2` | `-DWITH_NGHTTP2=ON` | HTTP/2 support |
| `--with-kcp` | `-DWITH_KCP=ON` | KCP reliable UDP |
| `--with-mqtt` | `-DWITH_MQTT=ON` | MQTT client |
| `--with-protocol` | `-DWITH_PROTOCOL=ON` | ICMP, DNS, FTP, SMTP |
| `--with-evpp` | `-DWITH_EVPP=ON` | C++ wrappers (default: yes) |
| `--without-evpp` | `-DWITH_EVPP=OFF` | Pure C build, no C++ |
| `--enable-uds` | `-DENABLE_UDS=ON` | Unix Domain Socket |
| `--with-io-uring` | `-DWITH_IO_URING=ON` | io_uring event backend (Linux 5.1+) |

## Architecture

```
Application / Examples
├── http/server, http/client HTTP/WebSocket/gRPC (C++)
├── mqtt/ MQTT client (C)
├── protocol/ ICMP, DNS, FTP, SMTP (C)
├── evpp/ C++ wrappers: TcpServer, TcpClient, UdpServer, EventLoop
├── event/ Core event loop: hloop, hio, htimer
│ └── backends: epoll (Linux), kqueue (macOS/BSD), iocp/wepoll (Windows), io_uring (Linux 5.1+), select (fallback)
│ └── kcp/ KCP reliable UDP transport
├── ssl/ Unified SSL interface (OpenSSL / GnuTLS / mbedTLS / platform)
├── base/ Platform abstraction, sockets, threads, logging, data structures
├── util/ C utilities (base64, md5, sha1)
└── cpputil/ C++ utilities (string, path, file, json, threadpool, ini parser)
```

**Layering rules**: `base/` has no dependencies on other modules. `event/` depends on `base/` and `ssl/`. `evpp/` wraps `event/`. `http/` depends on `evpp/`. Higher layers are optional and controlled by build flags.

**Public API entry point**: `hv.h` includes all base headers. Module-specific headers (e.g., `HttpServer.h`, `TcpServer.h`, `mqtt_client.h`) are the primary include for each feature. Installed headers go to `include/hv/`.

**Key types**: `hloop_t` (event loop), `hio_t` (IO handle), `htimer_t` (timer) in the C API. `EventLoop`, `TcpServer`, `TcpClient`, `Channel` in the C++ API. `HttpRequest`, `HttpResponse`, `HttpService` for HTTP.

## Code Style

- **Formatting**: `.clang-format` — LLVM-based, 4-space indent, 160 column limit, pointer right-aligned, `catch`/`else` on new line (custom brace wrapping), no include sorting.
- **C API naming**: `h` prefix for functions (`hloop_new`, `hio_read`), `_t` suffix for types (`hloop_t`, `hio_t`), UPPERCASE macros (`HV_EXPORT`).
- **C++ API naming**: PascalCase classes (`EventLoop`, `TcpServer`), `hv` namespace.
- **File naming**: lowercase with underscores (`.c` for C, `.cpp` for C++).
- **Platform-specific code**: isolated via `hplatform.h` and `#ifdef` conditional compilation.

## CI

GitHub Actions (`.github/workflows/CI.yml`): builds and tests on Linux (with OpenSSL+nghttp2+KCP+MQTT), Windows (CMake + VS2022), macOS, Android (NDK cross-compile), iOS (Xcode cross-compile). Benchmark workflow runs echo-server throughput and HTTP performance comparisons.
1 change: 1 addition & 0 deletions CLAUDE.md
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ unittest: prepare
$(CXX) -g -Wall -O0 -std=c++11 -I. -Ibase -Icpputil -o bin/synchronized_test unittest/synchronized_test.cpp -pthread
$(CXX) -g -Wall -O0 -std=c++11 -I. -Ibase -Icpputil -o bin/threadpool_test unittest/threadpool_test.cpp -pthread
$(CXX) -g -Wall -O0 -std=c++11 -I. -Ibase -Icpputil -o bin/objectpool_test unittest/objectpool_test.cpp -pthread
$(CXX) -g -Wall -O0 -std=c++11 -Ihttp/server -o bin/http_router_test unittest/http_router_test.cpp
$(CXX) -g -Wall -O0 -std=c++11 -I. -Ibase -Issl -Ievent -Ievpp -Icpputil -Ihttp -Ihttp/client -Ihttp/server -o bin/sizeof_test unittest/sizeof_test.cpp
$(CC) -g -Wall -O0 -std=c99 -I. -Ibase -Iprotocol -o bin/nslookup unittest/nslookup_test.c protocol/dns.c base/hsocket.c base/htime.c
$(CC) -g -Wall -O0 -std=c99 -I. -Ibase -Iprotocol -o bin/ping unittest/ping_test.c protocol/icmp.c base/hsocket.c base/htime.c -DPRINT_DEBUG
Expand Down
23 changes: 18 additions & 5 deletions base/hbase.c
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,7 @@ time_t hv_parse_time(const char* str) {
}

int hv_parse_url(hurl_t* stURL, const char* strURL) {
int ret = 0;
if (stURL == NULL || strURL == NULL) return -1;
memset(stURL, 0, sizeof(hurl_t));
const char* begin = strURL;
Expand Down Expand Up @@ -501,8 +502,20 @@ int hv_parse_url(hurl_t* stURL, const char* strURL) {
stURL->fields[HV_URL_PORT].off = port + 1 - begin;
stURL->fields[HV_URL_PORT].len = ep - port - 1;
// atoi
unsigned int parsed_port = 0;
for (unsigned short i = 1; i <= stURL->fields[HV_URL_PORT].len; ++i) {
stURL->port = stURL->port * 10 + (port[i] - '0');
if (port[i] < '0' || port[i] > '9') {
ret = -2;
break;
}
parsed_port = parsed_port * 10 + (port[i] - '0');
if (parsed_port > 65535) {
ret = -3;
break;
}
}
if (ret == 0) {
stURL->port = (unsigned short)parsed_port;
}
} else {
port = ep;
Expand All @@ -518,25 +531,25 @@ int hv_parse_url(hurl_t* stURL, const char* strURL) {
stURL->fields[HV_URL_HOST].off = host - begin;
stURL->fields[HV_URL_HOST].len = port - host;
}
if (ep == end) return 0;
if (ep == end) return ret;
// /path
sp = ep;
ep = strchr(sp, '?');
if (ep == NULL) ep = end;
stURL->fields[HV_URL_PATH].off = sp - begin;
stURL->fields[HV_URL_PATH].len = ep - sp;
if (ep == end) return 0;
if (ep == end) return ret;
// ?query
sp = ep + 1;
ep = strchr(sp, '#');
if (ep == NULL) ep = end;
stURL->fields[HV_URL_QUERY].off = sp - begin;
stURL->fields[HV_URL_QUERY].len = ep - sp;
if (ep == end) return 0;
if (ep == end) return ret;
// #fragment
sp = ep + 1;
ep = end;
stURL->fields[HV_URL_FRAGMENT].off = sp - begin;
stURL->fields[HV_URL_FRAGMENT].len = ep - sp;
return 0;
return ret;
}
2 changes: 1 addition & 1 deletion base/hbase.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ HV_EXPORT bool hv_wildcard_match(const char* str, const char* pattern);
HV_EXPORT char* hv_strncpy(char* dest, const char* src, size_t n);

// strncat n = sizeof(dest_buf)-1-strlen(dest)
// hv_strncpy n = sizeof(dest_buf)
// hv_strncat n = sizeof(dest_buf)
HV_EXPORT char* hv_strncat(char* dest, const char* src, size_t n);

#if !HAVE_STRLCPY
Expand Down
2 changes: 1 addition & 1 deletion base/hdef.h
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@

#ifndef MAKE_FOURCC
#define MAKE_FOURCC(a, b, c, d) \
( ((uint32)d) | ( ((uint32)c) << 8 ) | ( ((uint32)b) << 16 ) | ( ((uint32)a) << 24 ) )
( ((uint32_t)d) | ( ((uint32_t)c) << 8 ) | ( ((uint32_t)b) << 16 ) | ( ((uint32_t)a) << 24 ) )
#endif

#ifndef MAX
Expand Down
74 changes: 53 additions & 21 deletions base/hlog.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,44 @@
//#include "htime.h"
#define SECONDS_PER_HOUR 3600
#define SECONDS_PER_DAY 86400 // 24*3600
#define SECONDS_PER_WEEK 604800 // 7*24*3600;
#define SECONDS_PER_WEEK 604800 // 7*24*3600

static inline struct tm* hv_localtime_r(time_t ts, struct tm* tm) {
#ifdef _WIN32
localtime_s(tm, &ts);
#else
tm = localtime_r(&ts, tm);
#endif
return tm;
}

static inline struct tm* hv_gmtime_r(time_t ts, struct tm* tm) {
#ifdef _WIN32
gmtime_s(tm, &ts);
#else
tm = gmtime_r(&ts, tm);
#endif
return tm;
}

static int s_gmtoff = 28800; // 8*3600
static void init_gmtoff() {
time_t ts = time(NULL);
struct tm local_tm, gmt_tm;
memset(&local_tm, 0, sizeof(local_tm));
memset(&gmt_tm, 0, sizeof(gmt_tm));
hv_localtime_r(ts, &local_tm);
hv_gmtime_r(ts, &gmt_tm);
s_gmtoff = (local_tm.tm_hour - gmt_tm.tm_hour) * 3600 +
(local_tm.tm_min - gmt_tm.tm_min) * 60 +
(local_tm.tm_sec - gmt_tm.tm_sec);

if (local_tm.tm_yday > gmt_tm.tm_yday) {
s_gmtoff += SECONDS_PER_DAY;
} else if (local_tm.tm_yday < gmt_tm.tm_yday) {
s_gmtoff -= SECONDS_PER_DAY;
}
}

struct logger_s {
logger_handler handler;
Expand Down Expand Up @@ -79,13 +114,7 @@ static void logger_init(logger_t* logger) {
}

logger_t* logger_create() {
// init gmtoff here
time_t ts = time(NULL);
struct tm* local_tm = localtime(&ts);
int local_hour = local_tm->tm_hour;
struct tm* gmt_tm = gmtime(&ts);
int gmt_hour = gmt_tm->tm_hour;
s_gmtoff = (local_hour - gmt_hour) * SECONDS_PER_HOUR;
init_gmtoff();

logger_t* logger = (logger_t*)malloc(sizeof(logger_t));
logger_init(logger);
Expand Down Expand Up @@ -214,12 +243,14 @@ const char* logger_get_cur_file(logger_t* logger) {
}

static void logfile_name(const char* filepath, time_t ts, char* buf, int len) {
struct tm* tm = localtime(&ts);
struct tm tm;
memset(&tm, 0, sizeof(tm));
hv_localtime_r(ts, &tm);
snprintf(buf, len, "%s.%04d%02d%02d.log",
filepath,
tm->tm_year+1900,
tm->tm_mon+1,
tm->tm_mday);
tm.tm_year+1900,
tm.tm_mon+1,
tm.tm_mday);
}

static void logfile_truncate(logger_t* logger) {
Expand Down Expand Up @@ -375,16 +406,17 @@ int logger_print(logger_t* logger, int level, const char* fmt, ...) {
us = tm.wMilliseconds * 1000;
#else
struct timeval tv;
struct tm* tm = NULL;
gettimeofday(&tv, NULL);
time_t tt = tv.tv_sec;
tm = localtime(&tt);
year = tm->tm_year + 1900;
month = tm->tm_mon + 1;
day = tm->tm_mday;
hour = tm->tm_hour;
min = tm->tm_min;
sec = tm->tm_sec;
time_t ts = tv.tv_sec;
struct tm tm;
memset(&tm, 0, sizeof(tm));
localtime_r(&ts, &tm);
year = tm.tm_year + 1900;
month = tm.tm_mon + 1;
day = tm.tm_mday;
hour = tm.tm_hour;
min = tm.tm_min;
sec = tm.tm_sec;
us = tv.tv_usec;
#endif

Expand Down
2 changes: 1 addition & 1 deletion base/hmath.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ static inline int asn1_encode(long long value, unsigned char* buf) {
*p = (unsigned char)value;
return 3;
}
else if (value < 16777126)
else if (value < 16777216)
{
*p = 0x83;
p++;
Expand Down
36 changes: 21 additions & 15 deletions base/htime.c
Original file line number Diff line number Diff line change
Expand Up @@ -73,23 +73,26 @@ datetime_t datetime_now() {
}

datetime_t datetime_localtime(time_t seconds) {
struct tm* tm = localtime(&seconds);
struct tm tm;
memset(&tm, 0, sizeof(tm));
hv_localtime_r(seconds, &tm);
datetime_t dt;
dt.year = tm->tm_year + 1900;
dt.month = tm->tm_mon + 1;
dt.day = tm->tm_mday;
dt.hour = tm->tm_hour;
dt.min = tm->tm_min;
dt.sec = tm->tm_sec;
dt.year = tm.tm_year + 1900;
dt.month = tm.tm_mon + 1;
dt.day = tm.tm_mday;
dt.hour = tm.tm_hour;
dt.min = tm.tm_min;
dt.sec = tm.tm_sec;
dt.ms = 0;
return dt;
}

time_t datetime_mktime(datetime_t* dt) {
struct tm tm;
time_t ts;
time(&ts);
struct tm* ptm = localtime(&ts);
memcpy(&tm, ptm, sizeof(struct tm));
memset(&tm, 0, sizeof(tm));
hv_localtime_r(ts, &tm);
tm.tm_year = dt->year - 1900;
tm.tm_mon = dt->month - 1;
tm.tm_mday = dt->day;
Expand Down Expand Up @@ -171,12 +174,14 @@ char* datetime_fmt_iso(datetime_t* dt, char* buf) {
}

char* gmtime_fmt(time_t time, char* buf) {
struct tm* tm = gmtime(&time);
//strftime(buf, GMTIME_FMT_BUFLEN, "%a, %d %b %Y %H:%M:%S GMT", tm);
struct tm tm;
memset(&tm, 0, sizeof(tm));
hv_gmtime_r(time, &tm);
//strftime(buf, GMTIME_FMT_BUFLEN, "%a, %d %b %Y %H:%M:%S GMT", &tm);
sprintf(buf, GMTIME_FMT,
s_weekdays[tm->tm_wday],
tm->tm_mday, s_months[tm->tm_mon], tm->tm_year + 1900,
tm->tm_hour, tm->tm_min, tm->tm_sec);
s_weekdays[tm.tm_wday],
tm.tm_mday, s_months[tm.tm_mon], tm.tm_year + 1900,
tm.tm_hour, tm.tm_min, tm.tm_sec);
return buf;
}

Expand Down Expand Up @@ -228,7 +233,8 @@ time_t cron_next_timeout(int minute, int hour, int day, int week, int month) {
struct tm tm;
time_t tt;
time(&tt);
tm = *localtime(&tt);
memset(&tm, 0, sizeof(tm));
hv_localtime_r(tt, &tm);
time_t tt_round = 0;

tm.tm_sec = 0;
Expand Down
Loading