您现在的位置是:首页 >技术教程 >golang web学习随便记2网站首页技术教程

golang web学习随便记2

sjg20010414 2023-07-25 00:00:03
简介golang web学习随便记2

在前一篇中,我们直接在 index 这个 handler func 中解析了模板,定义了数据,然后执行模板显示“拼合”了数据的网页。这是一个客户被动看的页面。实际的应用显然需要能够处理用户的请求。对于浏览器客户端的请求,我们先要来了解和请求有关的东西,如 URL、请求头部、请求主体、表单、文件上传、JSON主体、cookie等。

继续前面web学习随便记1中的代码:添加一个 handler  func  headers 如下

func headers(w http.ResponseWriter, r *http.Request) {
	url := r.URL
	// fmt.Fprintln(w, url.Scheme)
	// fmt.Fprintln(w, url.Opaque)
	// fmt.Fprintln(w, url.User.Username())
	// fmt.Fprintln(w, url.Host)
	fmt.Fprintln(w, url.Path)
	fmt.Fprintln(w, url.RawQuery)
	// fmt.Fprintln(w, url.Fragment)
	h := r.Header
	fmt.Fprintln(w, h)
	fmt.Fprintln(w, h["User-Agent"])
}

主函数中添加路由 /headers 的处理

// ...............
	mux.HandleFunc("/", index)
	mux.HandleFunc("/headers", headers)

	server := &http.Server{
		Addr:    "0.0.0.0:8088",
		Handler: mux,
	}
// .................

运行后,在浏览器地址栏输入 http://localhost:8088/headers?k1=v1&k2=bbb 结果类似如下:

看来,读取 URL 和 请求头部中的信息还是比较容易的。

类似地,我们添加 handler func  body 如下:获取请求主体的长度,定义一个字节数组,将主体读入该数组,转成字符串显示

func body(w http.ResponseWriter, r *http.Request) {
	len := r.ContentLength
	body := make([]byte, len)
	r.Body.Read(body)
	fmt.Fprintln(w, string(body))
}

路由添加 mux.HandleFunc("/body", body),运行。对于显示主体,因为我们没有表单,所以,用 curl 命令来查看结果

sjg@sjg-PC:~/go/src$ curl -id "family_name=Shen&name=Beta" 127.0.0.1:8088/body
HTTP/1.1 200 OK
Date: Fri, 05 May 2023 09:22:55 GMT
Content-Length: 27
Content-Type: text/plain; charset=utf-8

family_name=Shen&name=Beta

我们来进入表单的世界。在用来存放静态文件的目录 chitchat/public 下创建 client.html:

<html>

<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Golang表单数据获取</title>
</head>

<body>
    <form action="/process?key1=张三&key2=123" method="post" enctype="application/x-www-form-urlencoded">
        <input type="text" name="key1" value="李四" />
        <input type="text" name="key3" value="456" />
        <input type="submit" />
    </form>
</body>

</html>

表单最后会提交到 /process 这个路由路径上,所以,我们在主函数main中添加 mux.HandleFunc("/process", process),同时创建 handler  func  process 如下:http.Request对象方法ParseForm()会对请求进行语法分析,而该对象Form字段可以获取表单字段信息

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

现在打开浏览器,地址栏输入 http://localhost:8088/static/client.html,将显示如下表单

点击提交后,显示如下

 

 上述显示结果表明,使用 r.Form字段获取的信息为一个map,对于表单和URL两者同名的键,对应的值都会保存在该字段。简单来说,r.Form字段信息中某个key对应的值为一个切片,当key在表单和URL中是唯一的时候,用r.Form获取该key对应值没有问题;而key不唯一时,将无法区分两个值(虽然一般表单值会排在前面,但通常不该依赖顺序)。那么,要只获取表单Post提交的数据怎么办呢?答案是使用 r.PostForm 字段

修改 handler func process:

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

重复前面的过程,显示如下:

上图只显示表单中的字段和值,这个道理和 PHP 中超级全局变量$_REQUEST可以同时获取GET和POST提交的信息,而超级全局变量$_POST只获取POST提交的信息是类似的。但下面的情况,两者就不类似了。

我们把表单enctype修改一下:

    <form action="/process?key1=张三&key2=123" method="post" enctype="multipart/form-data">

重复前面的过程,显示将是

 这表明,r.PostForm 字段无法获取 multipart/form-data 格式编码的表单信息。而如果使用r.Form字段,显示将变成

只能获取URL中的字段信息,还是没有表单信息。此时,我们需要通过 r.MultipartForm 字段来获取 multipart/form-data 格式编码的表单数据(注意:multipart/form-data格式和url编码格式不同,语法解析的方法也不同,要用 r.ParseMultipartForm(指定长度),整个请求体解析后,指定长度的数据会放入内存,其余部分放在临时文件中)。

func process(w http.ResponseWriter, r *http.Request) {
	r.ParseMultipartForm(1024)
	fmt.Fprintln(w, r.MultipartForm)
}

显示结果为

我们可以发现,r.MultipartForm字段的值不是单个map,而是两个map组成的结构体。上图中第一个map包含了表单的值,第二个map是空的,因为它是用于记录用户上传的文件的(第二个map相当于PHP中的超级全局变量$_FILES,而第一个对应$_POST)。

有时候,我们只需要一两个表单字段的值,获取包含全体字段的map,再根据键取值有点麻烦。此时,我们可以使用 r.FormValue(键名)r.PostFormValue(键名) 这两个方法(使用这两个方法时,不需要在其前面使用ParseForm或者ParseMultipartForm方法)

 修改 handler func  process 如下,表单使用url编码:

func process(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintln(w, r.FormValue("key1"))
	fmt.Fprintln(w, r.FormValue("key2"))
	fmt.Fprintln(w, r.FormValue("key3"))
}

显示结果为:

注意:key1对应的值,只显示表单中的值,没有显示URL中的值。查询 r.FormValue 文档可以知道,它返回查询到的第一个值,而 POST和PUT传递的值会优先于URL查询串中的值。

将上述代码中的 FormValue 全部替换为 PostFormValue,显示结果如下:

此时 key2因为没有Post方法对应值,所以没有输出。也许是因为我的Golang版本1.17,书上说的(Golang1.4)表单编码使用multipart/form-data编码时,FormValue或PostFormValue无法获取值的情况不存在。

事实上,不是为了文件上传,我们不会使用multipart/form-data来编码表单,因为url编码方式更高效。下面来看看文件上传问题。我们给表单添加一个file类型的字段:

................
        <input type="text" name="key1" value="李四" />
        <input type="text" name="key3" value="456" />
        <input type="file" name="upload" />
        <input type="submit" />
................

修改 handler func  process 如下 (解析表单,获得上传的第1个文件的文件头,打开该文件头对应的文件对象,读取文件数据并在浏览器输出)

func process(w http.ResponseWriter, r *http.Request) {
	r.ParseMultipartForm(1024)
	fileHeader := r.MultipartForm.File["upload"][0]
	file, err := fileHeader.Open()
	if err == nil {
		data, err := ioutil.ReadAll(file)
		if err == nil {
			fmt.Fprintln(w, string(data))
		}
	}
}

试验时,注意选择文本文件,并且最好只有英文字符(因为中文涉及编码,有可能看到的是“乱码”)。我们要直接获取上传的文件的信息也是可能的,从而不用先parse,再获取头部,最后打开这样的复制步骤。答案是使用 r.FormFile()方法返回3值,parse、获取头部,打开一气呵成。

func process(w http.ResponseWriter, r *http.Request) {
	// r.ParseMultipartForm(1024)
	// fileHeader := r.MultipartForm.File["upload"][0]
	// file, err := fileHeader.Open()
	file, _, err := r.FormFile("upload")
	if err == nil {

对于JSON编码的请求的响应处理,其实和前面读取主体的要点差不多,无非可能涉及对JSON格式数据的Marshal 和 Unmarshal。下面的代码演示了要点:

将原来在 index 内的结构体 User 移到函数外面,因为我们用该结构体作为演示。import  encoding/json标准库,在主函数添加路由 mux.HandleFunc("/json", myjson),这里handler func 名字用 myjson 而非 json,是为了避免和包名 json 冲突。myjson 实现如下:

func myjson(w http.ResponseWriter, r *http.Request) {
	len := r.ContentLength
	body := make([]byte, len)
	r.Body.Read(body)
	user := User{}
	json.Unmarshal(body, &user)
	user.Name = user.Name + " aaa"
	user.Password = user.Password + "bbb"
	data, _ := json.Marshal(user)
	fmt.Fprintln(w, string(data))
}

上述实现中,前面部分和之前的读取 body 是一样的,之后是把 body 内容 unmarshal 到结构体对象 user 中,这样相当于完成了JSON格式数据的读取。我们修改user数据,相当于对数据进行了处理,把经过处理的 user 对象 marshal 之后作为数据返回给客户端,相当于是完成了对用户的响应。go run . 启动服务器后,我们用curl来模拟用户请求过程:

sjg@sjg-PC:/usr/local/go/src/net/http$ curl -i -X POST -H "Content-Type: application/json" -d '{"name":"张三","password":"mypassword"}' http://127.0.0.1:8088/json
HTTP/1.1 200 OK
Date: Sat, 06 May 2023 07:39:04 GMT
Content-Length: 112
Content-Type: text/plain; charset=utf-8

{"Id":0,"Uuid":"","Name":"张三 aaa","Email":"","Password":"mypasswordbbb","CreatedAt":"0001-01-01T00:00:00Z"}

Cookie是服务器要求浏览器记忆的东西,是属于服务器响应的内容,后面是服务器响应的内容。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。