请求输入依靠 ghttp.Request 对象实现,ghttp.Request继承了底层的http.Request对象。ghttp.Request包含一个与当前请求对应的返回输出对象Response,用于数据的返回处理。

相关方法: https://pkg.go.dev/github.com/gogf/gf/v2/net/ghttp#Request

简要说明

可以看到Request对象的参数获取方法非常丰富,常用方法如下:

常用方法描述
Get

常用方法,简化参数获取,GetRequest的别名。

GetQuery获取GET方式传递过来的参数,包括Query StringBody参数解析。
GetForm获取表单方式传递过来的参数,表单方式提交的参数Content-Type往往为application/x-www-form-urlencoded, application/form-data, multipart/form-data, multipart/mixed等等。
GetRequest获取客户端提交的所有参数,按照参数优先级进行覆盖,不区分提交方式。
Get*Struct将指定提交类型的所有请求参数绑定到指定的struct对象上,注意给定的参数为对象指针。绝大部分场景中往往使用Parse方法将请求数据转换为请求对象,具体详见后续章节。
GetBody/GetBodyString获取客户端提交的原始数据,该数据是客户端写入到body中的原始数据,与HTTP Method无关,例如客户端提交JSON/XML数据格式时可以通过该方法获取原始的提交数据。
GetJson自动将原始请求信息解析为gjson.Json对象指针返回,gjson.Json对象具体在 通用编解码-gjson 章节中介绍。
Exit*用于请求流程退出控制,详见本章后续说明。

提交方式

GoFrame框架的参数获取不是通过HTTP Method来做区分,而是通过参数提交类型来区分。例如,分别通过HTTP Method: POST、INPUT、DELETE来提交表单参数,在服务端获取参数不是通过GetPost/GetInput/GetDelete的方式来获取,而是统一通过GetForm方法来获取表单参数,针对其他的HTTP Method也是如此。

GoFrame框架下,有以下几种提交类型:

提交类型描述
Router路由参数。来源于路由规则匹配。
Query

Query参数。URL中的Query String参数解析,如:http://127.0.0.1/index?id=1&name=john 中的id=1&name=john

Form

表单参数。最常见的提交方式,提交的Content-Type往往为:application/x-www-form-urlencodedmultipart/form-datamultipart/mixed

Body

内容参数。从Body中获取并解析得到的参数,JSON/XML请求往往使用这种方式提交。

Custom

自定义参数,往往在服务端的中间件、服务函数中通过SetParam/GetParam方法管理。

参数类型

获取的参数方法可以对指定键名的数据进行自动类型转换,例如:http://127.0.0.1:8199/?amount=19.66,通过Get(xxx).String()将会返回19.66的字符串类型,Get(xxx).Float32()/Get(xxx).Float64()将会分别返回float32float64类型的数值19.66。但是,Get(xxx).Int()/Get(xxx).Uint()将会返回19(如果参数为float类型的字符串,将会按照向下取整进行整型转换)。

聪明的您一定发现了,获取到的参数都是泛型变量,根据该泛型变量再根据需要调用对应的方法转换为对应的数据类型。

使用示例:

package main

import (
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/net/ghttp"
)

func main() {
	s := g.Server()
	s.BindHandler("/", func(r *ghttp.Request) {
		r.Response.Writeln(r.Get("amount").String())
		r.Response.Writeln(r.Get("amount").Int())
		r.Response.Writeln(r.Get("amount").Float32())
	})
	s.SetPort(8199)
	s.Run()
}

执行后我们访问地址 http://127.0.0.1:8199/?amount=19.66 页面输出

19.66
19
19.66

参数优先级

我们考虑一种场景,当不同的提交方式中存在同名的参数名称会怎么样?在GoFrame框架下,我们根据不同的获取方法,将会按照不同的优先级进行获取,优先级高的方式提交的参数将会优先覆盖其他方式的同名参数。优先级规则如下:

  1. GetGetRequset方法:Router < Query < Body < Form < Custom,也就是说自定义参数的优先级最高,其次是Form表单参数,再次是Body提交参数,以此类推。例如,QueryForm中都提交了同样名称的参数id,参数值分别为12,那么Get("id")/GetForm("id")将会返回2,而GetQuery("id")将会返回1
  2. GetQuery方法:Query > Body,也就是说query string的参数将会覆盖Body中提交的同名参数。例如,QueryBody中都提交了同样名称的参数id,参数值分别为12,那么Get("id")将会返回2,而GetQuery("id")将会返回1
  3. GetForm方法:由于该类型的方法仅用于获取Form表单参数,因此没什么优先级的差别。

使用示例:

package main

import (
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/net/ghttp"
)

func main() {
	s := g.Server()
	s.BindHandler("/input", func(r *ghttp.Request) {
		r.Response.Writeln(r.Get("amount"))
	})
	s.BindHandler("/query", func(r *ghttp.Request) {
		r.Response.Writeln(r.GetQuery("amount"))
	})
	s.SetPort(8199)
	s.Run()
}

执行后,我们通过curl工具进行测试:

$ curl -d "amount=1" -X POST "http://127.0.0.1:8199/input?amount=100"
1
$ curl -d "amount=1" -X POST "http://127.0.0.1:8199/query?amount=100"
100

可以看到,当我们访问/input路由时,该路由方法中采用了Get方法获取amount参数,按照同名优先级的规则返回了1,即body中传递的参数。而当我们通过/query路由访问时,该路由方法内部使用了GetQuery方法获取amount参数,因此获取到的是query string参数中的amount值,返回了100

大小写敏感

需要注意哦,参数名称是大小写敏感的哦,例如客户端提交的参数nameName是两个参数。 由于服务端默认是通过字符串名称获取参数,因此大小写敏感并不会产生什么问题,但是如果服务端接收的是一个API对象,那么可能需要注意一下。我们来看一个例子。

服务端:

package main

import (
	"context"

	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/net/ghttp"
)

type Controller struct{}

type PathReq struct {
	g.Meta `path:"/api/v1/*path" method:"post"`
	Path   string
}

type PathRes struct {
	Path string
}

func (c *Controller) Path(ctx context.Context, req *PathReq) (res *PathRes, err error) {
	return &PathRes{Path: req.Path}, nil
}

func main() {
	s := g.Server()
	s.SetPort(8199)
	s.Use(ghttp.MiddlewareHandlerResponse)
	s.Group("/", func(group *ghttp.RouterGroup) {
		group.Bind(&Controller{})
	})
	s.Run()
}

服务端的接口设计原意是定义一个路由参数path,并且通过API对象的Path属性来接收。

客户端:

package main

import (
	"fmt"

	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/os/gctx"
)

func main() {
	var (
		ctx    = gctx.New()
		client = g.Client()
	)
	client.SetPrefix("http://127.0.0.1:8199")
	for i := 0; i < 10; i++ {
		fmt.Println(client.PostContent(ctx, "/api/v1/user/info", `{"Path":"user/profile"}`))
	}
}

根据我们的理解,客户端提交后,服务端应该会收到路由参数path的值为user/info。但是很奇怪,由于程序BUG,客户端提交的Body中同样提交了一个JSON,参数名称为Path,参数值为user/profile。那么这个时候服务端API对象中的属性Path将会是哪个值呢?

我们将客户端反复执行多次,发现输出的结果都是:

{"code":0,"message":"","data":{"Path":"user/profile"}}
{"code":0,"message":"","data":{"Path":"user/profile"}}
{"code":0,"message":"","data":{"Path":"user/profile"}}
{"code":0,"message":"","data":{"Path":"user/profile"}}
{"code":0,"message":"","data":{"Path":"user/profile"}}
{"code":0,"message":"","data":{"Path":"user/profile"}}
{"code":0,"message":"","data":{"Path":"user/profile"}}
{"code":0,"message":"","data":{"Path":"user/profile"}}
{"code":0,"message":"","data":{"Path":"user/profile"}}
{"code":0,"message":"","data":{"Path":"user/profile"}}

即参数都是Body中提交的值,而不是路由参数中的值。其实,由于参数大小写敏感的缘故,服务端这个时候接受到了两个参数,一个是路由参数path,一个是Body JSON提交的Path。服务端在转换API参数对象的时候,由于属性PathBody JSON中提交的Path更匹配,因此,使用的是Body JSON中的Path的值。

继续了解

Content Menu

  • No labels

19 Comments

  1. 大神,请问怎么判断用户是否传了某个参数?
    实际开发过程中有这个个需求:

    1、用户传了某个参数才会修改,不传不修改。

    2、如果用户不传获取到的参数是空(字符串举例)

    3、如果用户传空拿到的也是空,就无法区分用户到底是想修改成空,还不不想修改。

    fmt.Println("remark: &v", r.GetFormBool("remark"))
    if r.GetFormBool("remark") {
        data["remark"] = r.GetFormString("remark")
    }

    打印结果:remark: &v false

    前端请求:

    {"card_name":"一年级课时卡","card_type":"1","remark":"","status":"1"}
    1. 有多种方式,常见的方法是:

      1、可以通过r.GetForm("remark") == nil判断客户端是否有提交remark参数。

      2、如果你定义对象接受参数,那么将对象的指定属性值定义为指针即可,例如:

      type struct Req {
          Remark *string
          ...
      }

      随后使用r.Parse将客户端提交的参数解析到对象中,然后通过 req.Remark == nil 判断客户端是否有提交remark参数。


      1. 谢谢大佬指点!

  2. 请教下 文档上例子都是 json的,如果我用http协议 数据用protobuf格式的话,如何写api.func啊。是用

    r.GetBody() 得到字节流再proto.unmarshal到pb对象上么?
  3. 观点:参数优先级的出现是因为彻底放弃掉 http method 后引入,

    此外,对http method的放弃,对restful的支持上是否也因此有问题呢?

    郭强 能讲两句这块的考量吗?


    1. 其实文档里面也提到了为什么会有参数优先级的设计。是为了解决,当不同的提交方式中存在同名的参数名称的场景。

      此外,参数并不是按照http method进行获取的,参数主要有5种提交方式,文档也有提及到。

  4. 这个怎么获取前端传的Content-Type是text/plain的数据?

    1. 直接用r.Get就可以获取

    2. 你也可以直接使用r.GetBody获取客户端的原始提交内容。

  5. 发现两个问题,

    func Test(r *ghttp.Request) {
    r.Request.URL.Scheme //返回空,有的时候能拿到
    如果是ip访问设置cookie会报错
    }


    1. 第一个问题,这样判断是不靠谱的。

      具体参考https://github.com/golang/go/issues/28940

      第二个问题,列举一下代码和触发条件?例如谁设置cookie?谁报错?报了什么错?

  6. 请问 HTTP 服务是否支持http/2协议,如果支持是自适应http/1.x 和http/2 吗?

  7. 大神们请教一下我这个问题是出在哪里?

    入参如下:

    {ip: "192.168.0.134", enable: true}

    结构体定义

    type searchDeviceReq struct {
    ip string `v:"required#ip不可为空"`
    enable bool `p:"enable"`
    }
    这个时候用  r.Parse 都是空的
    但是用r.Get 都有值 。。这个是什么原因?
    1. 结构体首字母大写.你这学go基础学的也太随意了吧.

      1. 还真是。。。

  8. 感谢框架工作者的付出,这里提一个小的问题,ghttp Request这个结构体的私有属性在一些业务中也有使用场景,比如某些场景下通信验证签名是需要入参排序后做加密,也就是paramMap,queryMap这些集合所有参数集。目前只能通过反射来获取这些内容,框架本身没有暴露访问整个map的api。如果这个是一个普适场景的话,作者有没有考虑增加可以获取这些私有属性的api呢,这样参数处理会更灵活。

    1. 可以提个issue记录一下,目前可以通过自定义的Param参数来做覆盖,不修改原有的Query/Form参数。

      1. 写了一个简单的函数来导出参数所有的map集合,已提交了pr,#1840,希望可以做出贡献

  9. 用Get获取请求输入的时候报错这个错误是啥意思:

    Stack:
    1. r.Request.ParseForm failed
    1). github.com/gogf/gf/v2/net/ghttp.(*Request).parseForm
    2). github.com/gogf/gf/v2/net/ghttp.(*Request).GetForm
    3). github.com/gogf/gf/v2/net/ghttp.(*Request).GetRequest
    4). github.com/gogf/gf/v2/net/ghttp.(*Request).Get
    5). main.remote
    6). github.com/gogf/gf/v2/net/ghttp.(*middleware).callHandlerFunc.func1
    7). github.com/gogf/gf/v2/net/ghttp.niceCallFunc
    8). github.com/gogf/gf/v2/net/ghttp.(*middleware).callHandlerFunc
    9). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next.func1.4
    10). github.com/gogf/gf/v2/net/ghttp.niceCallFunc
    11). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next.func1
    12). github.com/gogf/gf/v2/util/gutil.TryCatch
    13). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next
    14). main.middlewareErrorHandler
    16). github.com/gogf/gf/v2/net/ghttp.niceCallFunc
    17). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next.func1
    18). github.com/gogf/gf/v2/util/gutil.TryCatch
    19). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next
    20). github.com/gogf/gf/v2/net/ghttp.internalMiddlewareServerTracing
    21). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next.func1.5
    22). github.com/gogf/gf/v2/net/ghttp.niceCallFunc
    23). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next.func1
    24). github.com/gogf/gf/v2/util/gutil.TryCatch
    25). github.com/gogf/gf/v2/net/ghttp.(*middleware).Next
    26). github.com/gogf/gf/v2/net/ghttp.(*Server).ServeHTTP
    2. read tcp x.x.x.x:xxxx->x.x.x.x:40302: i/o timeout


    这里为啥还能IO超时