feat(http): 具名化 JS 导出并动态包裹错误(by AI)
This commit is contained in:
parent
90e8b6c742
commit
04aca70369
@ -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`),强化协议语义。
|
||||||
|
|||||||
6
TEST.md
6
TEST.md
@ -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
20
go.mod
@ -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.2
|
apigo.cc/go/cast v1.5.3
|
||||||
apigo.cc/go/encoding v1.5.3
|
apigo.cc/go/encoding v1.5.4
|
||||||
apigo.cc/go/file v1.5.4
|
apigo.cc/go/file v1.5.5
|
||||||
apigo.cc/go/jsmod v1.5.2
|
apigo.cc/go/jsmod v1.5.3
|
||||||
apigo.cc/go/log v1.5.6
|
apigo.cc/go/log v1.5.8
|
||||||
apigo.cc/go/rand v1.5.2
|
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.2 // indirect
|
apigo.cc/go/config v1.5.3 // indirect
|
||||||
apigo.cc/go/id v1.5.3 // indirect
|
apigo.cc/go/id v1.5.4 // indirect
|
||||||
apigo.cc/go/safe v1.5.1 // indirect
|
apigo.cc/go/safe v1.5.2 // indirect
|
||||||
apigo.cc/go/shell v1.5.2 // 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
|
||||||
|
|||||||
87
js_export.go
87
js_export.go
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user