From 7e15181c0b49694187ee8c87ec1bb85c4c2d0c73 Mon Sep 17 00:00:00 2001 From: wjqserver <114663932+WJQSERVER@users.noreply.github.com> Date: Sun, 29 Mar 2026 16:26:48 +0800 Subject: [PATCH] feat(render): add Buf variants for JSON/GOB/WANF/HTML Add buffered rendering methods that encode to a buffer first, then write the response. This allows returning a proper 500 status code if encoding fails, unlike the streaming variants which must write the status code before encoding (an inherent HTTP constraint). New methods: - JSONBuf(code int, obj any) - GOBBuf(code int, obj any) - WANFBuf(code int, obj any) - HTMLBuf(code int, name string, obj any) Trade-off: one extra memory allocation per call in exchange for correct error status codes on encoding failure. --- context.go | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/context.go b/context.go index c37371f..885b10f 100644 --- a/context.go +++ b/context.go @@ -417,6 +417,21 @@ func (c *Context) JSON(code int, obj any) { } } +// JSONBuf 先将 JSON 编码到 buffer, 成功后再写入状态码和响应体. +// 与 JSON 相比, 编码失败时可以正确返回 500 状态码, 代价是多一次内存分配. +func (c *Context) JSONBuf(code int, obj any) { + data, err := json.Marshal(obj) + if err != nil { + c.AddError(fmt.Errorf("failed to marshal JSON: %w", err)) + c.Errorf("failed to marshal JSON: %s", err) + c.ErrorUseHandle(http.StatusInternalServerError, fmt.Errorf("failed to marshal JSON: %w", err)) + return + } + c.Writer.Header().Set("Content-Type", "application/json; charset=utf-8") + c.Writer.WriteHeader(code) + c.Writer.Write(data) +} + // GOB 向响应写入GOB数据 // 设置 Content-Type 为 application/octet-stream func (c *Context) GOB(code int, obj any) { @@ -431,6 +446,20 @@ func (c *Context) GOB(code int, obj any) { } } +// GOBBuf 先将 GOB 编码到 buffer, 成功后再写入状态码和响应体. +func (c *Context) GOBBuf(code int, obj any) { + var buf bytes.Buffer + encoder := gob.NewEncoder(&buf) + if err := encoder.Encode(obj); err != nil { + c.AddError(fmt.Errorf("failed to encode GOB: %w", err)) + c.ErrorUseHandle(http.StatusInternalServerError, fmt.Errorf("failed to encode GOB: %w", err)) + return + } + c.Writer.Header().Set("Content-Type", "application/octet-stream") + c.Writer.WriteHeader(code) + c.Writer.Write(buf.Bytes()) +} + // WANF向响应写入WANF数据 // 设置 application/vnd.wjqserver.wanf; charset=utf-8 func (c *Context) WANF(code int, obj any) { @@ -445,6 +474,19 @@ func (c *Context) WANF(code int, obj any) { } } +// WANFBuf 先将 WANF 编码到 buffer, 成功后再写入状态码和响应体. +func (c *Context) WANFBuf(code int, obj any) { + data, err := wanf.Marshal(obj) + if err != nil { + c.AddError(fmt.Errorf("failed to encode WANF: %w", err)) + c.ErrorUseHandle(http.StatusInternalServerError, fmt.Errorf("failed to encode WANF: %w", err)) + return + } + c.Writer.Header().Set("Content-Type", "application/vnd.wjqserver.wanf; charset=utf-8") + c.Writer.WriteHeader(code) + c.Writer.Write(data) +} + // HTML 渲染 HTML 模板 // 如果 Engine 配置了 HTMLRender,则使用它进行渲染 // 否则,会进行简单的字符串输出 @@ -469,6 +511,29 @@ func (c *Context) HTML(code int, name string, obj any) { c.Writer.Write(fmt.Appendf(nil, "\n
%v", name, obj)) } +// HTMLBuf 先将 HTML 模板渲染到 buffer, 成功后再写入状态码和响应体. +func (c *Context) HTMLBuf(code int, name string, obj any) { + if c.engine != nil && c.engine.HTMLRender != nil { + if tpl, ok := c.engine.HTMLRender.(*template.Template); ok { + var buf bytes.Buffer + err := tpl.ExecuteTemplate(&buf, name, obj) + if err != nil { + c.AddError(fmt.Errorf("failed to render HTML template '%s': %w", name, err)) + c.ErrorUseHandle(http.StatusInternalServerError, fmt.Errorf("failed to render HTML template '%s': %w", name, err)) + return + } + c.Writer.Header().Set("Content-Type", "text/html; charset=utf-8") + c.Writer.WriteHeader(code) + c.Writer.Write(buf.Bytes()) + return + } + } + // 默认简单输出 + c.Writer.Header().Set("Content-Type", "text/html; charset=utf-8") + c.Writer.WriteHeader(code) + c.Writer.Write(fmt.Appendf(nil, "\n
%v", name, obj)) +} + // Redirect 执行 HTTP 重定向 // code 应为 3xx 状态码 (如 http.StatusMovedPermanently, http.StatusFound) func (c *Context) Redirect(code int, location string) {