Skip to content

Commit 98fcfcd

Browse files
committed
feat: add ConnTimeout option, improve GenId entropy and refine logging
options.go / transport.go: - Add field (default 10s) to Options for configuring TCP dial timeout independently from the overall request - Pass to socket dialer instead of hard-coded value - Add doc note warning that unix:// path is session-level only stat.go / trace.go / response.go / request.go: - Refactor Stat struct fields and Print/String output format - Add response body truncation in LogS (cap at 1024 chars) and drop redundant json.Marshal path; use a2s helper uniformly uid.go: - Switch GenId from nanosecond to microsecond precision multiplied by 1000 to cleanly separate time bits from random bits, reducing collision risk under high concurrency while staying within uint64 util.go: - Remove unused encoding/json import - Simplify LogS body logging via a2s helper with 1024-byte truncation requests_test.go / request_test.go / response_test.go / stat_test.go: - Update and extend tests to cover new fields, LogS truncation, GenId uniqueness, and revised Stat output format
1 parent f9815c0 commit 98fcfcd

12 files changed

Lines changed: 328 additions & 132 deletions

options.go

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,10 @@ type Options struct {
5454
Cookies []http.Cookie // HTTP Cookies
5555

5656
// ===== 客户端配置 / Client Configuration =====
57-
Timeout time.Duration // 请求超时时间 / Request timeout
58-
MaxConns int // 最大连接数(连接池大小)/ Maximum connections (connection pool size)
59-
Verify bool // 是否验证TLS证书 / Whether to verify TLS certificates
57+
Timeout time.Duration // 请求超时时间(整个请求的总超时)/ Request timeout (total timeout for the whole request)
58+
ConnTimeout time.Duration // TCP 连接建立超时(拨号超时)/ TCP connection establishment timeout (dial timeout)
59+
MaxConns int // 最大连接数(连接池大小)/ Maximum connections (connection pool size)
60+
Verify bool // 是否验证TLS证书 / Whether to verify TLS certificates
6061

6162
// ===== 传输层配置 / Transport Layer Configuration =====
6263
Transport http.RoundTripper // 自定义传输层 / Custom transport
@@ -114,12 +115,13 @@ type Option func(*Options)
114115
// - Options: 合并后的配置选项 / Merged configuration options
115116
func newOptions(opts []Option, extends ...Option) Options {
116117
opt := Options{
117-
URL: "http://127.0.0.1:80",
118-
RawQuery: make(url.Values),
119-
Header: make(http.Header),
120-
Timeout: 30 * time.Second,
121-
MaxConns: 100,
122-
Proxy: http.ProxyFromEnvironment,
118+
URL: "http://127.0.0.1:80",
119+
RawQuery: make(url.Values),
120+
Header: make(http.Header),
121+
Timeout: 30 * time.Second,
122+
ConnTimeout: 10 * time.Second,
123+
MaxConns: 100,
124+
Proxy: http.ProxyFromEnvironment,
123125

124126
OnStart: func(s *http.Server) { log.Printf("http(s) serve %s", s.Addr) },
125127
OnShutdown: func(s *http.Server) { log.Printf("http shutdown") },
@@ -228,6 +230,12 @@ func Method(method string) Option {
228230
// requests.Body("data"),
229231
// )
230232
//
233+
// ⚠️ Unix Socket 限制 / Unix Socket Limitation:
234+
// - Unix socket 路径只能在「会话级」(requests.New)设置,因为 Transport 在创建会话时即固化拨号逻辑。
235+
// - 在「请求级」传入 unix:// 不会改变已建好的 Transport,不会生效;如需切换 socket 请新建 Session。
236+
// - The Unix socket path can only be set at the "session level" (requests.New), because the Transport
237+
// fixes its dial logic when the session is created. Passing unix:// at the "request level" does NOT
238+
// take effect; create a new Session to switch sockets.
231239
// 参数 / Parameters:
232240
// - url: 目标 URL 地址 / Target URL address
233241
//
@@ -361,6 +369,10 @@ func Body(body any) Option {
361369
// - 发送大量数据时,减少网络传输量 / Reduce network transmission when sending large data
362370
// - 服务器支持 gzip 解压时 / When server supports gzip decompression
363371
//
372+
// 错误处理 / Error Handling:
373+
// - 压缩出错不再 panic,也不中断请求:仅记录日志,跳过压缩(不设置 body 与编码头)
374+
// - Compression errors no longer panic nor abort the request: just logged, compression is skipped (body and encoding headers left unset)
375+
//
364376
// 示例 / Example:
365377
//
366378
// largeData := strings.Repeat("data", 10000)
@@ -548,6 +560,33 @@ func Timeout(timeout time.Duration) Option {
548560
}
549561
}
550562

563+
// ConnTimeout 设置 TCP 连接建立(拨号)超时时间
564+
// ConnTimeout sets the TCP connection establishment (dial) timeout duration
565+
//
566+
// 参数 / Parameters:
567+
// - timeout: 拨号超时时间 / Dial timeout duration
568+
//
569+
// 说明 / Notes:
570+
// - 仅控制建立连接(TCP 握手 + DNS 解析)阶段的超时
571+
// - Only controls the timeout for the connection establishment phase (TCP handshake + DNS)
572+
// - 与 Timeout 不同:Timeout 控制整个请求的总耗时,ConnTimeout 仅控制拨号
573+
// - Differs from Timeout: Timeout controls the total request duration, ConnTimeout only the dial phase
574+
// - 默认值为 10 秒 / Default is 10 seconds
575+
// - 仅在会话级(New)配置生效,因 Transport 在创建时固化拨号逻辑
576+
// - Only effective at session level (New), since the Transport fixes dial logic at creation time
577+
//
578+
// 示例 / Example:
579+
//
580+
// sess := requests.New(
581+
// requests.URL("https://api.example.com"),
582+
// requests.ConnTimeout(3*time.Second), // 3秒拨号超时 / 3s dial timeout
583+
// )
584+
func ConnTimeout(timeout time.Duration) Option {
585+
return func(o *Options) {
586+
o.ConnTimeout = timeout
587+
}
588+
}
589+
551590
// Verify 设置是否验证 TLS/SSL 证书
552591
// Verify sets whether to verify TLS/SSL certificates
553592
//

request.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,14 @@ func NewRequestWithContext(ctx context.Context, options Options) (*http.Request,
126126

127127
// 设置请求头
128128
// Set headers
129-
r.Header = options.Header
129+
// 仅在 options.Header 非 nil 时覆盖,否则保留 http.NewRequestWithContext 已初始化的非 nil Header,
130+
// 避免后续 r.AddCookie 等操作在 nil map 上 panic(用户绕过 newOptions 直接构造 Options 时可能为 nil)
131+
// Only overwrite when options.Header is non-nil; otherwise keep the non-nil Header initialized by
132+
// http.NewRequestWithContext, to avoid panics in subsequent r.AddCookie on a nil map (possible when
133+
// users construct Options directly, bypassing newOptions)
134+
if options.Header != nil {
135+
r.Header = options.Header
136+
}
130137

131138
// 添加Cookie
132139
// Add cookies

request_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,3 +193,28 @@ func TestNewRequestWithContext(t *testing.T) {
193193
})
194194
}
195195
}
196+
197+
// TestNewRequestWithContext_NilHeader 验证 Options.Header 为 nil 时不会覆盖掉
198+
// http.NewRequestWithContext 初始化的非 nil Header,从而保证后续 AddCookie 不 panic。
199+
// (用户绕过 newOptions 直接构造 Options 时 Header 可能为 nil)
200+
func TestNewRequestWithContext_NilHeader(t *testing.T) {
201+
// 直接构造 Options,Header 故意保持 nil,并带上 Cookie 触发 r.AddCookie
202+
options := Options{
203+
Method: "GET",
204+
URL: "http://example.com",
205+
Header: nil,
206+
Cookies: []http.Cookie{{Name: "session", Value: "123"}},
207+
}
208+
209+
req, err := NewRequestWithContext(context.Background(), options)
210+
if err != nil {
211+
t.Fatalf("NewRequestWithContext() 错误 = %v", err)
212+
}
213+
if req.Header == nil {
214+
t.Fatal("Header 不应为 nil,应保留 http.NewRequestWithContext 初始化的非 nil Header")
215+
}
216+
cookies := req.Cookies()
217+
if len(cookies) != 1 || cookies[0].Name != "session" || cookies[0].Value != "123" {
218+
t.Errorf("Cookie 设置不正确: %+v", cookies)
219+
}
220+
}

0 commit comments

Comments
 (0)