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) {
.RLock()
defer .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) {
.Lock()
defer .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,其中的源码大家可以自己分析。
















