Go 是一门新语言。很多人都是用 Go 来开发 Web 服务。Web 开发很多同学急于求成,直接使用beego,echo或iris等知名框架。对标准库net/http的了解甚少。这里我就主要聊一下标准库net/http开发 Web 服务时的使用细节。
创建 HTTP 服务
在 Go 中,创建 HTTP 服务很简单:
package main
// in main.go
import (
"fmt"
"net/http"
)
func main(){
if err := http.ListenAndServe(":12345",nil); err != nil{
fmt.Println("start http server fail:",err)
}
}
这样就会启动一个 HTTP 服务在端口12345。浏览器输入http://localhost:12345/就可以访问。当然从代码看出,没有给这个 HTTP 服务添加实际的处理逻辑,所有的访问都是默认的404 Not Found。
添加 http.Handler
添加 HTTP 的处理逻辑的方法简单直接:
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, Go HTTP Server"))
})
http.HandleFunc("/abc", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, Go HTTP abc"))
})
if err := http.ListenAndServe(":123456", nil); err != nil {
fmt.Println("start http server fail:", err)
}
}
访问http://localhost:12345/就可以看到页面输出Hello, Go HTTP Server的内容。访问http://localhost:12345/abc就可以看到页面输出Hello, Go HTTP abc的内容。但是 Go 默认的路由匹配机制很弱。上面的代码除了/abc,其他的请求都匹配到/,不足以使用,肯定需要自己写路由的过程。一个简单的方式就是写一个自己的http.Handler。
type MyHandler struct{} // 实现 http.Handler 接口的 ServeHTTP 方法
func (mh MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/abc" {
w.Write([]byte("abc"))
return
}
if r.URL.Path == "/xyz" {
w.Write([]byte("xyz"))
return
}
w.Write([]byte("index"))
// 这里你可以写自己的路由匹配规则
}
func main() {
http.Handle("/", MyHandler{})
if err := http.ListenAndServe(":12345", nil); err != nil {
fmt.Println("start http server fail:", err)
}
}
这样可以在自己的MyHandler写复杂的路由规则和处理逻辑。http.ListenAndServe的第二个参数写的会更优雅:
func main() {
if err := http.ListenAndServe(":12345", MyHandler{}); err != nil {
fmt.Println("start http server fail:", err)
}
}
http.ServeMux 路由
net/http提供了一个非常简单的路由结构http.ServeMux。方法http.HandleFunc()和http.Handler()就是把路由规则和对应函数注册到默认的一个http.ServeMux上。当然,你可以自己创建http.ServeMux来使用:
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, HTTP Server")
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", handler)
http.ListenAndServe(":12345", mux)
}
但是因为http.ServeMux路由规则简单,功能有限,实践都不会用的,如同鸡肋。更推荐使用httprouter。
import (
"fmt"
"net/http"
"github.com/julienschmidt/httprouter"
)
// httprouter.Params 是匹配到的路由参数,比如规则 /user/:id 中 的 :id 的对应值
func handle(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
fmt.Fprint(w, "hello, httprouter")
}
func main() {
router := httprouter.New()
router.GET("/", handle)
if err := http.ListenAndServe(":12345", router); err != nil {
fmt.Println("start http server fail:", err)
}
}
http.Handler/http.HandlerFunc 中间件
Go 的 HTTP 处理过程可以不仅是一个 http.HandlerFunc,而且是一组 http.HandlerFunc,比如:
func handle1(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("handle1"))
}
func handle2(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("handle2"))
}
// 把几个函数组合起来
func makeHandlers(handlers ...http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
for _, handler := range handlers {
handler(w, r)
}
}
}
func main() {
http.HandleFunc("/", makeHandlers(handle1, handle2))
if err := http.ListenAndServe(":12345", nil); err != nil {
fmt.Println("start http server fail:", err)
}
}
这种模式开发的框架可以参考 negroni。它的中间件都是以实现 http.Handler 的结构体来组合的。
Request
HTTP 过程的操作主要是针对客户端发来的请求数据在 *http.Request,和返回给客户端的 http.ResponseWriter 两部分。
请求数据 *http.Request 有两个部分:基本数据和传递参数。基本数据比如请求的方法、协议、URL、头信息等可以直接简单获取:
func HttpHandle(w http.ResponseWriter, r *http.Request) {
fmt.Println("Method:", r.Method)
fmt.Println("URL:", r.URL, "URL.Path", r.URL.Path) // 这里是 *net/url.URL 结构,对应的内容可以查API
fmt.Println("RemoteAddress", r.RemoteAddr)
fmt.Println("UserAgent", r.UserAgent())
fmt.Println("Header.Accept", r.Header.Get("Accept"))
fmt.Println("Cookies",r.Cookies())
// 还有很多肯定会有的基本数据,可以查阅 API 找寻一下
}
http.HandleFunc("/", HttpHandle)
if err := http.ListenAndServe(":12345", nil); err != nil {
fmt.Println("start http server fail:", err)
}
表单数据
请求传递的参数,也就是 表单数据,保存在 *http.Request.Form 和 *http.Request.PostForm (POST 或 PUT 的数据),类似 PHP 的 $_REQUEST 和 $_POST/$_PUT 两个部分。
例如 GET /?abc=xyz,获取这个数据并打印到返回内容:
func HttpHandle(w http.ResponseWriter, r *http.Request) {
value := r.FormValue("abc")
w.Write([]byte("GET: abc=" value))
}
访问 http://localhost:12345/?abc=123 就可以看到页面内容 GET: abc=123。POST 的表单数据也是类似:
func HttpHandle(w http.ResponseWriter, r *http.Request) {
v1 := r.FormValue("abc")
v2 := r.PostFormValue("abc")
v3 := r.Form.Get("abc")
v4 := r.PostForm.Get("abc")
fmt.Println(v1 == v2, v1 == v3, v1 == v4)
w.Write([]byte("POST: abc=" v1))
}
注意,这四个值 v1,v2,v3,v4 是相同的值。
如果同一个表单域传递了多个值,需要直接操作 r.Form 或 r.PostForm,比如 GET /?abc=123 |