Go | http
最简单的http服务
go里面最简单的http服务只有
package main
import (
"net/http"
)
func main() {
// 启动 HTTP 服务
http.ListenAndServe(":1234", nil))
}
运行,并且然后访问 http://<ip>:1234/
应该会显示404。
ListenAndServe 就是一个监听端口、解析请求并不断处理请求的一个函数。如果监听失败,比如说端口被占用了,就会返回一个错误,说明是为什么出问题。
ping?pong!
为了让服务变的有意义,我们需要给让http可以正确处理各个URL。
package main
import (
"net/http"
)
func main() {
// 处理访问/ping的请求
http.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("pong"))
})
http.ListenAndServe(":1234", nil)
}
这次我们访问 http://<ip>:1234/ping
服务器给我们返回了pong。
主页
那如果我想在网站首页显示一个页面,而不是404,怎么办?再加一个http.HandleFunc呗:
package main
import (
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(""))
})
http.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("pong"))
})
http.ListenAndServe(":1234", nil)
}
Handler和HandlerFunc
http包处理每个请求都需要一个handler,一个handler接口的定义是这样的(go1.13):
// net/http/server.go
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
不管是什么类型的请求,都通过一个Handler来处理。比如说我们有个接口/api/user
,GET请求列举用户,POST请求创建用户。那么我们可以新建一个类型
type UserController struct {
// ...
}
func (controller UserController) ServeHTTP(w ResponseWriter, r *Request) {
switch r.Method {
case "GET":
// ...
case "POST":
// ...
}
}
但是有的时候我们不需要这样一个类,比如说我就是要ping一下,要一个pong的结果呢?为了简tou单lan,http包带一个adapter类型叫HandlerFunc:
// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)
// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
这样我们写一些简单的请求处理过程时,直接写
http.Handle("/...", http.HandlerFunc(func(w ResponseWriter, r *Request) {
_, _ = w.Write([]byte("pong"))
}))
// or
http.HandleFunc("/...", func(w ResponseWriter, r *Request) {
_, _ = w.Write([]byte("pong"))
})
就行了
Mux
上面代码里面还有个比较奇怪的地方
package main
import (
"net/http"
)
func main() {
// 这里第二个参数类型也是一个handler接口
http.ListenAndServe(":1234", nil))
}
第二个参数的handler到底是做什么的呢?
单步跟踪一下,我们可以看到两个值得注意的地方。
一个是跟踪进去的第一句话:
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
handler被包进了server这个结构体里面。
另一个是,在http包里面,每个请求处理时会进行一次判断:
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
handler := sh.srv.Handler
if handler == nil {
handler = DefaultServeMux
}
if req.RequestURI == "*" && req.Method == "OPTIONS" {
handler = globalOptionsHandler{}
}
handler.ServeHTTP(rw, req)
}
如果handler为nil,那么会使用http包内的DefaultServeMux来作为handler。而这里的handler就是我们在ListenAndServe时,传入的第二个参数。而DefaultServeMux类型则是ServeMux。
那么这个ServeMux是个啥?
我们可以单步,也可以直接看godoc:
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) ServeHTTP dispatches the request to the handler whose pattern most closely matches the request URL.
对应代码就是(比较长,可以跳过去)
// Handler returns the handler to use for the given request,
// consulting r.Method, r.Host, and r.URL.Path. It always returns
// a non-nil handler. If the path is not in its canonical form, the
// handler will be an internally-generated handler that redirects
// to the canonical path. If the host contains a port, it is ignored
// when matching handlers.
//
// The path and host are used unchanged for CONNECT requests.
//
// Handler also returns the registered pattern that matches the
// request or, in the case of internally-generated redirects,
// the pattern that will match after following the redirect.
//
// If there is no registered handler that applies to the request,
// Handler returns a ``page not found'' handler and an empty pattern.
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
// CONNECT requests are not canonicalized.
if r.Method == "CONNECT" {
// If r.URL.Path is /tree and its handler is not registered,
// the /tree -> /tree/ redirect applies to CONNECT requests
// but the path canonicalization does not.
if u, ok := mux.redirectToPathSlash(r.URL.Host, r.URL.Path, r.URL); ok {
return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
}
return mux.handler(r.Host, r.URL.Path)
}
// All other requests have any port stripped and path cleaned
// before passing to mux.handler.
host := stripHostPort(r.Host)
path := cleanPath(r.URL.Path)
// If the given path is /tree and its handler is not registered,
// redirect for /tree/.
if u, ok := mux.redirectToPathSlash(host, path, r.URL); ok {
return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
}
if path != r.URL.Path {
_, pattern = mux.handler(host, path)
url := *r.URL
url.Path = path
return RedirectHandler(url.String(), StatusMovedPermanently), pattern
}
return mux.handler(host, r.URL.Path)
}
// ServeHTTP dispatches the request to the handler whose
// pattern most closely matches the request URL.
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
if r.RequestURI == "*" {
if r.ProtoAtLeast(1, 1) {
w.Header().Set("Connection", "close")
}
w.WriteHeader(StatusBadRequest)
return
}
h, _ := mux.Handler(r)
h.ServeHTTP(w, r)
}
说ServeMux就是用来把请求派发给URL符合特定模式的handler的。并且ServeMux还有两个函数有点眼熟:
func (mux *ServeMux) Handle(pattern string, handler Handler) Handle registers the handler for the given pattern. If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) HandleFunc registers the handler function for the given pattern.
再去看http的Handle和HandleFunc:
// Handle registers the handler for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }
// HandleFunc registers the handler function for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
就很清楚了,这两个函数就是给DefaultServeMux注册Handler的。
那么如果我们要用自己的Mux,就可以这么写:
package main
import (
"net/http"
)
func main() {
mux := &http.ServeMux{}
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(""))
})
mux.HandleFunc("/ping", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("pong"))
})
http.ListenAndServe(":1234", mux)
}
更复杂的Mux
如果我们觉得现在的Mux还是太麻烦了,无法区分hostname,无法根据请求的方法来单独写handler,无法用正则匹配url,函数形式太单一、无法/难以满足需求,那么我们可以用其他的mux实现来满足我们的需求。
如果是go开荒时期,可能我们需要自己写这样的mux。好在我们现在有了非常多的第三方工具来做这样的事情。比如说我自己常用的chi,可能会把首页写成这个样子:
package main
import (
"github.com/go-chi/chi"
"net/http"
)
func main() {
server := BlogServer{...}
r := chi.NewRouter()
r.Get("/", HomePage)
r.Route("/api", func(r *chi.Mux) {
r.Get("/user", server.ListUser)
r.Post("/user", server.CreateUser)
// ...
}
http.ListenAndServe(":1234", r)
}
go的框架太多了,给大家推荐一些,然后自己去探索吧:
- 框架
- 路由
值得一提的有一个gorilla,除了mux以外还有很多有用的包,比如说websocket的或者涉及安全的。我个人则是常用chi,然后自己做剩下的事情。