From 04aca7036917e5583e9fcf9026273d0d8de9e749 Mon Sep 17 00:00:00 2001 From: AI Engineer Date: Sun, 21 Jun 2026 10:22:57 +0800 Subject: [PATCH] =?UTF-8?q?feat(http):=20=E5=85=B7=E5=90=8D=E5=8C=96=20JS?= =?UTF-8?q?=20=E5=AF=BC=E5=87=BA=E5=B9=B6=E5=8A=A8=E6=80=81=E5=8C=85?= =?UTF-8?q?=E8=A3=B9=E9=94=99=E8=AF=AF=EF=BC=88by=20AI=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 4 +++ TEST.md | 6 +++- go.mod | 20 ++++++------ js_export.go | 87 ++++++++++++++++++++++++++++++++-------------------- 4 files changed, 72 insertions(+), 45 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28cd6fd..bc9722c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # 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) - **JS 对齐 & 安全加固**: - HTTP 方法名统一更正为全大写(`GET`, `POST`, `PUT`, `DELETE`),强化协议语义。 diff --git a/TEST.md b/TEST.md index 7faf673..5fb2e21 100644 --- a/TEST.md +++ b/TEST.md @@ -15,8 +15,12 @@ goos: darwin goarch: amd64 pkg: apigo.cc/go/http 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 diff --git a/go.mod b/go.mod index cfaa9b3..07adc85 100644 --- a/go.mod +++ b/go.mod @@ -3,20 +3,20 @@ module apigo.cc/go/http go 1.25.0 require ( - apigo.cc/go/cast v1.5.2 - apigo.cc/go/encoding v1.5.3 - apigo.cc/go/file v1.5.4 - apigo.cc/go/jsmod v1.5.2 - apigo.cc/go/log v1.5.6 - apigo.cc/go/rand v1.5.2 + apigo.cc/go/cast v1.5.3 + apigo.cc/go/encoding v1.5.4 + apigo.cc/go/file v1.5.5 + apigo.cc/go/jsmod v1.5.3 + apigo.cc/go/log v1.5.8 + apigo.cc/go/rand v1.5.3 golang.org/x/net v0.54.0 ) require ( - apigo.cc/go/config v1.5.2 // indirect - apigo.cc/go/id v1.5.3 // indirect - apigo.cc/go/safe v1.5.1 // indirect - apigo.cc/go/shell v1.5.2 // indirect + apigo.cc/go/config v1.5.3 // indirect + apigo.cc/go/id v1.5.4 // indirect + apigo.cc/go/safe v1.5.2 // indirect + apigo.cc/go/shell v1.5.3 // indirect golang.org/x/crypto v0.52.0 // indirect golang.org/x/sys v0.45.0 // indirect golang.org/x/text v0.37.0 // indirect diff --git a/js_export.go b/js_export.go index b2333e2..c77d66b 100644 --- a/js_export.go +++ b/js_export.go @@ -11,43 +11,59 @@ import ( func init() { jsmod.Register("http", map[string]any{ // Static requests with default timeout (30s) - "GET": func(ctx context.Context, url string, headers map[string]string) *jsResult { - return wrapResult(ctx, Get(url, flattenHeaders(headers)...)) - }, - "POST": 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, 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)...)) - }, + "GET": jsGET, + "POST": jsPOST, + "PUT": jsPUT, + "DELETE": jsDELETE, // Client creation - "New": func(ms int) *jsClient { - return &jsClient{c: NewClient(time.Duration(ms) * time.Millisecond)} - }, - "NewH2C": func(ms int) *jsClient { - return &jsClient{c: NewClientH2C(time.Duration(ms) * time.Millisecond)} - }, + "New": jsNewClient, + "NewH2C": jsNewClientH2C, // Data markers - "Form": func(data map[string]string) Form { - return Form(data) - }, - "Multipart": func(data map[string]any) Multipart { - return Multipart(data) - }, + "Form": jsForm, + "Multipart": jsMultipart, }) } +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 { if len(m) == 0 { 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) { 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 } func (jc *jsClient) PUT(ctx context.Context, url string, data any, headers map[string]string) (*jsResult, error) { 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 } @@ -142,7 +158,10 @@ func (jr *jsResult) Error() string { func (jr *jsResult) Save(filename string) error { p, err := file.VerifyPathForSafeMode(jr.ctx, filename) 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 }