test: validate native TLS on Linux

This commit is contained in:
2026-09-22 01:14:21 +08:00
parent e6c82a01fe
commit 9c9c8ab59a
9 changed files with 1385 additions and 32 deletions

View File

@@ -9,6 +9,7 @@
- native 响应实现 `http.Flusher`:HTTP/1.1 流式响应自动使用 chunked framing,普通响应继续自动生成 `Content-Length`。
- native 在长 Handler 运行期间监测客户端断开并取消请求 context,流式写失败同步取消;短请求延迟启动监测,管线数据保留给下一次请求。补充断开、监测退出和长连接复用验收。
- native 增加 `ServeTLS` / `RunTLS` 和 `TLSConfig` 启动入口,App.RunWith 可以直接启用 TLS;补充真实 TLS 客户端握手、请求和优雅退出验收。
- native 请求补齐 `Request.TLS` 连接状态;Linux/arm64 容器通过全量 race、vet、build,以及 TLS 并发、Keep-Alive、慢握手和连接上限回收验收。
- 补充慢请求体超时、大请求体和同连接管线请求验收,确认请求体未完整读取时不会进入业务 Handler,连续请求不会串线。
- 补充 `MaxConnections` 满载拒绝与槽位释放验收,确认请求完成后连接容量可再次使用。
- 增加 8 并发、800 次建连/断连的状态池 churn 验收,覆盖连接对象回收与容量复用。

View File

@@ -54,6 +54,12 @@ go run ./internal/perfcheck --duration 2s --count 3 --output /tmp/goOmniPerf.jso
## benchmark 口径修正
## TLS 验收记录
macOS arm64 本机的 5 秒 TLS 矩阵见 [tlsBaseline.json](performance/tlsBaseline.json):1 / 32 / 128 并发和 128 / 1,024 条空闲连接均为 0 错误;native 在 128 并发约 114,232 req/s,1,024 条空闲 TLS 连接的服务端堆增量约 8.94 MiB,连接关闭后 goroutine 回到基线。
Linux arm64 容器使用 Go 1.25.14、GOMAXPROCS=4 运行 3 秒矩阵,见 [tlsBaselineLinux.json](performance/tlsBaselineLinux.json):1 / 32 / 128 并发和 128 / 512 条空闲连接均为 0 错误;native 在 128 并发约 99,876 req/s,512 条空闲 TLS 连接的服务端堆增量约 4.47 MiB,连接关闭后 goroutine 回到基线。容器验证同时通过全量 race、vet、build。
`BenchmarkTransportTCPApplicationRequest/{nethttp,native}/concurrency{1,32,128}` 使用同一测试入口、客户端、应用与响应检查。原先独立的 `BenchmarkNativeApplicationRequest` 已合并,避免两个入口配置漂移。`verifyPerformance.py` 会一并运行。
并行 benchmark 的 **`ns/op` 是总经过时间除以请求数,反映吞吐成本,不是单请求延迟**。`B/op` / `allocs/op` 统计同一进程中的客户端与服务端总分配。此前报告的 native 15.4–16.7 µs/op、约 7.8 KB/op、100 allocs/op 应按这个含义理解,不能作为服务端延迟或独立内存指标。

View File

@@ -44,7 +44,7 @@
- native 已支持受 `MaxBodyBytes` 约束的 `Content-Length` / chunked 请求体,仍使用标准 `http.Request` / `http.ResponseWriter` 作为桥接;解析器、连接抽象和生产压测将在后续阶段继续下沉。
- native 响应已支持标准 `http.Flusher`;HTTP/1.1 流式响应使用 chunked framing,普通响应继续走 `Content-Length`,HTTP/1.0 回退到关闭连接分帧。
- native 长请求已补客户端断开及流式写失败取消,监测退出后再复用 Reader;已验收普通/流式断开、慢 Handler 后连续 Keep-Alive 和管线数据不被误消费。短请求延迟启动监测,已有管线数据时取消依赖后续写失败或 Handler 返回。
- 已补充慢请求体超时、大请求体、HTTP/1.0 Keep-Alive、HEAD/204、管线请求和基础 TLS 客户端验收;Linux、慢客户端规模、TLS 吞吐和持续运行压测仍待在目标环境验证。
- 已补充慢请求体超时、大请求体、HTTP/1.0 Keep-Alive、HEAD/204、管线请求和基础 TLS 客户端验收;Linux/arm64 容器已通过全量 race、vet、build,以及 3 秒 TLS 并发/空闲连接矩阵。原始报告见 [tlsBaselineLinux.json](performance/tlsBaselineLinux.json);慢客户端规模和持续生产压测仍待真实部署环境验证。
- 已验证 `MaxConnections` 满载时快速拒绝新连接、请求完成后释放容量;后续仍需在目标部署环境观察文件描述符、内核 socket 缓冲和反向代理联动。
- 已用 8 个并发 worker 完成 800 次建连/断连 race 验收;真实部署仍需补文件描述符和跨平台 socket 行为观测。
- 已具备全量 race、vet、build 验收;生产压测、长连接规模和真实部署验证仍待完成。

View File

@@ -0,0 +1,592 @@
{
"createdAt": "2026-09-20T21:27:15Z",
"goVersion": "go1.25.0",
"platform": "darwin/arm64",
"gomaxprocs": 12,
"duration": "5s",
"tls": true,
"notes": [
"Loopback HTTP/1.1 health response, fixed requestId, optional TLS, no access logs or database.",
"Each case starts a fresh server process; load client runs in the parent process on the same machine.",
"Closed-loop concurrency; throughput is successful responses / actual elapsed time, including drain.",
"Latency percentiles include failures and are histogram upper bounds with \u003c=6.25% rounding.",
"Memory is server-only runtime.MemStats (not RSS or kernel socket memory); sampling interval 200ms.",
"Idle memory measured after a forced GC; delta includes heap growth amortized across connections, excluding stacks.",
"After is after closing clients and one forced GC; afterReclaim is a second GC to observe pooled-cache release.",
"Native limits: 8192 connections / 4096 active requests. These cases do not benchmark overload rejection."
],
"results": [
{
"adapter": "nethttp",
"scenario": "traffic",
"concurrency": 1,
"run": 1,
"requests": 86935,
"errors": 0,
"elapsedSeconds": 5.000022,
"requestsPerSecond": 17386.92349753661,
"p50Millis": 0.0512,
"p95Millis": 0.1024,
"p99Millis": 0.180224,
"serverBytesPerRequest": 4514.537988152068,
"serverAllocsPerRequest": 49.1346753321447,
"sampledPeakHeapAlloc": 3349712,
"before": {
"heapAlloc": 1678936,
"heapInuse": 3661824,
"stackInuse": 655360,
"totalAlloc": 3167032,
"mallocs": 24353,
"numGc": 2,
"goroutines": 3,
"connections": 1,
"accepted": 1
},
"during": {
"heapAlloc": 3337216,
"heapInuse": 5341184,
"stackInuse": 753664,
"totalAlloc": 395638392,
"mallocs": 4295876,
"numGc": 238,
"goroutines": 3,
"connections": 1,
"accepted": 1
},
"after": {
"heapAlloc": 1774168,
"heapInuse": 3792896,
"stackInuse": 688128,
"totalAlloc": 395642616,
"mallocs": 4295888,
"numGc": 239,
"goroutines": 2,
"connections": 0,
"accepted": 1
},
"afterReclaim": {
"heapAlloc": 1741008,
"heapInuse": 3620864,
"stackInuse": 688128,
"totalAlloc": 395644800,
"mallocs": 4295894,
"numGc": 240,
"goroutines": 2,
"connections": 0,
"accepted": 1
}
},
{
"adapter": "native",
"scenario": "traffic",
"concurrency": 1,
"run": 1,
"requests": 91798,
"errors": 0,
"elapsedSeconds": 5.000019125,
"requestsPerSecond": 18359.52977479861,
"p50Millis": 0.049152,
"p95Millis": 0.090112,
"p99Millis": 0.16384,
"serverBytesPerRequest": 4635.560426153075,
"serverAllocsPerRequest": 60.11798731998518,
"sampledPeakHeapAlloc": 3267488,
"before": {
"heapAlloc": 1653880,
"heapInuse": 3678208,
"stackInuse": 622592,
"totalAlloc": 3145792,
"mallocs": 24330,
"numGc": 2,
"goroutines": 4,
"connections": 1,
"accepted": 1
},
"during": {
"heapAlloc": 1828360,
"heapInuse": 4022272,
"stackInuse": 688128,
"totalAlloc": 428680968,
"mallocs": 5543041,
"numGc": 264,
"goroutines": 4,
"connections": 1,
"accepted": 1
},
"after": {
"heapAlloc": 1687208,
"heapInuse": 3817472,
"stackInuse": 655360,
"totalAlloc": 428685256,
"mallocs": 5543054,
"numGc": 265,
"goroutines": 3,
"connections": 0,
"accepted": 1
},
"afterReclaim": {
"heapAlloc": 1668072,
"heapInuse": 3588096,
"stackInuse": 655360,
"totalAlloc": 428687440,
"mallocs": 5543060,
"numGc": 266,
"goroutines": 3,
"connections": 0,
"accepted": 1
}
},
{
"adapter": "nethttp",
"scenario": "traffic",
"concurrency": 32,
"run": 1,
"requests": 443035,
"errors": 0,
"elapsedSeconds": 5.000301084,
"requestsPerSecond": 88601.66469127762,
"p50Millis": 0.278528,
"p95Millis": 1.015808,
"p99Millis": 1.507328,
"serverBytesPerRequest": 4524.947022244292,
"serverAllocsPerRequest": 49.153904319071856,
"sampledPeakHeapAlloc": 4957328,
"before": {
"heapAlloc": 2174976,
"heapInuse": 5406720,
"stackInuse": 1277952,
"totalAlloc": 4859520,
"mallocs": 39778,
"numGc": 4,
"goroutines": 34,
"connections": 32,
"accepted": 32
},
"during": {
"heapAlloc": 3437936,
"heapInuse": 6717440,
"stackInuse": 2490368,
"totalAlloc": 2009569424,
"mallocs": 21816678,
"numGc": 1167,
"goroutines": 34,
"connections": 32,
"accepted": 32
},
"after": {
"heapAlloc": 2301792,
"heapInuse": 4833280,
"stackInuse": 2195456,
"totalAlloc": 2009575408,
"mallocs": 21816709,
"numGc": 1168,
"goroutines": 2,
"connections": 0,
"accepted": 32
},
"afterReclaim": {
"heapAlloc": 1983832,
"heapInuse": 3915776,
"stackInuse": 2195456,
"totalAlloc": 2009577336,
"mallocs": 21816714,
"numGc": 1169,
"goroutines": 2,
"connections": 0,
"accepted": 32
}
},
{
"adapter": "native",
"scenario": "traffic",
"concurrency": 32,
"run": 1,
"requests": 422203,
"errors": 0,
"elapsedSeconds": 5.0001875,
"requestsPerSecond": 84437.43359624014,
"p50Millis": 0.294912,
"p95Millis": 1.015808,
"p99Millis": 1.507328,
"serverBytesPerRequest": 4655.5327176737255,
"serverAllocsPerRequest": 60.24485614739829,
"sampledPeakHeapAlloc": 3953128,
"before": {
"heapAlloc": 1991704,
"heapInuse": 5013504,
"stackInuse": 1081344,
"totalAlloc": 4644928,
"mallocs": 39578,
"numGc": 3,
"goroutines": 35,
"connections": 32,
"accepted": 32
},
"during": {
"heapAlloc": 2969168,
"heapInuse": 6266880,
"stackInuse": 1212416,
"totalAlloc": 1970224808,
"mallocs": 25475137,
"numGc": 1687,
"goroutines": 35,
"connections": 32,
"accepted": 32
},
"after": {
"heapAlloc": 2053344,
"heapInuse": 5062656,
"stackInuse": 851968,
"totalAlloc": 1970231512,
"mallocs": 25475175,
"numGc": 1688,
"goroutines": 3,
"connections": 0,
"accepted": 32
},
"afterReclaim": {
"heapAlloc": 1754136,
"heapInuse": 3792896,
"stackInuse": 851968,
"totalAlloc": 1970233696,
"mallocs": 25475181,
"numGc": 1689,
"goroutines": 3,
"connections": 0,
"accepted": 32
}
},
{
"adapter": "nethttp",
"scenario": "traffic",
"concurrency": 128,
"run": 1,
"requests": 564759,
"errors": 0,
"elapsedSeconds": 5.000520667,
"requestsPerSecond": 112940.03916972512,
"p50Millis": 0.8192,
"p95Millis": 3.2768,
"p99Millis": 4.718592,
"serverBytesPerRequest": 4499.995148373023,
"serverAllocsPerRequest": 49.13783401415471,
"sampledPeakHeapAlloc": 7449240,
"before": {
"heapAlloc": 3409760,
"heapInuse": 6938624,
"stackInuse": 2457600,
"totalAlloc": 9389504,
"mallocs": 81651,
"numGc": 8,
"goroutines": 118,
"connections": 116,
"accepted": 116
},
"during": {
"heapAlloc": 5261072,
"heapInuse": 9068544,
"stackInuse": 3276800,
"totalAlloc": 2550802264,
"mallocs": 27832685,
"numGc": 775,
"goroutines": 130,
"connections": 128,
"accepted": 128
},
"after": {
"heapAlloc": 3142160,
"heapInuse": 6086656,
"stackInuse": 2162688,
"totalAlloc": 2550814744,
"mallocs": 27832752,
"numGc": 776,
"goroutines": 2,
"connections": 0,
"accepted": 128
},
"afterReclaim": {
"heapAlloc": 2014424,
"heapInuse": 4038656,
"stackInuse": 2162688,
"totalAlloc": 2550816928,
"mallocs": 27832758,
"numGc": 777,
"goroutines": 2,
"connections": 0,
"accepted": 128
}
},
{
"adapter": "native",
"scenario": "traffic",
"concurrency": 128,
"run": 1,
"requests": 571218,
"errors": 0,
"elapsedSeconds": 5.000520459,
"requestsPerSecond": 114231.70941574985,
"p50Millis": 0.720896,
"p95Millis": 3.538944,
"p99Millis": 5.24288,
"serverBytesPerRequest": 4630.001169430935,
"serverAllocsPerRequest": 60.118471406713375,
"sampledPeakHeapAlloc": 6594168,
"before": {
"heapAlloc": 2900712,
"heapInuse": 6209536,
"stackInuse": 1966080,
"totalAlloc": 9196776,
"mallocs": 86400,
"numGc": 6,
"goroutines": 131,
"connections": 128,
"accepted": 128
},
"during": {
"heapAlloc": 5161328,
"heapInuse": 8462336,
"stackInuse": 1998848,
"totalAlloc": 2653936784,
"mallocs": 34427153,
"numGc": 1026,
"goroutines": 131,
"connections": 128,
"accepted": 128
},
"after": {
"heapAlloc": 2950048,
"heapInuse": 6201344,
"stackInuse": 851968,
"totalAlloc": 2653948208,
"mallocs": 34427217,
"numGc": 1027,
"goroutines": 3,
"connections": 0,
"accepted": 128
},
"afterReclaim": {
"heapAlloc": 1824888,
"heapInuse": 3809280,
"stackInuse": 851968,
"totalAlloc": 2653950392,
"mallocs": 34427223,
"numGc": 1028,
"goroutines": 3,
"connections": 0,
"accepted": 128
}
},
{
"adapter": "nethttp",
"scenario": "idle",
"concurrency": 128,
"run": 1,
"errors": 0,
"idleHeapDelta": 1857928,
"idleHeapPerConnection": 14515.0625,
"before": {
"heapAlloc": 1632120,
"heapInuse": 3538944,
"stackInuse": 524288,
"totalAlloc": 3074624,
"mallocs": 23744,
"numGc": 1,
"goroutines": 2,
"connections": 0,
"accepted": 0
},
"during": {
"heapAlloc": 3490048,
"heapInuse": 5808128,
"stackInuse": 1835008,
"totalAlloc": 9802696,
"mallocs": 86273,
"numGc": 5,
"goroutines": 130,
"connections": 128,
"accepted": 128
},
"after": {
"heapAlloc": 2814128,
"heapInuse": 5062656,
"stackInuse": 720896,
"totalAlloc": 9818288,
"mallocs": 86380,
"numGc": 6,
"goroutines": 2,
"connections": 0,
"accepted": 128
},
"afterReclaim": {
"heapAlloc": 1732536,
"heapInuse": 3661824,
"stackInuse": 720896,
"totalAlloc": 9820216,
"mallocs": 86385,
"numGc": 7,
"goroutines": 2,
"connections": 0,
"accepted": 128
}
},
{
"adapter": "native",
"scenario": "idle",
"concurrency": 128,
"run": 1,
"errors": 0,
"idleHeapDelta": 1212976,
"idleHeapPerConnection": 9476.375,
"before": {
"heapAlloc": 1626208,
"heapInuse": 3448832,
"stackInuse": 491520,
"totalAlloc": 3068760,
"mallocs": 23727,
"numGc": 1,
"goroutines": 3,
"connections": 0,
"accepted": 0
},
"during": {
"heapAlloc": 2839184,
"heapInuse": 5210112,
"stackInuse": 1802240,
"totalAlloc": 9147832,
"mallocs": 85868,
"numGc": 5,
"goroutines": 131,
"connections": 128,
"accepted": 128
},
"after": {
"heapAlloc": 2838168,
"heapInuse": 5169152,
"stackInuse": 688128,
"totalAlloc": 9163568,
"mallocs": 85970,
"numGc": 6,
"goroutines": 3,
"connections": 0,
"accepted": 128
},
"afterReclaim": {
"heapAlloc": 1729792,
"heapInuse": 3563520,
"stackInuse": 688128,
"totalAlloc": 9166056,
"mallocs": 85980,
"numGc": 7,
"goroutines": 3,
"connections": 0,
"accepted": 128
}
},
{
"adapter": "nethttp",
"scenario": "idle",
"concurrency": 1024,
"run": 1,
"errors": 0,
"idleHeapDelta": 14527832,
"idleHeapPerConnection": 14187.3359375,
"before": {
"heapAlloc": 1626760,
"heapInuse": 3497984,
"stackInuse": 491520,
"totalAlloc": 3069240,
"mallocs": 23740,
"numGc": 1,
"goroutines": 2,
"connections": 0,
"accepted": 0
},
"during": {
"heapAlloc": 16154592,
"heapInuse": 20807680,
"stackInuse": 9207808,
"totalAlloc": 56229808,
"mallocs": 522226,
"numGc": 12,
"goroutines": 1026,
"connections": 1024,
"accepted": 1024
},
"after": {
"heapAlloc": 10815896,
"heapInuse": 14376960,
"stackInuse": 720896,
"totalAlloc": 56293624,
"mallocs": 522449,
"numGc": 13,
"goroutines": 2,
"connections": 0,
"accepted": 1024
},
"afterReclaim": {
"heapAlloc": 2202576,
"heapInuse": 4227072,
"stackInuse": 720896,
"totalAlloc": 56296208,
"mallocs": 522460,
"numGc": 14,
"goroutines": 2,
"connections": 0,
"accepted": 1024
}
},
{
"adapter": "native",
"scenario": "idle",
"concurrency": 1024,
"run": 1,
"errors": 0,
"idleHeapDelta": 9372528,
"idleHeapPerConnection": 9152.859375,
"before": {
"heapAlloc": 1625904,
"heapInuse": 3457024,
"stackInuse": 491520,
"totalAlloc": 3068472,
"mallocs": 23724,
"numGc": 1,
"goroutines": 3,
"connections": 0,
"accepted": 0
},
"during": {
"heapAlloc": 10998432,
"heapInuse": 14934016,
"stackInuse": 9240576,
"totalAlloc": 51165352,
"mallocs": 519121,
"numGc": 13,
"goroutines": 1027,
"connections": 1024,
"accepted": 1024
},
"after": {
"heapAlloc": 11019640,
"heapInuse": 15032320,
"stackInuse": 6553600,
"totalAlloc": 51226512,
"mallocs": 519340,
"numGc": 14,
"goroutines": 3,
"connections": 0,
"accepted": 1024
},
"afterReclaim": {
"heapAlloc": 2196104,
"heapInuse": 4153344,
"stackInuse": 6553600,
"totalAlloc": 51229000,
"mallocs": 519350,
"numGc": 15,
"goroutines": 3,
"connections": 0,
"accepted": 1024
}
}
]
}

View File

@@ -0,0 +1,592 @@
{
"createdAt": "2026-09-21T17:11:34Z",
"goVersion": "go1.25.14",
"platform": "linux/arm64",
"gomaxprocs": 4,
"duration": "3s",
"tls": true,
"notes": [
"Loopback HTTP/1.1 health response, fixed requestId, optional TLS, no access logs or database.",
"Each case starts a fresh server process; load client runs in the parent process on the same machine.",
"Closed-loop concurrency; throughput is successful responses / actual elapsed time, including drain.",
"Latency percentiles include failures and are histogram upper bounds with \u003c=6.25% rounding.",
"Memory is server-only runtime.MemStats (not RSS or kernel socket memory); sampling interval 200ms.",
"Idle memory measured after a forced GC; delta includes heap growth amortized across connections, excluding stacks.",
"After is after closing clients and one forced GC; afterReclaim is a second GC to observe pooled-cache release.",
"Native limits: 8192 connections / 4096 active requests. These cases do not benchmark overload rejection."
],
"results": [
{
"adapter": "nethttp",
"scenario": "traffic",
"concurrency": 1,
"run": 1,
"requests": 36326,
"errors": 0,
"elapsedSeconds": 3.000028418,
"requestsPerSecond": 12108.551966390074,
"p50Millis": 0.08192,
"p95Millis": 0.1024,
"p99Millis": 0.139264,
"serverBytesPerRequest": 4475.917854979904,
"serverAllocsPerRequest": 49.04346748885096,
"sampledPeakHeapAlloc": 3669264,
"before": {
"heapAlloc": 1555696,
"heapInuse": 3522560,
"stackInuse": 491520,
"totalAlloc": 3107240,
"mallocs": 24268,
"numGc": 2,
"goroutines": 3,
"connections": 1,
"accepted": 1
},
"during": {
"heapAlloc": 1789280,
"heapInuse": 3874816,
"stackInuse": 819200,
"totalAlloc": 165699432,
"mallocs": 1805821,
"numGc": 82,
"goroutines": 3,
"connections": 1,
"accepted": 1
},
"after": {
"heapAlloc": 1645816,
"heapInuse": 3629056,
"stackInuse": 753664,
"totalAlloc": 165700968,
"mallocs": 1805830,
"numGc": 83,
"goroutines": 2,
"connections": 0,
"accepted": 1
},
"afterReclaim": {
"heapAlloc": 1625904,
"heapInuse": 3522560,
"stackInuse": 720896,
"totalAlloc": 165701872,
"mallocs": 1805836,
"numGc": 84,
"goroutines": 2,
"connections": 0,
"accepted": 1
}
},
{
"adapter": "native",
"scenario": "traffic",
"concurrency": 1,
"run": 1,
"requests": 36410,
"errors": 0,
"elapsedSeconds": 3.000096335,
"requestsPerSecond": 12136.276950586656,
"p50Millis": 0.08192,
"p95Millis": 0.1024,
"p99Millis": 0.131072,
"serverBytesPerRequest": 4807.387201318319,
"serverAllocsPerRequest": 61.041444658060975,
"sampledPeakHeapAlloc": 3473824,
"before": {
"heapAlloc": 1546544,
"heapInuse": 3563520,
"stackInuse": 524288,
"totalAlloc": 3099040,
"mallocs": 24253,
"numGc": 2,
"goroutines": 4,
"connections": 1,
"accepted": 1
},
"during": {
"heapAlloc": 1987016,
"heapInuse": 4014080,
"stackInuse": 524288,
"totalAlloc": 178136008,
"mallocs": 2246772,
"numGc": 96,
"goroutines": 4,
"connections": 1,
"accepted": 1
},
"after": {
"heapAlloc": 1557080,
"heapInuse": 3637248,
"stackInuse": 425984,
"totalAlloc": 178137480,
"mallocs": 2246784,
"numGc": 97,
"goroutines": 3,
"connections": 0,
"accepted": 1
},
"afterReclaim": {
"heapAlloc": 1543112,
"heapInuse": 3473408,
"stackInuse": 425984,
"totalAlloc": 178138128,
"mallocs": 2246789,
"numGc": 98,
"goroutines": 3,
"connections": 0,
"accepted": 1
}
},
{
"adapter": "nethttp",
"scenario": "traffic",
"concurrency": 32,
"run": 1,
"requests": 297651,
"errors": 0,
"elapsedSeconds": 3.000748959,
"requestsPerSecond": 99192.23636061586,
"p50Millis": 0.172032,
"p95Millis": 1.179648,
"p99Millis": 2.097152,
"serverBytesPerRequest": 4478.059122932562,
"serverAllocsPerRequest": 49.0660605877353,
"sampledPeakHeapAlloc": 4147936,
"before": {
"heapAlloc": 2046152,
"heapInuse": 4554752,
"stackInuse": 884736,
"totalAlloc": 4736656,
"mallocs": 39461,
"numGc": 3,
"goroutines": 34,
"connections": 32,
"accepted": 32
},
"during": {
"heapAlloc": 3132976,
"heapInuse": 5529600,
"stackInuse": 1212416,
"totalAlloc": 1337635432,
"mallocs": 14644023,
"numGc": 642,
"goroutines": 34,
"connections": 32,
"accepted": 32
},
"after": {
"heapAlloc": 1946392,
"heapInuse": 4136960,
"stackInuse": 884736,
"totalAlloc": 1337638376,
"mallocs": 14644047,
"numGc": 643,
"goroutines": 2,
"connections": 0,
"accepted": 32
},
"afterReclaim": {
"heapAlloc": 1657656,
"heapInuse": 3596288,
"stackInuse": 884736,
"totalAlloc": 1337639280,
"mallocs": 14644053,
"numGc": 644,
"goroutines": 2,
"connections": 0,
"accepted": 32
}
},
{
"adapter": "native",
"scenario": "traffic",
"concurrency": 32,
"run": 1,
"requests": 271586,
"errors": 0,
"elapsedSeconds": 3.008536543,
"requestsPerSecond": 90271.79697448004,
"p50Millis": 0.172032,
"p95Millis": 1.114112,
"p99Millis": 2.097152,
"serverBytesPerRequest": 4810.125264188876,
"serverAllocsPerRequest": 61.068320900193676,
"sampledPeakHeapAlloc": 3706424,
"before": {
"heapAlloc": 1833336,
"heapInuse": 4136960,
"stackInuse": 1048576,
"totalAlloc": 4566744,
"mallocs": 39398,
"numGc": 3,
"goroutines": 35,
"connections": 32,
"accepted": 32
},
"during": {
"heapAlloc": 2032480,
"heapInuse": 4661248,
"stackInuse": 851968,
"totalAlloc": 1310929424,
"mallocs": 16624699,
"numGc": 742,
"goroutines": 35,
"connections": 32,
"accepted": 32
},
"after": {
"heapAlloc": 1875360,
"heapInuse": 4186112,
"stackInuse": 524288,
"totalAlloc": 1310933392,
"mallocs": 16624734,
"numGc": 743,
"goroutines": 3,
"connections": 0,
"accepted": 32
},
"afterReclaim": {
"heapAlloc": 1593792,
"heapInuse": 3465216,
"stackInuse": 524288,
"totalAlloc": 1310934296,
"mallocs": 16624740,
"numGc": 744,
"goroutines": 3,
"connections": 0,
"accepted": 32
}
},
{
"adapter": "nethttp",
"scenario": "traffic",
"concurrency": 128,
"run": 1,
"requests": 322494,
"errors": 0,
"elapsedSeconds": 3.001132293,
"requestsPerSecond": 107457.44223012164,
"p50Millis": 0.589824,
"p95Millis": 2.752512,
"p99Millis": 25.165824,
"serverBytesPerRequest": 4475.7173528809835,
"serverAllocsPerRequest": 49.07075790557344,
"sampledPeakHeapAlloc": 7137248,
"before": {
"heapAlloc": 3503480,
"heapInuse": 6340608,
"stackInuse": 2228224,
"totalAlloc": 9426048,
"mallocs": 83598,
"numGc": 5,
"goroutines": 124,
"connections": 122,
"accepted": 122
},
"during": {
"heapAlloc": 6849936,
"heapInuse": 9125888,
"stackInuse": 2162688,
"totalAlloc": 1452818040,
"mallocs": 15908623,
"numGc": 415,
"goroutines": 130,
"connections": 128,
"accepted": 128
},
"after": {
"heapAlloc": 2816896,
"heapInuse": 5300224,
"stackInuse": 1048576,
"totalAlloc": 1452825560,
"mallocs": 15908667,
"numGc": 416,
"goroutines": 2,
"connections": 0,
"accepted": 128
},
"afterReclaim": {
"heapAlloc": 1717496,
"heapInuse": 3710976,
"stackInuse": 1015808,
"totalAlloc": 1452826464,
"mallocs": 15908673,
"numGc": 417,
"goroutines": 2,
"connections": 0,
"accepted": 128
}
},
{
"adapter": "native",
"scenario": "traffic",
"concurrency": 128,
"run": 1,
"requests": 299953,
"errors": 0,
"elapsedSeconds": 3.003243168,
"requestsPerSecond": 99876.36139359062,
"p50Millis": 0.622592,
"p95Millis": 2.752512,
"p99Millis": 32.505856,
"serverBytesPerRequest": 4817.3341823552355,
"serverAllocsPerRequest": 61.15272726060416,
"sampledPeakHeapAlloc": 5668056,
"before": {
"heapAlloc": 2118392,
"heapInuse": 4661248,
"stackInuse": 1605632,
"totalAlloc": 6143952,
"mallocs": 56351,
"numGc": 4,
"goroutines": 61,
"connections": 58,
"accepted": 58
},
"during": {
"heapAlloc": 3375320,
"heapInuse": 5996544,
"stackInuse": 1802240,
"totalAlloc": 1451117792,
"mallocs": 18399295,
"numGc": 528,
"goroutines": 131,
"connections": 128,
"accepted": 128
},
"after": {
"heapAlloc": 2766336,
"heapInuse": 5382144,
"stackInuse": 1769472,
"totalAlloc": 1451125632,
"mallocs": 18399341,
"numGc": 529,
"goroutines": 3,
"connections": 0,
"accepted": 128
},
"afterReclaim": {
"heapAlloc": 1658104,
"heapInuse": 3588096,
"stackInuse": 1769472,
"totalAlloc": 1451126280,
"mallocs": 18399346,
"numGc": 530,
"goroutines": 3,
"connections": 0,
"accepted": 128
}
},
{
"adapter": "nethttp",
"scenario": "idle",
"concurrency": 128,
"run": 1,
"errors": 0,
"idleHeapDelta": 1838600,
"idleHeapPerConnection": 14364.0625,
"before": {
"heapAlloc": 1521656,
"heapInuse": 3260416,
"stackInuse": 458752,
"totalAlloc": 3029904,
"mallocs": 23674,
"numGc": 1,
"goroutines": 2,
"connections": 0,
"accepted": 0
},
"during": {
"heapAlloc": 3360256,
"heapInuse": 5718016,
"stackInuse": 1540096,
"totalAlloc": 9686296,
"mallocs": 86122,
"numGc": 5,
"goroutines": 130,
"connections": 128,
"accepted": 128
},
"after": {
"heapAlloc": 2682728,
"heapInuse": 4792320,
"stackInuse": 425984,
"totalAlloc": 9695376,
"mallocs": 86176,
"numGc": 6,
"goroutines": 2,
"connections": 0,
"accepted": 128
},
"afterReclaim": {
"heapAlloc": 1604552,
"heapInuse": 3440640,
"stackInuse": 425984,
"totalAlloc": 9696584,
"mallocs": 86186,
"numGc": 7,
"goroutines": 2,
"connections": 0,
"accepted": 128
}
},
{
"adapter": "native",
"scenario": "idle",
"concurrency": 128,
"run": 1,
"errors": 0,
"idleHeapDelta": 1191856,
"idleHeapPerConnection": 9311.375,
"before": {
"heapAlloc": 1521040,
"heapInuse": 3325952,
"stackInuse": 458752,
"totalAlloc": 3029464,
"mallocs": 23663,
"numGc": 1,
"goroutines": 3,
"connections": 0,
"accepted": 0
},
"during": {
"heapAlloc": 2712896,
"heapInuse": 4939776,
"stackInuse": 2555904,
"totalAlloc": 9083760,
"mallocs": 85842,
"numGc": 5,
"goroutines": 131,
"connections": 128,
"accepted": 128
},
"after": {
"heapAlloc": 2711312,
"heapInuse": 4907008,
"stackInuse": 425984,
"totalAlloc": 9091576,
"mallocs": 85898,
"numGc": 6,
"goroutines": 3,
"connections": 0,
"accepted": 128
},
"afterReclaim": {
"heapAlloc": 1608416,
"heapInuse": 3416064,
"stackInuse": 425984,
"totalAlloc": 9092784,
"mallocs": 85908,
"numGc": 7,
"goroutines": 3,
"connections": 0,
"accepted": 128
}
},
{
"adapter": "nethttp",
"scenario": "idle",
"concurrency": 512,
"run": 1,
"errors": 0,
"idleHeapDelta": 7269176,
"idleHeapPerConnection": 14197.609375,
"before": {
"heapAlloc": 1521080,
"heapInuse": 3383296,
"stackInuse": 458752,
"totalAlloc": 3029480,
"mallocs": 23669,
"numGc": 1,
"goroutines": 2,
"connections": 0,
"accepted": 0
},
"during": {
"heapAlloc": 8790256,
"heapInuse": 11960320,
"stackInuse": 4718592,
"totalAlloc": 29547624,
"mallocs": 272853,
"numGc": 9,
"goroutines": 514,
"connections": 512,
"accepted": 512
},
"after": {
"heapAlloc": 6121872,
"heapInuse": 8749056,
"stackInuse": 458752,
"totalAlloc": 29580224,
"mallocs": 272941,
"numGc": 10,
"goroutines": 2,
"connections": 0,
"accepted": 512
},
"afterReclaim": {
"heapAlloc": 1812232,
"heapInuse": 3817472,
"stackInuse": 458752,
"totalAlloc": 29581432,
"mallocs": 272951,
"numGc": 11,
"goroutines": 2,
"connections": 0,
"accepted": 512
}
},
{
"adapter": "native",
"scenario": "idle",
"concurrency": 512,
"run": 1,
"errors": 0,
"idleHeapDelta": 4687096,
"idleHeapPerConnection": 9154.484375,
"before": {
"heapAlloc": 1520480,
"heapInuse": 3293184,
"stackInuse": 458752,
"totalAlloc": 3028752,
"mallocs": 23658,
"numGc": 1,
"goroutines": 3,
"connections": 0,
"accepted": 0
},
"during": {
"heapAlloc": 6207576,
"heapInuse": 9035776,
"stackInuse": 8880128,
"totalAlloc": 27129544,
"mallocs": 271820,
"numGc": 9,
"goroutines": 515,
"connections": 512,
"accepted": 512
},
"after": {
"heapAlloc": 6228280,
"heapInuse": 9027584,
"stackInuse": 491520,
"totalAlloc": 27168240,
"mallocs": 271921,
"numGc": 10,
"goroutines": 3,
"connections": 0,
"accepted": 512
},
"afterReclaim": {
"heapAlloc": 1812240,
"heapInuse": 3645440,
"stackInuse": 491520,
"totalAlloc": 27169448,
"mallocs": 271931,
"numGc": 11,
"goroutines": 3,
"connections": 0,
"accepted": 512
}
}
]
}

View File

@@ -48,7 +48,7 @@ if err := application.RunWith(ctx, server, ":8443"); err != nil {
}
```
需要自己持有 listener 或配置证书文件时,分别使用 `ServeTLS` 或 `RunTLS`;TLS 握手仍受 native 的读超时、连接上限和优雅关闭控制。
需要自己持有 listener 或配置证书文件时,分别使用 `ServeTLS` 或 `RunTLS`;TLS 握手仍受 native 的读超时、连接上限和优雅关闭控制,Handler 可通过 `Request.TLS` 读取标准 `tls.ConnectionState`。
## 可移植路由规则(v0.3.0)

View File

@@ -614,6 +614,10 @@ func readRequest(reader *bufio.Reader, conn net.Conn, maxHeaderBytes int, maxBod
RemoteAddr: conn.RemoteAddr().String(),
Trailer: trailer,
}
if tlsConn, ok := conn.(*tls.Conn); ok {
state := tlsConn.ConnectionState()
request.TLS = &state
}
if chunked {
request.TransferEncoding = []string{"chunked"}
}

View File

@@ -903,36 +903,6 @@ func TestAppRunWithNativeTLS(t *testing.T) {
}
}
func TestNativeTLSSlowHandshakeTimesOut(t *testing.T) {
application := omni.NewWithRouter(netrouter.New())
application.GET("/health", func(ctx *omni.Context) error { return ctx.OK("ok") })
server := native.New(application)
server.ReadHeaderTimeout = 30 * time.Millisecond
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
certificate := testCertificate(t)
done := make(chan error, 1)
go func() {
done <- server.ServeTLS(context.Background(), listener, &tls.Config{Certificates: []tls.Certificate{certificate}})
}()
waitForServer(t, listener.Addr().String())
defer stopServer(t, server, listener, done)
conn, err := net.Dial("tcp", listener.Addr().String())
if err != nil {
t.Fatal(err)
}
defer conn.Close()
if err := conn.SetReadDeadline(time.Now().Add(time.Second)); err != nil {
t.Fatal(err)
}
if _, err := conn.Read(make([]byte, 1)); err == nil {
t.Fatal("slow TLS handshake was not closed")
}
}
func testCertificate(t *testing.T) tls.Certificate {
t.Helper()
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)

View File

@@ -0,0 +1,188 @@
package native_test
import (
"bufio"
"context"
"crypto/tls"
"crypto/x509"
"errors"
"fmt"
"io"
"net"
"net/http"
"sync/atomic"
"testing"
"time"
"git.yuntuo.hk/k2safe/go-omni/transport/native"
)
func startNativeTLS(t *testing.T, server *native.Server) (string, *tls.Config) {
t.Helper()
certificate := testCertificate(t)
leaf, err := x509.ParseCertificate(certificate.Certificate[0])
if err != nil {
t.Fatal(err)
}
roots := x509.NewCertPool()
roots.AddCert(leaf)
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
done := make(chan error, 1)
go func() {
done <- server.ServeTLS(context.Background(), listener, &tls.Config{
Certificates: []tls.Certificate{certificate}, MinVersion: tls.VersionTLS12,
NextProtos: []string{"http/1.1"},
})
}()
t.Cleanup(func() {
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
t.Errorf("TLS shutdown: %v", err)
}
select {
case err := <-done:
if err != nil {
t.Errorf("TLS serve: %v", err)
}
case <-ctx.Done():
t.Error("TLS serve did not exit")
}
})
return listener.Addr().String(), &tls.Config{
RootCAs: roots, ServerName: "localhost", MinVersion: tls.VersionTLS12,
NextProtos: []string{"http/1.1"},
}
}
func requirePeerClose(t *testing.T, conn net.Conn) {
t.Helper()
conn.SetReadDeadline(time.Now().Add(2 * time.Second))
_, err := conn.Read(make([]byte, 1))
if err == nil {
t.Fatal("expected server to close the connection")
}
var netErr net.Error
if errors.As(err, &netErr) && netErr.Timeout() {
t.Fatalf("client timed out; server did not close the connection: %v", err)
}
}
func TestNativeTLSSlowHandshakeTimesOut(t *testing.T) {
for _, prefix := range []string{"", "\x16\x03\x01\x00\x64\x01"} {
t.Run(fmt.Sprintf("partialBytes=%d", len(prefix)), func(t *testing.T) {
var calls atomic.Int32
server := native.New(http.HandlerFunc(func(http.ResponseWriter, *http.Request) { calls.Add(1) }))
server.ReadHeaderTimeout = 50 * time.Millisecond
address, _ := startNativeTLS(t, server)
conn, err := net.DialTimeout("tcp", address, time.Second)
if err != nil {
t.Fatal(err)
}
defer conn.Close()
if _, err := io.WriteString(conn, prefix); err != nil {
t.Fatal(err)
}
requirePeerClose(t, conn)
if calls.Load() != 0 {
t.Fatal("unfinished TLS handshake reached the handler")
}
})
}
}
func TestNativeTLSConnectionLimitAndKeepAlive(t *testing.T) {
var calls atomic.Int32
server := native.New(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
calls.Add(1)
_, _ = io.WriteString(w, "healthy")
}))
server.MaxConnections = 1
address, config := startNativeTLS(t, server)
dial := func() (*tls.Conn, error) {
return tls.DialWithDialer(&net.Dialer{Timeout: time.Second}, "tcp", address, config)
}
first, err := dial()
if err != nil {
t.Fatal(err)
}
defer first.Close()
first.SetDeadline(time.Now().Add(3 * time.Second))
reader := bufio.NewReader(first)
for index := 0; index < 3; index++ {
if _, err := io.WriteString(first, "GET /health HTTP/1.1\r\nHost: localhost\r\n\r\n"); err != nil {
t.Fatal(err)
}
response, err := http.ReadResponse(reader, &http.Request{Method: http.MethodGet})
if err != nil {
t.Fatal(err)
}
body, err := io.ReadAll(response.Body)
response.Body.Close()
if err != nil || string(body) != "healthy" || response.Close {
t.Fatalf("Keep-Alive response body=%q err=%v close=%v", body, err, response.Close)
}
}
// A completed TLS handshake proves the first slot is held before testing
// rejection. TCP accept can still succeed; the TLS handshake must fail.
rejected, err := dial()
if err == nil {
rejected.Close()
t.Fatal("TLS connection exceeded MaxConnections")
}
var netErr net.Error
if errors.As(err, &netErr) && netErr.Timeout() {
t.Fatalf("over-limit handshake hung: %v", err)
}
first.Close()
deadline := time.Now().Add(2 * time.Second)
var next *tls.Conn
for time.Now().Before(deadline) {
next, err = dial()
if err == nil {
break
}
time.Sleep(time.Millisecond)
}
if err != nil {
t.Fatalf("TLS connection capacity not released: %v", err)
}
defer next.Close()
next.SetDeadline(time.Now().Add(time.Second))
if _, err := io.WriteString(next, "GET /health HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n"); err != nil {
t.Fatal(err)
}
response, err := http.ReadResponse(bufio.NewReader(next), &http.Request{Method: http.MethodGet})
if err != nil {
t.Fatal(err)
}
body, err := io.ReadAll(response.Body)
response.Body.Close()
if err != nil || string(body) != "healthy" || calls.Load() != 4 {
t.Fatalf("recovered response=%q calls=%d err=%v", body, calls.Load(), err)
}
}
func TestNativeTLSRequestMetadata(t *testing.T) {
server := native.New(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.TLS == nil || !r.TLS.HandshakeComplete || r.TLS.NegotiatedProtocol != "http/1.1" {
http.Error(w, "missing TLS request metadata", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusNoContent)
}))
address, config := startNativeTLS(t, server)
client := &http.Client{Transport: &http.Transport{TLSClientConfig: config}, Timeout: 2 * time.Second}
defer client.CloseIdleConnections()
response, err := client.Get("https://" + address + "/")
if err != nil {
t.Fatal(err)
}
defer response.Body.Close()
if response.StatusCode != http.StatusNoContent {
t.Fatalf("TLS metadata unavailable to handler: status=%d", response.StatusCode)
}
}