Compare commits

...

2 Commits

Author SHA1 Message Date
AI Engineer
04aca70369 feat(http): 具名化 JS 导出并动态包裹错误(by AI) 2026-06-21 10:22:57 +08:00
AI Engineer
90e8b6c742 chore: align infrastructure to v1.5.x (by AI) 2026-06-11 22:24:18 +08:00
4 changed files with 72 additions and 45 deletions

View File

@ -1,5 +1,9 @@
# CHANGELOG # CHANGELOG
## v1.5.3 (2026-06-21)
- **JS 对齐**: 重构 JS 导出为具名函数,并引入 `jsmod.MakeError` 动态包装错误以获取调用栈。
- **依赖更新**: 升级依赖 `jsmod``v1.5.3``cast``v1.5.3``rand``v1.5.3``encoding``v1.5.4``shell``v1.5.3``safe``v1.5.2``id``v1.5.4``file``v1.5.5``config``v1.5.3``log``v1.5.8`
## v1.5.1 (2026-06-08) ## v1.5.1 (2026-06-08)
- **JS 对齐 & 安全加固**: - **JS 对齐 & 安全加固**:
- HTTP 方法名统一更正为全大写(`GET`, `POST`, `PUT`, `DELETE`),强化协议语义。 - HTTP 方法名统一更正为全大写(`GET`, `POST`, `PUT`, `DELETE`),强化协议语义。

View File

@ -15,8 +15,12 @@ goos: darwin
goarch: amd64 goarch: amd64
pkg: apigo.cc/go/http pkg: apigo.cc/go/http
cpu: Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz cpu: Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz
BenchmarkGet-16 16364 74735 ns/op BenchmarkGet-16 13606 79289 ns/op
``` ```
## 🛡️ 鲁棒性防御 (Robustness)
- **可选参数与验证**`POST`/`PUT` 请求自动前置验证 `Multipart` 文件的安全路径,并受安全沙箱控制。
- **JS 错误调用栈**JS 桥接层改用具名导出并使用 `jsmod.MakeError` 包裹错误,确保 JS 抛出异常时携带准确的 Go 运行时堆栈。
## 测试覆盖率 ## 测试覆盖率
- **整体覆盖率**: 56.5% of statements - **整体覆盖率**: 56.5% of statements

20
go.mod
View File

@ -3,20 +3,20 @@ module apigo.cc/go/http
go 1.25.0 go 1.25.0
require ( require (
apigo.cc/go/cast v1.5.0 apigo.cc/go/cast v1.5.3
apigo.cc/go/encoding v1.5.0 apigo.cc/go/encoding v1.5.4
apigo.cc/go/file v1.5.0 apigo.cc/go/file v1.5.5
apigo.cc/go/jsmod v1.5.0 apigo.cc/go/jsmod v1.5.3
apigo.cc/go/log v1.5.0 apigo.cc/go/log v1.5.8
apigo.cc/go/rand v1.5.0 apigo.cc/go/rand v1.5.3
golang.org/x/net v0.54.0 golang.org/x/net v0.54.0
) )
require ( require (
apigo.cc/go/config v1.5.0 // indirect apigo.cc/go/config v1.5.3 // indirect
apigo.cc/go/id v1.5.0 // indirect apigo.cc/go/id v1.5.4 // indirect
apigo.cc/go/safe v1.5.0 // indirect apigo.cc/go/safe v1.5.2 // indirect
apigo.cc/go/shell v1.5.0 // indirect apigo.cc/go/shell v1.5.3 // indirect
golang.org/x/crypto v0.52.0 // indirect golang.org/x/crypto v0.52.0 // indirect
golang.org/x/sys v0.45.0 // indirect golang.org/x/sys v0.45.0 // indirect
golang.org/x/text v0.37.0 // indirect golang.org/x/text v0.37.0 // indirect

View File

@ -11,43 +11,59 @@ import (
func init() { func init() {
jsmod.Register("http", map[string]any{ jsmod.Register("http", map[string]any{
// Static requests with default timeout (30s) // Static requests with default timeout (30s)
"GET": func(ctx context.Context, url string, headers map[string]string) *jsResult { "GET": jsGET,
return wrapResult(ctx, Get(url, flattenHeaders(headers)...)) "POST": jsPOST,
}, "PUT": jsPUT,
"POST": func(ctx context.Context, url string, data any, headers map[string]string) (*jsResult, error) { "DELETE": jsDELETE,
if err := verifyMultipart(ctx, data); err != nil {
return nil, err
}
return wrapResult(ctx, Post(url, data, flattenHeaders(headers)...)), nil
},
"PUT": func(ctx context.Context, url string, data any, headers map[string]string) (*jsResult, error) {
if err := verifyMultipart(ctx, data); err != nil {
return nil, err
}
return wrapResult(ctx, Put(url, data, flattenHeaders(headers)...)), nil
},
"DELETE": func(ctx context.Context, url string, data any, headers map[string]string) *jsResult {
return wrapResult(ctx, Delete(url, data, flattenHeaders(headers)...))
},
// Client creation // Client creation
"New": func(ms int) *jsClient { "New": jsNewClient,
return &jsClient{c: NewClient(time.Duration(ms) * time.Millisecond)} "NewH2C": jsNewClientH2C,
},
"NewH2C": func(ms int) *jsClient {
return &jsClient{c: NewClientH2C(time.Duration(ms) * time.Millisecond)}
},
// Data markers // Data markers
"Form": func(data map[string]string) Form { "Form": jsForm,
return Form(data) "Multipart": jsMultipart,
},
"Multipart": func(data map[string]any) Multipart {
return Multipart(data)
},
}) })
} }
func jsGET(ctx context.Context, url string, headers map[string]string) *jsResult {
return wrapResult(ctx, Get(url, flattenHeaders(headers)...))
}
func jsPOST(ctx context.Context, url string, data any, headers map[string]string) (*jsResult, error) {
if err := verifyMultipart(ctx, data); err != nil {
return nil, jsmod.MakeError(err)
}
return wrapResult(ctx, Post(url, data, flattenHeaders(headers)...)), nil
}
func jsPUT(ctx context.Context, url string, data any, headers map[string]string) (*jsResult, error) {
if err := verifyMultipart(ctx, data); err != nil {
return nil, jsmod.MakeError(err)
}
return wrapResult(ctx, Put(url, data, flattenHeaders(headers)...)), nil
}
func jsDELETE(ctx context.Context, url string, data any, headers map[string]string) *jsResult {
return wrapResult(ctx, Delete(url, data, flattenHeaders(headers)...))
}
func jsNewClient(ms int) *jsClient {
return &jsClient{c: NewClient(time.Duration(ms) * time.Millisecond)}
}
func jsNewClientH2C(ms int) *jsClient {
return &jsClient{c: NewClientH2C(time.Duration(ms) * time.Millisecond)}
}
func jsForm(data map[string]string) Form {
return Form(data)
}
func jsMultipart(data map[string]any) Multipart {
return Multipart(data)
}
func flattenHeaders(m map[string]string) []string { func flattenHeaders(m map[string]string) []string {
if len(m) == 0 { if len(m) == 0 {
return nil return nil
@ -83,14 +99,14 @@ func (jc *jsClient) GET(ctx context.Context, url string, headers map[string]stri
func (jc *jsClient) POST(ctx context.Context, url string, data any, headers map[string]string) (*jsResult, error) { func (jc *jsClient) POST(ctx context.Context, url string, data any, headers map[string]string) (*jsResult, error) {
if err := verifyMultipart(ctx, data); err != nil { if err := verifyMultipart(ctx, data); err != nil {
return nil, err return nil, jsmod.MakeError(err)
} }
return wrapResult(ctx, jc.c.Post(url, data, flattenHeaders(headers)...)), nil return wrapResult(ctx, jc.c.Post(url, data, flattenHeaders(headers)...)), nil
} }
func (jc *jsClient) PUT(ctx context.Context, url string, data any, headers map[string]string) (*jsResult, error) { func (jc *jsClient) PUT(ctx context.Context, url string, data any, headers map[string]string) (*jsResult, error) {
if err := verifyMultipart(ctx, data); err != nil { if err := verifyMultipart(ctx, data); err != nil {
return nil, err return nil, jsmod.MakeError(err)
} }
return wrapResult(ctx, jc.c.Put(url, data, flattenHeaders(headers)...)), nil return wrapResult(ctx, jc.c.Put(url, data, flattenHeaders(headers)...)), nil
} }
@ -142,7 +158,10 @@ func (jr *jsResult) Error() string {
func (jr *jsResult) Save(filename string) error { func (jr *jsResult) Save(filename string) error {
p, err := file.VerifyPathForSafeMode(jr.ctx, filename) p, err := file.VerifyPathForSafeMode(jr.ctx, filename)
if err != nil { if err != nil {
return err return jsmod.MakeError(err)
} }
return jr.r.Save(p) if err := jr.r.Save(p); err != nil {
return jsmod.MakeError(err)
}
return nil
} }