GoWeb 处理请求

go语言的 net/http包提供了一系列用于表示HTTP报文的结构,可以使用它处理请求发送响应,其中Request结构代表了客户端发送的请求报文

type Request

type Request struct {
    // Method指定HTTP方法(GET、POST、PUT等)。对客户端,""代表GET。
    Method string
    // URL在服务端表示被请求的URI,在客户端表示要访问的URL。
    //
    // 在服务端,URL字段是解析请求行的URI(保存在RequestURI字段)得到的,
    // 对大多数请求来说,除了Path和RawQuery之外的字段都是空字符串。
    // (参见RFC 2616, Section 5.1.2)
    //
    // 在客户端,URL的Host字段指定了要连接的服务器,
    // 而Request的Host字段(可选地)指定要发送的HTTP请求的Host头的值。
    URL *url.URL
    // 接收到的请求的协议版本。本包生产的Request总是使用HTTP/1.1
    Proto      string // "HTTP/1.0"
    ProtoMajor int    // 1
    ProtoMinor int    // 0
    // Header字段用来表示HTTP请求的头域。如果头域(多行键值对格式)为:
    //	accept-encoding: gzip, deflate
    //	Accept-Language: en-us
    //	Connection: keep-alive
    // 则:
    //	Header = map[string][]string{
    //		"Accept-Encoding": {"gzip, deflate"},
    //		"Accept-Language": {"en-us"},
    //		"Connection": {"keep-alive"},
    //	}
    // HTTP规定头域的键名(头名)是大小写敏感的,请求的解析器通过规范化头域的键名来实现这点。
    // 在客户端的请求,可能会被自动添加或重写Header中的特定的头,参见Request.Write方法。
    Header Header
    // Body是请求的主体。
    //
    // 在客户端,如果Body是nil表示该请求没有主体买入GET请求。
    // Client的Transport字段会负责调用Body的Close方法。
    //
    // 在服务端,Body字段总是非nil的;但在没有主体时,读取Body会立刻返回EOF。
    // Server会关闭请求的主体,ServeHTTP处理器不需要关闭Body字段。
    Body io.ReadCloser
    // ContentLength记录相关内容的长度。
    // 如果为-1,表示长度未知,如果>=0,表示可以从Body字段读取ContentLength字节数据。
    // 在客户端,如果Body非nil而该字段为0,表示不知道Body的长度。
    ContentLength int64
    // TransferEncoding按从最外到最里的顺序列出传输编码,空切片表示"identity"编码。
    // 本字段一般会被忽略。当发送或接受请求时,会自动添加或移除"chunked"传输编码。
    TransferEncoding []string
    // Close在服务端指定是否在回复请求后关闭连接,在客户端指定是否在发送请求后关闭连接。
    Close bool
    // 在服务端,Host指定URL会在其上寻找资源的主机。
    // 根据RFC 2616,该值可以是Host头的值,或者URL自身提供的主机名。
    // Host的格式可以是"host:port"。
    //
    // 在客户端,请求的Host字段(可选地)用来重写请求的Host头。
    // 如过该字段为"",Request.Write方法会使用URL字段的Host。
    Host string
    // Form是解析好的表单数据,包括URL字段的query参数和POST或PUT的表单数据。
    // 本字段只有在调用ParseForm后才有效。在客户端,会忽略请求中的本字段而使用Body替代。
    Form url.Values
    // PostForm是解析好的POST或PUT的表单数据。
    // 本字段只有在调用ParseForm后才有效。在客户端,会忽略请求中的本字段而使用Body替代。
    PostForm url.Values
    // MultipartForm是解析好的多部件表单,包括上传的文件。
    // 本字段只有在调用ParseMultipartForm后才有效。
    // 在客户端,会忽略请求中的本字段而使用Body替代。
    MultipartForm *multipart.Form
    // Trailer指定了会在请求主体之后发送的额外的头域。
    //
    // 在服务端,Trailer字段必须初始化为只有trailer键,所有键都对应nil值。
    // (客户端会声明哪些trailer会发送)
    // 在处理器从Body读取时,不能使用本字段。
    // 在从Body的读取返回EOF后,Trailer字段会被更新完毕并包含非nil的值。
    // (如果客户端发送了这些键值对),此时才可以访问本字段。
    //
    // 在客户端,Trail必须初始化为一个包含将要发送的键值对的映射。(值可以是nil或其终值)
    // ContentLength字段必须是0或-1,以启用"chunked"传输编码发送请求。
    // 在开始发送请求后,Trailer可以在读取请求主体期间被修改,
    // 一旦请求主体返回EOF,调用者就不可再修改Trailer。
    //
    // 很少有HTTP客户端、服务端或代理支持HTTP trailer。
    Trailer Header
    // RemoteAddr允许HTTP服务器和其他软件记录该请求的来源地址,一般用于日志。
    // 本字段不是ReadRequest函数填写的,也没有定义格式。
    // 本包的HTTP服务器会在调用处理器之前设置RemoteAddr为"IP:port"格式的地址。
    // 客户端会忽略请求中的RemoteAddr字段。
    RemoteAddr string
    // RequestURI是被客户端发送到服务端的请求的请求行中未修改的请求URI
    // (参见RFC 2616, Section 5.1)
    // 一般应使用URI字段,在客户端设置请求的本字段会导致错误。
    RequestURI string
    // TLS字段允许HTTP服务器和其他软件记录接收到该请求的TLS连接的信息
    // 本字段不是ReadRequest函数填写的。
    // 对启用了TLS的连接,本包的HTTP服务器会在调用处理器之前设置TLS字段,否则将设TLS为nil。
    // 客户端会忽略请求中的TLS字段。
    TLS *tls.ConnectionState
}

Request类型代表一个服务端接受到的或者客户端发送出去的HTTP请求。

Request各字段的意义和用途在服务端和客户端是不同的。除了字段本身上方文档,还可参见Request.Write方法和RoundTripper接口的文档

获取请求URL

Request结构中的URL字段用于表示请求行中包含的URL,改字段是一个指向url.URL结构的指针

type URL

type URL struct {
    Scheme     string
    Opaque     string    //编码的不透明数据
    User       *Userinfo //用户名和密码信息
    Host       string    //主机或主机:端口
    Path       string    //路径(相对路径可能省略前斜杠)
    RawPath    string    //编码路径提示(请参阅EscapedPath方法)
    ForceQuery bool      //追加查询('?'),即使RawQuery为空
    RawQuery   string    //编码的查询值,不带'?'
    Fragment   string    //用于引用的片段,不带'#'
}

表示的一般形式是

[scheme:][//[userinfo@]host][/]path[?query][#fragment]

scheme 后 不以斜杠开头的URL解释为

scheme:opaque[?query][#fragment]

请注意,Path字段以解码形式存储:/%47%6f%2f变为/ Go /。结果是,无法确定路径中的斜线是原始URL中的斜线,以及哪个是%2f。这种区别很少很重要,但是在这种情况下,代码应使用RawPath,这是一个可选字段,仅当默认编码与Path不同时才设置该字段

  1. Path字段

通过 r.URL.Path 只能得到 /hello

  1. RawQuery字段

通过 r.URL.RawQuery 得到的是 username=admin&password=123456

package main

import (
	"fmt"
	"net/http"
)

//创建处理器函数
func handler(w http.ResponseWriter, r *http.Request)  {
	fmt.Fprintln(w, "你发送的请求的请求地址是:", r.URL.Path)
	fmt.Fprintln(w, "你发送的请求的请求地址后的查询字符串是:", r.URL.RawQuery)
}
func main() {
	http.HandleFunc("/hello", handler)
	http.ListenAndServe(":8080", nil)
}

获取请求头中的信息

通过Request结果中的Header字段用来获取请求头中的所有信息,Header字段的类型Header类型,而Header类型是一个map[string][]string, string类型的key,string切片类型的值

  • type Header
type Header map[string][]string

Header代表HTTP头域的键值对。

  • func (Header) Get
func (h Header) Get(key string) string

Get返回键对应的第一个值,如果键不存在会返回""。如要获取该键对应的值切片,请直接用规范格式的键访问map。

  • func (Header) Set
func (h Header) Set(key, value string)

Set添加键值对到h,如键已存在则会用只有新值一个元素的切片取代旧值切片。

  • func (Header) Add
func (h Header) Add(key, value string)

Add添加键值对到h,如键已存在则会将新的值附加到旧值切片后面。

  • func (Header) Del
func (h Header) Del(key string)

Del删除键值对。

  • func (Header) Write
func (h Header) Write(w io.Writer) error

Write以有线格式将头域写入w

  1. 获取请求头中的所有信息
    r.Header
    结果
map[Connection:[keep-alive] Cache-Control:[max-age=0] User-Agent:[Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36] Accept:[text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8] Accept-Encoding:[gzip, deflate, br] Upgrade-Insecure-Requests:[1] Accept-Language:[zh-CN,zh;q=0.9]]
  1. 获取请求头中的某个具体属性的值,如 获取 Accept-Encoding的值
  • 方式1: r.Header[“Accept-Encoding”]
    得到的是一个字符串切片
    结果
[gzip, deflate, br]
  • 方式2: r.Header.Get(“Accept-Encoding”)
    得到的是字符串形式的值,多个值使用逗号分隔
    结果
gzip, deflate, br
package main

import (
	"fmt"
	"net/http"
)

//创建处理器函数
func handler(w http.ResponseWriter, r *http.Request)  {
	fmt.Fprintln(w, "你发送的请求的请求地址是:", r.URL.Path)
	fmt.Fprintln(w, "你发送的请求的请求地址后的查询字符串是:", r.URL.RawQuery)
	fmt.Fprintln(w, "请求头中的所有信息:", r.Header)
	fmt.Fprintln(w, "请求头中Accept-Encoding的信息:", r.Header["Accept-Encoding"])
	fmt.Fprintln(w, "请求头中Accept-Encoding的属性值是:", r.Header.Get("Accept-Encoding"))

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

获取请求体中的信息

请求和响应的主体都是有Request结构中的Body字段表示,这个字段的类型是 io.ReadCloser接口,该接口包含了Reader接口和Closer接口,Reader接口拥有Read方法,Closer接口拥有Close方法

  • type ReadCloser
type ReadCloser interface {
    Reader
    Closer
}

ReadCloser 接口组合了基本的 Read 和 Close 方法

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

Reader 接口包装了基本的 Read 方法。

Read 将 len§ 个字节读取到 p 中。它返回读取的字节数 n(0 <= n <= len§) 以及任何遇到的错误。即使 Read 返回的 n < len§,它也会在调用过程中使用 p 的全部作为暂存空间。若一些数据可用但不到 len§ 个字节,Read 会照例返回可用的东西, 而不是等待更多。

当 Read 在成功读取 n > 0 个字节后遇到一个错误或 EOF 情况,它就会返回读取的字节数。 它会从相同的调用中返回(非nil的)错误或从随后的调用中返回错误(和 n == 0)。 这种一般情况的一个例子就是 Reader 在输入流结束时会返回一个非零的字节数, 可能的返回不是 err == EOF 就是 err == nil。无论如何,下一个 Read 都应当返回 0, EOF。

调用者应当总在考虑到错误 err 前处理 n > 0 的字节。这样做可以在读取一些字节, 以及允许的 EOF 行为后正确地处理I/O错误。

Read 的实现在 len§ == 0 以外的情况下会阻止返回零字节的计数和 nil 错误, 调用者应将返回 0 和 nil 视作什么也没有发生;特别是它并不表示 EOF

  • type Closer
type Closer interface {
    Close() error
}

Closer 接口包装了基本的 Close 方法。

Close 的行为在第一次调用后没有定义。具体实现可能有自己的行为描述

1.由于GET请求没有请求体,所以需要在HTML页面中创建一个form表单,通过指定 method=“post” 来发送一个POST请求

表单

<html>
    <head>
        <meta charset="UTF-8" />
    </head>
    <body>
        <form action="http://localhost:8080/hello" method="POST">
            用户名: <input type="text" name="username" /><br/>
            密码: <input type="password" name="password" /><br/>
            <input type="submit" />
        </form>
    </body>
</html>

服务器处理请求代码

package main

import (
	"fmt"
	"net/http"
)

//创建处理器函数
func handler(w http.ResponseWriter, r *http.Request)  {
	fmt.Fprintln(w, "你发送的请求的请求地址是:", r.URL.Path)
	fmt.Fprintln(w, "你发送的请求的请求地址后的查询字符串是:", r.URL.RawQuery)
	fmt.Fprintln(w, "请求头中的所有信息:", r.Header)
	fmt.Fprintln(w, "请求头中Accept-Encoding的信息:", r.Header["Accept-Encoding"])
	fmt.Fprintln(w, "请求头中Accept-Encoding的属性值是:", r.Header.Get("Accept-Encoding"))

	//获取请求体内容的长度
	len := r.ContentLength
	//创建byte切片
	body := make([]byte, len)
	//将请求体中的内容读到body中
	r.Body.Read(body)
	//在浏览器中显示请求体中的内容
	fmt.Fprintln(w, "请求体中的内容有: ", string(body))

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

浏览器显示结果

请求体中的内容有:  username=admin&password=123456

获取请求参数

form字段

  1. 类型是 url.Values类型,form是解析好的表单数据,包括URL字段的query参数和POST或PUT的表单数据
  • type Values
type Values map[string][]string

值将字符串键映射到值列表。它通常用于查询参数和表单值。与http.Header映射不同,Values映射中的键区分大小写

  1. form字段只有在调用Request的ParseForm方法后才有效。在客户端,会忽略请求中的本字段而使用Body替代
  • func (*Request) ParseForm
func (r *Request) ParseForm() error

ParseForm解析URL中的查询字符串,并将解析结果更新到r.Form字段。

对于POST或PUT请求,ParseForm还会将body当作表单解析,并将结果既更新到r.PostForm也更新到r.Form。解析结果中,POST或PUT请求主体要优先于URL查询字符串(同名变量,主体的值在查询字符串的值前面)

如果请求的主体的大小没有被MaxBytesReader函数设定限制,其大小默认限制为开头10MB

ParseMultipartForm会自动调用ParseForm。重复调用本方法是无意义的

  1. 获取表单中提交的请求参数(username和password)
package main

import (
	"fmt"
	"net/http"
)

//创建处理器函数
func handler(w http.ResponseWriter, r *http.Request)  {
	//解析表单,在调用r.Form之前必须执行该操作
	r.ParseForm()
	//获取请求参数
	fmt.Fprintln(w, "请求参数有:", r.Form)

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

表单

<html>
    <head>
        <meta charset="UTF-8" />
    </head>
    <body>
        <form action="http://localhost:8080/hello?username=admin&password=123456" method="POST">
            用户名: <input type="text" name="username"><br/>
            密码: <input type="password" name="password"><br/>
            <input type="submit" />
        </form>
    </body>
</html>

运行结果:

请求参数有: map[username:[linux admin] password:[888 123456]]

发现: 表单中的请求参数username和URL中的请求参数username都获取到了,而且表单中的请求参数的值排在URL请求参数值的前面

PostForm字段

  1. 类型也是 url.Values 类型,用来获取表单中的请求参数
package main

import (
	"fmt"
	"net/http"
)

//创建处理器函数
func handler(w http.ResponseWriter, r *http.Request)  {
	//解析表单,在调用r.Form之前必须执行该操作
	r.ParseForm()
	//获取请求参数
	fmt.Fprintln(w, "请求参数有:", r.Form)
	fmt.Fprintln(w, "POST请求的form表单中的请求参数有:", r.PostForm)

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

运行结果:

请求参数有: map[username:[admin admin] password:[123 123456]]
POST请求的form表单中的请求参数有: map[username:[admin] password:[123]]
  1. 但是PostForm字段只支持 application/x-www-form-urlencoded 编码,如果form表单的enctype属性值为 multipart/form-data, 那么使用PostForm字段无法获取表单中的数据,此时需要使用 MultipartForm 字段

说明: form表单的 enctype 属性的默认值是 application/x-www-form-urlencoded 编码,实现上传文件时需要将该属性的值设置为 multipart/form-data 编码格式

FormValue方法和 PostFormValue方法

  1. FormValue方法
    可以通过FormValue 方法快速获取某一个请求参数,该方法调用之前会自动调用 ParseMultipartFormParseForm方法对表单进行解析
  • func (*Request) FormValue
func (r *Request) FormValue(key string) string

FormValue返回key为键查询r.Form字段得到结果[]string切片的第一个值。POST和PUT主体中的同名参数优先于URL查询字符串。如果必要,本函数会隐式调用ParseMultipartForm和ParseForm

  1. PostFormValue方法
    可以通过PostFormValue方法快速获取表单中的某一个请求参数,该方法调用之前会自动调用ParseMultipartFormParseForm方法对表单进行解析
  • func (*Request) PostFormValue
func (r *Request) PostFormValue(key string) string

PostFormValue返回key为键查询r.PostForm字段得到结果[]string切片的第一个值。如果必要,本函数会隐式调用ParseMultipartForm和ParseForm

代码

package main

import (
	"fmt"
	"net/http"
)

//创建处理器函数
func handler(w http.ResponseWriter, r *http.Request)  {
	//通过直接调用FormValue方法和PostFormValue方法直接获取请求参数的值
	fmt.Fprintln(w, "URL中的user请求参数的值:", r.FormValue("user"))
	fmt.Fprintln(w, "Form表单中的username请求参数的值:", r.PostFormValue("username"))
	
}
func main() {
	http.HandleFunc("/hello", handler)
	http.ListenAndServe(":8080", nil)
}

表单

<html>
    <head>
        <meta charset="UTF-8" />
    </head>
    <body>
        <form action="http://localhost:8080/hello?user=wuxing&password=666" method="POST" enctype="application/x-www-form-urlencoded">
            用户名: <input type="text" name="username"><br/>
            密码: <input type="password" name="password"><br/>
            <input type="submit" />
        </form>
    </body>
</html>

运行结果:

URL中的user请求参数的值: wuxing
Form表单中的username请求参数的值: admin

MultipartForm字段

为了取得 multipart/form-data编码的表单数据,需要用到Request结构的 ParseMultipartForm方法和MultipartForm字段,通常上传文件时会将form表单的 enctype属性值设置为 multipart/form-data

  • func (*Request) ParseMultipartForm
func (r *Request) ParseMultipartForm(maxMemory int64) error

ParseMultipartForm将请求的主体作为multipart/form-data解析。请求的整个主体都会被解析,得到的文件记录最多maxMemery字节保存在内存,其余部分保存在硬盘的temp文件里。如果必要,ParseMultipartForm会自行调用ParseForm。重复调用本方法是无意义的

给客户端响应

  • type ResponseWriter
type ResponseWriter interface {
    // Header返回一个Header类型值,该值会被WriteHeader方法发送。
    // 在调用WriteHeader或Write方法后再改变该对象是没有意义的。
    Header() Header
    // WriteHeader该方法发送HTTP回复的头域和状态码。
    // 如果没有被显式调用,第一次调用Write时会触发隐式调用WriteHeader(http.StatusOK)
    // WriterHeader的显式调用主要用于发送错误码。
    WriteHeader(int)
    // Write向连接中写入作为HTTP的一部分回复的数据。
    // 如果被调用时还未调用WriteHeader,本方法会先调用WriteHeader(http.StatusOK)
    // 如果Header中没有"Content-Type"键,
    // 本方法会使用包函数DetectContentType检查数据的前512字节,将返回值作为该键的值。
    Write([]byte) (int, error)
}

ResponseWriter接口被HTTP处理器用于构造HTTP回复

  1. 给客户端响应一个字符串
  • 处理器中的代码
package main

import (
	"net/http"
)

//创建处理器函数
func testString(w http.ResponseWriter, r *http.Request)  {
	w.Write([]byte("你的请求我已经收到!"))
}


func main() {
	http.HandleFunc("/testStr", testString)
	http.ListenAndServe(":8080", nil)
}
  • 浏览器中的结果
你的请求我已经收到!
  • 响应报文中的内容
HTTP/1.1 200 OK
Date: Thu, 02 Apr 2020 05:18:15 GMT
Content-Length: 30
Content-Type: text/plain; charset=utf-8
  1. 给客户端响应一个HTML页面
  • 处理器中的代码
package main

import (
	"net/http"
)

//创建处理器函数
func testHtml(w http.ResponseWriter, r *http.Request)  {
	html := `
	<html>
		<head>
			<title>测试响应内容为网页</title>
			<meta charset="utf-8"/>
		</head>
		<body>
			我是以网页的形式响应过来的!
		</body>
	</html>`
	w.Write([]byte(html))
}


func main() {
	http.HandleFunc("/html", testHtml)
	http.ListenAndServe(":8080", nil)
}
  • 浏览器中的结果
我是以网页的形式响应过来的!
  • 响应报文中的内容
HTTP/1.1 200 OK
Date: Thu, 02 Apr 2020 05:25:31 GMT
Content-Length: 174
Content-Type: text/html; charset=utf-8
  1. 给客户端响应JSON格式数据
  • 处理器端代码

user.go

package model

//User 结构体
type User struct {
	ID int
	Username string
	Password string
	Email string
}

main.go

package main

import (
	"encoding/json"
	"fmt"
	"goweb/web02_req/model"
	"net/http"
)

//创建处理器函数
func testJsonRes(w http.ResponseWriter, r *http.Request)  {
	//设置响应内容的类型
	w.Header().Set("Content-Type", "application/json")
	//创建User
	user := model.User{
		ID: 1,
		Username: "admin",
		Password: "123456",
		Email: "admin@wuxing.com",
	}
	//将User转换为Json格式
	json, _ := json.Marshal(user)
	//将json格式的数据响应给客户端
	w.Write(json)
}

func main() {
	http.HandleFunc("/testJson", testJsonRes)
	http.ListenAndServe(":8080", nil)
}
  • 浏览器中的结果
{"ID":1,"Username":"admin","Password":"123456","Email":"admin@wuxing.com"}
  • 响应报文中的内容
HTTP/1.1 200 OK
Content-Type: application/json
Date: Thu, 02 Apr 2020 04:17:34 GMT
Content-Length: 74
  1. 让客户端重定向
  • 处理器端代码
package main

import (
	"net/http"
)

//创建处理器函数
func testRedire(w http.ResponseWriter, r *http.Request)  {
	//设置响应头中的Location属性
	w.Header().Set("Location", "https://www.baidu.com")
	//设置响应的状态码
	w.WriteHeader(302)
}


func main() {
	http.HandleFunc("/testRedirect", testRedire)
	http.ListenAndServe(":8080", nil)
}
  • 响应报文中的内容
HTTP/1.1 302 Found
Location: https://www.baidu.com
Date: Thu, 02 Apr 2020 04:30:42 GMT
Content-Length: 0
Content-Type: text/plain; charset=utf-8
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wuxingge

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值