好的博客:Go HTTP编程
一、服务端
Go语言标准库内建提供了net/http包,涵盖了HTTP客户端和服务端的具体实现。
1.1 构建服务器
首先,我们编写一个最简单的Web服务器。编写这个Web服务只需要两步:
- 注册一个处理器函数(注册到DefaultServeMux);
- 设置监听的TCP地址并启动服务;
代码示例:
package main
import (
"fmt"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "正在通过处理器函数处理请求!", r.URL.Path)
}
func main() {
http.HandleFunc("/", handler)
err := http.ListenAndServe("127.0.0.1:8080", nil)
fmt.Println(err)
}
运行该程序,通过浏览器访问localhost:8080
还可以通过 Server 结构对服务器进行更详细的配置:
-
结构体Server的结构: type Server struct {
Addr string
Handler Handler
ReadTimeout time.Duration
WriteTimeout time.Duration
MaxHeaderBytes int
TLSConfig *tls.Config
TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
ConnState func(net.Conn, ConnState)
ErrorLog *log.Logger
}
自定义Server:
type h1 struct{}
func (h1 *h1) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "h1")
}
type h2 struct{}
func (h2 *h2) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "h2")
}
func main() {
h1 := h1{}
h2 := h2{}
server := http.Server{
Addr : "127.0.0.1:8080",
Handler : nil,
ReadTimeout : 2 * time.Second,
}
http.Handle("/h1", &h1)
http.Handle("/h2", &h2)
server.ListenAndServe()
}
1.2 处理器和处理器函数
1.2.1 处理器
-
一个处理器就是实现了Handler这个接口:实现了Handler接口的对象可以注册到HTTP服务端,为特定的路径及其子树提供服务。 -
那么也就是说,任何接口只要有一个ServeHTTP 方法,并且该方法带有以下的签名,那么他就是一个处理器 type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
-
多路复用器DefaultServeMux 是一个特殊的处理器,它的任务是根据请求的URL将请求重定向到不同的处理器
实现一个处理器:
package main
import (
"log"
"net/http"
)
type MyHandler struct {}
func (m *MyHandler)ServeHTTP(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte("Hello World!"))
}
func main() {
http.Handle("/", &MyHandler{})
err := http.ListenAndServe("127.0.0.1:8080", nil)
log.Fatal(err)
}
1.2.2 处理器函数
-
处理器函数就是与ServeHTTP方法拥有相同签名的函数。 -
HandlerFunc 函数,它可以把一个带有正确签名的函数f转换成一个带有方法f的Handler func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
使用处理器函数处理请求:
package main
import (
"fmt"
"net/http"
)
func index(w http.ResponseWriter, r *http.Request) {
hello := "Hello Wrold!"
fmt.Fprintf(w, "%s\n", hello)
}
func login(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "You are in the login\n")
}
func home(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "You are in the home\n")
}
func news(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "You are in the news\n")
}
func main() {
server := &http.Server{
Addr: "0.0.0.0:9090",
}
http.HandleFunc("/", index)
http.HandleFunc("/login", login)
http.HandleFunc("/home", home)
http.HandleFunc("/news", news)
err := server.ListenAndServe()
if err != nil {
fmt.Println(err)
}
}
处理器函数的实现原理:
通过源码可知,这个函数实际上是调用了默认的DefaultServeMux的HandleFunc方法,即:默认注册到DefaultServeMux中
1.3 多路复用器
ServeMux和DefaultServeMux:
-
ServeMux是一个HTTP请求多路复用器,它负责接受HTTP请求并根据请求中的URL将请求重定向到正确的处理器。 -
ServeMux结构也实现了ServeHTTP方法,所以它也是一个处理器 -
结构体 ServeMux 的相关方法: -
DefaultServeMux 是ServeMux的一个实例,当用户没有为Server结构指定处理器时,处理器就会使用DefaultServeMux作为ServeMux的默认实例
使用NewServeMux创建的多路复用器代码:
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "通过自己创建的多路复用器处理请求!", r.URL.Path)
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", handler)
http.ListenAndServe(":8080", mux)
}
二、处理请求
好的博客:Go Web:处理请求
Go 语言的 net/http 包提供了一系列用于表示 HTTP 报文的结构,我们可以使用它处理请求和发送相应,其中 Request 结构代表了客户端发送的请求报文,下面让我们看一下 Request 结构体
type Request struct {
Method string
URL *url.URL
Proto string
ProtoMajor int
ProtoMinor int
Header Header
Body io.ReadCloser
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
}
一些比较重要的字段:
- URL:请求 URL
- Method:请求方法
- Proto:HTTP 协议版本
- Header:请求头(字典类型的键值对集合)
- Body:请求实体(实现了
io.ReadCloser 接口的只读类型) - Form、PostForm、MultipartForm:请求表单相关字段,可用于存储表单请求信息
2.1 获取请求URL
URL也是一个结构体:
type URL struct {
Scheme string
Opaque string
User *Userinfo
Host string
Path string
RawQuery string
Fragment string
}
URL解析示例:
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Printf("URL: %#v\n", r.URL)
fmt.Printf("URL.Path: %#v\n", r.URL.Path)
fmt.Printf("URL.RawQuery: %#v\n", r.URL.RawQuery)
}
func main() {
http.HandleFunc("/hello", handler)
http.ListenAndServe(":8080", nil)
}
访问http://127.0.0.1:8080/hello?username=admin&password=123456 ,执行结果:
2.2 获取请求头中的信息
通过 Request 结果中的Header 字段 用来获取请求头中的所有信息,Header 字段的类型是 Header 类型,而 Header 类型是一个 map[string][]string ,string 类型的 key,string 切片类型的值。
下面是 Header 类型及它的方法:
获取请求头中的某个具体属性的值,如获取 Accept-Encoding 的值:
-
方式一:r.Header["Accept-Encoding"] ,得到的是一个字符串切片 -
方式二:r.Header.Get("Accept-Encoding") ,得到的是字符串形式的值,多个值使用逗号分隔 -
代码示例: func handler(w http.ResponseWriter, r *http.Request) {
fmt.Printf("Header: %#v\n", r.Header)
fmt.Println("请求头中Accept-Encoding属性的值:",r.Header["Accept-Encoding"])
fmt.Println("请求头中Accept-Encoding属性的值:",r.Header.Get("Accept-Encoding"))
}
func main() {
http.HandleFunc("/hello", handler)
http.ListenAndServe(":8080", nil)
}
访问http://127.0.0.1:8080/hello?username=admin&password=123456 ,执行结果:
2.3 获取请求体中的信息
请求和响应的主体都是有 Request 结构中的 Body 字段表示,该字段的类型是io.ReadCloser 接口。该接口包含了 Reader 接口和 Closer 接口,Reader 接口拥有 Read方法,Closer 接口拥有 Close 方法
-
代码示例:从请求中读取Body并输出 func handler(w http.ResponseWriter, r *http.Request) {
len := r.ContentLength
body := make([]byte, len)
r.Body.Read(body)
fmt.Fprintln(w, "请求体中的内容有:", string(body))
}
func main() {
http.HandleFunc("/getBody", handler)
http.ListenAndServe(":8080", nil)
}
使用表单发送post请求 <form action="http://localhost:8080/getBody" method="POST">
用户名:<input type="text" name="username" value="zhangsan" /><br/>
密码:<input type="password" name="password" value="666666" /><br/>
<input type="submit" />
</form>
访问http://localhost:8080/getBody ,浏览器显示结果:
2.4 获取请求参数
在Request结构中,有3个和form有关的字段:
// Form字段包含了解析后的form数据,包括URL的query、POST/PUT提交的form数据
// 该字段只有在调用了ParseForm()之后才有数据
Form url.Values
// PostForm字段不包含URL的query,只包括POST/PATCH/PUT提交的form数据
// 该字段只有在调用了ParseForm()之后才有数据
PostForm url.Values // Go 1.1
// MultipartForm字段包含multipart form的数据
// 该字段只有在调用了ParseMultipartForm()之后才有数据
MultipartForm *multipart.Form
一般获取请求参数的逻辑:
-
方法一:
- 先调用ParseForm()或ParseMultipartForm()解析请求中的数据
- 按需访问Request结构中的Form、PostForm或MultipartForm字段
-
方法二:直接使用Request的方法:
- FormValue(key)
- PostFormValue(key)
2.4.1 Form和PostForm字段
Form 和 PostForm字段都是 url.Values 类型,都能用来获取表单中的请求参数。
注意:Form 和 PostForm字段必须调用 Request 的 ParseForm() 方法后才有效
代码示例:(获取请求参数)
-
form表单 <form action="http://localhost:8080/getBody?username=admin&pwd=123456" method="POST">
用户名:<input type="text" name="username" value="zhangsan" /><br/>
密码:<input type="password" name="password" value="666666" /><br/>
<input type="submit" />
</form>
-
web handler代码: func handler(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
fmt.Fprintln(w, "请求参数有:", r.Form)
fmt.Fprintln(w, "POST请求的form表单中的请求参数有:", r.PostForm)
}
func main() {
http.HandleFunc("/getBody", handler)
http.ListenAndServe(":8080", nil)
}
-
点击提交按钮显示结果:
2.4.2 FormValue()和PostFormValue()
这两个方法在需要时会自动调用ParseForm()或ParseMultipartForm(),所以使用这两个方法取Form数据的时候,可以不用显式解析Form。
-
FormValue()返回form数据和url 请求参数后的第一个值。要取得完整的值,还是需要访问Request.Form或Request.PostForm字段。但因为FormValue()已经解析过Form了,所以无需再显式调用ParseForm()再访问request中Form相关字段。 -
PostFormValue()返回form数据的第一个值,因为它只能访问form数据,所以忽略URL的请求参数部分。
代码示例:
-
form表单 <form action="http://localhost:8080/getBody?user=admin&pwd=123456" method="POST">
用户名:<input type="text" name="username" value="zhangsan" /><br/>
密码:<input type="password" name="password" value="666666" /><br/>
<input type="submit" />
</form>
-
web handler代码: func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "URL中的user请求参数的值是:", r.FormValue("user"))
fmt.Fprintln(w, "Form表单中的username请求参数的值是:", r.PostFormValue("username"))
}
func main() {
http.HandleFunc("/getBody", handler)
http.ListenAndServe(":8080", nil)
}
-
点击提交按钮显示结果:
2.4.3 MultipartForm字段
为了取得 multipart/form-data 编码的表单数据,我们需要用到 Request 结构的ParseMultipartForm() 方法和 MultipartForm 字段,我们通常上传文件时会将 form 表单的enctype 属性值设置为 multipart/form-data
代码示例:
-
form表单: <form action="http://localhost:8080/upload" method="POST" enctype="multipart/form-data">
用 户 名 : <input type="text" name="username"value="hanzong"><br/>
文件:<input type="file" name="file" /><br/>
<input type="submit">
</form>
-
web handler代码: func handler(w http.ResponseWriter, r *http.Request) {
r.ParseMultipartForm(1024)
fmt.Fprintln(w, r.MultipartForm)
fileHeader := r.MultipartForm.File["file"][0]
file, err := fileHeader.Open()
if err == nil {
data, err := ioutil.ReadAll(file)
if err == nil {
fmt.Fprintln(w, string(data))
}
}
}
func main() {
http.HandleFunc("/upload", handler)
http.ListenAndServe(":8080", nil)
}
-
点击提交按钮显示结果:(上传.txt文件)
2.4.4 文件相关(待补)
三、请求响应
好的博客:Go 语言通过 ResponseWriter 对象发送 HTTP 响应
客户端请求信息都封装到了 Request 对象,而发送给客户端的响应通过ResponseWriter 对象完成
3.1 设置响应状态码
WriteHeader() 该方法支持传入一个整型数据用来表示响应状态码,如果不调用该方法的话,默认响应状态码是 200 OK 。
代码示例:
-
web handler代码: func testStatusCode(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(302)
w.Write([]byte("资源临时被移动"))
}
func main() {
http.HandleFunc("/testStatusCode", testStatusCode)
http.ListenAndServe(":8080", nil)
}
-
访问localhost:8080/testStatusCode 可查看状态码已经更改:
3.2 设置响应头
Header 方法用于设置响应头信息,我们可以通过 w.Header().Set 方法设置响应头(w.Header() 方法返回的是 Header 响应头对象,它和请求头共用一个结构体,因此请求头上支持的方法这里都支持,比如可以通过 w.Header().Add 方法新增响应头)。
让客户端重定向:
-
这里我们设置一个 302 重定向响应,只需要通过 w.WriteHeader 方法将响应状态码设置为 301,再通过 w.Header().Set 方法将负责重定向的响应头 Location 设置为一个可访问域名即可。 -
web handler代码: func testRedire(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Location", "https://www.baidu.com")
w.WriteHeader(302)))
}
3.3 写入数据到响应实体
Write 方法用于写入数据到 HTTP 响应实体
3.3.1 返回文本字符串
-
Write 方法接受的参数类型是 []byte 切片,所以需要将字符串转换为字节切片类型。 func handler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("你的请求我已经收到"));
}
-
启动 HTTP 服务器,就可以在浏览器中看到数据:
3.3.2 返回 HTML 文档
-
处理器中的代码: func handler(w http.ResponseWriter, r *http.Request) {
html := `<html>
<head>
<title>测试响应内容为网页</title>
<meta charset="utf-8"/>
</head>
<body>
我是以网页的形式响应过来的!
</body>
</html>`
w.Write([]byte(html))
}
-
在浏览器显示的结果: 可以看到,由于响应数据的内容类型变成了 HTML,在响应头中,也可以看到 Content-Type 也自动调整成了 text/html ,不再是纯文本格式。这里的 Content-Type 就是根据传入的数据自行判断出来的。
3.3.3 返回 JSON 格式数据
-
处理器中的代码: func testJsonRes(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
user := model.User{
ID: 1,
Username: "admin",
Password: "123456",
Email: "admin@atguigiu.com",
}
json, _ := json.Marshal(user)
w.Write(json)
}
func main() {
http.HandleFunc("/testJson", testJsonRes)
http.ListenAndServe(":8080", nil)
}
-
在浏览器显示的结果: 注意:返回JSON类型需要设置响应头中 Content-Type字段 为 text/html ,否则内容类型仍然为text/plain
|