go提供了详细且完整的标准库,同样我们使用go语言中的http
包也非常方便,只需要几行代码便可以开启一个服务。现在,我们尽量使用代码理解下http
包中的工作原理。
package main
import (
"fmt"
"log"
"net/http"
)
func helloWeb(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello GoWeb!") //这个写入到w的是输出到客户端的
}
func main() {
http.HandleFunc("/", helloWeb) // 设置访问的路由
err := http.ListenAndServe(":8085", nil) //设置监听的端口
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
当我们在浏览器输入http://localhost:8085/
时,页面上会呈现Hello GoWeb!
的文字,也就是我们通过这几行代码实现的。上述代码里,我们通过HandleFunc
设置了路由和对应的路由处理方法,再通过ListenAndServe
方法将我们设置的端口或服务地址开启一个服务,实现服务端的开启。接下来我们来看ListenAndServe
方法的实现。
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
上述代码根据服务地址和handler
创建了server
变量,最终还是调用了server
的ListenAndServe
方法,那我们继续来看ListenAndServe
做了什么事。
func (srv *Server) ListenAndServe() error {
if srv.shuttingDown() {
return ErrServerClosed
}
addr := srv.Addr
if addr == "" {
addr = ":http"
}
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return srv.Serve(ln)
}
这里的核心代码是调用net包的Listen
方法来创建一个tcp连接,接着又调用了Serve
方法。
func (srv *Server) Serve(l net.Listener) error {
if fn := testHookServerServe; fn != nil {
fn(srv, l) // call hook with unwrapped listener
}
origListener := l
l = &onceCloseListener{Listener: l}
defer l.Close()
if err := srv.setupHTTP2_Serve(); err != nil {
return err
}
if !srv.trackListener(&l, true) {
return ErrServerClosed
}
defer srv.trackListener(&l, false)
var tempDelay time.Duration // how long to sleep on accept failure
baseCtx := context.Background()
if srv.BaseContext != nil {
baseCtx = srv.BaseContext(origListener)
if baseCtx == nil {
panic("BaseContext returned a nil context")
}
}
ctx := context.WithValue(baseCtx, ServerContextKey, srv)
for {
rw, e := l.Accept()
if e != nil {
select {
case <-srv.getDoneChan():
return ErrServerClosed
default:
}
if ne, ok := e.(net.Error); ok && ne.Temporary() {
if tempDelay == 0 {
tempDelay = 5 * time.Millisecond
} else {
tempDelay *= 2
}
if max := 1 * time.Second; tempDelay > max {
tempDelay = max
}
srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)
time.Sleep(tempDelay)
continue
}
return e
}
if cc := srv.ConnContext; cc != nil {
ctx = cc(ctx, rw)
if ctx == nil {
panic("ConnContext returned nil")
}
}
tempDelay = 0
c := srv.newConn(rw)
c.setState(c.rwc, StateNew) // before Serve can return
go c.serve(ctx)
}
}
虽然serve
代码较长,但我们只需要关注for循环里代码逻辑即可。在for循环中,我们调用刚创建的tcp连接变量l
的Accept
方法,这个方法会进行网络监听,当有请求进来时会调用newConn
方法来生成 连接,最终通过go
关键字来调用serve
方法,这也就让go
能并发的处理请求,我们接着看serve
的实现。
func (c *conn) serve(ctx context.Context) {
c.remoteAddr = c.rwc.RemoteAddr().String()
...
// HTTP/1.x from here on.
ctx, cancelCtx := context.WithCancel(ctx)
c.cancelCtx = cancelCtx
defer cancelCtx()
c.r = &connReader{conn: c}
c.bufr = newBufioReader(c.r)
c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)
for {
w, err := c.readRequest(ctx)
if c.r.remain != c.server.initialReadLimitSize() {
// If we read any bytes off the wire, we're active.
c.setState(c.rwc, StateActive)
}
if err != nil {
const errorHeaders = "\r\nContent-Type: text/plain; charset=utf-8\r\nConnection: close\r\n\r\n"
switch {
case err == errTooLarge:
// Their HTTP client may or may not be
// able to read this if we're
// responding to them and hanging up
// while they're still writing their
// request. Undefined behavior.
const publicErr = "431 Request Header Fields Too Large"
fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+publicErr)
c.closeWriteAndWait()
return
case isUnsupportedTEError(err):
// Respond as per RFC 7230 Section 3.3.1 which says,
// A server that receives a request message with a
// transfer coding it does not understand SHOULD
// respond with 501 (Unimplemented).
code := StatusNotImplemented
// We purposefully aren't echoing back the transfer-encoding's value,
// so as to mitigate the risk of cross side scripting by an attacker.
fmt.Fprintf(c.rwc, "HTTP/1.1 %d %s%sUnsupported transfer encoding", code, StatusText(code), errorHeaders)
return
case isCommonNetReadError(err):
return // don't reply
default:
publicErr := "400 Bad Request"
if v, ok := err.(badRequestError); ok {
publicErr = publicErr + ": " + string(v)
}
fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+publicErr)
return
}
}
// Expect 100 Continue support
req := w.req
if req.expectsContinue() {
if req.ProtoAtLeast(1, 1) && req.ContentLength != 0 {
// Wrap the Body reader with one that replies on the connection
req.Body = &expectContinueReader{readCloser: req.Body, resp: w}
}
} else if req.Header.get("Expect") != "" {
w.sendExpectationFailed()
return
}
c.curReq.Store(w)
if requestBodyRemains(req.Body) {
registerOnHitEOF(req.Body, w.conn.r.startBackgroundRead)
} else {
w.conn.r.startBackgroundRead()
}
// HTTP cannot have multiple simultaneous active requests.[*]
// Until the server replies to this request, it can't read another,
// so we might as well run the handler in this goroutine.
// [*] Not strictly true: HTTP pipelining. We could let them all process
// in parallel even if their responses need to be serialized.
// But we're not going to implement HTTP pipelining because it
// was never deployed in the wild and the answer is HTTP/2.
serverHandler{c.server}.ServeHTTP(w, w.req)
w.cancelCtx()
if c.hijacked() {
return
}
w.finishRequest()
if !w.shouldReuseConnection() {
if w.requestBodyLimitHit || w.closedRequestBodyEarly() {
c.closeWriteAndWait()
}
return
}
c.setState(c.rwc, StateIdle)
c.curReq.Store((*response)(nil))
if !w.conn.server.doKeepAlives() {
// We're in shutdown mode. We might've replied
// to the user without "Connection: close" and
// they might think they can send another
// request, but such is life with HTTP/1.1.
return
}
if d := c.server.idleTimeout(); d != 0 {
c.rwc.SetReadDeadline(time.Now().Add(d))
if _, err := c.bufr.Peek(4); err != nil {
return
}
}
c.rwc.SetReadDeadline(time.Time{})
}
}
这里代码依然很多,但我们去除了一些边界逻辑,这里核心逻辑是通过生成的conn
实例c
来调用readRequest
方法。readRequest
主要生成req
请求信息和响应信息,响应信息的格式如下
// A response represents the server side of an HTTP response.
type response struct {
conn *conn
req *Request // request for this response
reqBody io.ReadCloser
cancelCtx context.CancelFunc // when ServeHTTP exits
wroteHeader bool
wroteContinue bool
wants10KeepAlive bool
wantsClose bool
w *bufio.Writer // buffers output in chunks to chunkWriter
cw chunkWriter
handlerHeader Header
calledHeader bool
written int64 // number of bytes written in body
contentLength int64
status int // status code passed to WriteHeader
closeAfterReply bool
requestBodyLimitHit bool
trailers []string
handlerDone atomicBool // set true when the handler exits
dateBuf [len(TimeFormat)]byte
clenBuf [10]byte
statusBuf [3]byte
closeNotifyCh chan bool
didCloseNotify int32 // atomic (only 0->1 winner should send)
}
通过readRequest
生成完这个响应实体后,再根据最初传入的server
实例整合成serverHandler
,再将请求信息和响应信息作为参数并调用它的ServeHTTP
方法。
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,一种是我们在最初ListenAndServe
方法中写的handler,例子中我们写了nil,另一种是默认的handler就是DefaultServeMux
,我们先来看Handler
的定义
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
可以看到Handler
是一个拥有ServeHTTP
方法的接口,那在handler实例为DefaultServeMux
时,我们先来看DefaultServeMux
类型的定义。
type ServeMux struct {
mu sync.RWMutex
m map[string]muxEntry
es []muxEntry // slice of entries sorted from longest to shortest.
hosts bool // whether any patterns contain hostnames
}
type muxEntry struct {
h Handler
pattern string
}
这时我们要去看DefaultServeMux
的ServeHTTP
方法实现。
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)
}
我们先来看Handler
方法
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
...
// All other requests have any port stripped and path cleaned
// before passing to mux.handler.
host := stripHostPort(r.Host)
path := cleanPath(r.URL.Path)
return mux.handler(host, r.URL.Path)
}
省去了一些分支代码,其实这个方法还是在调用handler
方法,
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
mux.mu.RLock()
defer mux.mu.RUnlock()
// Host-specific pattern takes precedence over generic ones
if mux.hosts {
h, pattern = mux.match(host + path)
}
if h == nil {
h, pattern = mux.match(path)
}
if h == nil {
h, pattern = NotFoundHandler(), ""
}
return
}
这里的path
是我们在Url里请求的路径,它调用了match
方法来匹配对应path
的回调函数。有了最终的handler,就调用了handler的ServeHTTP
方法,那handler是从哪里来的呢?其实我们在写http.HandleFunc("/", helloWeb) // 设置访问的路由
时就将路径和回调函数注册进了路由注册表中,所以在mux.match(path)
中可以匹配到对应路径的回调方法。那我们继续看HandleFunc
的实现。
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
DefaultServeMux.HandleFunc(pattern, handler)
}
实际上还是调用了HandleFunc
方法。
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
if handler == nil {
panic("http: nil handler")
}
mux.Handle(pattern, HandlerFunc(handler))
}
最后调用了Hanlder
方法。
func (mux *ServeMux) Handle(pattern string, handler Handler) {
mux.mu.Lock()
defer mux.mu.Unlock()
...
if mux.m == nil {
mux.m = make(map[string]muxEntry)
}
e := muxEntry{h: handler, pattern: pattern}
mux.m[pattern] = e
if pattern[len(pattern)-1] == '/' {
mux.es = appendSorted(mux.es, e)
}
if pattern[0] != '/' {
mux.hosts = true
}
}
这里省去了部分边界判断代码,还记得之前的ServeMux
的定义吗,这里其实就是将我们写的路径和回调函数保存在mux.m
中。再在mux.match(path)
中匹配将回调函数取出来执行。当然除了最初使用默认的ServeMux
,我们还可以自定义ServeMux
。
import (
"net/http"
"io"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/hanru", func(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "hello")
})
mux.HandleFunc("/hello", sayhello)
http.ListenAndServe(":8085", mux)
}
func sayhello(w http.ResponseWriter, r *http.Request) {
io.WriteString(w, "hello world")
}
通过NewServeMux
方法创建ServeMux
,并将ServeMux
当做ListenAndServe
第二个参数传入,这里内部执行的逻辑和之前差不多,只是之前用的是默认的ServeMux
,现在我们自定义了ServeMux
,其中的源码大家可以自己分析。