GoWeb——处理请求(Request)

处理请求

Request结构体

type Request struct {
	Method string //请求方法
	URL *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
}

Request结构体主要用于返回HTTP请求的响应,是HTTP处理请求中非常重要的一部分。只有正确地解析请求数据,才能向客户端返回响应。接下来通过简单示例来测试一下。

/*
Request结构体解析返回示例
*/
func main() {
	http.HandleFunc("/hello", request)
	if err := http.ListenAndServe(":8080", nil); err != nil {
		log.Fatal(err)
	}
}

func request(w http.ResponseWriter, r *http.Request) {
	fmt.Println("Request解析")
	//HTTP方法
	fmt.Println("method", r.Method)
	//RequestURI是被客户端发送到服务器端的请求行中未修改的请求URI
	fmt.Println("RequestURI:", r.RequestURI)
	//URL类型
	fmt.Println("URL_path", r.URL.Path)
	fmt.Println("URL_RawQuery", r.URL.RawQuery)
	fmt.Println("URL_Fragment", r.URL.Fragment)
	//协议版本
	fmt.Println("proto", r.Proto)
	fmt.Println("protomajor", r.ProtoMajor)
	fmt.Println("protominor", r.ProtoMinor)
	//HTTP请求头
	for k, v := range r.Header {
		for _, vv := range v {
			fmt.Println("header key:" + k + " value:" + vv)
		}
	}
	//判断是否为multipart方式
	isMultipart := false
	for _, v := range r.Header["Content-Type"] {
		if strings.Index(v, "multipart/form-data") != -1 {
			isMultipart = true
		}
	}
	fmt.Println("isMultipart:", isMultipart)
	//解析Form表单
	if isMultipart == true {
		r.ParseMultipartForm(128)
		fmt.Println("解析方式:ParseMultipartForm")
	} else {
		r.ParseForm()
		fmt.Println("解析方式:ParseForm")
	}
	//HTTP Body内容长度
	fmt.Println("ContentLength:", r.ContentLength)
	//是否在回复请求后关闭连接
	fmt.Println("Close", r.Close)
	//HOST
	fmt.Println("host", r.Host)
	//请求的来源地址
	fmt.Println("RemoteAddr", r.RemoteAddr)
	fmt.Fprintf(w, "hello")
}
//GET http://127.0.0.1:8080/hello?age=18
Request解析
method GET
RequestURI: /hello
URL_path /hello
URL_RawQuery 
URL_Fragment 
proto HTTP/1.1
protomajor 1
protominor 1
header key:Upgrade-Insecure-Requests value:1
header key:Accept value:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
header key:User-Agent value:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.4 Safari/605.1.15
header key:Accept-Language value:zh-cn
header key:Accept-Encoding value:gzip, deflate
header key:Connection value:keep-alive
isMultipart: false
解析方式:ParseForm
ContentLength: 0
Close false
host 127.0.0.1:8080
RemoteAddr 127.0.0.1:50554
Request解析
method GET
RequestURI: /hello?age=18
URL_path /hello
URL_RawQuery age=18
URL_Fragment 
proto HTTP/1.1
protomajor 1
protominor 1
header key:Accept-Encoding value:gzip, deflate
header key:Connection value:keep-alive
header key:Upgrade-Insecure-Requests value:1
header key:Accept value:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
header key:User-Agent value:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_2) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.4 Safari/605.1.15
header key:Accept-Language value:zh-cn
isMultipart: false
解析方式:ParseForm
ContentLength: 0
Close false
host 127.0.0.1:8080
RemoteAddr 127.0.0.1:50554

URL结构体

type URL struct {
	Scheme     string	 //方案
	Opaque     string    //编码后的不透明数据
	User       *Userinfo //基本验证方式中的username和password信息
	Host       string    //主机字段
	Path       string    //路径
	RawPath    string    
	ForceQuery bool      
	RawQuery   string    //查询字段
	Fragment   string    //分片字段
}

该结构体主要用来存储URL各部分的值。net/url包中的很多方法都是对URL结构体进行相关操作,其中Parse()函数的定义如下:

func Parse(rawrul string) (*URL, error)
/*
Parse()函数查看URL结构体
*/
func main() {
	path := "http://localhost:8080/article?id=1"
	p, _ := url.Parse(path)
	println(p.Host) //localhost:8080
	println(p.User) //0x0
	println(p.RawQuery) //id=1
	println(p.RequestURI())// /article?id=i
}

请求头

请求头和响应头使用Header类型表示。Header类型是一个映射(map)类型,表示HTTP请求头中的多个键值对。其定义如下:

type Header map[string][]string

通过请求对象的Header属性可以访问到请求头信息。Header属性是映射结构,提供了Get()方法以获取key对应的第一个值。Get()方法的定义如下:

func (h Header) Get(key string)
func (h Header) Set(key, value string)
func (h Header) Add(key, value string)
func (h Header) Del(key string)
func (h Header) Write(w io.Writer) error

例如,要返回一个JSON格式的数据:

func main() {
	http.HandleFunc("/", Hello)
	if err := http.ListenAndServe(":8080", nil); err != nil {
		log.Fatal(err)
	}
}

type Greeting struct {
	Message string `json:"message"`
}

func Hello(w http.ResponseWriter, r *http.Request) {
	//返回JSON格式数据
	greeting := Greeting{
		"hello world",
	}
	message, _ := json.Marshal(greeting)
	//通过Set()方法设置Content-Type为application/json类型
	w.Header().Set("Content-Type", "application/json")
	w.Write(message)
}

请求体

请求体和响应体都由Request结构中的Body字段表示。Body字段是一个io.ReadCloser接口。ReadCloser接口的定义如下:

type ReadCloser interface {
	Reader
	Closer
}

Body字段是Reader接口和Closer接口的结合,Reader接口的定义如下:

type Reader interface {
	Read(p []byte) (n int, err error)
}

通过Reader接口可以看到,Read()方法实现了ReadCloser接口。所以,可以通过Body.Read()方法来读取请求体信息。接下来通过示例来加深对Body.Read()方法的理解。

/*
Body.Read()方法
*/

func main() {
	http.HandleFunc("/getBody", getBody)
	http.ListenAndServe(":8080", nil)
}

func getBody(w http.ResponseWriter, r *http.Request) {
	//获取请求报文内容的长度
	len := r.ContentLength
	//新建一个字节切片,长度与请求报文的内容长度相同
	body := make([]byte, len)
	//读取r的请求体,并将具体内容写入Body中
	r.Body.Read(body)
	//将获取的参数内容写入响应的报文中
	fmt.Fprintf(w, string(body))
}

处理HTML表单

POST和GET请求都可以传递表单,但GET请求会暴露参数给用户,所以一般用POST请求传递表单。
在用GET请求传递表单时,表单数据以键值对的形式包含在请求的URL里。服务器在接收到浏览器发送的表单数据后,需要先对这些数据进行语法分析,才能提取数据中记录的键值对。

表单的enctype属性

HTML表单的内容类型(content type)决定了POST请求在发送键值对时将使用何种格式。HTML表单的内容类型是由表单的enctype属性指定的。enctype属性有以下3种:

  • application/x-www-form-urlencoded
    这是表单默认的编码类型。该类型会把表单中的数据编码为键值对,且所有字符会被编码(空格被转换为“+”号,特殊符号被转换为ASC||HEX值)。
    • 当Method属性为GET时,表单中的数据会被转换为“name1=value1&name2=value2&…”形式,并拼接到请求的URL后面,以“?”分隔。queryString的URL加密采用的编码字符集取决于浏览器。例如表单中有“age:28”,采用UTF-8编码,则请求的URL为“…?age=28”。
    • 当method属性为POST时,在数据被添加到HTTP Body(请求体)中后,浏览器会根据在网页的ContentType(“text/html;charset-=UTF-8”)中指定的编码对表单中的数据进行编码,请求数据同上为“age=28”。
  • multipart/form-data.
    如果不对字符编码,则此时表单通常采用POST方式提交。该类型对表单以控件为单位进行分隔,为每个部分加上Content–Disposition(form-data|file)、Content–Type(默认text/plain)、name(控件name)等信息,并加上分隔符(边界boundary)。该类型一般用于将二进制文件上传到服务器。
  • text/plain。
    text/plain类型用于发送纯文本内容,常用于向服务器传递大量文本数据。该类型会将空格转换为加号(+),不对特殊字符进行编码,一般用于发送E-mail之类的数据信息。
Go语言的Form与PostForm字段

Form字段支持URL编码,键值的来源是URL和表单。

PostForm字段支持URL编码,键值的来源是表单。如果一个键同时拥有表单键值和URL键值,同时用户只想获取表单键值,则可使用PostForm字段,示例代码如下:

func process(w http.ResponseWriter, r *http.Request) {
	if r.Method == "GET" {
		t, _ := template.ParseFiles("form.html")
		t.Execute(w, nil)
	} else {
		r.ParseForm() //语法分析
		fmt.Fprintln(w, "表单键值对和URL键值对:", r.Form)
		fmt.Fprintln(w, "表单键值对:", r.PostForm)
	}
}

对应的HTML:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html" charset="UTF-8">
    <title>Form提交</title>
</head>
<body>
<form action="http://127.0.0.1:8089?name=go&color=green" method="post" enctype="application/x-www-form-urlencoded">
    <input type="text" name="name" value="shirdon"/>
    <input type="text" name="color" value="green"/>
    <input type="submit"/>
</form>
</body>
</html>

在这里插入图片描述

Go语言的MultipartForm字段

Go语言的MultipartForm字段支持mutipart/form-data编码,键值来源是表单,常用于文件的上传。
MultipartForm字段的使用示例如下:

func multiProcess(w http.ResponseWriter, r *http.Request) {
	if r.Method == "GET" {
		t, _ := template.ParseFiles("form.html")
		t.Execute(w, nil)
	} else {
		r.ParseMultipartForm(1024)                 //从表单里提取多少字节的数据
		fmt.Fprintln(w, "表单键值对:", r.MultipartForm) //multipartform是包含2个映射的结构
	}
}

multpart/form-data编码通常用于实现文件上传,需要File类型的Input标签。其HTML示例如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html" charset="UTF-8">
    <title>upload上传文件</title>
</head>
<body>
<form action="http://localhost:8089/file" method="post" enctype="multipart/form-data">
    <input type="file" name="uploaded">
    <input type="submit">
</form>
</body>
</html>

Form表单上传的Go语言示例代码如下:

//上传
func upload(w http.ResponseWriter, r *http.Request) {
	if r.Method == "GET" {
		t, _ := template.ParseFiles("./src/bookWebPro/chapter3/upload.html")
		t.Execute(w, nil)
	} else {
		r.ParseMultipartForm(4096)
		fileHeader := r.MultipartForm.File["uploaded"][0] //获取名为"uploaded"的第一个文件头

		file, err := fileHeader.Open() //获取文件
		if err != nil {
			fmt.Println("error")
			return
		}
		data, err := ioutil.ReadAll(file) //读取文件
		if err != nil {
			fmt.Println("error!")
			return
		}
		fmt.Fprintln(w, string(data))
	}
}

func main() {
	http.HandleFunc("/", upload) //设置首页的路由
	http.ListenAndServe(":8089", nil) //设置监听的端口
}

ResponseWriter原理

G0语言对接口的实现,不需要显示的声明,只要实现了接口定义的方法,那就实现了相应的接口。

io.Writer是一个接口类型。如果要使用io.Writer接口的Write()方法,则需要实现Write(p []byte)(n int,err error)方法。

在Go语言中,客户端请求信息都被封装在Request对象中。但是发送给客户端的响应并不是Response对象,而是ResponseWriter接口。ResponseWriter接口是处理器用来创建HTTP响应的接口的。ResponseWriter接口的定义如下:

type ResponseWriter interface {
	//用于设置或者获取所有响应头信息
	Header() Header
	//用于写入数据到响应体中
	Write([]byte) (int, error)
	//用于设置响应状态码
	WriteHeader(int)
}

实际上,在底层支撑ResponseWriter接口的是http.response结构体。在调用处理器处理HTTP请求时,会调用readRequest()方法。readRequest()方法会声明response结构体,并且其返回值是response指针。这也是在处理器方法声明时,Request是指针类型,而ResponseWriter不是指针类型的原因。实际上,响应对象也是指针类型。readRequest()方法的核心代码如下:

// Read next request from connection.
func (c *conn) readRequest(ctx context.Context) (w *response, err error) {
	//....
	w = &response{
		conn:          c,
		cancelCtx:     cancelCtx,
		req:           req,
		reqBody:       req.Body,
		handlerHeader: make(Header),
		contentLength: -1,
		closeNotifyCh: make(chan bool, 1),

		wants10KeepAlive: req.wantsHttp10KeepAlive(),
		wantsClose:       req.wantsClose(),
	}
	if isH2Upgrade {
		w.closeAfterReply = true
	}
	w.cw.res = w
	w.w = newBufioWriterSize(&w.cw, bufferBeforeChunkingSize)
	return w, nil
}

response结构体的定义和ResponseWriter接口都位于server.go文件中。不过由于response结构体是私有的,对外不可见,所以只能通过ResponseWriter接口访问它。两者之间的关系是:ResponseWriter是一个接口,而response结构体实现了它。我们引用ResponseWriter接口,实际上引用的是response结构体的实例。

ResponseWriter接口包含WriteHeader()、Header()、Write()三个方法来设置响应状态码。

WriteHeader()方法

VriteHeader()方法支持传入一个整型数据来表示响应状态码。如果不调用该方法,则默认响应状态码是200。WriteHeader()方法的主要作用是在API接口中返回错误码。例如,可以自定义一个处理器方法noAuth(),并通过w.WriteHeader()方法返回一个401未认证状态码(注意,在运行时,w代表的是对应的response对象实例,而不是接口)。

/*
用WriteHeader()方法返回401未认证状态码
*/

func main() {
	http.HandleFunc("/noAuth", noAuth)
	if err := http.ListenAndServe(":8080", nil); err != nil {
		log.Fatal(err)
	}
}

func noAuth(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(401)
	fmt.Fprintf(w, "未授权,认证后才能访问该接口!")
}
Header()方法

Header()方法用于设置响应头。可以通过w.Header().Set()方法设置响应头。w.Header()方法返回的是Header响应头对象,它和请求头共用一个结构体。因此在请求头中支持的方法这里都支持,比如可以通过w.Header().Add()方法新增响应头。

例如,如果要设置一个301重定向响应,则只需要通过w.WriteHeader()方法将响应状态码设置为301,再通过w.Header().Set()方法将“Location”设置为一个可访问域名即可。

新建一个处理器方法Redirect(),在其中编写重定向实现代码,如下:

/*
用w.Header().Set()方法设置301重定向示例
*/
func main() {
	http.HandleFunc("/redirect", Redirect)
	if err := http.ListenAndServe(":8080", nil); err != nil {
		log.Fatal(err)
	}
}

func Redirect(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Location", "https://www.baidu.com")
	w.WriteHeader(301)
}
Write()方法

Write()方法用于将数据写入HTTP响应体中。如果在调用Write()方法时还不知道Content-Type类型,则可以通过数据的前512个byte进行判断。用Write()方法可以返回字符串
数据,也可以返回HTML文档和JSON等常见的文本格式。

== 返回文本字符串数据:==

func main() {
	http.HandleFunc("/welcome", Welcome1)
	if err := http.ListenAndServe(":8080", nil); err != nil {
		log.Fatal(err)
	}
}

func Welcome1(w http.ResponseWriter, r *http.Request) {
	w.Write([]byte("hello world"))
}

== 返回HTML文档:==

func Home(w http.ResponseWriter, r *http.Request) {
	html := `<html> 
        <head>
            <title>Write方法返回HTML文档</title>
        </head> 
        <body>
            <h1>你好 </h1>
        </body> 
    </html>`
	w.Write([]byte(html))
}

func main() {
	http.HandleFunc("/", Home)
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		fmt.Println(err)
	}
}

此外,由于响应数据的内容类型变成了HTML。在响应头中可以看到,Content-Type也自动调整成了text/html,不再是纯文本格式。这里的Content-Type是根据传入的数据自行判断出来的。

== 返回JSON格式数据:==

type Greeting1 struct {
	Message string `json:"message"`
}

func Hello1(w http.ResponseWriter, r *http.Request) {
	// 返回 JSON 格式数据
	greeting := Greeting1{
		"欢迎",
	}
	message, _ := json.Marshal(greeting)
	w.Header().Set("Content-Type", "application/json")
	w.Write(message)
}

func main() {
	http.HandleFunc("/", Hello1)
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		fmt.Println(err)
	}
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值