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
12 changes: 6 additions & 6 deletions .agents/docs/exec-plan-0.3.0.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
版本取 **0.3.0** 而不是 0.2.11:`HttpResponse` / `HttpClientConfig` 加了字段(非破坏),严格解析会把过去静默接受的畸形响应变成显式错误(**行为变更**)。语义化版本下,行为变更进 minor。

**无感升级的边界**(release note 必须点名):
- 现有代码**不需要改一行**就能编译:只加字段,不改签名,`ok()` 语义不变。
- ⚠️ 过去被静默接受的畸形响应现在会报错 —— 这不是新 bug,是过去在静默损坏数据。
- 现有代码**不需要改一行**就能编译:只加字段,不改签名,`ok()` 语义不变。
- 过去被静默接受的畸形响应现在会报错 —— 这不是新 bug,是过去在静默损坏数据。

---

Expand All @@ -45,7 +45,7 @@
```
T0 分支 + 计划 ✔
├─ T1 P0 SIGPIPE(socket.cppm) 必须最先
├─ T1 P0 SIGPIPE(socket.cppm) 必须最先
│ │ P0-1 send() + MSG_NOSIGNAL
│ │ P0-2 socket() 后 setsockopt(SO_NOSIGPIPE)
│ │ 理由:T5 让 pool_.erase() 在更多路径上执行,
Expand All @@ -66,7 +66,7 @@ T0 分支 + 计划 ✔
│ └ 吸收 P2-E2(分配上限,chunk 分片读)
│ │
│ ├─ T4 P3-A PooledConnection(默认丢弃)← 吸收 P1-A/B/C 全部十项
│ │ │ 递归重定向:递归前必须 keep() 或 drop(),不能让析构跨过递归
│ │ │ 递归重定向:递归前必须 keep() 或 drop(),不能让析构跨过递归
│ │ │
│ │ ├─ T5 P2-E1 bounded drain(重定向/非 2xx/文件打不开时省一次握手)
│ │ ├─ T6 P2-F1/F2 bodyComplete + bodyError(statusText 不再被覆盖)
Expand All @@ -75,7 +75,7 @@ T0 分支 + 计划 ✔
│ └─ T8 纯单测:parse_status_line / parse_chunk_size_line / parse_content_length
├─ T9 集成测试:进程内 mbedtls TLS listener(T4 后)
tinyhttps 只支持 https,本地明文 listener 用不了 → 必须自带 TLS 服务端
│ tinyhttps 只支持 https,本地明文 listener 用不了 → 必须自带 TLS 服务端
│ T1..T11 用例见 §4
├─ T10 openkal 体系(与 T1..T9 并行调研,结论落地在 T1/platform.cppm)
Expand Down Expand Up @@ -136,7 +136,7 @@ T0 分支 + 计划 ✔
| # | 用例 | 断言 | 覆盖 |
|---|---|---|---|
| T1 | 服务端发一半 CL body 后挂住 | 请求 2 `statusCode == 200` | P1-A1 |
| T2 | 同上,**并断言服务端 accept 次数 == 2** | 🔴 唯一能抓住「看起来对了」的断言 | P1-A1 |
| T2 | 同上,**并断言服务端 accept 次数 == 2** | 唯一能抓住「看起来对了」的断言 | P1-A1 |
| T3 | chunked 版本同上 | 同 T1+T2 | P1-A3 |
| T4 | 服务端发一半后关连接 | 不崩;进程未被信号杀死 | #16 |
| T5 | `SIGPIPE` 默认处置下跑 T4(子进程) | `WIFSIGNALED == false`,退出码 ≠ 141 | P0 |
Expand Down
108 changes: 54 additions & 54 deletions .agents/docs/fix-plan-issue-15-16.md

Large diffs are not rendered by default.

11 changes: 10 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Changelog

## 0.3.1

The socket interface is selected by the C library rather than by the operating
system ([mcpp-index#435](https://github.com/mcpplibs/mcpp-index/issues/435)).
Above openkal on Windows the C library is musl and `winsock2.h` is not in the
dependency graph; the POSIX interface is used there, selected in `mcpp.toml` by
`cfg(c-abi = "musl")`. A Windows build with the platform's C runtime is
unchanged. `platform::is_windows` is renamed `platform::uses_winsock`.

## 0.3.0

Closes [#15](https://github.com/mcpplibs/tinyhttps/issues/15) (a socket returned
Expand All @@ -14,7 +23,7 @@ truncation at all, plus a `Content-Length` past 32 bits — and two more (the
streaming reader's `Content-Length` exits) were regressions 0.2.10 had
introduced.

### ⚠️ Read this first
### Read this first

**If you are on 0.2.10 and use `send_stream`, upgrade.** 0.2.10 added a
`Content-Length` branch to the streaming reader whose two exits — a read timeout
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ mcpp add tinyhttps@0.3.0

```toml
[dependencies]
tinyhttps = "0.3.0"
tinyhttps = "0.3.1"
```

### 构建
Expand Down
9 changes: 8 additions & 1 deletion mcpp.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
namespace = "mcpplibs"
name = "tinyhttps"
version = "0.3.0"
version = "0.3.1"
description = "Minimal C++23 HTTP/HTTPS client with SSE streaming support"
license = "Apache-2.0"
repo = "https://github.com/mcpplibs/tinyhttps"
Expand All @@ -14,3 +14,10 @@ mbedtls = "3.6.1"

[dev-dependencies.compat]
gtest = "1.15.2"

# The socket interface follows the C library rather than the operating system
# (src/platform.cppm). Where the C library is musl, including musl above openkal
# on Windows, it is the POSIX interface; the platform's own headers are not part
# of that graph.
[target.'cfg(c-abi = "musl")'.build]
cxxflags = ["-DTINYHTTPS_POSIX_SOCKETS"]
4 changes: 2 additions & 2 deletions src/http.cppm
Original file line number Diff line number Diff line change
Expand Up @@ -786,7 +786,7 @@ static bool drain_body(TlsSocket& sock, const ResponseHead& head,
// calling `keep()` drops the connection, and a path added tomorrow is safe
// before anyone reviews it.
//
// ⚠️ Ordering: `drop()` destroys a `TlsSocket`, which sends `close_notify`,
// Ordering: `drop()` destroys a `TlsSocket`, which sends `close_notify`,
// which writes to a socket whose peer may be gone. That write is why P0 —
// `MSG_NOSIGNAL` in `Socket::write` — had to land before this class existed;
// without it, making the drop path more common would have made issue #16 more
Expand All @@ -808,7 +808,7 @@ public:
// Drop now rather than at the end of the scope. Idempotent, and a no-op
// after `keep()`.
//
// ⚠️ Callers that recurse — the redirect paths — MUST settle the guard
// Callers that recurse — the redirect paths — MUST settle the guard
// before recursing. The inner call puts its own connection into the pool
// under the same key when the redirect is to the same host, and a guard
// still armed when the outer scope ends would then delete it.
Expand Down
31 changes: 20 additions & 11 deletions src/platform.cppm
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
module;

#ifndef _WIN32
// The socket interface is chosen by the C library, not by the operating
// system. On Windows with the platform's own C runtime it is Windows Sockets;
// where the C library is POSIX-shaped (openkal-musl, selected in mcpp.toml by
// `cfg(c-abi = "musl")`) it is the POSIX interface on every system, Windows
// included.
#if defined(_WIN32) && !defined(TINYHTTPS_POSIX_SOCKETS)
#define TINYHTTPS_WINSOCK 1
#endif

#ifndef TINYHTTPS_WINSOCK
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
Expand All @@ -14,7 +23,7 @@ import std;

// mcpplibs.tinyhttps:platform — the one place OS-specific networking quirks
// live. Everything else (socket.cppm, http.cppm…) stays portable and branches
// on `platform::is_windows` with `if constexpr`, never raw #ifdef.
// on `platform::uses_winsock` with `if constexpr`, never raw #ifdef.
//
// ── The platforms this library is known to run on, and what differs ──────────
//
Expand All @@ -26,7 +35,7 @@ import std;
// | Windows | nothing to do | no SIGPIPE, neither macro |
// | above openkal | MSG_NOSIGNAL, and moot | see below |
//
// ABOVE openkal there are no signals at all, so the hazard issue #16
// ABOVE openkal there are no signals at all, so the hazard issue #16
// describes cannot arise — and the same code is nevertheless correct there
// without a branch. openkal-musl defines MSG_NOSIGNAL
// (`musl/include/sys/socket.h:344`) and accepts it as a no-op, in its own words:
Expand All @@ -36,7 +45,7 @@ import std;
// -ENOSYS (`:550`, `:592-593`). SO_NOSIGPIPE is a BSD spelling that musl does
// not define, so `socket.cppm`'s `#ifdef` for it simply does not compile in.
//
// ⚠️ One difference that IS live above openkal: connect() completes before it
// One difference that IS live above openkal: connect() completes before it
// returns even on a non-blocking descriptor, because `kal_net_connect` has no
// form that begins a connection and reports its outcome later
// (`port/src/okm_net.c:454-467`). `Socket::connect_addrinfo` therefore never
Expand All @@ -58,14 +67,14 @@ import std;
namespace mcpplibs::tinyhttps::platform {

// Compile-time platform flag for `if constexpr` at call sites.
export inline constexpr bool is_windows =
#ifdef _WIN32
export inline constexpr bool uses_winsock =
#ifdef TINYHTTPS_WINSOCK
true;
#else
false;
#endif

#ifndef _WIN32
#ifndef TINYHTTPS_WINSOCK

namespace {

Expand Down Expand Up @@ -185,7 +194,7 @@ inline std::vector<std::string> dns_query_a(const std::string& server, const cha

} // anonymous namespace

#endif // !_WIN32
#endif // !TINYHTTPS_WINSOCK

// True when libc's own resolver has a usable config (/etc/resolv.conf). When
// false, callers should prefer resolve_fallback() to avoid a multi-second stall
Expand All @@ -195,9 +204,9 @@ inline std::vector<std::string> dns_query_a(const std::string& server, const cha
// `if constexpr` still compiles its discarded branch, which would reference the
// POSIX-only helpers above that don't exist on Windows. Concentrating that
// preprocessor divergence here is exactly why this platform module exists; call
// sites elsewhere branch on `is_windows` with `if constexpr`.
// sites elsewhere branch on `uses_winsock` with `if constexpr`.
export bool system_resolver_configured() {
#ifdef _WIN32
#ifdef TINYHTTPS_WINSOCK
return true;
#else
std::error_code ec;
Expand All @@ -211,7 +220,7 @@ export bool system_resolver_configured() {
// on query failure. A numeric host is returned unchanged.
export std::vector<std::string> resolve_fallback([[maybe_unused]] const char* host,
[[maybe_unused]] int timeoutMs) {
#ifdef _WIN32
#ifdef TINYHTTPS_WINSOCK
return {};
#else
if (is_numeric_host(host)) return { std::string(host) };
Expand Down
29 changes: 19 additions & 10 deletions src/socket.cppm
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
module;

#ifdef _WIN32
// The socket interface is chosen by the C library, not by the operating
// system. On Windows with the platform's own C runtime it is Windows Sockets;
// where the C library is POSIX-shaped (openkal-musl, selected in mcpp.toml by
// `cfg(c-abi = "musl")`) it is the POSIX interface on every system, Windows
// included.
#if defined(_WIN32) && !defined(TINYHTTPS_POSIX_SOCKETS)
#define TINYHTTPS_WINSOCK 1
#endif

#ifdef TINYHTTPS_WINSOCK
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
Expand All @@ -21,7 +30,7 @@ import :platform;

namespace mcpplibs::tinyhttps {

#ifdef _WIN32
#ifdef TINYHTTPS_WINSOCK
using SocketHandle = SOCKET;
constexpr SocketHandle INVALID_SOCKET_FD = INVALID_SOCKET;
#else
Expand Down Expand Up @@ -88,7 +97,7 @@ public:
return ok;
};

if constexpr (platform::is_windows) {
if constexpr (platform::uses_winsock) {
return try_resolved(host, /*numeric=*/false);
} else {
// Fall back to a manual DNS query when libc can't resolve (Termux:
Expand Down Expand Up @@ -131,7 +140,7 @@ public:
// option rather than as a send flag; `write` below carries the flag
// for the platforms that have one.
//
// NOTHING SELECTS THIS AND NOTHING MAY. It is not a feature, not
// NOTHING SELECTS THIS AND NOTHING MAY. It is not a feature, not
// a config field and not a runtime probe: the preprocessor reads the
// target's own <sys/socket.h> and the answer is already complete.
// Measured on this machine — glibc: SO_NOSIGPIPE absent,
Expand Down Expand Up @@ -175,7 +184,7 @@ public:
if (rc == 0) {
connected = true;
} else {
#ifdef _WIN32
#ifdef TINYHTTPS_WINSOCK
if (WSAGetLastError() == WSAEWOULDBLOCK) {
#else
if (errno == EINPROGRESS) {
Expand Down Expand Up @@ -266,14 +275,14 @@ public:
}

static void platform_init() {
#ifdef _WIN32
#ifdef TINYHTTPS_WINSOCK
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
#endif
}

static void platform_cleanup() {
#ifdef _WIN32
#ifdef TINYHTTPS_WINSOCK
WSACleanup();
#endif
}
Expand All @@ -282,7 +291,7 @@ private:
SocketHandle fd_ = INVALID_SOCKET_FD;

static bool set_non_blocking(SocketHandle fd, bool nonBlocking) {
#ifdef _WIN32
#ifdef TINYHTTPS_WINSOCK
u_long mode = nonBlocking ? 1 : 0;
return ioctlsocket(fd, FIONBIO, &mode) == 0;
#else
Expand All @@ -298,7 +307,7 @@ private:
}

static bool poll_fd(SocketHandle fd, int timeoutMs, bool forRead) {
#ifdef _WIN32
#ifdef TINYHTTPS_WINSOCK
WSAPOLLFD pfd{};
pfd.fd = fd;
pfd.events = forRead ? POLLIN : POLLOUT;
Expand All @@ -314,7 +323,7 @@ private:
}

static void close_handle(SocketHandle fd) {
#ifdef _WIN32
#ifdef TINYHTTPS_WINSOCK
::closesocket(fd);
#else
::close(fd);
Expand Down
17 changes: 13 additions & 4 deletions tests/test_pool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,16 @@
#include <gtest/gtest.h>
#include "tls_test_server.hpp"

#ifndef _WIN32
// The socket interface is chosen by the C library, not by the operating
// system. On Windows with the platform's own C runtime it is Windows Sockets;
// where the C library is POSIX-shaped (openkal-musl, selected in mcpp.toml by
// `cfg(c-abi = "musl")`) it is the POSIX interface on every system, Windows
// included.
#if defined(_WIN32) && !defined(TINYHTTPS_POSIX_SOCKETS)
#define TINYHTTPS_WINSOCK 1
#endif

#ifndef TINYHTTPS_WINSOCK
#include <csignal>
#include <sys/wait.h>
#include <unistd.h>
Expand Down Expand Up @@ -56,7 +65,7 @@ class PoolTest : public ::testing::Test {
// does. The read times out with 50 bytes still owed, and those 50 bytes then
// arrive on a socket nobody is reading.
//
// ⚠️ THE LATE SECOND HALF IS WHAT MAKES THIS A TEST. An earlier form of this
// THE LATE SECOND HALF IS WHAT MAKES THIS A TEST. An earlier form of this
// server stalled and never sent the rest; the connection was then silent rather
// than dirty, and the stale-connection retry rescued the second request whether
// or not the pool guard worked. Verified by mutation: with the guard's
Expand Down Expand Up @@ -719,7 +728,7 @@ TEST_F(PoolTest, AnEndlessTrailerSectionIsRefused) {

// ── issue #16 · SIGPIPE ──────────────────────────────────────────────────────

#ifndef _WIN32
#ifndef TINYHTTPS_WINSOCK
// THE TEST HAS TO FORK, AND THE REASON IS ITSELF EVIDENCE.
//
// `mbedtls_net_bind` calls `net_prepare`, which does `signal(SIGPIPE, SIG_IGN)`
Expand Down Expand Up @@ -801,4 +810,4 @@ TEST(SigPipe, WritingToADepartedPeerReturnsInsteadOfKillingTheProcess) {
EXPECT_EQ(WEXITSTATUS(status), 0)
<< "child exit code 3 = could not connect, 4 = the write never failed";
}
#endif // !_WIN32
#endif // !TINYHTTPS_WINSOCK
11 changes: 10 additions & 1 deletion tests/tls_test_server.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,16 @@
#include <mbedtls/pk.h>
#include <mbedtls/net_sockets.h>

#ifdef _WIN32
// The socket interface is chosen by the C library, not by the operating
// system. On Windows with the platform's own C runtime it is Windows Sockets;
// where the C library is POSIX-shaped (openkal-musl, selected in mcpp.toml by
// `cfg(c-abi = "musl")`) it is the POSIX interface on every system, Windows
// included.
#if defined(_WIN32) && !defined(TINYHTTPS_POSIX_SOCKETS)
#define TINYHTTPS_WINSOCK 1
#endif

#ifdef TINYHTTPS_WINSOCK
#include <winsock2.h>
#include <ws2tcpip.h>
#else
Expand Down
Loading