原文链接:https://xiets.blog.csdn.net/article/details/130866478
版权声明:原创文章禁止转载
专栏目录:Golang 专栏(总目录)
Go 语言内置的 net/http 包除了支持 HTTP(S) 客户端,还支持 HTTP(S) 服务端(并且支持 HTTP/2.0 版本的服务端)。
http
包中 HTTP Server 相关常用函数和类型:
// HTTP 请求处理器接口
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
// 实现了 Handler 接口的 HTTP 多路复用请求处理器,
// ServeMux 可以管理多个具体请求路径 (路由) 对应的 Handler。
type ServeMux struct {
// ...
}
// 全局默认的一个 HTTP 多路复用请求处理器 (ServeMux实例)
var DefaultServeMux = ...
// 添加 HTTP (路径) 路由处理器到 DefaultServeMux, 匹配 pattern 的 URL 请求将调用 handler.ServeHTTP()
func Handle(pattern string, handler Handler)
// 添加 HTTP (路径) 路由处理器到 DefaultServeMux, handler 函数内部将被封装成 Handler 实例
func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
// 创建一个 HTTP 多路复用请求处理器
func NewServeMux() *ServeMux
// 监听端口并启动 HTTP(S) 服务
func ListenAndServe(addr string, handler Handler) error
func ListenAndServeTLS(addr, certFile, keyFile string, handler Handler) error
1. 简单的 HTTP 服务
使用 http.ListenAndServe() 函数监听 TCP 端口并启动服务:
package main
import (
"fmt"
"net/http"
"os"
)
// HttpHandle 定义一个 HTTP 请求处理器类型
type HttpHandle struct {
}
// DemoHandle 实现 http.Handler 接口的方法, 当 HTTP 请求到来时自动调用此方法。
// ResponseWriter 表示客户端响应写出器, 把 状态码、响应头、响应正文 写入此对象。
// Request 表示客户端请求对象, 封装了客户端的请求信息。
func (h *HttpHandle) ServeHTTP(respWriter http.ResponseWriter, req *http.Request) {
// 读取客户端信息
fmt.Printf("ClientAddr: %s\n", req.Host) // 客户端地址, "Host:Port"
fmt.Printf("Method: %s\n", req.Method) // 请求方法, "GET"、"POST" ...
fmt.Printf("Proto: %s\n", req.Proto) // HTTP协议, "HTTP/1.1"
fmt.Printf("URL: %s\n", req.URL) // 请求URL
fmt.Printf("RequestURI: %s\n", req.RequestURI) // 请求资源路径, Host:Port 后面部分
fmt.Printf("Header: %v\n\n", req.Header) // 请求头, Header 底层类型为 map[string][]string
// req.Cookie(name string) // 获取指定 Cookie
// req.Cookies() (*Cookie, error) // 获取所有 Cookie
// req.ContentLength int64 // 获取请求Body长度
// req.Body.Read(buf []byte)) // 读取请求Body, req.Body 是 io.ReadCloser 接口类型
// 获取在 URL 上的查询参数, req.URL.Query() 返回 url.Values 类型 (底层类型为 map[string][]string)
// queries := req.URL.Query()
// queries.Has(key string) bool // 判断是否包含指参数
// queries.Get(key string)) string // 获取指定名称的参数值
// 1. 添加/设置 响应头
// 这一步只是添加到一个 map 中, 并没有真正写出, Header 底层类型为 map[string][]string
// 添加响应头必须在调用 WriteHeader() 之前
respWriter.Header().Add("Key-Hello", "Value-World")
respWriter.Header().Set("Content-Type", "text/plain; charset=utf-8")
// 2. 写出 状态码 和 响应头
// 没有调用此方法, 状态码默认为 http.StatusOK (即 200)
// 必须在调用 Write() 方法前调用, 先写出状态行信息(如 "HTTP/1.1 200 OK"), 再写响应头
respWriter.WriteHeader(200)
clientInfo := fmt.Sprintf("ServeHTTP: %s %s %s\n", req.RemoteAddr, req.Method, req.RequestURI)
// 3. 写出 响应Body
_, _ = respWriter.Write([]byte(clientInfo))
}
func main() {
// 创建 HTTP 请求处理器
handle := &HttpHandle{}
// 监听端口并启动 HTTP 服务: 第1个参数是监听的 地址端口, 第2个参数是请求处理器(http.Handler接口)
// 如果正常启动监听服务, 此方法将一直阻塞等待请求, 当 HTTP 请求到来时自动调 handle 的 ServeHTTP() 方法
err := http.ListenAndServe(":8000", handle)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "error: %v\n", err)
}
}
浏览器访问: http://localhost:8000
2. ServeMux
多路复用请求处理器
上面示例使用单个 Handler
处理了所有的 URL 路径请求。可以使用 http.ServeMux 多路复用请求处理器路由具体的 URL 路径请求。ServeMux
也是一个 Handler 实例,可以向它添加多个具体路径请求对应的 Handler 实例。当前一个 HTTP 请求到来时,先由 ServeMux 解析路径,再调用路径对应的具体 Handler 处理请求。
http.DefaultServeMux
使用示例:
package main
import (
"fmt"
"net/http"
"os"
)
func main() {
// "http://{host:port}/hello" 格式的 URL 请求, 调用 HandleHello() 函数处理
http.HandleFunc("/hello", HandleHello)
// "http://{host:port}/world/*" 格式的 URL 请求, 调用 HandleWorld() 函数处理
http.HandleFunc("/world/", HandleWorld)
// 其他 URL 请求, 调用 Handle() 函数处理
http.HandleFunc("/", Handle)
// 以上 路由处理函数 将封装成 Handler 实例,
// 并加到一个 全局默认的多路复用请求器(ServeMux) 中, 即 http.DefaultServeMux
// 监听端口启动服务, handler 传 nil 表示使用 http.DefaultServeMux
err := http.ListenAndServe(":8000", nil)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "error: %v\n", err)
}
}
func Handle(writer http.ResponseWriter, req *http.Request) {
clientInfo := fmt.Sprintf("Handle: %s %s %s\n", req.RemoteAddr, req.Method, req.RequestURI)
fmt.Printf(clientInfo)
_, _ = fmt.Fprintf(writer, clientInfo)
}
func HandleHello(writer http.ResponseWriter, req *http.Request) {
clientInfo := fmt.Sprintf("HandleHello: %s %s %s\n", req.RemoteAddr, req.Method, req.RequestURI)
fmt.Printf(clientInfo)
_, _ = fmt.Fprintf(writer, clientInfo)
}
func HandleWorld(writer http.ResponseWriter, req *http.Request) {
clientInfo := fmt.Sprintf("HandleWorld: %s %s %s\n", req.RemoteAddr, req.Method, req.RequestURI)
fmt.Printf(clientInfo)
_, _ = fmt.Fprintf(writer, clientInfo)
}
实际开发中,一般新建 ServeMux
使用:
package main
import (
"fmt"
"net/http"
"os"
)
func main() {
// 创建一个多路复用请求处理, 作为 HTTP 服务的总 Handler 管理路由
serverMux := http.NewServeMux()
// "http://{host:port}/hello" 格式的 URL 请求, 调用 HandleHello() 函数处理
serverMux.HandleFunc("/hello", HandleHello)
// "http://{host:port}/world/*" 格式的 URL 请求, 调用 HandleWorld() 函数处理
serverMux.HandleFunc("/world/", HandleWorld)
// 其他 URL 请求, 调用 Handle() 函数处理
serverMux.HandleFunc("/", Handle)
// 监听端口启动服务
err := http.ListenAndServe(":8000", serverMux)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "error: %v\n", err)
}
}
func HandleHello(writer http.ResponseWriter, req *http.Request) {
clientInfo := fmt.Sprintf("HandleHello: %s %s %s\n", req.RemoteAddr, req.Method, req.RequestURI)
fmt.Printf(clientInfo)
_, _ = fmt.Fprintf(writer, clientInfo)
}
func HandleWorld(writer http.ResponseWriter, req *http.Request) {
clientInfo := fmt.Sprintf("HandleWorld: %s %s %s\n", req.RemoteAddr, req.Method, req.RequestURI)
fmt.Printf(clientInfo)
_, _ = fmt.Fprintf(writer, clientInfo)
}
func Handle(writer http.ResponseWriter, req *http.Request) {
clientInfo := fmt.Sprintf("Handle: %s %s %s\n", req.RemoteAddr, req.Method, req.RequestURI)
fmt.Printf(clientInfo)
_, _ = fmt.Fprintf(writer, clientInfo)
}
3. 处理表单提交请求
Go 内置的 HTTP Server 不但可以处理普通表单提交,还可以处理包含文件字段的复杂表单。
3.1 普通表单: application/x-www-form-urlencoded
HTTP Server 处理 普通表单(application/x-www-form-urlencoded
) 提交:
package main
import (
"fmt"
"net/http"
"os"
)
func main() {
// 创建一个多路复用请求处理, 作为 HTTP 服务总 Handler
serverMux := http.NewServeMux()
// "http://{host:port}/post" 格式的 URL 请求, 调用 HandlePost() 函数处理
serverMux.HandleFunc("/post", HandlePost)
// 监听端口启动服务
err := http.ListenAndServe(":8000", serverMux)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "error: %v\n", err)
}
}
func HandlePost(writer http.ResponseWriter, req *http.Request) {
// 客户端信息
clientInfo := fmt.Sprintf("HandlePost: %s %s %s\n", req.RemoteAddr, req.Method, req.RequestURI)
fmt.Printf(clientInfo)
// 解析 form 表单, Content-Type 为 "application/x-www-form-urlencoded"
err := req.ParseForm()
if err != nil {
http.Error(writer, fmt.Sprintf("error: %v\n", err), http.StatusInternalServerError)
return
}
// req.Form // req.Form 包含了 URL 中的 Query
// 获取表单字段, 必须先调用 req.ParseForm() 方法解析表单
// req.PostForm 是 url.Values 类型 (底层类型为 map[string][]string)
formFields := req.PostForm
fmt.Printf("Form Fields: %v\n", formFields)
// formFields.Has(key string) bool // 判断是否包含指定字段
// formFields.Get(key string) string // 获取指定名称的字段值
// 也可以直接获取指定名称的字段。如果表单没有解析, 内部自动调用解析方法。
// req.FormValue(key string) string
// req.PostFormValue(key string) string
// 写出响应
writer.WriteHeader(http.StatusOK)
_, _ = writer.Write([]byte("提交成功\n"))
}
提交表单测试: curl -d "aa=bb&cc=123" http://localhost:8000/post
3.2 复杂表单: multipart/form-data
HTTP Server 处理包含文件字段的 复杂表单(multipart/form-data; boundary=******
) 提交:
package main
import (
"fmt"
"io/ioutil"
"net/http"
"os"
)
func main() {
// 创建一个多路复用请求处理, 作为 HTTP 服务总 Handler
serverMux := http.NewServeMux()
// "http://{host:port}/post" 格式的 URL 请求, 调用 HandlePost() 函数处理
serverMux.HandleFunc("/post", HandlePost)
// 监听端口启动服务
err := http.ListenAndServe(":8000", serverMux)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "error: %v\n", err)
}
}
func HandlePost(writer http.ResponseWriter, req *http.Request) {
// 客户端信息
clientInfo := fmt.Sprintf("HandlePost: %s %s %s\n", req.RemoteAddr, req.Method, req.RequestURI)
fmt.Printf(clientInfo)
// 解析 Multipart Form 表单,
// Content-Type 为 "multipart/form-data; boundary=------------------------******"
// 也可以解析 "application/x-www-form-urlencoded" 表单数据
err := req.ParseMultipartForm(50 * 1024 * 1024)
if err != nil {
http.Error(writer, fmt.Sprintf("error: %v\n", err), http.StatusInternalServerError)
return
}
// 获取复杂表单所有参数, 必须先调用 req.ParseMultipartForm() 方法解析表单参数
formFields := req.MultipartForm
fmt.Printf("Multipart Form Fields: %v\n", formFields) // 所有字段
fmt.Printf("Multipart Form Value Fields: %v\n", formFields.Value) // 普通字段, map[string][]string
fmt.Printf("Multipart Form File Fields: %v\n", formFields.File) // 文件字段, map[string][]*FileHeader
fmt.Printf("Read Form Fields:\n")
// 获取表单的所有 普通字段
for key, values := range formFields.Value {
fmt.Printf("%s: %v\n", key, values)
}
// 获取表单的所有 文件字段
for key, fileHeaders := range formFields.File {
if len(fileHeaders) > 0 {
fileHeader := fileHeaders[0]
// 获取 文件名、文件大小
fmt.Printf("%s: filename=%s, filesize=%d\n", key, fileHeader.Filename, fileHeader.Size)
// 文件的 MIME 相关信息
fmt.Printf("%s: MIME=%v\n", key, fileHeader.Header)
// 读取文件内容
file, err := fileHeader.Open()
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "open file error: %v\n", err)
continue
}
fileContent, err := ioutil.ReadAll(file)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "read file error: %v\n", err)
continue
}
// 如果文件内容 (如果是文本文件)
fmt.Printf("%s: content=%s\n", key, string(fileContent))
_ = file.Close()
}
}
// 也可以直接获取指定名称的字段。如果表单没有解析, 内部自动调用 req.ParseMultipartForm()。
// req.FormFile(key string) string
// req.PostFormValue(key string) string
// req.FormFile(key string) (multipart.File, *multipart.FileHeader, error)
// 写出响应
writer.WriteHeader(http.StatusOK)
_, _ = fmt.Fprintf(writer, "提交成功\n")
}
提交复杂表单测试: curl -F "aa=bb" -F "cc=123" -F "file=@/files/demo.txt" http://localhost:8000/post
4. HTTPS 服务
http.ListenAndServeTLS() 函数与 http.ListenAndServe() 用法基本相同。ListenAndServeTLS()
需要传递 证书文件 和 私钥文件,并启动 HTTPS 服务。客户端需使用 HTTPS 访问,并且支持 HTTP/2.0
。
服务端证书一般由权威的第三方机构签名颁发,浏览器(HTTP 客户端)才会信任。作为测试可以使用 OpenSSL
工具生成自签名证书。
使用 openssl
工具命令生成自签名证书步骤:
# 1. 生成 私钥文件(key.cer)
$ openssl genrsa -out key.cer 2048
# 2. 根据私钥生成 证书申请文件(cert_req.cer), 需要输入国家/地区、省份/城市、域名、邮箱等信息 (密码可以不输入)
$ openssl req -new -key key.cer -out cert_req.cer
# 3. 使用 私钥 对 证书申请文件 进行签名, 从而生成 证书文件(cert.cer)
$ openssl x509 -req -in cert_req.cer -out cert.cer -signkey key.cer -days 365
使用 http.ListenAndServeTLS() 函数监听 TCP 端口并启动 HTTPS 服务,需要使用上面步骤生成的两个文件:证书文件(cert.cer)
和 私钥文件(key.cer)
package main
import (
"fmt"
"net/http"
"os"
)
func main() {
// 创建一个多路复用请求处理, 作为 HTTP 服务总 Handler
serverMux := http.NewServeMux()
// "http://{host:port}/*" 格式的 URL 请求, 调用 HandleHttps() 函数处理
serverMux.HandleFunc("/", HandleHttps)
certFile := "cert.cer" // 证书文件路径
keyFile := "key.cer" // 私钥文件路径
// 监听端口, 启动 HTTPS 服务
err := http.ListenAndServeTLS(":8443", certFile, keyFile, serverMux)
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "error: %v\n", err)
}
}
func HandleHttps(writer http.ResponseWriter, req *http.Request) {
// 客户端信息
clientInfo := fmt.Sprintf("HandleHttps: %s %s %s %s\n", req.RemoteAddr, req.Method, req.RequestURI, req.Proto)
fmt.Printf(clientInfo)
// 写出响应
writer.WriteHeader(http.StatusOK)
_, _ = fmt.Fprintf(writer, clientInfo)
}
使用 HTTPS 方式访问:
- 浏览器访问:
https://localhost:8443/
- CURL 命令访问:
curl -k https://localhost:8443
由于是自签名的证书,没有经过权威的第三方机构签名认证,浏览器访问时会警告提示不安全并停止访问(也就是自己无法证明自己,需要值得信任的第三方机构来证明自己),可以选择信任此证书继续访问或安装到本地信任证书。CURL 命令加 -k 参数表示不校验证书。