Go语言标准库内置了强大的net/http
包,它为我们提供了丰富的HTTP协议处理工具,基于该包实现HTTP服务端是非常简单的。这篇笔记我们介绍如何使用net/http
包构建一个简单的HTTP服务端程序。
使用net/http
创建HTTP服务端很简单,我们需要先注册HTTP路由处理函数然后启动HTTP服务端。
package main
import (
"log/slog"
"net/http"
)
func main() {
// 注册路由处理函数
http.HandleFunc("/demo", func(writer http.ResponseWriter, request *http.Request) {
_, err := writer.Write([]byte("Hello, world!"))
if err != nil {
slog.Error(err.Error())
return
}
})
// 启动HTTP服务器
if err := http.ListenAndServe(":8080", nil); err != nil {
slog.Error(err.Error())
return
}
}
net/http
包实现的HTTP服务端中有一个重要的概念,多路复用器ServeMux
,它在HTTP服务端中起到并发请求路由的作用,简单来说就是根据请求URL找到对应的处理函数。DefaultServeMux
是标准库中默认的内置多路复用器,此外还有第三方包gorilla/mux
等,我们这里以内置的DefaultServeMux
作为例子进行讲解。
代码中,http.HandleFunc()
函数用于在默认的处理器DefaultServeMux
上注册路由处理函数,我们定义了一个匿名函数作为处理函数,它的2个参数分别用于向响应写数据和读取请求信息。注册完路由处理函数后我们启动HTTP服务器,我们绑定了所有网络接口的8080
端口来提供HTTP服务。代码中http.ListenAndServe()
用于启动HTTP服务,它的第1个参数是绑定的主机和端口,第2个参数是处理器,我们这里由于使用默认的DefaultServeMux
,因此传nil
即可。
Go语言内置的DefaultServeMux
路由比较简单,不已/
结尾的路由表示精确匹配,我们可以定义路由字符串,当HTTP请求路径匹配路由字符串时,对应的处理函数会被调用。
http.HandleFunc("/demo", demoHandler)
http.HandleFunc()
函数中,第1个参数就是路由,第2个参数是处理函数。
以/
结尾的路由表示前缀匹配,下面例子会匹配所有/demo/
为前缀的URL,例如/demo/123
等。
http.HandleFunc("/demo/", demoHandler)
类似于Nginx的路由匹配优先级,当请求URL有两条路由规则同时满足时,DefaultServeMux
会优先匹配更精确的路径,只有当没有精确匹配的路径时,才会进行前缀匹配。
如果路由字符串只有一个/
表示它是一个默认路由(catch-all路由),它能匹配所有URL但优先级最低,任何没有匹配其他已注册路径的请求都会被这个默认路由捕获。
http.HandleFunc("/", demoHandler)
所有的请求信息都被封装在http.Request
结构体中,我们可以从其中读取请求方法、请求路径、请求头、各种参数和请求体等,这里我们简单介绍一下。
URL参数可以通过如下方式获取,下面代码中我们读取了name
参数。
func demoHandler(writer http.ResponseWriter, request *http.Request) {
query := request.URL.Query()
name := query.Get("name")
slog.Info("name: " + name)
}
读取表单参数复杂一些,我们需要先调用request.ParseForm()
方法将请求解析为表单数据,然后调用request.Form.Get()
方法读取表单字段。
func demoHandler(writer http.ResponseWriter, request *http.Request) {
if err := request.ParseForm(); err != nil {
slog.Error(err.Error())
return
}
name := request.Form.Get("name")
slog.Info("name: " + name)
}
下面例子我们读取了JSON格式的请求体信息,代码中我们定义了一个结构体,并用encoding/json
包解析JSON。
type Message struct {
Name string `json:"name"`
}
func demoHandler(writer http.ResponseWriter, request *http.Request) {
// 以JSON格式解析请求体
message := Message{}
decoder := json.NewDecoder(request.Body)
if err := decoder.Decode(&message); err != nil {
slog.Error(err.Error())
return
}
// 打印解析结果
slog.Info("Name " + message.Name)
}
HTTP服务端返回的响应信息主要包括响应码、响应体等。我们可以调用writer.WriteHeader()
方法设置响应码,net/http
包中定义了很多常量供我们使用。下面代码我们返回了http.StatusOK
响应码,它的值实际上是200
。
func demoHandler(writer http.ResponseWriter, request *http.Request) {
writer.WriteHeader(http.StatusOK)
}
对于响应体,我们直接调用writer.Write()
方法写数据即可。
func demoHandler(writer http.ResponseWriter, request *http.Request) {
_, err := writer.Write([]byte("Hello, world!"))
if err != nil {
slog.Error(err.Error())
return
}
}
http.FileServer()
函数可以用来响应静态文件,下面是一个例子。
package main
import (
"log/slog"
"net/http"
)
func main() {
// 注册静态文件处理路由
fsHandler := http.FileServer(http.Dir("D:\\static"))
http.Handle("/static/", fsHandler)
// 启动HTTP服务器
if err := http.ListenAndServe(":8080", nil); err != nil {
slog.Error(err.Error())
return
}
}
代码中,我们使用http.FileServer()
注册了一个文件系统路径作为响应静态文件的目录,假如我们有一个静态资源D:\\static\\1.png
,我们使用浏览器请求/static/1.png
即可获取该文件。
net/http
支持创建HTTPS服务器,下面是一个例子。
package main
import (
"log/slog"
"net/http"
)
func main() {
// 注册路由处理函数
http.HandleFunc("/demo", func(writer http.ResponseWriter, request *http.Request) {
_, err := writer.Write([]byte("Hello, world!"))
if err != nil {
slog.Error(err.Error())
return
}
})
// 启动HTTPS服务器
if err := http.ListenAndServeTLS(":8443", "server.crt", "server.key", nil); err != nil {
slog.Error(err.Error())
return
}
}
启动HTTPS服务器需要证书和私钥,前面介绍TLS时我们已经详细解释过X.509证书相关的用法以及如何签发自签名证书用于测试,这里就不赘述了。代码和之前区别不大,只是我们调用的是http.ListenAndServeTLS()
来启动HTTPS服务端。