Golang_16: Go语言 网络编程:HTTP/HTTPS Server 服务端

原文链接: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 参数表示不校验证书。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

谢TS

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

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

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

打赏作者

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

抵扣说明:

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

余额充值