引言
Go 语言提供功能丰富的 net/http
,实现了基础的 HTTP 中的 client
和 server
功能。在这一篇文章也有介绍一个基础的 HelloWorld 应用。
如果没看过,也可以使用下面的代码创建一个简易 HTTP 的 server
服务:
package main import ( "log" "net/http" ) type Handler struct{} func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { _, err := w.Write([]byte("Welcome to my Website!")) if err != nil { log.Println(err) } } func main() { log.Println(http.ListenAndServe("127.0.0.1:8080", &Handler{})) }
http.ListenAndServe
的第一个参数是传入监听的地址,第二个参数是 http.Handle
接口实现者。该接口需要 ServeHTTP(w http.ResponseWriter, r *http.Request)
函数。这个函数将处理我们服务器上的所有请求。
第一个终端上使用 go run main.go
运行该服务器:
$ go run main.go
然后打开另个一终端:
$ curl localhost:8080/ Welcome to my Website! $ curl localhost:8080/hello Welcome to my Website!
我们还可以配置服务器来处理特定的 URL,例如 /hello
:
package main import ( "log" "net/http" ) type Handler struct{} func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { _, err := w.Write([]byte("Welcome to my Website!")) if err != nil { log.Println(err) } } func main() { http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) { _, err := w.Write([]byte("Hello, world!")) if err != nil { log.Println(err) } }) log.Println(http.ListenAndServe("127.0.0.1:8080", nil)) }
运行另一个终端,能看到如下结果::
$ curl localhost:8080/hello Hello, world! $ curl localhost:8080/ 404 page not found
它是如何工作的?当我们调用 http.HandleFunc
时,路径和处理函数被添加到 net/http.DefaultServeMux
结构中,即包含所有这些。当我们调用特定的 URL 时,net/http.DefaultServeMux
用于查找特定的处理程序。如果未找到处理程序,将生成标准 404 响应。
标准路由对每个注册的 URL 实现唯一的纯 URL 比较。模式匹配,例如匹配 /hello/{page}
之类的表达式,其中页面参数在多个调用之间可能不同,这对于标准路由器是不可能的。要实现这一点,我们需要自己实现模式匹配或使用路由器库之一。
自定义路由
我们可以使用任何其他路由器,例如 gorilla/mux :一个强大的 HTTP 路由器和 URL 匹配器,用于构建 Go web 服务器 🦍。目前已经在 Github 收获 16.4 k 的 star,它支持模式匹配。
func main() { r := mux.NewRouter() r.HandleFunc("/products/{key}", ProductHandler) r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler) r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler) http.Handle("/", r) }
接下来,在处理程序中,我们可以从 URL 中获取变量:
中间件
在标准 Web 服务中,不仅有处理程序的代码在每个请求上运行。还有一些通用代码执行一些通用工作——日志记录、统计信息抓取、授权检查等。这些通用代码称为中间件。
所谓中间件,就是连接上下级不同功能的函数或者软件,通常进行一些包裹函数的行为,为被包裹函数提供添加一些功能或行为。前文的 HandleFunc
就能把签名为 func(w http.ResponseWriter, r *http.Reqeust)
的函数包裹成 handler 。这个函数也算是中间件。
GO 标准库中没有明确的中间件实现。但是任何人都可以通过使用嵌套处理程序来实现类似的东西。让我们看一下日志中间件,它将每个请求的 URL 打印到控制台:
package main import ( "log" "net/http" ) type LoggingMiddleware struct { handler http.Handler } func NewLoggingMiddleware(handler http.Handler) *LoggingMiddleware { return &LoggingMiddleware{ handler: handler, } } func (l *LoggingMiddleware) ServeHTTP(w http.ResponseWriter, req *http.Request) { log.Println(req.URL, "requested") l.handler.ServeHTTP(w, req) } type Handler struct{} func (h *Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) { _, err := w.Write([]byte("hello")) if err != nil { log.Println(err) } } func main() { log.Println(http.ListenAndServe(":9090", NewLoggingMiddleware(&Handler{}))) }
$ curl localhost:8080/ hello $ curl localhost:8080/hello hello
在对我们服务器的每个请求中,我们现在可以在控制台中看到以下数据:
$ go run main.go 2022/04/27 23:44:18 / requested 2022/04/27 23:44:23 /hello requested
很多游戏的 Go Web 库如 echo
、gin
可以显着地帮助实现中间件。此外,许多库已经涵盖了最流行的中间件任务。
HTTP 客户端
在 net/http
包中有一个 Client 结构,它负责 HTTP 客户端请求:
package main import ( "fmt" "io/ioutil" "log" "net/http") func main() { client := http.Client{} resp, err := client.Get("https://bing.com/") if err != nil { log.Fatal(err) } buf, err := ioutil.ReadAll(resp.Body) resp.Body.Close() if err != nil { log.Fatal(err) } fmt.Println(string(buf))}
复制代
package main import ( "fmt" "io/ioutil" "log" "net/http" ) func main() { client := http.Client{} resp, err := client.Get("https://bing.com/") if err != nil { log.Fatal(err) } buf, err := ioutil.ReadAll(resp.Body) resp.Body.Close() if err != nil { log.Fatal(err) } fmt.Println(string(buf)) }
运行结果会返回该网页的内容:
总结
Go 语言 net/http 涵盖了 HTTP 客户端和服务器实现。支持客户端 Get、Head、Post 和 PostForm 发出 HTTP(或 HTTPS)请求,正因为这些强大的基础功能,所以 Go Web 编程也能够占据一席之地,更多 HTTP 的知识可以跟着官方文档学。最近也发现了一个简易 HTTP 和 REST 客户端的 resty 项目,目前 star 6k,打算学习一波。下一篇文章见~