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
104 changes: 81 additions & 23 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ jobs:

steps:
# ------------------------------------------------------------------ #
# 1. Check out this extension repo #
# 1. Check out this extension repo #
# ------------------------------------------------------------------ #
- name: Checkout extension
uses: actions/checkout@v5

# ------------------------------------------------------------------ #
# 2. Set platform identifiers #
# 2. Set platform identifiers #
# ------------------------------------------------------------------ #
- name: Set platform identifiers
id: platform
Expand All @@ -54,7 +54,7 @@ jobs:
esac

# ------------------------------------------------------------------ #
# 3. Determine release tag #
# 3. Determine release tag #
# ------------------------------------------------------------------ #
- name: Determine csbuild release
id: cs-release
Expand All @@ -69,7 +69,7 @@ jobs:
fi

# ------------------------------------------------------------------ #
# 4. Download & extract covscript SDK + cspkg repo #
# 4. Download & extract covscript SDK + cspkg repo #
# ------------------------------------------------------------------ #
- name: Download covscript SDK
id: sdk
Expand Down Expand Up @@ -104,7 +104,7 @@ jobs:
ls -la cspkg-repo/

# ------------------------------------------------------------------ #
# 5. Configure cspkg #
# 5. Configure cspkg #
# ------------------------------------------------------------------ #
- name: Configure cspkg
shell: bash
Expand Down Expand Up @@ -134,7 +134,7 @@ jobs:
cat "$HOME/.cspkg/config.json"

# ------------------------------------------------------------------ #
# 6. Persist environment for later steps #
# 6. Persist environment for later steps #
# ------------------------------------------------------------------ #
- name: Setup environment
shell: bash
Expand All @@ -144,7 +144,7 @@ jobs:
echo "${CS_HOME}/bin" >> "$GITHUB_PATH"

# ------------------------------------------------------------------ #
# 7. Run cspkg install #
# 7. Setup cspkg environment #
# ------------------------------------------------------------------ #
- name: Run cspkg install
shell: bash
Expand All @@ -155,10 +155,56 @@ jobs:
cspkg install --import --yes
cspkg install ecs_bootstrap --yes
cspkg version
cspkg doctor
cspkg list

# ------------------------------------------------------------------ #
# 8. Setup MSYS2 + OpenSSL (Windows only) #
# 8. Reproducible build check (Date header line is volatile) #
# ------------------------------------------------------------------ #
- name: Reproducible build check
shell: bash
run: |
set -e
# Check every source package in the repo, not a hardcoded list.
# nullglob avoids iterating the literal glob when nothing matches.
shopt -s nullglob
for src in *.ecs; do
pkg="${src%.ecs}"
echo "=== Checking ${pkg}.csp reproducibility ==="
cp "${pkg}.csp" "/tmp/${pkg}.csp.bak"
if [ -f "${pkg}.csym" ]; then
cp "${pkg}.csym" "/tmp/${pkg}.csym.bak"
fi
cspkg build "${pkg}.ecs" --compile
# The .csp header contains a Date line that always differs.
# Strip it by pattern before comparing; everything else must
# be byte-identical when the source has not changed.
sed '/^# Date:/d' "/tmp/${pkg}.csp.bak" > "/tmp/${pkg}.csp.old"
sed '/^# Date:/d' "${pkg}.csp" > "/tmp/${pkg}.csp.new"
if ! diff -q "/tmp/${pkg}.csp.old" "/tmp/${pkg}.csp.new"; then
echo "ERROR: ${pkg}.csp changed beyond the date line!"
echo " Did you forget to recompile after editing ${pkg}.ecs?"
diff "/tmp/${pkg}.csp.old" "/tmp/${pkg}.csp.new" || true
exit 1
fi
# The .csym has no volatile header — it must be byte-identical.
if [ -f "/tmp/${pkg}.csym.bak" ]; then
if [ ! -f "${pkg}.csym" ]; then
echo "ERROR: ${pkg}.csym is missing after build!"
echo " Did you forget to regenerate ${pkg}.csym after editing ${pkg}.ecs?"
exit 1
fi
if ! cmp -s "/tmp/${pkg}.csym.bak" "${pkg}.csym"; then
echo "ERROR: ${pkg}.csym is stale!"
echo " Did you forget to regenerate ${pkg}.csym after editing ${pkg}.ecs?"
exit 1
fi
fi
echo " ${pkg} reproducible — ok"
done

# ------------------------------------------------------------------ #
# 9a. Setup MSYS2 + OpenSSL (Windows only) #
# ------------------------------------------------------------------ #
- name: Setup MSYS2
if: runner.os == 'Windows'
Expand All @@ -173,16 +219,6 @@ jobs:
mingw-w64-ucrt-x86_64-make
mingw-w64-ucrt-x86_64-openssl

# ------------------------------------------------------------------ #
# 9a. Build extension (Linux / macOS) #
# ------------------------------------------------------------------ #
- name: Build extension
if: runner.os != 'Windows'
shell: bash
env:
CS_DEV_PATH: ${{ steps.sdk.outputs.cs_home }}/
run: bash csbuild/make.sh

# ------------------------------------------------------------------ #
# 9b. Build extension (Windows / MSYS2 UCRT64) #
# ------------------------------------------------------------------ #
Expand All @@ -195,6 +231,16 @@ jobs:
export PATH="${{ steps.msys2.outputs.msys2-location }}/ucrt64/bin:${PATH}"
export CS_DEV_PATH="$(cygpath -u "${{ steps.sdk.outputs.cs_home }}")/"
bash csbuild/make.sh

# ------------------------------------------------------------------ #
# 9c. Build extension (Linux / macOS) #
# ------------------------------------------------------------------ #
- name: Build extension
if: runner.os != 'Windows'
shell: bash
env:
CS_DEV_PATH: ${{ steps.sdk.outputs.cs_home }}/
run: bash csbuild/make.sh

# ------------------------------------------------------------------ #
# 10. Install built extension + packages #
Expand Down Expand Up @@ -229,14 +275,16 @@ jobs:
cs tests/test_udp.csc

# ------------------------------------------------------------------ #
# 13. Run HTTP round-trip tests #
# 13. Run HTTP client + server tests #
# ------------------------------------------------------------------ #
- name: Run HTTP round-trip tests
- name: Run HTTP client/server tests
shell: bash
run: |
set -e
cs tests/test_http_roundtrip.csc
cs tests/test_http_client.csc
cs tests/test_http_server.csc
cs tests/test_proxy.csc

# ------------------------------------------------------------------ #
# 14. Run fiber + socket tests #
Expand All @@ -257,17 +305,27 @@ jobs:
cs tests/test_async_tcp.csc

# ------------------------------------------------------------------ #
# 16. Run integration tests #
# 16. Run TLS integration tests #
# ------------------------------------------------------------------ #
- name: Run integration tests
- name: Run TLS integration tests
shell: bash
run: |
set -e
cs tests/test_tls_trust.csc
cs tests/test_tls_errors.csc

# ------------------------------------------------------------------ #
# 16b. Run TLS custom trust mode test (self-signed cert) #
# 16b. Run master-slave integration tests #
# ------------------------------------------------------------------ #
- name: Run master-slave integration tests
shell: bash
run: |
set -e
cs tests/test_master_slave.csc
cs tests/test_http_compliance.csc

# ------------------------------------------------------------------ #
# 16c. Run TLS custom trust mode test (self-signed cert) #
# ------------------------------------------------------------------ #
- name: Run TLS custom trust mode test
shell: bash
Expand Down
1 change: 1 addition & 0 deletions CNI_API.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ sock.connect_ssl("localhost", {"trust_mode": "insecure"}.to_hash_map())
| `set_opt_no_delay` | `(value: boolean)` | 设置 `TCP_NODELAY` 选项(禁用 Nagle 算法) |
| `set_opt_keep_alive` | `(value: boolean)` | 设置 `SO_KEEPALIVE` 选项 |
| `available` | `() → int` | 可读取的字节数(非阻塞) |
| `peer_closed` | `() → boolean` | 对端是否已关闭连接(非阻塞、非破坏性,使用 1 字节 `MSG_PEEK` 探测)。空闲但存活返回 `false`;收到 FIN 或连接已失效返回 `true`。有异步读挂起时返回 `false`(该读操作自身会暴露 EOF)。TLS 套接字上探测的是底层传输而非解密流 |
| `receive` | `(max: int) → string` | 读取最多 `max` 字节。阻塞直到至少 1 字节可读 |
| `read` | `(size: int) → string` | 读取恰好 `size` 字节。阻塞直到全部读完 |
| `send` | `(data: string) → int` | 发送数据(单次部分写入,返回实际写入字节数)。需完整发送时使用 `write` |
Expand Down
10 changes: 5 additions & 5 deletions NETUTILS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# CovScript NetUtils 协议文档

版本:2.0
版本:2.1

作者:Covariant Script OSC

Expand All @@ -27,7 +27,7 @@
## 2. 基本常量与工具函数

* `server_name = "CovScript-NetUtils"`
* `server_version = "2.0"`
* `server_version = "2.1"`

与协议/实现相关的重要工具函数:

Expand Down Expand Up @@ -181,7 +181,7 @@ Master 分配 `rank`:先尝试使用 `deprecated_rank`(回收的编号),
控制逻辑:

* 若某连接空闲时间超过 `keep_alive_timeout`,Master 会在关闭前尝试回写 `408 Request Timeout`(或直接关闭)。
* 若单条连接处理的请求数超过 `max_keep_alive`,Master 会主动向客户端发送 `408` 并关闭连接
* 若单条连接处理的请求数达到 `max_keep_alive`,服务器会正常处理最后一个请求,并在该响应中携带 `Connection: close`,随后关闭连接(自 2.1 起不再发送 `408`
* Slave 的请求处理若超时(`receive_content_s` 超时),Master 将用错误码构造响应并关闭该 Slave 连接。

## 7. 静态文件服务与安全(wwwroot、path_normalize)
Expand Down Expand Up @@ -443,8 +443,8 @@ sequenceDiagram
M->>C: 发回 HTTP 回复
end

opt 超出 Keep-alive 限制
M->>C: 发回 HTTP 回复(408)
opt 达到 Keep-alive 请求数上限
M->>C: 最后一个 HTTP 回复携带 Connection: close<br/>随后关闭连接
end
```

Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ A high-performance network extension for the [Covariant Script](http://covscript

| Package | Type | Version | Description |
|---|---|---|---|
| `network` | C++ Extension | `1.38.0_v6.4` | TCP/UDP sockets, TLS/SSL, async I/O, event loop |
| `netutils` | CovScript | `2.0` | HTTP server/client framework with single-process, distributed master/slave, and OpenAI API modes |
| `network` | C++ Extension | `1.38.0_v6.5` | TCP/UDP sockets, TLS/SSL, async I/O, event loop |
| `netutils` | CovScript | `2.1` | HTTP server/client framework with single-process, distributed master/slave, and OpenAI API modes |
| `argparse` | CovScript | `1.1` | Lightweight command-line argument parser |

> **Note:** `netutils` and `argparse` are provided as both source (`.ecs`), compiled package (`.csp`), and bytecode module (`.csym`) — place them in your project's `imports/` directory.
Expand Down
2 changes: 1 addition & 1 deletion csbuild/netutils.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"Name": "netutils",
"Info": "Network Utilities",
"Author": "CovScript Organization",
"Version": "2.0",
"Version": "2.1",
"Source": "netutils.ecs",
"Target": "netutils.csp",
"Dependencies": [
Expand Down
2 changes: 1 addition & 1 deletion csbuild/network.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"Name": "network",
"Info": "Socket Extension",
"Author": "CovScript Organization",
"Version": "1.38.0_v6.4",
"Version": "1.38.0_v6.5",
"Target": "build/imports/network.cse",
"Dependencies": []
}
32 changes: 32 additions & 0 deletions include/network.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -717,6 +717,38 @@ namespace cs_impl {
return sock.available();
}

// For TLS sockets the probe reflects the raw transport, not
// the decrypted stream — MSG_PEEK sees encrypted bytes, so
// peer_closed() detects transport-level FIN but not TLS
// close_notify. Callers that need TLS-level closure should
// read the stream until eof.
bool peer_closed() noexcept
{
if (!try_begin_io_job(io_direction::read))
return false;
bool closed = false;
if (!sock.is_open()) {
closed = true;
}
else {
asio::error_code ec;
sock.non_blocking(true, ec);
if (!ec) {
char probe = 0;
std::size_t peeked = sock.receive(
asio::buffer(&probe, 1),
asio::socket_base::message_peek, ec);
asio::error_code restore_ec;
sock.non_blocking(false, restore_ec);
closed = peeked == 0 &&
ec != asio::error::would_block &&
ec != asio::error::try_again;
}
}
finish_io_job(io_direction::read);
return closed;
}

std::string receive(std::size_t maximum)
{
scoped_io_job job(*this, io_direction::read);
Expand Down
Loading