深入理解 Hertz RequestContext 与 请求 关联特性

URI

func (ctx *RequestContext) Host() []byte
func (ctx *RequestContext) FullPath() string
func (ctx *RequestContext) SetFullPath(p string)
func (ctx *RequestContext) Path() []byte
func (ctx *RequestContext) Param(key string) string
func (ctx *RequestContext) Query(key string) string
func (ctx *RequestContext) DefaultQuery(key, defaultValue string) string
func (ctx *RequestContext) GetQuery(key string) (string, bool)
func (ctx *RequestContext) QueryArgs() *protocol.Args
func (ctx *RequestContext) URI() *protocol.URI
  • Host:获取请求的主机地址

  • FullPath:获取匹配的路由完整路径,对于未匹配的路由返回空字符串。

  • SetFullPath:设置 FullPath 的值

    注意:FullPath 由路由查找时分配,通常你不需要使用 SetFullPath 去覆盖它。

  • Path:获取请求的路径。

    注意:出现参数路由时 Path 给出命名参数匹配后的路径,而 FullPath 给出原始路径。

  • Param:获取路由参数的值。

  • Query:获取路由 Query String 参数中指定属性的值,如果没有返回空字符串。

  • DefaultQuery:获取路由 Query String 参数中指定属性的值,如果没有返回设置的默认值。

  • GetQuery:获取路由 Query String 参数中指定属性的值以及属性是否存在。

  • QueryArgs:获取路由 Query String 参数对象。

  • URI:返回请求的 URI 对象

例子

package main

import (
	"context"
	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/app/server"
	"github.com/cloudwego/hertz/pkg/common/hlog"
)

func main() {
	h := server.Default()
	h.GET(
		"/hertz/:name", func(ctx context.Context, c *app.RequestContext) {
			hlog.Info(string(c.Host())) // 127.0.0.1:8888

			hlog.Info(string(c.Path())) // /hertz/liming
			hlog.Info(c.FullPath())     // /hertz/:name
			c.SetFullPath("/v1/hertz/:name")
			hlog.Info(string(c.Path())) // /hertz/liming
			hlog.Info(c.FullPath())     // /v1/hertz/:name

			hlog.Info(c.Param("name")) // liming

			hlog.Info(c.Query("key1")) // value1
			hlog.Info(c.DefaultQuery("key2", "defaultValue"))
			if v, ok := c.GetQuery("key3"); ok {
				hlog.Info(v)
			}

			c.String(200, "Hello, World!")
		},
	)
	routeInfo := h.Routes()
	hlog.Info(routeInfo)
	h.Spin()
}

分别访问

  • 127.0.0.1:8888/hertz/liming?key1=value1
  • 127.0.0.1:8888/hertz/liming?key1=value1&key2=value2&key3=vlue3

Args 对象

通过QueryArgs方法获取一个Args 对象

签名是 func (ctx *RequestContext) QueryArgs() *protocol.Args

Args 对象提供了以下方法获取/设置 Query String 参数。

函数签名 说明
func (a *Args) Set(key, value string) 设置 Args 对象 key 的值
func (a *Args) Reset() 重置 Args 对象
func (a *Args) CopyTo(dst *Args) 将 Args 对象拷贝到 dst
func (a *Args) Del(key string) 删除 Args 对象 key 的键值对
func (a *Args) DelBytes(key []byte) 删除 Args 对象字节数组类型 key 的键值对
func (a *Args) Has(key string) bool 获取 Args 对象是否存在 key 的键值对
func (a *Args) String() string 将 Args 对象转换为字符串类型的 Query String
func (a *Args) QueryString() []byte 将 Args 对象转换为字节数组类型的 Query String
func (a *Args) ParseBytes(b []byte) 解析字节数组并将键值对存入 Args 对象
func (a *Args) Peek(key string) []byte 获取 Args 对象 key 的值
func (a *Args) PeekExists(key string) (string, bool) 获取 Args 对象 key 的值以及是否存在
func (a *Args) PeekAll(key string) [][]byte 获取 Args 对象 key 的所有值
func (a *Args) Len() int 获取 Args 对象键值对数量
func (a *Args) AppendBytes(dst []byte) []byte 将 Args 对象 Query String 附加到 dst 中并返回
func (a *Args) VisitAll(f func(key, value []byte)) 遍历 Args 对象所有的键值对
func (a *Args) WriteTo(w io.Writer) (int64, error) 将 Args 对象 Query String 写入 io.Writer 中
func (a *Args) Add(key, value string) 添加 Args 对象键为 key 的值
package main

import (
	"context"
	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/app/server"
	"github.com/cloudwego/hertz/pkg/common/hlog"
	"github.com/cloudwego/hertz/pkg/protocol"
)

func main() {
	h := server.Default()
	// GET http://127.0.0.1:8888/user?name=bar&age=&pets=dog&pets=cat
	h.GET(
		"/user", func(ctx context.Context, c *app.RequestContext) {
			args := c.QueryArgs()

			// 从args获取信息
			hlog.Info(args.String())                         // "name=bar&age=&pets=dog&pets=cat"
			hlog.Info(string(args.QueryString()))            // []byte("name=bar&age=&pets=dog&pets=cat")
			hlog.Info(string(args.AppendBytes([]byte(nil)))) // []byte("name=bar&age=&pets=dog&pets=cat")
			hlog.Info(string(args.Peek("name")))             // []byte("bar")
			hlog.Info(args.Has("name"))                      // true
			if age, hasAge := args.PeekExists("age"); hasAge {
				hlog.Info(string(age), hasAge) // age == "", hasAge == true
			}
			hlog.Info(args.Len()) // 4

			args.VisitAll(
				func(key, value []byte) {
                      // 自动遍历
					// 1. key == []byte("name"), value == []byte("bar")
					// 2. key == []byte("age"), value == nil
					// 3. key == []byte("pets"), value == []byte("dog")
					// 4. key == []byte("pets"), value == []byte("cat")
					hlog.Info(string(key), "==>", string(value))
				},
			)

			pets := args.PeekAll("pets") // pets == [][]byte{[]byte("dog"), []byte("cat")}
			for _, pet := range pets {
				hlog.Info(string(pet)) // dog, cat
			}

			// 将信息发送给io.Writer
			req := protocol.AcquireRequest()
			_, err := args.WriteTo(req.BodyWriter())
			if err != nil {
				hlog.Error(err)
			}
			// n == 31 err == nil
			hlog.Info(req.BodyBuffer().String()) // "name=bar&age=&pets=dog&pets=cat"

			// 更改args
			var newArgs protocol.Args
			args.CopyTo(&newArgs)

			newArgs.Set("version", "v1")
			hlog.Warn(string(newArgs.Peek("version"))) // []byte("v1")

			newArgs.Del("age")
			hlog.Warn(newArgs.Has("age")) // false

			newArgs.DelBytes([]byte("name"))
			hlog.Warn(newArgs.Has("name")) // false

			newArgs.Add("name", "foo")
			hlog.Warn(string(newArgs.Peek("name"))) // []byte("foo")

			newArgs.Reset()
			hlog.Warn("|" + newArgs.String() + "|") // ""

			// 解析 args
			var newArgs2 protocol.Args
			newArgs2.ParseBytes([]byte("name=bar&age=20"))
			hlog.Warn(newArgs2.String()) // "name=bar&age=20"
		},
	)

	h.Spin()
}

Get访问 http://127.0.0.1:8888/user?name=bar&age=&pets=dog&pets=cat

URI对象

通过URI方法获取一个Args 对象

签名是 func (ctx *RequestContext) URI() *protocol.URI

URI 对象提供了以下方法获取/设置 URI

函数签名 说明
func (u *URI) CopyTo(dst *URI) 拷贝 URI 对象的副本到 dst
func (u *URI) QueryArgs() *Args 获取 Args 对象
func (u *URI) Hash() []byte 获取 Hash 值,比如 http://example.com/user?baz=123#qwe 的 Hash 是 qwe
func (u *URI) SetHash(hash string) 设置 Hash
func (u *URI) SetHashBytes(hash []byte) 设置 []byte 类型 Hash
func (u *URI) Username() []byte 获取 Username
func (u *URI) SetUsername(username string) 设置 Username
func (u *URI) SetUsernameBytes(username []byte) 设置 []byte 类型 Username
func (u *URI) Password() []byte 获取 Password
func (u *URI) SetPassword(password string) 设置 Password
func (u *URI) SetPasswordBytes(password []byte) 设置 []byte 类型 Password
func (u *URI) QueryString() []byte 获取 Query String,比如 http://example.com/user?baz=123Query Stringbaz=123
func (u *URI) SetQueryString(queryString string) 设置 Query String,注意,在该方法之后使用 RequestHeader.SetRequestURI 可能会覆盖掉原来想设置的值
func (u *URI) SetQueryStringBytes(queryString []byte) 设置 []byte 类型的 Query String,注意,在该方法之后使用 RequestHeader.SetRequestURI 可能会覆盖掉原来想设置的值
func (u *URI) Path() []byte 获取 Path,比如 [http://example.com/user/he rtz](http://example.com/user/he rtz) 的 Path 是 /user/he rtz
func (u *URI) PathOriginal() []byte 获取未转义的 Path,比如 [http://example.com/user/he rtz](http://example.com/user/he rtz) 的 Path 是 /user/he%20rtz
func (u *URI) SetPath(path string) 设置 Path
func (u *URI) SetPathBytes(path []byte) 设置 []byte 类型 Path
func (u *URI) String() string 获取完整 URI 比如 http://example.com/user?baz=123 的完整 URI 是 http://example.com/user?baz=123
func (u *URI) FullURI() []byte 获取 []byte 类型的完整 URI
func (u *URI) Scheme() []byte 获取协议,如 http
func (u *URI) SetScheme(scheme string) 设置协议
func (u *URI) SetSchemeBytes(scheme []byte) 设置 []byte 类型的协议
func (u *URI) Host() []byte 获取 Host,比如 http://example.com/user 的 Host 是 example.com
func (u *URI) SetHost(host string) 设置 Host
func (u *URI) SetHostBytes(host []byte) 设置 []byte 类型 Host
func (u *URI) LastPathSegment() []byte 获取 Path 的最后一部分,比如 Path /foo/bar/baz.html 的最后一部分是 baz.html
func (u *URI) Update(newURI string) 更新 URI
func (u *URI) UpdateBytes(newURI []byte) 更新 []byte 类型的 URI
func (u *URI) Parse(host, uri []byte) 初始化 URI
func (u *URI) AppendBytes(dst []byte) []byte 将完整的 URI 赋值到 dst 中并返回 dst
func (u *URI) RequestURI() []byte 获取 RequestURI,比如 http://example.com/user?baz=123 的 RequestURI 是 /user?baz=123
func (u *URI) Reset() 重置 URI
package main

import (
	"context"
	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/app/server"
	"github.com/cloudwego/hertz/pkg/common/hlog"
	"github.com/cloudwego/hertz/pkg/protocol"
)

func main() {
	h := server.Default()
	// GET http://127.0.0.1:8888/user?name=bar&age=aaa
	h.GET(
		"/user", func(ctx context.Context, c *app.RequestContext) {
			u := c.URI()
			// CopyTo: 将当前URI对象复制到另一个URI对象
			dst := &protocol.URI{}
			u.CopyTo(dst)

			hlog.Info("初始化前:", dst.String()) // 初始化前:http://127.0.0.1:8888/user?name=bar&age=aaa
			// 使用Parse初始化URI对象,这里我们用一个简单的URL进行初始化
			dst.Parse([]byte("example.com"), []byte("/user?baz=123#qwe"))
			hlog.Info("初始化后:", dst.String()) // 初始化后:http://example.com/user?baz=123#qwe

			// QueryArgs: 获取Args对象,用于查询参数操作(这里不详细展开Args对象的操作)
			args := u.QueryArgs()
			hlog.Info(args.Len()) // 2
			args.VisitAll(
				func(key, value []byte) {
					hlog.Infof("key: %s ==> value: %s", key, value)
					// key: name ==> value: bar
					// key: age ==> value: aaa
				},
			)

			// SetHash: 设置新的Hash值
			u.SetHash("newhash")
			// Hash: 获取Hash值
			hlog.Info("Hash:", string(u.Hash())) // Hash: newhash
			// SetHashBytes: 以[]byte形式设置Hash值
			u.SetHashBytes([]byte("newhashbytes"))
			// Username 和 Password: 获取和设置用户名和密码
			u.SetUsername("myusername")
			hlog.Info("Username:", string(u.Username())) // Username: myusername
			u.SetPassword("mypassword")
			hlog.Info("Password:", string(u.Password())) // Password: mypassword

			// SetUsernameBytes 和 SetPasswordBytes: 以[]byte形式设置用户名和密码
			u.SetUsernameBytes([]byte("myusernamebytes"))
			u.SetPasswordBytes([]byte("mypasswordbytes"))

			// QueryString: 获取查询字符串
			hlog.Info("QueryString:", string(u.QueryString())) // QueryString: name=bar&age=aaa
			// SetQueryString 和 SetQueryStringBytes: 设置查询字符串
			u.SetQueryString("new=query&string")
			u.SetQueryStringBytes([]byte("new=querybytes&string"))
			// Path 和 PathOriginal: 获取路径和原始未转义的路径
			hlog.Info("Path:", string(u.Path()))                 // Path: /user
			hlog.Info("PathOriginal:", string(u.PathOriginal())) // PathOriginal: /user

			// SetPath 和 SetPathBytes: 设置路径
			u.SetPath("/new/path")
			u.SetPathBytes([]byte("/new/pathbytes"))

			// String 和 FullURI: 获取完整URI
			hlog.Info("String:", u.String()) // String:http://127.0.0.1:8888/new/pathbytes?name=bar&age=aaa#newhashbytes
			hlog.Info(
				"FullURI:",
				string(u.FullURI()),
			) // FullURI:http://127.0.0.1:8888/new/pathbytes?name=bar&age=aaa#newhashbytes

			// Scheme: 获取协议
			hlog.Info("Scheme:", string(u.Scheme())) // Scheme:http

			// SetScheme 和 SetSchemeBytes: 设置协议
			u.SetScheme("https")
			u.SetSchemeBytes([]byte("httpsbytes"))

			// Host: 获取主机名
			hlog.Info("Host:", string(u.Host())) // Host:127.0.0.1:8888
			hlog.Info(u.String())                // httpsbytes://127.0.0.1:8888/new/pathbytes?name=bar&age=aaa#newhashbytes
			// SetHost 和 SetHostBytes: 设置主机名
			u.SetHost("new.example.com")
			u.SetHostBytes([]byte("new.example.combytes"))

			// LastPathSegment: 获取路径的最后一部分
			hlog.Info("LastPathSegment:", string(u.LastPathSegment())) // LastPathSegment:pathbytes
			hlog.Info(u.String())                                      // httpsbytes://new.example.combytes/new/pathbytes?name=bar&age=aaa#newhashbytes
			// Update 和 UpdateBytes: 更新整个URI
			u.Update("http://updated.uri/newpath?query=newvalue#newhash")
			u.UpdateBytes([]byte("http://updated.uri/newpathbytes?query=newvaluebytes#newhashbytes"))

			// 将完整的 URI 赋值到 dst 中并返回 dst
			appendedURI := u.AppendBytes([]byte{})
			hlog.Info(
				"Appended URI:",
				string(appendedURI),
			) // Appended URI:http://updated.uri/newpathbytes?query=newvaluebytes#newhashbytes

			// Reset: 重置URI对象
			u.Reset()
			hlog.Info(u.String()) // http:///
		},
	)

	h.Spin()
}

Header

// RequestHeader 相关方法
// Add: 向请求头中添加键值对(不覆盖)
func (h *RequestHeader) Add(key, value string)

// Set: 设置请求头的键值对(覆盖)只覆盖第一个
func (h *RequestHeader) Set(key, value string)

// Header: 返回请求头的原始字节数据
func (h *RequestHeader) Header() []byte

// String: 返回请求头的字符串表示
func (h *RequestHeader) String() string

// VisitAll: 遍历请求头中的所有键值对
func (h *RequestHeader) VisitAll(f func(key, value []byte))



// RequestContext 相关方法
// IsGet: 判断是否为 GET 请求
func (ctx *RequestContext) IsGet() bool

// IsHead: 判断是否为 HEAD 请求
func (ctx *RequestContext) IsHead() bool

// IsPost: 判断是否为 POST 请求
func (ctx *RequestContext) IsPost() bool

// Method: 返回请求的 HTTP 方法
func (ctx *RequestContext) Method() []byte

// ContentType: 返回请求的 Content-Type 头
func (ctx *RequestContext) ContentType() []byte

// IfModifiedSince: 检查 If-Modified-Since 是否早于指定时间
func (ctx *RequestContext) IfModifiedSince(lastModified time.Time) bool

// Cookie: 返回指定键的 Cookie 值
func (ctx *RequestContext) Cookie(key string) []byte

// UserAgent: 返回请求的 User-Agent 头
func (ctx *RequestContext) UserAgent() []byte

// GetHeader: 返回指定键的请求头值
func (ctx *RequestContext) GetHeader(key string) []byte
  • Add:添加或设置键为 key 的 Header

    注意:

    Add 通常用于为同一个 Key 设置多个 Header,若要为同一个 Key 设置单个 Header 请使用 Set

    当作用于 Content-Type, Content-Length, Connection, Cookie, Transfer-Encoding, Host, User-Agent 这些 Header 时,使用多个 Add 会覆盖掉旧值。

  • Set:设置 Header 键值 只覆盖第一个值,后面有多的值,不会动

    注意:Set 通常用于为同一个 Key 设置单个 Header,若要为同一个 Key 设置多个 Header 请使用 Add

  • Header:获取 []byte 类型的完整的 Header

  • String:获取完整的 Header

  • VisitAll:遍历所有 Header 的键值并执行 f 函数

  • Method:获取请求方法的类型

  • ContentType:获取请求头 Content-Type 的值

  • IfModifiedSince:判断时间是否超过请求头 If-Modified-Since 的值。

    注意:如果请求头不包含 If-Modified-Since 也返回 true。

  • Cookie:获取请求头 Cookie 中 key 的值。

  • UserAgent:获取请求头 User-Agent 的值

  • GetHeader:获取请求头中 key 的值

package main

import (
	"context"
	"github.com/cloudwego/hertz/pkg/app"
	"github.com/cloudwego/hertz/pkg/app/server"
	"github.com/cloudwego/hertz/pkg/common/hlog"
)

func main() {
	h := server.Default()
	// GET http://127.0.0.1:8888/user
	h.GET(
		"/user", func(ctx context.Context, c *app.RequestContext) {
			// 不覆盖的添加头
			c.Request.Header.Add("hertz1", "value1")
			c.Request.Header.Add("hertz1", "value2")
			c.Request.Header.SetContentTypeBytes([]byte("application/x-www-form-urlencoded"))
			// 获取所有hertz1的值
			hertz1 := c.Request.Header.GetAll("hertz1")
			hlog.Debugf("hertz1: %#v", hertz1) // hertz1: []string{"value1", "value2"}
			// 覆盖的添加头  只覆盖第一个
			c.Request.Header.Set("hertz1", "value3")
			hertz1 = c.Request.Header.GetAll("hertz1")
			hlog.Debugf("hertz1: %#v", hertz1) //  hertz1: []string{"value3", "value2"}
			c.Request.Header.Set("hertz1", "value4")
			hertz1 = c.Request.Header.GetAll("hertz1")
			hlog.Debugf("hertz1: %#v", hertz1) //  hertz1: []string{"value4", "value2"}

			// 获取所有header
			hs := c.Request.Header.Header()
			hlog.Debugf("所有的header: %s", string(hs))
			// 所有的header: []byte(" GET /user HTTP/1.1
			// Host: 127.0.0.1:8888
			// Content-Type: application/x-www-form-urlencoded
			// Authorization: Basic am9objoxMjM0NTY=
			// key1=value; key2=value
			// Accept: */*
			// Postman-Token: fe9527ee-d0a0-4dc0-ad23-966a2aae3014
			// Accept-Encoding: gzip, deflate, br
			// Connection: keep-alive
			// Hertz1: value4
			// Hertz1: value2")

			hlog.Debug(c.Request.Header.String())
			// GET /user HTTP/1.1
			// Host: 127.0.0.1:8888
			// Content-Type: application/x-www-form-urlencoded
			// Authorization: Basic am9objoxMjM0NTY=
			// key1=value; key2=value
			// Accept: */*
			// Postman-Token: fe9527ee-d0a0-4dc0-ad23-966a2aae3014
			// Accept-Encoding: gzip, deflate, br
			// Connection: keep-alive
			// Hertz1: value4
			// Hertz1: value2

			// 遍历所有header
			c.Request.Header.VisitAll(
				func(key, value []byte) {
					hlog.Debugf("%s ===> %s", string(key), string(value))
					// Host ===> 127.0.0.1:8888
					// Content-Type ===> application/x-www-form-urlencoded
					// User-Agent ===> PostmanRuntime/7.26.10
					// Authorization ===> Basic am9objoxMjM0NTY=
					// Cookie ===> key1=value; key2=value
					// Accept ===> */*
					// Postman-Token ===> 770742e2-7aa2-4405-80a5-10ca77d3527e
					// Accept-Encoding ===> gzip, deflate, br
					// Connection ===> keep-alive
					// Hertz1 ===> value4
					// Hertz1 ===> value2
				},
			)

			// 设置ContentType
			c.Request.Header.SetContentTypeBytes([]byte("application/x-www-form-urlencoded"))
			hlog.Debugf("ContentType为:%s", string(c.ContentType())) // ContentType为:application/x-www-form-urlencoded
			c.Request.Header.Add("Content-Type", "text/plain")
			hlog.Debugf("ContentType为:%s", string(c.ContentType())) // ContentType为:text/plain

			// RequestContext相关
			hlog.Debugf("是否为Get请求:%t", c.IsGet())
			hlog.Debugf("是否为head请求:%t", c.IsHead())
			hlog.Debugf("是否为POST请求:%t", c.IsPost())

			hlog.Debugf("请求方法为:%s", string(c.Method())) // 请求方法为:GET

			hlog.Debugf("Cookie key1  值为:%s", string(c.Cookie("key1")))
			hlog.Debugf("Cookie key2  值为:%s", string(c.Cookie("key2")))
			// Cookie key1  值为:value
			// Cookie key2  值为:value

			hlog.Debugf("UserAgent为:%s", string(c.UserAgent()))
			// UserAgent为:PostmanRuntime/7.26.10

			hlog.Debugf("请求头中 Connection 的值为:%s", string(c.GetHeader("Connection")))
			// 请求头中 Connection 的值为:keep-alive

		},
	)

	h.Spin()
}

RequestHeader 对象

使用 RequestContext.Request.Header 获取 RequestHeader 对象,该对象提供了以下方法获取/设置请求头部。

函数签名 说明
func (h *RequestHeader) Method() []byte 获取 Method
func (h *RequestHeader) SetMethod(method string) 设置 Method
func (h *RequestHeader) SetMethodBytes(method []byte) 设置 []byte 类型的 Method
func (h *RequestHeader) IsGet() bool 判断 Method 是否是 GET
func (h *RequestHeader) IsHead() bool 判断 Method 是否是 HEAD
func (h *RequestHeader) IsPost() bool 判断 Method 是否是 POST
func (h *RequestHeader) IsPut() bool 判断 Method 是否是 PUT
func (h *RequestHeader) IsDelete() bool 判断 Method 是否是 DELETE
func (h *RequestHeader) IsConnect() bool 判断 Method 是否是 CONNECT
func (h *RequestHeader) IsOptions() bool 判断 Method 是否是 OPTIONS
func (h *RequestHeader) IsTrace() bool 判断 Method 是否是 TRACE
func (h *RequestHeader) IgnoreBody() bool 判断是否忽略 Body (Method GET/HEAD 忽略 Body)
func (h *RequestHeader) RequestURI() []byte 获取 RequestURI
func (h *RequestHeader) SetRequestURI(requestURI string) 设置 RequestURI
func (h *RequestHeader) SetRequestURIBytes(requestURI []byte) 设置 []byte 类型的 RequestURI
func (h *RequestHeader) SetProtocol(p string) 设置协议类型,比如 HTTP/1.0
func (h *RequestHeader) GetProtocol() string 获取协议类型,比如 HTTP/1.1
func (h *RequestHeader) IsHTTP11() bool 判断是否是 HTTP/1.1
func (h *RequestHeader) SetNoHTTP11(b bool) 设置是否不是 HTTP/1.1
func (h *RequestHeader) Host() []byte 获取 Host
func (h *RequestHeader) SetHost(host string) 设置 Host
func (h *RequestHeader) SetHostBytes(host []byte) 设置 []byte 类型的 Host
func (h *RequestHeader) ContentLength() int 获取 Content-Length
func (h *RequestHeader) ContentLengthBytes() []byte 获取 []byte 类型的 Content-Length
func (h *RequestHeader) SetContentLength(contentLength int) 设置 Content-Length
func (h *RequestHeader) SetContentLengthBytes(contentLength []byte) 设置 []byte 类型的 Content-Length
func (h *RequestHeader) InitContentLengthWithValue(contentLength int) 初始化 Content-Length
func (h *RequestHeader) ContentType() []byte 获取 Content-Type
func (h *RequestHeader) SetContentTypeBytes(contentType []byte) 设置 Content-Type
func (h *RequestHeader) SetNoDefaultContentType(b bool) 控制未指定 Content-Type 时的默认发送行为,false 发送默认 Content-Type 的值,true 不发送 Content-Type
func (h *RequestHeader) UserAgent() []byte 获取 User-Agent
func (h *RequestHeader) SetUserAgentBytes(userAgent []byte) 设置 User-Agent
func (h *RequestHeader) ConnectionClose() bool 判断是否包含 Connection: close
func (h *RequestHeader) SetConnectionClose(close bool) 设置 connectionClose 标志
func (h *RequestHeader) ResetConnectionClose() 重置 connectionClose 标志为 false 并删除 Connection Header
func (h *RequestHeader) SetByteRange(startPos, endPos int) 设置 Range (Range: bytes=startPos-endPos)
func (h *RequestHeader) SetMultipartFormBoundary(boundary string) 当 Content-Type=multipart/form-data 时为其设置 boundary
func (h *RequestHeader) MultipartFormBoundary() []byte 获取 boundary 的值
func (h *RequestHeader) Trailer() *Trailer 获取 Trailer
func (h *RequestHeader) Cookie(key string) []byte 获取 Cookie 键为 key 的值
func (h *RequestHeader) SetCookie(key, value string) 设置 Cookie 的键值
func (h *RequestHeader) DelCookie(key string) 删除键为 key 的 Cookie
func (h *RequestHeader) DelAllCookies() 删除所有 Cookie
func (h *RequestHeader) FullCookie() []byte 获取所有 Cookie
func (h *RequestHeader) Cookies() []*Cookie 获取所有 Cookie 对象
func (h *RequestHeader) VisitAllCookie(f func(key, value []byte)) 遍历所有 Cookie 的键值并执行 f 函数
func (h *RequestHeader) Peek(key string) []byte 获取 []byte 类型的键为 key 的值
func (h *RequestHeader) Get(key string) string 获取键为 key 的值
func (h *RequestHeader) PeekArgBytes(key []byte) []byte 获取键为 key 的值
func (h *RequestHeader) PeekAll(key string) [][]byte 获取 []byte 类型的键为 key 的所有值(用于获取存在相同 key 的多个值)
func (h *RequestHeader) GetAll(key string) []string 获取键为 key 的所有值
func (h *RequestHeader) PeekIfModifiedSinceBytes() []byte 获取 If-Modified-Since
func (h *RequestHeader) PeekContentEncoding() []byte 获取 Content-Encoding
func (h *RequestHeader) PeekRange() []byte 获取 Range
func (h *RequestHeader) HasAcceptEncodingBytes(acceptEncoding []byte) bool 判断是否存在 Accept-Encoding 以及 Accept-Encoding 是否包含 acceptEncoding
func (h *RequestHeader) RawHeaders() []byte 获取原始 Header
func (h *RequestHeader) SetRawHeaders(r []byte) 设置原始 Header
func (h *RequestHeader) Add(key, value string) 添加或设置键为 key 的 Header,用于为同一个 Key 设置多个 Header,但 key 会覆盖以下 Header: Content-Type, Content-Length, Connection, Cookie, Transfer-Encoding, Host, User-Agent
func (h *RequestHeader) InitBufValue(size int) 初始化缓冲区大小
func (h *RequestHeader) GetBufValue() []byte 获取缓冲区的值
func (h *RequestHeader) SetCanonical(key, value []byte) 设置 Header 键值,假设该键是规范形式
func (h *RequestHeader) Set(key, value string) 设置 Header 键值,用于为同一个 Key 设置单个 Header
func (h *RequestHeader) SetBytesKV(key, value []byte) 设置 []byte 类型的 Header 键值,用于为同一个 Key 设置单个 Header
func (h *RequestHeader) DelBytes(key []byte) 删除 Header 中键为 key 的键值对
func (h *RequestHeader) AddArgBytes(key, value []byte, noValue bool) 添加 Header 键值(与 Add 不同,key 一定不会被规范化且 key 为 Content-Type, Content-Length, Connection, Cookie, Transfer-Encoding, Host, User-Agent 时不会做特殊处理)
func (h *RequestHeader) SetArgBytes(key, value []byte, noValue bool) 设置 Header 键值(与 Set 不同,key 一定不会被规范化且 key 为 Content-Type, Content-Length, Connection, Cookie, Transfer-Encoding, Host, User-Agent 时不会做特殊处理)
func (h *RequestHeader) AppendBytes(dst []byte) []byte 将完整的 Header 附加到 dst 中并返回
func (h *RequestHeader) Header() []byte 获取 []byte 类型的完整的 Header
func (h *RequestHeader) String() string 获取完整的 Header
func (h *RequestHeader) CopyTo(dst *RequestHeader) 获取 RequestHeader 的副本
func (h *RequestHeader) VisitAll(f func(key, value []byte)) 遍历所有 Header 的键值并执行 f 函数
func (h *RequestHeader) VisitAllCustomHeader(f func(key, value []byte)) 遍历所有 Header 的键值并执行 f 函数,以下 key 除外:Content-Type, Content-Length, Cookie, Host, User-Agent)
func (h *RequestHeader) Len() int 返回 Header 的数量
func (h *RequestHeader) DisableNormalizing() 禁用 Header 名字的规范化 (首字母和破折号后第一个字母大写)
func (h *RequestHeader) IsDisableNormalizing() bool 是否禁用 Header 名字的规范化,默认不禁用
func (h *RequestHeader) ResetSkipNormalize() 重置 Headers,除了 disableNormalizing 状态
func (h *RequestHeader) Reset() 重置 Headers

Body

// 获取请求的原始数据(字节形式),通常用于处理非文本类型的请求体,比如文件上传。
func (ctx *RequestContext) GetRawData() []byte

// 获取请求体的内容(字节形式),如果读取过程中出错,会返回错误信息。
func (ctx *RequestContext) Body() ([]byte, error)

// 获取请求体的流式读取器(io.Reader),适用于处理大文件或流式数据。
func (ctx *RequestContext) RequestBodyStream() io.Reader

// 解析并返回 multipart/form-data 类型的表单数据,通常用于文件上传。
func (ctx *RequestContext) MultipartForm() (*multipart.Form, error)

// 获取表单中指定 key 的值(字符串形式),如果 key 不存在,返回空字符串。
func (ctx *RequestContext) PostForm(key string) string

// 获取表单中指定 key 的值(字符串形式),如果 key 不存在,返回默认值 defaultValue。
func (ctx *RequestContext) DefaultPostForm(key, defaultValue string) string

// 获取表单中指定 key 的值(字符串形式),并返回一个布尔值表示该 key 是否存在。
func (ctx *RequestContext) GetPostForm(key string) (string, bool)

// 获取 POST 请求的表单参数,返回一个包含所有参数的 Args 对象。
func (ctx *RequestContext) PostArgs() *protocol.Args

// 获取表单中指定 key 的值(字节形式),适用于处理二进制数据。
func (ctx *RequestContext) FormValue(key string) []byte

// 设置一个自定义函数,用于从请求中提取表单值。可以用于扩展或自定义表单值的解析逻辑。
func (ctx *RequestContext) SetFormValueFunc(f FormValueFunc)

Body

获取请求的 body 数据,如果发生错误返回 error。

函数签名:

func (ctx *RequestContext) Body() ([]byte, error)

示例:

// POST http://example.com/pet
// Content-Type: application/json
// {"pet":"cat"}
h.Post("/pet", func(ctx context.Context, c *app.RequestContext) {
    data, err := c.Body() // data == []byte("{\"pet\":\"cat\"}") , err == nil
})

RequestBodyStream

获取请求的 BodyStream。

函数签名:

func (ctx *RequestContext) RequestBodyStream() io.Reader

示例:

// POST http://example.com/user
// Content-Type: text/plain
// abcdefg
h := server.Default(server.WithStreamBody(true))
h.Post("/user", func(ctx context.Context, c *app.RequestContext) {
    sr := c.RequestBodyStream()
    data, _ := io.ReadAll(sr) // data == []byte("abcdefg")
})

MultipartForm

获取 multipart.Form 对象,(详情请参考 multipart#Form)

注意:此函数既可以获取普通值也可以获取文件,此处给出了获取普通值的示例代码,获取文件的示例代码可参考 MultipartForm

函数签名:

func (ctx *RequestContext) MultipartForm() (*multipart.Form, error)

示例:

// POST http://example.com/user
// Content-Type: multipart/form-data;
// Content-Disposition: form-data; name="name"
// tom
h.POST("/user", func(ctx context.Context, c *app.RequestContext) {
    form, err := c.MultipartForm()
    name := form.Value["name"][0] // name == "tom"
})

PostForm

按名称检索 multipart.Form.Value,返回给定 name 的第一个值。

注意:该函数支持从 application/x-www-form-urlencoded 和 multipart/form-data 这两种类型的 content-type 中获取 value 值,且不支持获取文件值。

函数签名:

func (ctx *RequestContext) PostForm(key string) string

示例:

// POST http://example.com/user
// Content-Type: multipart/form-data;
// Content-Disposition: form-data; name="name"
// tom
h.POST("/user", func(ctx context.Context, c *app.RequestContext) {
    name := c.PostForm("name") // name == "tom"
})

DefaultPostForm

按名称检索 multipart.Form.Value,返回给定 name 的第一个值,如果不存在返回 defaultValue。

注意:该函数支持从 application/x-www-form-urlencoded 和 multipart/form-data 这两种类型的 content-type 中获取 value 值,且不支持获取文件值。

函数签名:

func (ctx *RequestContext) DefaultPostForm(key, defaultValue string) string

示例:

// POST http://example.com/user
// Content-Type: multipart/form-data;
// Content-Disposition: form-data; name="name"
// tom
h.POST("/user", func(ctx context.Context, c *app.RequestContext) {
    name := c.PostForm("name", "jack") // name == "tom"
    age := c.PostForm("age", "10") // age == "10"
})

PostArgs

获取 application/x-www-form-urlencoded 参数对象。(详情请参考 Args 对象)

函数签名:

func (ctx *RequestContext) PostArgs() *protocol.Args

示例:

// POST http://example.com/user
// Content-Type: application/x-www-form-urlencoded
// name=tom&pet=cat&pet=dog
h.POST("/user", func(ctx context.Context, c *app.RequestContext) {
    args := c.PostArgs()
    name := args.Peek("name") // name == "tom"

    var pets []string
    args.VisitAll(func(key, value []byte) {
        if string(key) == "pet" {
        pets = append(pets, string(value))
        }
    })
    // pets == []string{"cat", "dog"}
})

FormValue

按照以下顺序获取 key 的值。

  1. QueryArgs 中获取值。
  2. PostArgs 中获取值。
  3. MultipartForm 中获取值。

函数签名:

func (ctx *RequestContext) FormValue(key string) []byte

示例:

// POST http://example.com/user?name=tom
// Content-Type: application/x-www-form-urlencoded
// age=10
h.POST("/user", func(ctx context.Context, c *app.RequestContext) {
    name := c.FormValue("name") // name == []byte("tom"), get by QueryArgs
    age := c.FormValue("age") // age == []byte("10"), get by PostArgs
})

// POST http://example.com/user
// Content-Type: multipart/form-data;
// Content-Disposition: form-data; name="name"
// tom
h.POST("/user", func(ctx context.Context, c *app.RequestContext) {
    name := c.FormValue("name") // name == []byte("tom"), get by MultipartForm
})

SetFormValueFunc

若 FormValue 函数提供的默认获取 key 的值的方式不满足需求,用户可以使用该函数自定义获取 key 的值的方式。

函数签名:

func (ctx *RequestContext) SetFormValueFunc(f FormValueFunc)

示例:

// POST http://example.com/user?name=tom
// Content-Type: multipart/form-data;
// Content-Disposition: form-data; name="age"
// 10
h.POST("/user", func(ctx context.Context, c *app.RequestContext) {
    // only return multipart form value
    c.SetFormValueFunc(func(rc *app.RequestContext, s string) []byte {
        mf, err := rc.MultipartForm()
        if err == nil && mf.Value != nil {
            vv := mf.Value[s]
            if len(vv) > 0 {
                return []byte(vv[0])
            }
        }
        return nil
    })

    name := c.FormValue("name") // name == nil
    age := c.FormValue("age")   // age == []byte("10")
})

文件操作

func (ctx *RequestContext) MultipartForm() (*multipart.Form, error)
func (ctx *RequestContext) FormFile(name string) (*multipart.FileHeader, error)
func (ctx *RequestContext) SaveUploadedFile(file *multipart.FileHeader, dst string) error

MultipartForm

获取 multipart.Form 对象。(详情请参考 multipart#Form)

函数签名:

func (ctx *RequestContext) MultipartForm() (*multipart.Form, error)

示例:

// POST http://example.com/user
// Content-Type: multipart/form-data;
// Content-Disposition: form-data; name="avatar"; filename="abc.jpg"
h.POST("/user", func(ctx context.Context, c *app.RequestContext) {
    form, err := c.MultipartForm()
    avatarFile := form.File["avatar"][0] // avatarFile.Filename == "abc.jpg"
})

FormFile

按名称检索 multipart.Form.File,返回给定 name 的第一个 multipart.FileHeader。(详情请参考 multipart#FileHeader)

函数签名:

func (ctx *RequestContext) FormFile(name string) (*multipart.FileHeader, error)

示例:

// POST http://example.com/user
// Content-Type: multipart/form-data;
// Content-Disposition: form-data; name="avatar"; filename="abc.jpg"
h.Post("/user", func(ctx context.Context, c *app.RequestContext) {
    avatarFile, err := c.FormFile("avatar") // avatarFile.Filename == "abc.jpg", err == nil
})

SaveUploadedFile

保存 multipart 文件到磁盘。

函数签名:

func (ctx *RequestContext) SaveUploadedFile(file *multipart.FileHeader, dst string) error

示例:

// POST http://example.com/user
// Content-Type: multipart/form-data;
// Content-Disposition: form-data; name="avatar"; filename="abc.jpg"
h.Post("/user", func(ctx context.Context, c *app.RequestContext) {
    avatarFile, err := c.FormFile("avatar") // avatarFile.Filename == "abc.jpg", err == nil
    // save file
    c.SaveUploadedFile(avatarFile, avatarFile.Filename) // save file "abc.jpg"
})

RequestContext 元数据存储

注意:

RequestContext 在请求结束后会被回收,元数据会被置为 nil。

函数签名 说明
func (ctx *RequestContext) Set(key string, value interface{}) 在上下文中存储键值对
func (ctx *RequestContext) Value(key interface{}) interface{} 获取上下文键为 key 的值
func (ctx *RequestContext) Get(key string) (value interface{}, exists bool) 获取上下文键为 key 的值以及 key 是否存在
func (ctx *RequestContext) MustGet(key string) interface{} 获取上下文键为 key 的值,如果不存在会发生 panic
func (ctx *RequestContext) GetString(key string) (s string) 获取上下文键为 key 的值,并转换为 string 类型
func (ctx *RequestContext) GetBool(key string) (b bool) 获取上下文键为 key 的值,并转换为 bool 类型
func (ctx *RequestContext) GetInt(key string) (i int) 获取上下文键为 key 的值,并转换为 int 类型
func (ctx *RequestContext) GetInt32(key string) (i32 int32) 获取上下文键为 key 的值,并转换为 int32 类型
func (ctx *RequestContext) GetInt64(key string) (i64 int64) 获取上下文键为 key 的值,并转换为 int64 类型
func (ctx *RequestContext) GetUint(key string) (ui uint) 获取上下文键为 key 的值,并转换为 uint 类型
func (ctx *RequestContext) GetUint32(key string) (ui32 uint32) 获取上下文键为 key 的值,并转换为 uint32 类型
func (ctx *RequestContext) GetUint64(key string) (ui64 uint64) 获取上下文键为 key 的值,并转换为 uint64 类型
func (ctx *RequestContext) GetFloat32(key string) (f32 float32) 获取上下文键为 key 的值,并转换为 float32 类型
func (ctx *RequestContext) GetFloat64(key string) (f64 float64) 获取上下文键为 key 的值,并转换为 float64 类型
func (ctx *RequestContext) GetTime(key string) (t time.Time) 获取上下文键为 key 的值,并转换为 time.Time 类型
func (ctx *RequestContext) GetDuration(key string) (d time.Duration) 获取上下文键为 key 的值,并转换为 time.Duration 类型
func (ctx *RequestContext) GetStringSlice(key string) (ss []string) 获取上下文键为 key 的值,并转换为 []string 类型
func (ctx *RequestContext) GetStringMap(key string) (sm map[string]interface{}) 获取上下文键为 key 的值,并转换为 map[string]interface{} 类型
func (ctx *RequestContext) GetStringMapString(key string) (sms map[string]string) 获取上下文键为 key 的值,并转换为 map[string]string 类型
func (ctx *RequestContext) GetStringMapStringSlice(key string) (smss map[string][]string) 获取上下文键为 key 的值,并转换为 map[string][]string 类型
func (ctx *RequestContext) ForEachKey(fn func(k string, v interface{})) 为上下文中的每个键值对调用 fn

示例:

h.GET(
	"/user", func(ctx context.Context, c *app.RequestContext) {
		c.Set("version1", "v1")
		v := c.Value("version1") // v == interface{}(string) "v1"
        
		c.Set("version2", "v2")
		v, exists := c.Get("version2") // v == interface{}(string) "v2", exists == true
		v, exists = c.Get("pet")       // v == interface{} nil, exists == false
        
		c.Set("version3", "v3")
		v := c.MustGet("version3") // v == interface{}(string) "v3"
        
		c.Set("version4", "v4")
		vString := c.GetString("version4") // vString == "v4"
        
		c.Set("isAdmin", true)
		vBool := c.GetBool("isAdmin") // vBool == true
        
		c.Set("age1", 20)
		vInt := c.GetInt("age1") // vInt == 20
        
		c.Set("age2", int32(20))
		vInt32 := c.GetInt32("age2") // vInt32 == 20
        
		c.Set("age3", int64(20))
		vInt64 := c.GetInt64("age3") // vInt64 == 20
        
		c.Set("age4", uint(20))
		vUInt := c.GetUint("age4") // vUInt == 20
        
		c.Set("age5", uint32(20))
		vUInt32 := c.GetUint32("age5") // vUInt32 == 20
        
		c.Set("age6", uint64(20))
		vUInt64 := c.GetUint64("age6") // vUInt64 == 20
        
		c.Set("age7", float32(20.1))
		vFloat32 := c.GetFloat32("age7") // vFloat32 == 20.1
        
		c.Set("age8", 20.1)
		vFloat64 := c.GetFloat64("age8") // vFloat64 == 20.1
        
		t2022, _ := time.Parse(time.RFC1123, "Wed, 21 Oct 2022 07:28:00 GMT")
		c.Set("birthday", t2022)
		vTime := c.GetTime("birthday") // vTime == t2022
        
		c.Set("duration", time.Minute)
		vDuration := c.GetDuration("duration") // vDuration == time.Minute
        
		c.Set("pet", []string{"cat", "dog"})
		vStringSlice := c.GetStringSlice("pet") // vStringSlice == []string{"cat", "dog"}
        
		c.Set("info1", map[string]interface{}{"name": "tom"})
		vStringMap := c.GetStringMap("info1") // vStringMap == map[string]interface{}{"name": "tom"}
        
		c.Set("info2", map[string]string{"name": "tom"})
		vStringMapString := c.GetStringMapString("info2")
		// vStringMapString == map[string]string{}{"name": "tom"}
        
		c.Set("smss", map[string][]string{"pets": {"cat", "dog"}})
		vStringMapStringSlice := c.GetStringMapStringSlice("smss")
		// vStringMapStringSlice == map[string][]string{"pets": {"cat", "dog"}}
        
		c.Set("duration", time.Minute)
		c.Set("version", "v1")
		c.ForEachKey(
			func(k string, v interface{}) {
				// 1. k == "duration", v == interface{}(time.Duration) time.Minute
				// 2. k == "version", v == interface{}(string) "v1"
			},
		)
	},
)

Handler

// 继续执行下一个 Handler(通常在中间件中使用),用于将控制权交给后续的 Handler。
func (ctx *RequestContext) Next(c context.Context)

// 获取当前请求的 Handler 链(即所有待执行的 Handler 列表)。
func (ctx *RequestContext) Handlers() HandlersChain

// 获取当前正在执行的 Handler 函数。
func (ctx *RequestContext) Handler() HandlerFunc

// 设置当前请求的 Handler 链(覆盖原有的 Handler 链)。
func (ctx *RequestContext) SetHandlers(hc HandlersChain)

// 获取当前正在执行的 Handler 的名称(通常是函数名),用于调试或日志记录。
func (ctx *RequestContext) HandlerName() string

// 获取当前执行的 Handler 在 Handler 链中的索引位置(从 0 开始)。
func (ctx *RequestContext) GetIndex() int8

// 终止当前请求的处理流程,后续的 Handler 将不会被执行(通常在中间件中使用)。
func (ctx *RequestContext) Abort()

// 检查当前请求是否已被终止(即是否调用了 Abort())。
func (ctx *RequestContext) IsAborted() bool
  1. 中间件中使用 NextAbort:

    func AuthMiddleware() app.HandlerFunc {
        return func(c context.Context, ctx *app.RequestContext) {
            if !checkAuth(ctx) {
                ctx.Abort() // 验证失败,终止请求
                ctx.String(401, "Unauthorized")
                return
            }
            ctx.Next(c) // 验证通过,继续执行下一个 Handler
        }
    }
    
  2. 动态修改 Handler 链:

    func DynamicHandler() app.HandlerFunc {
        return func(c context.Context, ctx *app.RequestContext) {
            if someCondition {
                newHandlers := someNewHandlersChain
                ctx.SetHandlers(newHandlers) // 动态替换 Handler 链
            }
            ctx.Next(c)
        }
    }
    
  3. 调试 Handler 执行流程:

    func DebugMiddleware() app.HandlerFunc {
        return func(c context.Context, ctx *app.RequestContext) {
            fmt.Printf("Current Handler: %s, Index: %d\n", ctx.HandlerName(), ctx.GetIndex())
            ctx.Next(c)
        }
    }
    

参数绑定与校验

https://www.cloudwego.io/zh/docs/hertz/tutorials/basic-feature/binding-and-validate/

Hertz 支持的参数绑定与校验相关功能及用法。

func (ctx *RequestContext) Bind(obj interface{}) error
func (ctx *RequestContext) Validate(obj interface{}) error
func (ctx *RequestContext) BindAndValidate(obj interface{}) error

使用方法

func main() {
	r := server.New()

    r.GET("/hello", func(ctx context.Context, c *app.RequestContext) {
        // 参数绑定需要配合特定的 go tag 使用
		type Test struct {
            A string `query:"a" vd:"$!='Hertz'"`
        }

        // BindAndValidate
        var req Test
        err := c.BindAndValidate(&req)

        ...

	    // Bind 只做参数绑定
        req = Test{}
        err = c.Bind(&req)

        ...

        // Validate,需要使用 "vd" tag
        err = c.Validate(&req)

        ...
    })
...
}

全部 API

hertz version >= v0.7.0

API 说明
ctx.BindAndValidate 利用下述的 go-tag 进行参数绑定,并在绑定成功后做一次参数校验 (如果有校验 tag 的话)
ctx.Bind BindAndValidate 但是不做参数校验
ctx.BindQuery 绑定所有 Query 参数,相当于给每一个 field 声明一个 query tag,适用于没写 tag 的场景
ctx.BindHeader 绑定所有 Header 参数,相当于给每一个 field 声明一个 header tag,适用于没写 tag 的场景
ctx.BindPath 绑定所有 Path 参数,相当于给每一个 field 声明一个 path tag,适用于没写 tag 的场景
ctx.BindForm 绑定所有 Form 参数,相当于给每一个 field 声明一个 form tag,需要 Content-Type 为:application/x-www-form-urlencoded/multipart/form-data, 适用于没写 tag 的场景
ctx.BindJSON 绑定 JSON Body,调用 json.Unmarshal() 进行反序列化,需要 Body 为 application/json 格式
ctx.BindProtobuf 绑定 Protobuf Body,调用 proto.Unmarshal() 进行反序列化,需要 Body 为 application/x-protobuf 格式
ctx.BindByContentType 根据 Content-Type 来自动选择绑定的方法,其中 GET 请求会调用 BindQuery, 带有 Body 的请求会根据 Content-Type 自动选择
ctx.Validate 进行参数校验,需要校验 tag 配合使用 (默认使用 vd tag 校验)

支持的 tag 及参数绑定优先级

支持的 tag

不通过 IDL 生成代码时若字段不添加任何 tag 则会遍历所有 tag 并按照优先级绑定参数,添加 tag 则会根据对应的 tag 按照优先级去绑定参数。

通过 IDL 生成代码时若不添加 api 注解 则字段默认添加 formjsonquery tag,添加 api 注解 会为字段添加相应需求的 tag。

go tag 说明
path 绑定 url 上的路径参数,相当于 hertz 路由 :param*param 中拿到的参数。例如:如果定义的路由为:/v:version/example,可以把 path 的参数指定为路由参数:path:"version",此时,url: http://127.0.0.1:8888/v1/example,可以绑定path参数"1"
form 绑定请求的 body 内容。content-type -> multipart/form-dataapplication/x-www-form-urlencoded,绑定 form 的 key-value
query 绑定请求的 query 参数
cookie 绑定请求的 cookie 参数
header 绑定请求的 header 参数
json 绑定请求的 body 内容 content-type -> application/json,绑定 json 参数
raw_body 绑定请求的原始 body(bytes),绑定的字段名不指定,也能绑定参数。(注:raw_body 绑定优先级最低,当指定多个 tag 时,一旦其他 tag 成功绑定参数,则不会绑定 body 内容。)
vd 参数校验,校验语法
default 设置默认值

参数校验

具体校验语法可参考 校验语法

不通过 IDL 生成代码时直接在对应结构体字段打 tag,示例:

type InfoRequest struct {
		Name         string   `vd:"$!='your string'"`
}

通过 IDL 生成代码时需添加相应的注解,可参考 Field 注解

下面给出常见用法:

  • string 和 list 的长度验证 len($)>0
  • 字符串正则匹配 regexp('^\\w*$')"
  • 验证数字字段的的值 $>0
  • 验证指针字段 num==nil || num>0
  • 验证枚举类型 type=="hello" || type == "world"
  • 自定义错误信息 msg:'C must be false when S.A>0'"

参数绑定优先级

path > form > query > cookie > header > json > raw_body

注:如果请求的 content-type 为 application/json,使用 BindAndValidate, Bind 方法会在参数绑定前做一次 json unmarshal 处理。

必传参数

通过在 tag 中添加 required,可以将参数标记为必传。当绑定失败时 BindBindAndValidate 将会返回错误。当多个 tag 包含 required 时,将会按照优先级绑定。如果所有 tag 都没有绑定上,则会返回错误。

type TagRequiredReq struct {
	// 当 JSON 中没有 hertz 字段时,会返回 required 错误
	Hertz string `json:"hertz,required"`
	// 当 query 和 JSON 中同时没有 kitex 字段时,会返回 required 错误
	Kitex string `query:"kitex,required" json:"kitex,required" `
}

获取 ClientIP

func (ctx *RequestContext) ClientIP() string
func (ctx *RequestContext) SetClientIPFunc(f ClientIP)

ClientIP

获取客户端 IP 的地址。

该函数的默认行为:若 X-Forwarded-ForX-Real-IP Header 中存在 ip,则从这两个 Header 中读 ip 并返回(优先级 X-Forwarded-For 大于 X-Real-IP),否则返回 remote address。

函数签名:

func (ctx *RequestContext) ClientIP() string

示例:

// X-Forwarded-For: 20.20.20.20, 30.30.30.30
// X-Real-IP: 10.10.10.10
h.Use(func(ctx context.Context, c *app.RequestContext) {
    ip := c.ClientIP() // 20.20.20.20
})

SetClientIPFunc

ClientIP 函数提供的默认方式不满足需求,用户可以使用该函数自定义获取客户端 ip 的方式。

用户可以自己实现自定义函数,也可以通过设置 app.ClientIPOptions 实现。

注意:

在设置 app.ClientIPOptions 时,TrustedCIDRs 需用户自定义(若不设置则固定返回 remote address),代表可信任的路由。

若 remote address 位于可信任的路由范围内,则会选择从 RemoteIPHeaders 中获取 ip,否则返回 remote address。

函数签名:

func (ctx *RequestContext) SetClientIPFunc(f ClientIP)

示例:

// POST http://example.com/user
// X-Forwarded-For: 30.30.30.30
h.POST("/user", func(ctx context.Context, c *app.RequestContext) {
    // method 1
    customClientIPFunc := func(c *app.RequestContext) string {
			return "127.0.0.1"
	}
	c.SetClientIPFunc(customClientIPFunc)
	ip := c.ClientIP() // ip == "127.0.0.1"

    // method 2
    _, cidr, _ := net.ParseCIDR("127.0.0.1/32")
	opts := app.ClientIPOptions{
		RemoteIPHeaders: []string{"X-Forwarded-For", "X-Real-IP"},
		TrustedCIDRs:    []*net.IPNet{cidr},
	}
	c.SetClientIPFunc(app.ClientIPWithOption(opts))

	ip = c.ClientIP() // ip == "30.30.30.30"
})

并发安全

拷贝 RequestContext 副本,提供协程安全的访问方式。

func (ctx *RequestContext) Copy() *RequestContext

示例:

h.POST("/user", func(ctx context.Context, c *app.RequestContext) {
    ctx1 := c.Copy()
    go func(context *app.RequestContext) {
        // safely
    }(ctx1)
})
posted @   厚礼蝎  阅读(13)  评论(0编辑  收藏  举报
阅读排行:
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
历史上的今天:
2023-02-26 go结构体打印格式化成json
2023-02-26 openEuler22.09初始化脚本
点击右上角即可分享
微信分享提示