ginx

package module
v1.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 12, 2026 License: MIT Imports: 10 Imported by: 0

README

ginx

CI

github.com/gtkit/ginx 提供一组与业务无关的通用 gin 请求参数处理工具, 聚焦"gin 原生没有优雅解、又跨服务复用"的请求侧痛点:

  • body 只能读一次 —— 读取后回填,使后续 ShouldBind 仍可完整读取
  • 原始 body 获取 —— 读取、缓存并回填完整字节,webhook 验签后仍可绑定
  • 按 Content-Type 解析 body —— application/jsonapplication/x-www-form-urlencoded
  • 只绑 body、不混入 query —— 规避 gin form 模式把 URL query 并入绑定;支持基于缓存的重复绑定
  • 单值 header / query 校验 —— 去空、查重,防御 HTTP 参数污染
  • 泛型类型化取值 —— query / 路由参数直接取 int、bool 等,缺失或非法回退默认值
  • Content-Type 白名单 —— 解析与绑定前快速拒绝,便于返回 415
  • 请求 body 硬限长 —— 基于 http.MaxBytesReader,防御超大 body 撑爆内存

本包不包含任何中间件、响应封装或业务语义;中间件、统一响应等请放在各自的包中。

安装

go get github.com/gtkit/ginx

要求 Go 1.26+。

API

函数 / 类型 说明
ParseBody(c) BodySources 解析并按请求缓存 body,返回 Available / JSON / Form / Err
BodyString(c, field) string 从 body 取字段并转字符串(JSON 标量或 form 值)
BindBody(c, obj) error 只把 body 绑定到 obj,不信任 query
RawBody(c) ([]byte, error) 读取完整原始 body,缓存并回填(webhook 验签)
BindBodyCached(c, obj) error 基于缓存字节绑定,同一请求可重复调用
RequireContentType(c, types...) error Content-Type 白名单校验
SingleValueHeader(c, key) (string, error) 读取并校验单值 header
SingleValueQuery(c, key) (string, error) 读取并校验单值 query(防参数污染)
Query[T](c, key, def) T / Param[T](c, key, def) T 泛型类型化取值,缺失/非法回退默认值
LimitRequestBody(c, maxBytes) 为 body 设置硬上限(http.MaxBytesReader
IsRequestBodyTooLarge(err) bool 判断 err 是否因 body 超过硬上限产生
MaxBodyBytes ParseBody 的解析软上限(8 KiB)
ErrDuplicateHeader / ErrInvalidHeaderValue / ErrInvalidBindContext header / 绑定哨兵错误
ErrNoBody / ErrBodyTooLarge / ErrUnsupportedContentType / ErrMalformedBody ParseBody 失败原因哨兵错误(经 BodySources.Err 透出)

用法

解析 body(多来源、按请求缓存)
src := ginx.ParseBody(c)
if !src.Available {
    // 失败原因可判定:ErrNoBody / ErrBodyTooLarge / ErrUnsupportedContentType / ErrMalformedBody
    if errors.Is(src.Err, ginx.ErrBodyTooLarge) {
        // body 超过 MaxBodyBytes 软上限
    }
    return
}
name, _ := src.JSON["name"].(string) // JSON body(数字为 json.Number,原文不丢精度)
page := src.Form.Get("page")         // form body
只绑 body + 硬限长
ginx.LimitRequestBody(c, 1<<20) // 1 MiB 硬上限

var req CreateOrderReq
if err := ginx.BindBody(c, &req); err != nil {
    if ginx.IsRequestBodyTooLarge(err) {
        c.AbortWithStatus(http.StatusRequestEntityTooLarge) // 413
        return
    }
    c.AbortWithStatus(http.StatusBadRequest)
    return
}
webhook 验签:原始 body + 重复绑定
ginx.LimitRequestBody(c, 1<<20) // RawBody 不设软上限,务必配合硬限长

raw, err := ginx.RawBody(c) // 完整原始字节,读后回填并缓存
if err != nil { /* ... */ }
verifySignature(raw, c.GetHeader("X-Signature"))

var notify PayNotify
_ = ginx.BindBodyCached(c, &notify) // 可重复绑定,每次都是完整 body
类型化取参数
page := ginx.Query(c, "page", 1)        // ?page=3 -> 3;缺失/非法 -> 1
dry  := ginx.Query(c, "dry_run", false)
id   := ginx.Param(c, "id", int64(0))   // 路由 /user/:id

uid, err := ginx.SingleValueQuery(c, "uid") // ?uid=1&uid=2 -> ErrDuplicateQuery
Content-Type 白名单
if err := ginx.RequireContentType(c, "application/json"); err != nil {
    c.AbortWithStatus(http.StatusUnsupportedMediaType) // 415
    return
}
单值 header
token, err := ginx.SingleValueHeader(c, "X-Token")
switch {
case errors.Is(err, ginx.ErrDuplicateHeader):
    // 同名头出现多个值
case errors.Is(err, ginx.ErrInvalidHeaderValue):
    // 值含逗号等非法形式
}

软限长 vs 硬限长

  • MaxBodyBytes(软):仅约束 ParseBody 自身读取/解析的长度,超过则返回 Available=false不影响下游 ShouldBind
  • LimitRequestBody(硬):包装 c.Request.Body,对整个请求生命周期内的任何读取生效,超限即返回 *http.MaxBytesError

二者互补:ParseBody 用于"轻量探取 body 中的字段",LimitRequestBody 用于"防御性地封顶请求体"。

使用约束

  • 并发:与 gin.Context 一致,本包函数会替换 c.Request.Body、临时修改 URL.RawQuery,仅限在处理该请求的 handler goroutine 内调用
  • multipart/form-data 不支持:multipart 通常携带文件、体积大,与按 MaxBodyBytes 轻量探取的定位冲突
  • BindBody 一次性语义:绑定会消费 body 流,同一请求内二次调用读到空 body;如需先探取字段再绑定,先 ParseBody(会回填 body)再 BindBody
  • JSON 数字:以 json.Number 原文承载,BodyString 对任意大小的整数不丢精度(如雪花 ID)

Documentation

Overview

Package ginx 提供与业务无关的通用 gin 请求参数处理能力:解决 gin 请求 body 只能读取一次的痛点 (读取后回填,使后续 ShouldBind 仍可读)、按 Content-Type 解析 body、单值 header 校验、只绑 body 不混入 query,以及用 http.MaxBytesReader 对 body 设硬上限等。

这些函数不含任何业务语义,按请求在 gin.Context 上缓存解析结果,适合作为各服务的通用请求处理底座。

支持的 body 类型为 application/json 与 application/x-www-form-urlencoded;不支持 multipart/form-data(通常携带文件、体积大,与按 MaxBodyBytes 轻量探取的定位冲突)。

并发安全:与 gin.Context 本身一致。本包函数会替换 c.Request.Body、临时修改 URL.RawQuery, 必须在处理该请求的 handler goroutine 内调用,不得跨 goroutine 并发操作同一个 Context。

Index

Examples

Constants

View Source
const MaxBodyBytes = 8 * 1024

MaxBodyBytes 是 ParseBody 允许读取并解析的请求 body 软上限,超过则视为不可解析、返回空结果。 它只约束本包的解析行为;如需对整个请求生命周期施加硬上限,请用 LimitRequestBody。

View Source
const Version = "v1.1.1"

Version 是 ginx 的当前版本号。

Variables

View Source
var (
	// ErrDuplicateHeader 表示同一 header 出现了多个非空值。
	ErrDuplicateHeader = errors.New("header provided multiple times")
	// ErrInvalidHeaderValue 表示 header 的值非法(如含逗号的多值形式)。
	ErrInvalidHeaderValue = errors.New("header contains invalid value")
	// ErrInvalidBindContext 表示 BindBody 收到的 context / Request / URL 为 nil,无法绑定。
	ErrInvalidBindContext = errors.New("invalid bind body context")
	// ErrNoBody 表示请求没有可解析的 body:context 或 body 为 nil,或方法为 GET/HEAD。
	ErrNoBody = errors.New("request has no parsable body")
	// ErrBodyTooLarge 表示请求 body 长度超过 MaxBodyBytes 软上限,ParseBody 拒绝解析。
	ErrBodyTooLarge = errors.New("request body exceeds MaxBodyBytes")
	// ErrUnsupportedContentType 表示 Content-Type 不在 ParseBody 支持的类型范围内。
	ErrUnsupportedContentType = errors.New("unsupported content type")
	// ErrMalformedBody 表示 body 语法非法:JSON 语法错误、JSON 尾部存在多余数据或 form 编码非法,
	// 底层解析错误可经 errors.As / Unwrap 获取。
	ErrMalformedBody = errors.New("malformed request body")
)
View Source
var ErrDuplicateQuery = errors.New("query parameter provided multiple times")

ErrDuplicateQuery 表示同名 query 参数出现了多个非空值(HTTP 参数污染)。

Functions

func BindBody

func BindBody(c *gin.Context, obj any) error

BindBody 仅将请求 body 绑定到 obj。绑定前临时清空 URL.RawQuery,避免 gin 在 form 模式下把 query 参数一并并入绑定结果——确保只信任 body、不信任 query;绑定结束后恢复 RawQuery。 context、Request 或 URL 为 nil 时返回 ErrInvalidBindContext。

绑定会消费 body 流(一次性语义):同一请求内第二次调用将读到空 body。如需先探取字段再绑定, 先调用 ParseBody(它会回填 body)、再调用本函数。

Example
package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"

	"github.com/gin-gonic/gin"

	"github.com/gtkit/ginx"
)

// newPostContext 构造一个带 body 的 POST 请求测试上下文,仅用于 Example。
func newPostContext(contentType, body string) *gin.Context {
	gin.SetMode(gin.TestMode)
	c, _ := gin.CreateTestContext(httptest.NewRecorder())
	req := httptest.NewRequest(http.MethodPost, "/demo", strings.NewReader(body))
	req.Header.Set("Content-Type", contentType)
	c.Request = req
	return c
}

func main() {
	c := newPostContext("application/json", `{"token":"body-token"}`)
	c.Request.URL.RawQuery = "token=query-token" // query 不会被并入绑定结果

	var req struct {
		Token string `json:"token" form:"token"`
	}
	if err := ginx.BindBody(c, &req); err != nil {
		fmt.Println("bind:", err)
		return
	}
	fmt.Println(req.Token)
}
Output:
body-token

func BindBodyCached

func BindBodyCached(c *gin.Context, obj any) error

BindBodyCached 基于 RawBody 缓存的原始字节将 body 绑定到 obj:同一请求内可重复调用,每次绑定 都读到完整 body,并继承 BindBody 的只信 body、不信 query 语义。首次调用会完整读取并缓存 body, 长度防御同样依赖 LimitRequestBody。context、Request 或 URL 为 nil 时返回 ErrInvalidBindContext。

Example
package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"

	"github.com/gin-gonic/gin"

	"github.com/gtkit/ginx"
)

// newPostContext 构造一个带 body 的 POST 请求测试上下文,仅用于 Example。
func newPostContext(contentType, body string) *gin.Context {
	gin.SetMode(gin.TestMode)
	c, _ := gin.CreateTestContext(httptest.NewRecorder())
	req := httptest.NewRequest(http.MethodPost, "/demo", strings.NewReader(body))
	req.Header.Set("Content-Type", contentType)
	c.Request = req
	return c
}

func main() {
	c := newPostContext("application/json", `{"token":"t1","scene":"s1"}`)

	var a struct {
		Token string `json:"token"`
	}
	var b struct {
		Scene string `json:"scene"`
	}
	_ = ginx.BindBodyCached(c, &a) // 同一请求可重复绑定
	_ = ginx.BindBodyCached(c, &b)
	fmt.Println(a.Token, b.Scene)
}
Output:
t1 s1

func BodyString

func BodyString(c *gin.Context, field string) string

BodyString 从请求 body 中读取指定字段并转为字符串:JSON body 支持 string/bool/number/Stringer 等标量类型,form body 取对应键值;body 不可解析时返回空串。

Example
package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"

	"github.com/gin-gonic/gin"

	"github.com/gtkit/ginx"
)

// newPostContext 构造一个带 body 的 POST 请求测试上下文,仅用于 Example。
func newPostContext(contentType, body string) *gin.Context {
	gin.SetMode(gin.TestMode)
	c, _ := gin.CreateTestContext(httptest.NewRecorder())
	req := httptest.NewRequest(http.MethodPost, "/demo", strings.NewReader(body))
	req.Header.Set("Content-Type", contentType)
	c.Request = req
	return c
}

func main() {
	c := newPostContext("application/x-www-form-urlencoded", "token=abc123")

	fmt.Println(ginx.BodyString(c, "token"))
}
Output:
abc123

func IsRequestBodyTooLarge

func IsRequestBodyTooLarge(err error) bool

IsRequestBodyTooLarge 判断 err 链中是否含 *http.MaxBytesError,即请求 body 是否超过了 LimitRequestBody 设定的硬上限,便于上游据此返回 413 Request Entity Too Large。

func LimitRequestBody

func LimitRequestBody(c *gin.Context, maxBytes int64)

LimitRequestBody 用 http.MaxBytesReader 为请求 body 设置硬上限 maxBytes:调用后,任何对 body 的读取(包括下游 c.ShouldBind / BindBody)一旦累计超过 maxBytes,都会立即返回 *http.MaxBytesError 并停止继续读入内存,从根上防御超大 body 撑爆内存,必要时还会关闭连接。 这与软上限 MaxBodyBytes 不同:MaxBodyBytes 仅用于本包解析时按长度跳过、不约束下游读取,而本函数的 限制对整个请求生命周期生效。应在读取或绑定 body 之前调用(如路由进入处或 handler 开头)。 maxBytes <= 0 或无 body 时直接返回、不设限。

Example
package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"

	"github.com/gin-gonic/gin"

	"github.com/gtkit/ginx"
)

// newPostContext 构造一个带 body 的 POST 请求测试上下文,仅用于 Example。
func newPostContext(contentType, body string) *gin.Context {
	gin.SetMode(gin.TestMode)
	c, _ := gin.CreateTestContext(httptest.NewRecorder())
	req := httptest.NewRequest(http.MethodPost, "/demo", strings.NewReader(body))
	req.Header.Set("Content-Type", contentType)
	c.Request = req
	return c
}

func main() {
	c := newPostContext("application/json", `{"data":"`+strings.Repeat("a", 64)+`"}`)
	ginx.LimitRequestBody(c, 16) // 16 字节硬上限

	var req struct {
		Data string `json:"data"`
	}
	err := ginx.BindBody(c, &req)
	fmt.Println(ginx.IsRequestBodyTooLarge(err))
}
Output:
true

func Param

func Param[T Scalar](c *gin.Context, key string, def T) T

Param 从路由参数(如 /user/:id 的 id)读取 key 并解析为 T,缺失与解析失败的回退语义同 Query。

Example
package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"

	"github.com/gin-gonic/gin"

	"github.com/gtkit/ginx"
)

// newPostContext 构造一个带 body 的 POST 请求测试上下文,仅用于 Example。
func newPostContext(contentType, body string) *gin.Context {
	gin.SetMode(gin.TestMode)
	c, _ := gin.CreateTestContext(httptest.NewRecorder())
	req := httptest.NewRequest(http.MethodPost, "/demo", strings.NewReader(body))
	req.Header.Set("Content-Type", contentType)
	c.Request = req
	return c
}

func main() {
	c := newPostContext("application/json", "{}")
	c.Params = gin.Params{{Key: "id", Value: "42"}} // 路由 /user/:id

	fmt.Println(ginx.Param(c, "id", int64(0)))
}
Output:
42

func Query

func Query[T Scalar](c *gin.Context, key string, def T) T

Query 从 query 参数读取 key 并解析为 T:值缺失、去空白后为空、或解析失败时返回 def, 不 panic、不返回错误。需要严格校验时请使用 gin 的 ShouldBindQuery 配合 binding tag。

Example
package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"

	"github.com/gin-gonic/gin"

	"github.com/gtkit/ginx"
)

// newPostContext 构造一个带 body 的 POST 请求测试上下文,仅用于 Example。
func newPostContext(contentType, body string) *gin.Context {
	gin.SetMode(gin.TestMode)
	c, _ := gin.CreateTestContext(httptest.NewRecorder())
	req := httptest.NewRequest(http.MethodPost, "/demo", strings.NewReader(body))
	req.Header.Set("Content-Type", contentType)
	c.Request = req
	return c
}

func main() {
	c := newPostContext("application/json", "{}")
	c.Request.URL.RawQuery = "page=3&size=abc"

	fmt.Println(ginx.Query(c, "page", 1), ginx.Query(c, "size", 20), ginx.Query(c, "dry_run", false))
}
Output:
3 20 false

func RawBody

func RawBody(c *gin.Context) ([]byte, error)

RawBody 读取并返回完整的原始请求 body:读取后回填 body(后续 BindBody / ShouldBind / ParseBody 仍可完整读取),并按请求缓存,同一请求内重复调用不再读流。适合 webhook 验签等先取原始字节、 再绑定结构体的场景。

本函数不设长度上限:请配合 LimitRequestBody 使用,超过硬上限时返回的错误可用 IsRequestBodyTooLarge 判定。context、Request 或 body 为 nil 时返回 ErrNoBody; 读取失败时如实返回错误且不缓存。

Example
package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"

	"github.com/gin-gonic/gin"

	"github.com/gtkit/ginx"
)

// newPostContext 构造一个带 body 的 POST 请求测试上下文,仅用于 Example。
func newPostContext(contentType, body string) *gin.Context {
	gin.SetMode(gin.TestMode)
	c, _ := gin.CreateTestContext(httptest.NewRecorder())
	req := httptest.NewRequest(http.MethodPost, "/demo", strings.NewReader(body))
	req.Header.Set("Content-Type", contentType)
	c.Request = req
	return c
}

func main() {
	// webhook 验签场景:先取原始字节算签名,再绑定结构体,两者不互斥
	c := newPostContext("application/json", `{"event":"pay.success"}`)

	raw, err := ginx.RawBody(c)
	if err != nil {
		fmt.Println("read:", err)
		return
	}
	// verifySignature(raw, c.GetHeader("X-Signature")) ...

	var notify struct {
		Event string `json:"event"`
	}
	if err := ginx.BindBody(c, &notify); err != nil {
		fmt.Println("bind:", err)
		return
	}
	fmt.Println(len(raw) > 0, notify.Event)
}
Output:
true pay.success

func RequireContentType

func RequireContentType(c *gin.Context, types ...string) error

RequireContentType 校验请求的 Content-Type(忽略参数与大小写)是否在 types 白名单内:命中返回 nil;不命中返回可用 errors.Is 判定 ErrUnsupportedContentType 的错误并附实际类型,便于上游返回 415 Unsupported Media Type。types 为空时不做约束、返回 nil;context 或 Request 为 nil 视为 无 Content-Type。

Example
package main

import (
	"errors"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"

	"github.com/gin-gonic/gin"

	"github.com/gtkit/ginx"
)

// newPostContext 构造一个带 body 的 POST 请求测试上下文,仅用于 Example。
func newPostContext(contentType, body string) *gin.Context {
	gin.SetMode(gin.TestMode)
	c, _ := gin.CreateTestContext(httptest.NewRecorder())
	req := httptest.NewRequest(http.MethodPost, "/demo", strings.NewReader(body))
	req.Header.Set("Content-Type", contentType)
	c.Request = req
	return c
}

func main() {
	c := newPostContext("text/plain", "hello")

	err := ginx.RequireContentType(c, "application/json", "application/x-www-form-urlencoded")
	fmt.Println(errors.Is(err, ginx.ErrUnsupportedContentType))
}
Output:
true

func SingleValueHeader

func SingleValueHeader(c *gin.Context, headerKey string) (string, error)

SingleValueHeader 读取并校验单值 header:去空白后忽略空值,值含逗号视为非法 (ErrInvalidHeaderValue),出现多个非空值视为重复(ErrDuplicateHeader)。 恰好一个非空值时返回该值,全部为空时返回空串且无错误。

Example
package main

import (
	"errors"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"

	"github.com/gin-gonic/gin"

	"github.com/gtkit/ginx"
)

// newPostContext 构造一个带 body 的 POST 请求测试上下文,仅用于 Example。
func newPostContext(contentType, body string) *gin.Context {
	gin.SetMode(gin.TestMode)
	c, _ := gin.CreateTestContext(httptest.NewRecorder())
	req := httptest.NewRequest(http.MethodPost, "/demo", strings.NewReader(body))
	req.Header.Set("Content-Type", contentType)
	c.Request = req
	return c
}

func main() {
	c := newPostContext("application/json", "{}")
	c.Request.Header.Add("X-Token", "a")
	c.Request.Header.Add("X-Token", "b")

	_, err := ginx.SingleValueHeader(c, "X-Token")
	fmt.Println(errors.Is(err, ginx.ErrDuplicateHeader))
}
Output:
true

func SingleValueQuery

func SingleValueQuery(c *gin.Context, key string) (string, error)

SingleValueQuery 读取并校验单值 query 参数:去空白后忽略空值,出现多个非空值视为参数污染、 返回 ErrDuplicateQuery。恰好一个非空值时返回该值,全部为空时返回空串且无错误。query 值中的 逗号视为合法(逗号合并是 header 的语义,query 没有)。context、Request 或 URL 为 nil 时 返回空串且无错误。

Example
package main

import (
	"errors"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"

	"github.com/gin-gonic/gin"

	"github.com/gtkit/ginx"
)

// newPostContext 构造一个带 body 的 POST 请求测试上下文,仅用于 Example。
func newPostContext(contentType, body string) *gin.Context {
	gin.SetMode(gin.TestMode)
	c, _ := gin.CreateTestContext(httptest.NewRecorder())
	req := httptest.NewRequest(http.MethodPost, "/demo", strings.NewReader(body))
	req.Header.Set("Content-Type", contentType)
	c.Request = req
	return c
}

func main() {
	c := newPostContext("application/json", "{}")
	c.Request.URL.RawQuery = "id=1&id=2" // HTTP 参数污染

	_, err := ginx.SingleValueQuery(c, "id")
	fmt.Println(errors.Is(err, ginx.ErrDuplicateQuery))
}
Output:
true

Types

type BodySources

type BodySources struct {
	Available bool
	JSON      map[string]any
	Form      url.Values
	Err       error
}

BodySources 承载一次请求 body 的解析结果:Available 表示是否解析成功,JSON 与 Form 分别对应 application/json 与 application/x-www-form-urlencoded 两种 body 的解析产物(另一种为 nil)。 JSON 中的数字以 json.Number 承载(UseNumber 解码),原样保留 body 中的字面量。

Err 在 Available 为 false 时说明失败原因,可用 errors.Is 判定 ErrNoBody、ErrBodyTooLarge、 ErrUnsupportedContentType、ErrMalformedBody(body 读取失败时为相应读取错误的包装); Available 为 true 时恒为 nil。结果按请求缓存,同一请求内重复调用返回相同的 Err。

func ParseBody

func ParseBody(c *gin.Context) (sources BodySources)

ParseBody 解析请求 body 并按请求缓存结果(同一请求内多次调用只读取/解析一次 body)。命中缓存直接 返回;否则经跳过判断(nil body、GET/HEAD、ContentLength 超 MaxBodyBytes)、限长读取并回填 body 后 按 Content-Type 解析。任一环节失败或不支持的类型均返回 Available 为 false 的结果, 失败原因经 Err 字段透出(见 BodySources)。

Example
package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"

	"github.com/gin-gonic/gin"

	"github.com/gtkit/ginx"
)

// newPostContext 构造一个带 body 的 POST 请求测试上下文,仅用于 Example。
func newPostContext(contentType, body string) *gin.Context {
	gin.SetMode(gin.TestMode)
	c, _ := gin.CreateTestContext(httptest.NewRecorder())
	req := httptest.NewRequest(http.MethodPost, "/demo", strings.NewReader(body))
	req.Header.Set("Content-Type", contentType)
	c.Request = req
	return c
}

func main() {
	c := newPostContext("application/json", `{"name":"alice"}`)

	src := ginx.ParseBody(c)
	fmt.Println(src.Available, src.JSON["name"])
}
Output:
true alice
Example (FailureReason)
package main

import (
	"errors"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"

	"github.com/gin-gonic/gin"

	"github.com/gtkit/ginx"
)

// newPostContext 构造一个带 body 的 POST 请求测试上下文,仅用于 Example。
func newPostContext(contentType, body string) *gin.Context {
	gin.SetMode(gin.TestMode)
	c, _ := gin.CreateTestContext(httptest.NewRecorder())
	req := httptest.NewRequest(http.MethodPost, "/demo", strings.NewReader(body))
	req.Header.Set("Content-Type", contentType)
	c.Request = req
	return c
}

func main() {
	c := newPostContext("text/plain", "hello")

	src := ginx.ParseBody(c)
	fmt.Println(src.Available, errors.Is(src.Err, ginx.ErrUnsupportedContentType))
}
Output:
false true

type Scalar

type Scalar interface {
	string | int | int64 | uint64 | bool | float64
}

Scalar 是 Query 与 Param 支持的取值类型集合。约束使用精确类型(不带 ~)以保证零反射解析; 命名类型(如 type ID int64)请按底层类型取值后自行转换。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL