当前位置: 首页 > news >正文

《Go Web 编程》之第4章 处理请求

《Go Web 编程》之第4章 处理请求

  • 第4章 处理请求
    • 4.1 请求和响应
    • 4.1.1 Request结构
      • 4.1.2 请求URL
      • 4.1.3 请求首部
      • 4.1.4 请求主体
    • 4.2 Go与HTML表单
      • 4.2.1 Form字段
      • 4.2.2 PostForm字段
      • 4.2.3 MultipartForm字段
      • 4.2.4 文件
      • 4.2.5 处理含JSON主体的POST请求
    • 4.3 ResponseWriter
      • 4.3.1 Write方法
      • 4.3.2 WriteHeader方法
      • 4.3.2 Header方法
      • 4.3.4 完整JSON响应
    • 4.4 cookie
      • 4.4.1Go与cookie
      • 4.4.2 发送cookie至浏览器
      • 4.4.3 从浏览器获取cookie
      • 4.4.4 cookie实现闪现消息

第4章 处理请求

4.1 请求和响应

HTTP报文在客户端和服务器间传递消息,分为HTTP请求和HTTP响应,结构如下:

1)请求行或响应行;
(2)零或多个首部;
(3)一个空行;
(4)可选报文主体。
GET /Protocols/rfc2616/rfc2616.html HTTP/1.1
Host: www.w3.org
User-Agent: Mozilla/5.0
(empty line)

4.1.1 Request结构

type Request struct {
	Method string
	
	URL *url.URL
	
	Proto      string // "HTTP/1.0"
	ProtoMajor int    // 1
	ProtoMinor int    // 0
	
	Header Header
	Body io.ReadCloser
	
	GetBody func() (io.ReadCloser, error)
	ContentLength int64
	TransferEncoding []string
	Close bool
	Host string
	
	Form url.Values
	PostForm url.Values
	MultipartForm *multipart.Form
	
	Trailer Header
	RemoteAddr string
	RequestURI string
	TLS *tls.ConnectionState
	Cancel <-chan struct{}
	Response *Response
	ctx context.Context
}

4.1.2 请求URL

type URL struct {
	Scheme      string
	Opaque      string    // encoded opaque data
	User        *Userinfo // username and password information
	Host        string    // host or host:port
	Path        string    // path (relative paths may omit leading slash)
	RawPath     string    // encoded path hint (see EscapedPath method)
	ForceQuery  bool      // append a query ('?') even if RawQuery is empty
	RawQuery    string    // encoded query values, without '?'
	Fragment    string    // fragment for references, without '#'
	RawFragment string    // encoded fragment hint (see EscapedFragment method)
}
//URL一般格式:
scheme://[userinfo@]host/path[?query][#fragment]
//解析为:
scheme:opaque[?query][#fragment]
http://www.example.com/post?id=123&thread_id=456
//RawQuery为
id=123&thread_id=456

浏览器向服务器发送请求时会剔除URL中的片段部分,服务器接收到URL中无Fragment字段。

4.1.3 请求首部

type Header map[string][]string

添加、删除、获取和设置。

package main

import (
	"fmt"
	"net/http"
)

func headers(w http.ResponseWriter, r *http.Request) {
	h := r.Header
	//h := r.Header["Accept-Encoding"]
	//h := r.Header.Get("Accept-Encoding")
	fmt.Fprintln(w, h)
}

func main() {
	server := http.Server{
		Addr: "127.0.0.1:8080",
	}
	http.HandleFunc("/headers", headers)
	server.ListenAndServe()
}

4.1.4 请求主体

Body io.ReadCloser

GET请求无报文主体。

package main

import (
	"fmt"
	"net/http"
)

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

func main() {
	server := http.Server{
		Addr: "127.0.0.1:8080",
	}
	http.HandleFunc("/body", body)
	server.ListenAndServe()
}
curl -id "first_name=sausheong&last_name=chang" 127.0.0.1:8080/body

4.2 Go与HTML表单

POST请求基本通过HTML表单发送:

<form action="/process" method="post">
	<input type="text" name="first_name"/>
	<input type="text" name="last_name"/>
	<input type="submit"/>
</form>

表单输入数据以键值形式记录在请求主体中。
决定POST请求发送键值对时格式的HTML表单内容类型(content type),由表单的enctype属性(默认"application/x-www-form-urlencoded")指定。

<form action="/process" method="post" enctype="application/x-www-form-urlencoded">
	<input type="text" name="first_name"/>
	<input type="text" name="last_name"/>
	<input type="submit"/>
</form>

浏览器至少支持application/x-www-form-urlencoded和multipart/form-data,HTML5还支持text/plain。

  • application/x-www-form-urlencoded
    &分隔的键值对保存在请求主体中。
  • multipart/form-data
    表单数据转换为MIME报文,键值对带有各自内容类型和内容配置(disposition)。
    传送大量数据(如上传文件),可Base64编码,以文本方式传送二进制数据。

HTML表单可发送GET请求,键值对在请求URL中,无主体。

4.2.1 Form字段

URL、主体数据提取到Form、PostForm和MultipartForm字段中。

Request获取表单数据的步骤:
(1)ParseFomr或者ParseMultipartForm方法,对请求进行语法分析;
(2)访问Form、PostForm或者MultipartForm字段。

client.html

<html>
  <head>    
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Go Web Programming</title>
  </head>
  <body>
    <form action="http://127.0.0.1:8080/process?hello=world&thread=123" method="post" enctype="application/x-www-form-urlencoded">
      <input type="text" name="hello" value="sau sheong"/>
      <input type="text" name="post" value="456"/>
      <input type="submit"/>
    </form>
  </body>
</html>
package main

import (
	"fmt"
	"net/http"
)

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

func main() {
	server := http.Server{
		Addr: "127.0.0.1:8080",
	}
	http.HandleFunc("/process", process)
	server.ListenAndServe()
}
//URL
http://127.0.0.1:8080/process?hello=world&thread=123
//post主体
hello=sau sheong&post=456

//输出
//r.ParseForm()和r.Form包含url和表单键值对
//且表单值总是排在URL值的前面
map[thread:[123] hello:[sau sheong world] post:[456]]

4.2.2 PostForm字段

//r.ParseForm()和r.PostForm(只支持application/x-www-form-urlencoded编码)
//只包含表单键值对
//r.ParseForm()和r.Form(multipart/form-data)
//只返回URL查询值

4.2.3 MultipartForm字段

获取multipart/form-data编码的表单数据,需要ParseMultipartForm方法(需要时会自行调用ParseFrom方法)和MultipartForm字段。

//从multipart编码表单里取出字节数
r.ParseMultipartForm(1024)

//&{map[hello:[sau sheong] post:[456]] map[]}
//只包含表单键值对
//第二个空映射map[]用来记录用户上传的文件
fmt.Fprintln(w, r.MultipartForm)


//FormValue只取第一个值
//需要时自动调用ParseFrom或ParseMultipartForm
fmt.Fprintln(w, r.FormValue("hello"))


//PostFormValue只返回表单值
//需要时自动调用ParseFrom或ParseMultipartForm
fmt.Fprintln(w, r.PostFormValue("hello"))

FormValue和PostFormValue解析multipart/form-data表单,无结果。

整理:
(1)
表单(application/x-www-form-urlencoded,默认)
+
ParseFrom方法
+
Form字段
👇
URL键值对+表单键值对;

(2)
表单(application/x-www-form-urlencoded,默认)
+
ParseFrom方法
+
PostForm字段
👇
表单键值对;

(3)
表单(multipart/form-data)
+
ParseMultipartFrom方法
+
MultipartFrom字段
👇
表单键值对;

(4)
表单(application/x-www-form-urlencoded,默认)
+
FromValue字段
👇
URL键值对+表单键值对;

(5)
表单(application/x-www-form-urlencoded,默认)
+
PostFromValue字段
👇
表单键值对;

4.2.4 文件

multipart/form-data编码常用于文件上传,需要file类型的input标签。

<html>
  <head>    
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Go Web Programming</title>
  </head>
  <body>
    <form action="http://localhost:8080/process?hello=world&thread=123" method="post" enctype="multipart/form-data">
      <input type="text" name="hello" value="sau sheong"/>
      <input type="text" name="post" value="456"/>
      <input type="file" name="uploaded">
      <input type="submit">
    </form>
  </body>
</html>
package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
)

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

func main() {
	server := http.Server{
		Addr: "127.0.0.1:8080",
	}
	http.HandleFunc("/process", process)
	server.ListenAndServe()
}
package main

import (
	"fmt"
	"io/ioutil"
	"net/http"
)

func process(w http.ResponseWriter, r *http.Request) {
	file, _, err := r.FormFile("uploaded")
	if err == nil {
		data, err := ioutil.ReadAll(file)
		if err == nil {
			fmt.Fprintln(w, string(data))
		}
	}
}

func main() {
	server := http.Server{
		Addr: "127.0.0.1:8080",
	}
	http.HandleFunc("/process", process)
	server.ListenAndServe()
}

4.2.5 处理含JSON主体的POST请求

不同客户端使用不同方式编码POST请求。

  • jQuery使用POST+默认表单编码+首部Content-Type: application/x-www-form-urlencoded
  • Angular使用POST+表单编码application/json

ParseForm方法不接受application/json编码。

4.3 ResponseWriter

处理器通过ResponseWriter接口创建HTTP响应。
ResponseWriter接口内部会使用http.response结构(非导出,nonexported)。

4.3.1 Write方法

字节数组作为参数,写入响应主体。
首部未设置内容类型时,通过写入前512字节决定。

package main

import (
	"fmt"
	"encoding/json"
	"net/http"
)

type Post struct {
	User    string
	Threads []string
}

func writeExample(w http.ResponseWriter, r *http.Request) {
	str := `<html>
<head><title>Go Web Programming</title></head>
<body><h1>Hello World</h1></body>
</html>`
	w.Write([]byte(str))
}

func main() {
	server := http.Server{
		Addr: "127.0.0.1:8080",
	}
	http.HandleFunc("/write", writeExample)
	server.ListenAndServe()
}
curl -i 127.0.0.1:8080/writer

4.3.2 WriteHeader方法

响应状态码,未设置时默认返回200 OK。

package main

import (
	"fmt"
	"encoding/json"
	"net/http"
)

type Post struct {
	User    string
	Threads []string
}

func writeHeaderExample(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(501)
	fmt.Fprintln(w, "No such service, try next door")
}

func main() {
	server := http.Server{
		Addr: "127.0.0.1:8080",
	}
	http.HandleFunc("/writeheader", writeHeaderExample)
	server.ListenAndServe()
}
curl -i 127.0.0.1:8080/writerheader

4.3.2 Header方法

写入首部映射。
先调用Header方法写入首部,再调用WriteHeader(执行后,不允许修改首部)写入状态码。

package main

import (
	"fmt"
	"encoding/json"
	"net/http"
)

type Post struct {
	User    string
	Threads []string
}

func headerExample(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Location", "http://google.com")
	w.WriteHeader(302)
}

func main() {
	server := http.Server{
		Addr: "127.0.0.1:8080",
	}
	http.HandleFunc("/redirect", headerExample)
	server.ListenAndServe()
}
curl -i 127.0.0.1:8080/redirect

4.3.4 完整JSON响应

package main

import (
	"fmt"
	"encoding/json"
	"net/http"
)

type Post struct {
	User    string
	Threads []string
}

func writeExample(w http.ResponseWriter, r *http.Request) {
	str := `<html>
<head><title>Go Web Programming</title></head>
<body><h1>Hello World</h1></body>
</html>`
	w.Write([]byte(str))
}

func writeHeaderExample(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(501)
	fmt.Fprintln(w, "No such service, try next door")
}

func headerExample(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Location", "http://google.com")
	w.WriteHeader(302)
}

func jsonExample(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")
	post := &Post{
		User:    "Sau Sheong",
		Threads: []string{"first", "second", "third"},
	}
	json, _ := json.Marshal(post)
	w.Write(json)
}

func main() {
	server := http.Server{
		Addr: "127.0.0.1:8080",
	}
	http.HandleFunc("/write", writeExample)
	http.HandleFunc("/writeheader", writeHeaderExample)
	http.HandleFunc("/redirect", headerExample)
	http.HandleFunc("/json", jsonExample)
	server.ListenAndServe()
}
curl -i 127.0.0.1:8080/json

4.4 cookie

存储在客户端的、体积较小的信息,最初通过服务器HTTP响应报文发送(set-cookie),之后客户端每次请求发送cookie。
cookie划分为会话cookie和持久cookie。

4.4.1Go与cookie

type Cookie struct {
	Name  string
	Value string

	Path       string
	Domain     string
	Expires    time.Time
	RawExpires string 

	MaxAge   int
	Secure   bool
	HttpOnly bool
	SameSite SameSite
	Raw      string
	Unparsed []string
}

会话cookie或临时cookie(未设置Expires字段),浏览器关闭时自动移除。
持久cookie(设置了Expires字段),时间过期或手动删除。
Expires绝对时间,几乎所有浏览器都支持。
MaxAge相对时间,HTTP 1.1推荐使用。

4.4.2 发送cookie至浏览器

package main

import (
	"fmt"
	"net/http"
)

func setCookie(w http.ResponseWriter, r *http.Request) {
	c1 := http.Cookie{
		Name:     "first_cookie",
		Value:    "Go Web Programming",
		HttpOnly: true,
	}
	c2 := http.Cookie{
		Name:     "second_cookie",
		Value:    "Manning Publications Co",
		HttpOnly: true,
	}
	w.Header().Set("Set-Cookie", c1.String())
	w.Header().Add("Set-Cookie", c2.String())
}

func main() {
	server := http.Server{
		Addr: "127.0.0.1:8080",
	}
	http.HandleFunc("/set_cookie", setCookie)
	server.ListenAndServe()
}
package main

import (
	"fmt"
	"net/http"
)

func setCookie(w http.ResponseWriter, r *http.Request) {
	c1 := http.Cookie{
		Name:     "first_cookie",
		Value:    "Go Web Programming",
		HttpOnly: true,
	}
	c2 := http.Cookie{
		Name:     "second_cookie",
		Value:    "Manning Publications Co",
		HttpOnly: true,
	}
	http.SetCookie(w, &c1)
	http.SetCookie(w, &c2)
}

func main() {
	server := http.Server{
		Addr: "127.0.0.1:8080",
	}
	http.HandleFunc("/set_cookie", setCookie)
	server.ListenAndServe()
}

4.4.3 从浏览器获取cookie

package main

import (
	"fmt"
	"net/http"
)

func setCookie1(w http.ResponseWriter, r *http.Request) {
	c1 := http.Cookie{
		Name:  "first_cookie",
		Value: "Go Programming",
	}
	c2 := http.Cookie{
		Name:     "second_cookie",
		Value:    "Go Web Programming",
		HttpOnly: true,
	}
	w.Header().Set("Set-Cookie", c1.String())
	w.Header().Add("Set-Cookie", c2.String())
	fmt.Fprintf(w, "%s\n%s\n", c1.String(), c2.String())
}

func setCookie2(w http.ResponseWriter, r *http.Request) {
	c1 := http.Cookie{
		Name:  "first_cookie",
		Value: "Go Programming",
	}
	c2 := http.Cookie{
		Name:  "second_cookie",
		Value: "Go Web Programming",
		HttpOnly: true,
	}
	http.SetCookie(w, &c1)
	http.SetCookie(w, &c2)
}

func getCookie1(w http.ResponseWriter, r *http.Request) {
	cookie := r.Header["Cookie"]
    fmt.Fprintf(w, "%s\n", cookie)
}

func getCookie2(w http.ResponseWriter, r *http.Request) {
	cookie, err := r.Cookie("first_cookie")
	if err != nil {
		fmt.Fprintln(w, "Cannot get Cookie")
	}
	cookies := r.Cookies()
	fmt.Fprintf(w, "%s\n%s\n", cookie, cookies)
}

func main() {
	server := http.Server{
		Addr: "127.0.0.1:8080",
	}
	http.HandleFunc("/set_cookie", setCookie2)
	http.HandleFunc("/get_cookie", getCookie2)
	server.ListenAndServe()
}
//获取的Set-Cookie保存在a.cookie文件中
curl -i -c a.cookie http://127.0.0.1:8080/set_cookie

//发送a.cookie文件中的cookies
curl -i -b a.cookie http://127.0.0.1:8080/get_cookie

4.4.4 cookie实现闪现消息

某个条件满足时,页面上显示临时消息(闪现消息,flash message),刷新页面后消失。

package main

import (
    "encoding/base64"
	"fmt"
	"net/http"
    "time"
)

func set_message(w http.ResponseWriter, r *http.Request) {
    msg := []byte("Hello World")
    cookie := http.Cookie{
        Name:  "flash",
		//响应首部对空格,百分号,中文等特殊字符的URL编码要求
        Value: base64.URLEncoding.EncodeToString(msg),
    }
    http.SetCookie(w, &cookie)
}

func show_message(w http.ResponseWriter, r *http.Request) {
    cookie, err := r.Cookie("flash")
    if err != nil {
        if err == http.ErrNoCookie {
            fmt.Fprintln(w, "no messages to show")
        }
    } else {
        expire_cookie := http.Cookie{
            Name:    "flash",
				MaxAge:   -1,              //负值
				Expires:  time.Unix(1, 0), //过去时间
        }
		
		//MaxAge设置负值,Expires设置过去时间
		//SetCookie将同名cookie("flash")发送到客户端
		//等价于完全移除这个cookie
        http.SetCookie(w, &expire_cookie)
		
        value, _ := base64.URLEncoding.DecodeString(cookie.Value)
        fmt.Fprintln(w, string(value))
    }
}

func main() {
	server := http.Server{
		Addr: "127.0.0.1:8080",
	}
    http.HandleFunc("/set_message", set_message)
    http.HandleFunc("/show_message", show_message)
	server.ListenAndServe()
}
curl -i -c b.cookie http://127.0.0.1:8080/set_message
curl -i -b b.cookie -c b.cookie http://127.0.0.1:8080/show_message


curl -i -b b.cookie -c b.cookie http://127.0.0.1:8080/show_message

相关文章:

  • ZYNQ之中断机制
  • Java JDK path环境变量配置
  • linux编译安装 php-nginx-mysql
  • 个人收入理财App的设计与实现
  • 【OpenStack云平台】网络控制节点 HA 集群配置
  • ​【原创】基于SSM的酒店预约管理系统(酒店管理系统毕业设计)
  • 基于node.js+Vue在线电子商务购物商城系统 Element
  • CVPR 2022:Generalized Few-shot Semantic Segmentation 解读
  • d二进制字面
  • 使用docker安装mysql
  • Vue.js入门教程(四)
  • SPA项目实现首页导航以及左侧菜单
  • 【java核心技术】Java知识总结 -- 对象和类
  • 猿创征文|SpringBoot概述及在idea中创建方式
  • 计算机毕业设计python基于django在线课程网站 含资源,考试,论坛等功能
  • CAP 一致性协议及应用解析
  • java第三方包学习之lombok
  • Java应用性能调优
  • Java知识点总结(JDBC-连接步骤及CRUD)
  • js递归,无限分级树形折叠菜单
  • Mac 鼠须管 Rime 输入法 安装五笔输入法 教程
  • Node.js 新计划:使用 V8 snapshot 将启动速度提升 8 倍
  • October CMS - 快速入门 9 Images And Galleries
  • Rancher如何对接Ceph-RBD块存储
  • Transformer-XL: Unleashing the Potential of Attention Models
  • webpack入门学习手记(二)
  • 从零开始的webpack生活-0x009:FilesLoader装载文件
  • 从伪并行的 Python 多线程说起
  • 如何设计一个比特币钱包服务
  • 小程序开发中的那些坑
  • 主流的CSS水平和垂直居中技术大全
  • [地铁译]使用SSD缓存应用数据——Moneta项目: 低成本优化的下一代EVCache ...
  • Spark2.4.0源码分析之WorldCount 默认shuffling并行度为200(九) ...
  • 函数计算新功能-----支持C#函数
  • 移动端高清、多屏适配方案
  • #NOIP 2014#Day.2 T3 解方程
  • #绘制圆心_R语言——绘制一个诚意满满的圆 祝你2021圆圆满满
  • (8)Linux使用C语言读取proc/stat等cpu使用数据
  • (黑客游戏)HackTheGame1.21 过关攻略
  • (每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理 第13章 项目资源管理(七)
  • (译) 函数式 JS #1:简介
  • (原創) 如何解决make kernel时『clock skew detected』的warning? (OS) (Linux)
  • (转)setTimeout 和 setInterval 的区别
  • ***原理与防范
  • .[backups@airmail.cc].faust勒索病毒的最新威胁:如何恢复您的数据?
  • .bashrc在哪里,alias妙用
  • .NET Core日志内容详解,详解不同日志级别的区别和有关日志记录的实用工具和第三方库详解与示例
  • .NET Framework 的 bug?try-catch-when 中如果 when 语句抛出异常,程序将彻底崩溃
  • .net/c# memcached 获取所有缓存键(keys)
  • .NET/C# 反射的的性能数据,以及高性能开发建议(反射获取 Attribute 和反射调用方法)
  • .NET/C# 项目如何优雅地设置条件编译符号?
  • .net开源工作流引擎ccflow表单数据返回值Pop分组模式和表格模式对比
  • .net中的Queue和Stack
  • @AliasFor注解
  • @Bean, @Component, @Configuration简析