整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

Go 语言 Web 编程系列-获取用户请求数据(上)

Go 语言 Web 编程系列-获取用户请求数据(上)

、GET/POST 请求数据

在 PHP 中,可以直接通过全局变量 $_GET 和 $_POST 快速获取 GET/POST 请求数据,GET 请求数据主要是 URL 查询字符串中包含的参数,以前面在线论坛项目的群组详情页为例:

http://localhost:8080/thread/read?id=adb02107-d7c3-4f27-4de4-b586f231380e

上述 URL 请求中的 id=adb02107-d7c3-4f27-4de4-b586f231380e 就属于 GET 请求数据,也就是查询字符串,而对于用户登录表单:

当用户输入注册邮箱和密码后点击「登录」按钮,会将输入框中的数据作为请求实体发送 POST 请求到服务端,执行登录认证,这里的表单数据就是 POST 请求数据,如果我们查看表单请求路由的 HTTP 报文:

就会看到表单请求数据,也就是上文提到的 HTTP 请求实体。

当然根据对服务端资源的操作类型不同,可以进一步细分为 POST、PUT、PATCH、DELETE 等包含请求实体的请求方法,为了简化模型,我们这里只讨论 POST 请求,而且其他几种请求方法也可以统一通过 POST 请求完成,通常只有在设计遵循 RESTful 风格的 API 接口时,才会严格划分不同的请求方法,关于这一点,后面介绍 REST + JSON 接口时再详细讨论。

1、Form

Go 语言中获取用户请求数据的方式要更复杂一些,Go 也为此提供多个不同的结构体帮助我们读取不同请求类型的数据,首先,我们可以通过请求对象上的 Form 读取所有 GET/POST 请求数据,在 handlers/post.go 中新增 EditPost 方法如下:

func EditPost(w http.ResponseWriter, r *http.Request)  {
    r.ParseForm()
    fmt.Fprintln(w, r.Form)
}

需要注意的是,在通过 r.Form 获取所有请求数据之前,必须要先通过 r.ParseForm() 解析所有请求数据,否则无法获取数据。

在 routes/web.go 中新增一个路由:

WebRoute{
    "UpdatePost",
    "POST",
    "/post/edit",
    handlers.EditPost,
},

然后我们重启 HTTP 服务器,在命令行通过 curl 发起一个包含不同类型请求数据的请求:

可以看到,打印结果表明 r.Form 是一个包含所有请求数据的字典类型(map),包含 URL 查询字符串和 POST 请求数据,这是一个 POST 请求,如果是 GET 请求呢?我们为 EditPost定义一个 GET 请求路由:

WebRoute{
    "EditPost",
    "GET",
    "/post/edit",
    handlers.EditPost,
},

再重启 HTTP 服务器,发起一个只包含查询字符串的 GET 请求(默认是 GET 请求,不需要通过 -X GET 指定):

同样可以打印出和 POST 请求完全一样的结果。

因此,request 对象上的 Form 可以获取所有请求参数,包括查询字符串和请求实体,并且不限请求类型。如果你想要进一步要获取指定的参数值,可以以索引方式获取指定参数对应的值,也可以通过 Form 提供的 Get 方法,就像我们从一个普通字典类型获取键值一样:

id1 := r.Form["id"]
id2 := r.Form.Get("id")
fmt.Println(id1)
fmt.Println(id2)

只不过两者的返回值类型不一样,前者是一个字符串切片,后者是一个字符串值:

[1]
1

2、PostForm

上面的结果同时返回了查询字符串和请求实体,如果只想获取请求实体(即 POST 表单中的数据),可以通过 PostForm 实现:

func EditPost(w http.ResponseWriter, r *http.Request)  {
    r.ParseForm()
    id := r.Form.Get("id")
    fmt.Println("post id:", id)
    fmt.Println("form data:", r.PostForm)
    io.WriteString(w, "表单提交成功")
}

这样一来,就只能获取到 POST 数据了:

然后在 HTTP 服务器日志,可以看到如下答应结果:

可以看到 r.PostForm 返回的也是字典类型数据,数据格式和 r.Form 完全一致,并且这次只包含了 POST 表单请求数据,不包含 URL 查询字符串,也就是说,通过 r.PostForm 只能获取 POST 请求数据(请求实体数据),无法获取 GET 请求数据(查询字符串中的数据),你可以再次发起 GET 请求进行验证:

这个时候,可以看到服务器打印日志之包含 id 信息,表单信息为空:

通过 PostForm 获取具体参数值的方式和 Form 一样,这里就不再赘述了:

title := r.PostForm.Get("title")
content := r.PostForm.Get("content")

实际上,我们在前面的在线论坛项目中,就是通过这个方式获取表单请求数据的:

err := request.ParseForm()
user, err := models.UserByEmail(request.PostFormValue("email"))

3、FormValue/PostFormValue

最后,还可以通过 FormValue 和 PostFormValue 获取用户请求数据,使用它们的好处是不再需要单独调用 ParseForm 对表单数据进行解析,不过使用这两个方法的时候只能获取特定请求数据,不能一次获取所有请求数据:

func EditPost(w http.ResponseWriter, r *http.Request)  {
    fmt.Println("post id:", r.FormValue("id"))
    fmt.Println("post title:", r.PostFormValue("title"))
    fmt.Println("post title:", r.PostFormValue("content"))
    io.WriteString(w, "表单提交成功")
}

FormValue/PostFormValue 的区别和 Form/PostForm 一样,这里通过命名就可以看出来,前者可以获取所有 GET/POST 请求数据(即查询字符串和请求实体),后者只能获取 POST 请求实体数据。

注:FormValue/PostFormValue 之所以不用显式调用 ParseForm 解析请求数据,是因为底层对其进行了封装,实际上还是要调用这个方法。

4、获取 JSON 请求数据

上面的示例默认都是基于 HTML 表单请求,对于客户端提交的 JSON 格式数据,使用ParseForm 是无法解析并获取数据的,因为 HTML 表单请求数据默认是通过 application/x-www-form-urlencoded 编码的,而 JSON 请求数据通常是通过 application/json 编码,ParseForm 只能解析通过 application/x-www-form-urlencoded 编码的数据。

对于 JSON 请求数据的解析,目前我们可以通过上篇教程介绍的,读取完整请求实体并进行 JSON 解码实现,下面我们改写 AddPost 方法实现如下:

type Post struct {
    Title string `json:"title"`
    Content string `json:"content"`
}

func AddPost(w http.ResponseWriter, r *http.Request)  {
    len := r.ContentLength   // 获取请求实体长度
    body := make([]byte, len)  // 创建存放请求实体的字节切片
    r.Body.Read(body)        // 调用 Read 方法读取请求实体并将返回内容存放到上面创建的字节切片
    // io.WriteString(w, string(body))
    post := Post{}
    json.Unmarshal(body, &post)  // 对读取的 JSON 数据进行解析
    fmt.Fprintf(w, "%#v\n", post)   // 格式化输出结果
}

我们将读取的请求实体数据通过 JSON 解码映射到 Post 结构体对象并将其输出到响应结果。

重启 HTTP 服务器,通过 curl 模拟客户端提交 JSON 请求数据:

我们通过 -H 选项指定请求数据编码格式为 application/json,然后请求数据调整为 JSON 格式字符串,最后通过输出结果可以看到在服务端 JSON 请求数据已经可以成功解析并获取。

5、小结

到这里,我们已经了解了在 Go Web 编程中,常见的用户请求数据如何解析并获取(URL 查询字符串、POST 表单数据、JSON 请求数据),实际上,和 PHP 中的 $_GET 和 $_POST 类似,Go 也是将 HTTP 请求数据映射到请求对象对应的结构体,然后开发者可以从上下文请求对象中解析并读取这些请求数据,使用这些封装好的对象的好处是它们屏蔽了底层的细节,统一了数据格式,可以大大提高开发效率,减少不必要的数据格式兼容成本。

下篇教程,学院君将给大家介绍如何从表单请求中获取文件数据,并实现简单的文件上传功能。

(全文完)

单是PHP程序中最常使用的收集站点访问者信息的数据输入界面。通过表单浏览器获取用户的输入数据,并传送给Web服务器的脚本程序中,以各种不同的方式进行处理。在表单中提供了多种输入方式,包括文本输入域、单选或多选按钮、下拉式列表域等。表单是网页上由<form>标签定义的一个特定区域,而表单的各种输入域可以由<input>、<select>和<textarea> 3个标签定义。

1 表单标签<form>

一个表单用<form></form>标签来创建,即定义表单的开始和结束位置,在开始和结束标签之间的一切定义都属于表单的内容。单击提交按钮时,提交的也是表单范围之内的内容。另外,在<form>标签中需要携带表单的相关信息,例如处理表单的脚本程序的位置、提交表单的方法等。这些信息对于浏览者是不可见的,但对于处理表单却有着决定性的作用。该标签的常用属性如表1所示。

表1 HTML表单标签中常用的属性


<form>标签中必须加action属性,并且不能为空。例如,<form action="login.php" method="post">。如果不需要使用action属性,也必须定义:<form action="no">。

2 文本域和密码域

在<form>标签内定义的<input>标签具有重要的地位,该标签是单个标签,没有结束标记。<input type="">标签用来定义一个用户输入区,用户可以在其中输入信息。<input>标签中共提供了9种类型的输入区域,具体是哪一种类型由type属性来决定。文本和密码输入域是一个单行文本框,它们基本相似,唯一不同的是,用户在密码域中输入的字符并不以原样显示,而是将每个字符用“*”代替。文本和密码输入域的基本语法格式如下:

<input type="text" name="field_name" value="field_value" size="n" maxlength="n"> <!-- 输入域 -->

<input type="password" name="field_name" value="field_value" size="n" maxlength="n"> <!-- 密码域 -->

这些属性的含义如表2所示。

表2 HTML中<input>标签的常用属性z

3 提交、重置和普通按钮

在<input>标签中,当type属性值为“submit”时,表示这是一个提交按钮,单击提交按钮后,可以实现表单内容的提交;当type属性为“reset”时,表示这是一个重置按钮,单击重置按钮后,表单的内容将恢复为默认值;当type属性为“button”时,表示这是一个普通按钮,并不实现任何功能,需要和JavaScript等脚本语言一起使用。button按钮必须定义在form之间,否则Netscape浏览器不支持。这3种按钮的基本语法格式如下:

<input type="submit" name="field_name" value="field_value"> <!-- 提交按钮 -->

<input type="reset" name="field_name" value="field_value"> <!-- 重置按钮 -->

<input type="button" name="field_name" value="field_value"> <!-- 普通按钮 -->

4 单选按钮和复选框

单选按钮和复选框都有“选中”和“未选中”两种状态。同一组单选按钮如果有多个选择框,则选择框之间是相互排斥的,只允许用户选择其中的一个。复选框和单选按钮的区别是,复选框允许用户同时选中同一表单中的多个或全部选项,当然,也可以只选其中的一个选项。它们都是只有在“选中”时,数据才能被提交到服务器端。其语法格式如下所示:

<input type="radio" name="field_name" value="field_value" checked> <!-- 单选按钮 -->

<input type="checkbox" name="field_name" value="field_value" checked> <!-- 复选框 -->

在<input>标签中,当type属性值为“checkbox”时,表示这是一个复选框输入域;当type属性值为“radio”时,则表示这是一个单选按钮输入域。但在同一组中的多个单选按钮名称必须相同,它们之间才能相互排斥。单选按钮和复选框都可以通过checked属性设置为选中状态。

.引用相关头文件

引入CSS:

<link href="Scripts/jquery-ui-1.8.1.custom.css" rel="stylesheet" type="text/css" />

<link href="Scripts/ui.jqgrid.css" rel="stylesheet" type="text/css" />

引入JS:

<script src="Scripts/jquery-1.5.1.js" type="text/javascript"></script>

<script src="Scripts/jquery-ui.min.js" type="text/javascript"></script>

<script src="Scripts/grid.locale-en.js" type="text/javascript"></script>

<script src="Scripts/jquery.jqGrid.min.js" type="text/javascript"></script>

因为jqGrid3.6及以后的版本集成了jQuery UI,所以,此处需要导入UI相关js和css。另外grid.locale-en.js这个语言文件必须在jquery.jqGrid.min.js之前加载,否则会出问题。

2.将jqgrid加入页面中

根据jqGrid的文档,要想生成一个jqGrid,最直接的方法就是:

$("#list").jqGrid(options);

其中list是页面上的一个table:<table id="list"></table>

下面是一个简单的例子:

<script type="text/javascript">

$(document).ready(function () {

jQuery("#list").jqGrid({

url: 'Handler.ashx',

datatype: "json",

mtype: 'GET',

colNames: ['SalesReasonID', 'Name', 'ReasonType', 'ModifiedDate'],

colModel: [

{ name: 'SalesReasonID', index: 'SalesReasonID', width: 40, align: "left", editable: true },

{ name: 'Name', index: 'Name', width: 100, align: "center" },

{ name: 'ReasonType', index: 'ReasonType', width: 100, align: "center" },

{ name: 'ModifiedDate', index: 'ModifiedDate', width: 150, align: "center", search: false }

],

rowList: [10, 20, 30],

sortname: 'SalesReasonID',

viewrecords: true,

sortorder: "desc",

jsonReader: {

root: "griddata",

total: "totalpages",

page: "currpage",

records: "totalrecords",

repeatitems: false

},

pager: jQuery('#pager'),

rowNum: 5,

altclass: 'altRowsColour',

//width: 'auto',

width: '500',

height: 'auto',

caption: "DemoGrid"

}).navGrid('#pager', { add: true, edit: true, del: true,search:false,refresh:false }); ;

})

二、 jqgrid的重要选项

具体的options参考,可以访问jqGrid文档关于option的章节(http://www.trirand.com/jqgridwiki/doku.php?id=wiki:options)。其中有几个是比较常用的,重点介绍一下:

  • url :jqGrid控件通过这个参数得到需要显示的数据,具体的返回值可以使XML也可以是Json。
  • datatype :这个参数用于设定将要得到的数据类型。类型包括:json 、xml、xmlstring、local、javascript、function。
  • mtype : 定义使用哪种方法发起请求,GET或者POST。
  • height :Grid的高度,可以接受数字、%值、auto,默认值为150。
  • width :Grid的宽度,如果未设置,则宽度应为所有列宽的之和;如果设置了宽度,则每列的宽度将会根据shrinkToFit选项的设置,进行设置。
  • shrinkToFit :此选项用于根据width计算每列宽度的算法。默认值为true。如果shrinkToFit为true且设置了width值,则每列宽度会根据width成比例缩放;如果shrinkToFit为false且设置了width值,则每列的宽度不会成比例缩放,而是保持原有设置,而Grid将会有水平滚动条。
  • autowidth :默认值为false。如果设为true,则Grid的宽度会根据父容器的宽度自动重算。重算仅发生在Grid初始化的阶段;如果当父容器尺寸变化了,同时也需要变化Grid的尺寸的话,则需要在自己的代码中调用setGridWidth方法来完成。
  • pager :定义页码控制条Page Bar,在上面的例子中是用一个div(<div id=”pager”></div>)来放置的。
  • sortname :指定默认的排序列,可以是列名也可以是数字。此参数会在被传递到Server端。
  • viewrecords :设置是否在Pager Bar显示所有记录的总数。
  • caption :设置Grid表格的标题,如果未设置,则标题区域不显示。
  • rowNum :用于设置Grid中一次显示的行数,默认值为20。正是这个选项将参数rows(prmNames中设置的)通过url选项设置的链接传递到Server。注意如果Server返回的数据行数超过了rowNum的设定,则Grid也只显示rowNum设定的行数。
  • rowList :一个数组,用于设置Grid可以接受的rowNum值。例如[10,20,30]。
  • colNames :字符串数组,用于指定各列的题头文本,与列的顺序是对应的。
  • colModel :最重要的数组之一,用于设定各列的参数。(稍后详述)
  • prmNames :这是一个数组,用于设置jqGrid将要向Server传递的参数名称。(稍后详述)
  • jsonReader :这又是一个数组,用来设定如何解析从Server端发回来的json数据。(稍后详述)

2.1 prmNames选项

prmNames是jqGrid的一个重要选项,用于设置jqGrid将要向Server传递的参数名称。其默认值为:

prmNames : {

page:"page", // 表示请求页码的参数名称

rows:"rows", // 表示请求行数的参数名称

sort: "sidx", // 表示用于排序的列名的参数名称

order: "sord", // 表示采用的排序方式的参数名称

search:"_search", // 表示是否是搜索请求的参数名称

nd:"nd", // 表示已经发送请求的次数的参数名称

id:"id", // 表示当在编辑数据模块中发送数据时,使用的id的名称

oper:"oper", // operation参数名称

editoper:"edit", // 当在edit模式中提交数据时,操作的名称

addoper:"add", // 当在add模式中提交数据时,操作的名称

deloper:"del", // 当在delete模式中提交数据时,操作的名称

subgridid:"id", // 当点击以载入数据到子表时,传递的数据名称

npage: null,

totalrows:"totalrows" // 表示需从Server得到总共多少行数据的参数名称,参见jqGrid选项中的rowTotal

}

2.2 jsonReader选项

jsonReader是jqGrid的一个重要选项,用于设置如何解析从Server端发回来的json数据,如果Server返回的是xml数据,则对应的使用xmlReader来解析。jsonReader的默认值为:

jsonReader : {

root: "rows", // json中代表实际模型数据的入口

page: "page", // json中代表当前页码的数据

total: "total", // json中代表页码总数的数据

records: "records", // json中代表数据行总数的数据

repeatitems: true, // 如果设为false,则jqGrid在解析json时,会根据name来搜索对应的数据元素(即可以json中元素可以不按顺序);而所使用的name是来自于colModel中的name设定。

cell: "cell",

id: "id",

userdata: "userdata",

subgrid: {

root:"rows",

repeatitems: true,

cell:"cell"

}

}

假如有下面一个json字符串:

{"totalpages":"3","currpage":"1","totalrecords":"11","griddata":[{"SalesReasonID":"1","Name":"Price","ReasonType":"Other","ModifiedDate":"1998年6月1日"},{"SalesReasonID":"2","Name":"On Promotion","ReasonType":"Promotion","ModifiedDate":"1998年6月1日"},{"SalesReasonID":"3","Name":"Magazine Advertisement","ReasonType":"Marketing","ModifiedDate":"1998年6月1日"},{"SalesReasonID":"4","Name":"Television Advertisement","ReasonType":"Marketing","ModifiedDate":"1998年6月1日"},{"SalesReasonID":"5","Name":"Manufacturer","ReasonType":"Other","ModifiedDate":"1998年6月1日"}]}

其对应的jsonReader为:jsonReader: {

root: "griddata",

total: "totalpages",

page: "currpage",

records: "totalrecords",

repeatitems: false

}

注:cell、id在repeatitems为true时可以用到,即每一个记录是由一对id和cell组合而成,即可以适用另一种json结构。援引文档中的例子:

repeatitems为true时:

jQuery("#gridid").jqGrid({

...

jsonReader : {

root:"invdata",

page: "currpage",

total: "totalpages",

records: "totalrecords"

},

...

});

json结构为:

{

"totalpages": "xxx",

"currpage": "yyy",

"totalrecords": "zzz",

"invdata" : [

{"id" :"1", "cell" :["cell11", "cell12", "cell13"]}, // cell中不需要各列的name,只要值就OK了,但是需要保持对应

{"id" :"2", "cell" :["cell21", "cell22", "cell23"]},

...

]

}

repeatitems为false时:

jQuery("#gridid").jqGrid({

...

jsonReader : {

root:"invdata",

page: "currpage",

total: "totalpages",

records: "totalrecords",

repeatitems: false,

id: "0"

},

...

});

json结构为:

{

"totalpages" : "xxx",

"currpage" : "yyy",

"totalrecords" : "zzz",

"invdata" : [

{"invid" : "1","invdate":"cell11", "amount" :"cell12", "tax" :"cell13", "total" :"1234", "note" :"somenote"}, // 数据中需要各列的name,但是可以不按列的顺序

{"invid" : "2","invdate":"cell21", "amount" :"cell22", "tax" :"cell23", "total" :"2345", "note" :"some note"},

...

]

}

2.3 colModel的重要选项

colModel也有许多非常重要的选项,在使用搜索、排序等方面都会用到。这里先只说说最基本的。

  • name :为Grid中的每个列设置唯一的名称,这是一个必需选项,其中保留字包括subgrid、cb、rn。
  • index :设置排序时所使用的索引名称,这个index名称会作为sidx参数(prmNames中设置的)传递到Server。
  • label :当jqGrid的colNames选项数组为空时,为各列指定题头。如果colNames和此项都为空时,则name选项值会成为题头。
  • width :设置列的宽度,目前只能接受以px为单位的数值,默认为150。
  • sortable :设置该列是否可以排序,默认为true。
  • search :设置该列是否可以被列为搜索条件,默认为true。
  • resizable :设置列是否可以变更尺寸,默认为true。
  • hidden :设置此列初始化时是否为隐藏状态,默认为false。
  • formatter :预设类型或用来格式化该列的自定义函数名。常用预设格式有:integer、date、currency、number等(具体参见文档 )。

三、 注意事项

1. 动态改变Add Form或者Edit Form中的select的内容,如:改变下图中的Comparator下拉中的内容。

$("#list_d").navGrid('#pager_d',{add:true,edit:true,del:true,search:false,refresh:false},

{

checkOnSubmit:false, closeAfterEdit: true,recreateForm:true,

beforeInitData:function(formid){

initComparator();

},

beforeShowForm: function(formid){

$("#list_d").jqGrid('setColProp', 'Name', { editrules:{required:false},});

$('#tr_Name', formid).hide();

}

},//edit

{},//add

{}//del

beforeInitData, beforeShowForm在每次点击编辑的时候都会执行。initComparator的作用是通过ajax获取数据,然后利用$("#list_d").jqGrid('setColProp', 'Comparator', { editoptions: { value: valueString} });来设置Comparator下拉中的内容。其中valueString的格式如下’ equal to: equal to; not equal to: not equal to’。键值之间用冒号隔开,2项之间用分号隔开。注意:把recreateForm设为true,否则'setColProp'只在第一次调用时有效。

2. var rowNum=parseInt($(this).getGridParam("records"), 10); 得到数据条数。

3. jQuery("#list_d").clearGridData();清空数据。

4. jQuery("#list").getCell(ids,"Key");获取第ids行的key列。

5. $("#list").jqGrid('setSelection', "1");选中第一行。放在loadComplete:中在gird加载完成的时候自动选中第一行。loadComplete:function(data){$("#list").jqGrid('setSelection', "1");

}

6. 对于像1中的可编辑的字段,可以设定rule,参见http://www.trirand.com/jqgridwiki/doku.php?id=wiki:common_rules#editrules

7. 修改Option,以URL为例

jQuery("#list_d").jqGrid('setGridParam',{url:"xxx.aspx",page:1}).trigger('reloadGrid');