Go | http

2020 01 6, Mon

最简单的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,然后自己做剩下的事情。