欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  网络运营

Go-HTTP

程序员文章站 2022-03-31 10:10:32
package net/http 这个package支持:web application, web APIs;简单地说,就是可以用net/http来实现HTTP客户端和服务器的开...

package net/http

这个package支持:web application, web APIs;简单地说,就是可以用net/http来实现HTTP客户端和服务器的开发。 另外,还要用到package html/template。

该书作者不建议初学者一开始就用第三方库,而是先用Go发布时的net/http等标准库。

在本系列中,会略过HTTP的概念,这方面可以找专门的HTTP书籍做参考。

处理HTTP请求

如果是构建WEB APIs,那么服务器会返回XML或JSON;如果是构建WEB应用,则服务器返回HTML网页。

net/http的两个主要构件(src/net/http/server.go):

- ServeMux: 多路复用,或HTTP请求路由。讲HTTP请求和预定义的URIs进行比较,然后调用对应的Handler来处理这个HTTP请求。——对应于Tomcat容器。

- Handler: 负责写HTTP响应结果(Header&Body)。http.Handler是一个接口,用户要做的就是实现这个接口。——可以和Spring @Controller进行对比。

Handler

源码

// A Handler responds to an HTTP request.
//
// ServeHTTP should write reply headers and data to the ResponseWriter
// and then return. Returning signals that the request is finished; it
// is not valid to use the ResponseWriter or read from the
// Request.Body after or concurrently with the completion of the
// ServeHTTP call.
//
// Depending on the HTTP client software, HTTP protocol version, and
// any intermediaries between the client and the Go server, it may not
// be possible to read from the Request.Body after writing to the
// ResponseWriter. Cautious handlers should read the Request.Body
// first, and then reply.
//
// Except for reading the body, handlers should not modify the
// provided Request.
//
// If ServeHTTP panics, the server (the caller of ServeHTTP) assumes
// that the effect of the panic was isolated to the active request.
// It recovers the panic, logs a stack trace to the server error log,
// and hangs up the connection.
type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

要点

ResponseWriter对象用来构件Response,包括Header和Body。 一旦往ResponseWriter中写了数据之后,就不要再访问Request对象了。 ServeHttp不能修改Request的值。

ResponseWriter

ResponseWriter也是一个接口,定义了3个方法。

// A ResponseWriter interface is used by an HTTP handler to
// construct an HTTP response.
//
// A ResponseWriter may not be used after the Handler.ServeHTTP method
// has returned.
type ResponseWriter interface {
    // Header returns the header map that will be sent by
    // WriteHeader. Changing the header after a call to
    // WriteHeader (or Write) has no effect unless the modified
    // headers were declared as trailers by setting the
    // "Trailer" header before the call to WriteHeader (see example).
    // To suppress implicit response headers, set their value to nil.
    Header() Header

    // Write writes the data to the connection as part of an HTTP reply.
    //
    // If WriteHeader has not yet been called, Write calls
    // WriteHeader(http.StatusOK) before writing the data. If the Header
    // does not contain a Content-Type line, Write adds a Content-Type set
    // to the result of passing the initial 512 bytes of written data to
    // DetectContentType.
    //
    // Depending on the HTTP protocol version and the client, calling
    // Write or WriteHeader may prevent future reads on the
    // Request.Body. For HTTP/1.x requests, handlers should read any
    // needed request body data before writing the response. Once the
    // headers have been flushed (due to either an explicit Flusher.Flush
    // call or writing enough data to trigger a flush), the request body
    // may be unavailable. For HTTP/2 requests, the Go HTTP server permits
    // handlers to continue to read the request body while concurrently
    // writing the response. However, such behavior may not be supported
    // by all HTTP/2 clients. Handlers should read before writing if
    // possible to maximize compatibility.
    Write([]byte) (int, error)

    // WriteHeader sends an HTTP response header with status code.
    // If WriteHeader is not called explicitly, the first call to Write
    // will trigger an implicit WriteHeader(http.StatusOK).
    // Thus explicit calls to WriteHeader are mainly used to
    // send error codes.
    WriteHeader(int)
}

FileServer/静态页面

package main 

import (
    "net/http"
)

func main() {
    mux := http.NewServeMux()
    fs := http.FileServer(http.Dir("public"))
    mux.Handle("/", fs)
    http.ListenAndServe(":8080", mux)
}

这里需要创建一个public文件夹,这个public文件夹和.go文件(如helloworld.go)是在同一级目录;——为此只需要注意到public采用的是相对路径。

比如接下来在public目录下创建一个about.html文件,那么就可以用https://localhost:8080/about.html来访问这个页面。

这里的FileServer()定义在src/http/fs.go中,这个函数返回的是type fileHandler struct,这个struct实现了Handler接口。

自定义的Handler/Creating Custom Handlers

 

用户可以创建自己的struct,只要实现了Handler接口,就可以用来作为HTTP消息处理的handler。

package main 

import (
    "fmt"
    "log"
    "net/http"
)

type MyServeMux struct {
    http.ServeMux
    id int
}

type MessageHandler struct {
    msg string 
}

func (m *MessageHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, m.msg)
}

/*
https://localhost:8080/first -> handler1
https://localhost:8080/second -> handler2
https://localhost:8080/second/item1 -> handler21
https://localhost:8080/second/item2 -> 404 page not found
*/
func main() {
    mux := new (MyServeMux)

    handler1 := &MessageHandler{"The first handler."}
    handler2 := &MessageHandler{"The second handler."}
    handler21 := &MessageHandler{"The second handler, and item is 1."}

    mux.Handle("/first",  handler1)
    mux.Handle("/second", handler2)
    mux.Handle("/second/item1", handler21)

    log.Println("Listening...")
    http.ListenAndServe(":8080", mux)
}

中间部分代码也可以写作:

handler1 := MessageHandler{"The first handler."}
handler2 := MessageHandler{"The second handler."}
handler21 := MessageHandler{"The second handler, and item is 1."}

mux.Handle("/first",  &handler1)
mux.Handle("/second", &handler2)
mux.Handle("/second/item1", &handler21)

要点:

需要通过指针/地址方式,实现多态功能。 ServeMux是用来维护URI和Handler之间的映射关系。 每个Handler就是Spring的@Controller。

http.HandlerFunc type

前面的例子中,先创建一个struct,并为之实现ServeHttp()接口,从而将struct变成一个Handler。有时候,如果struct没有field,则可以用http.HandlerFunc类型的方法。

定义/server.go

// 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)
}

这里的代码说明了,type不仅可以用于数据类型,也可以用于函数。

HandlerFunc是一个函数类型,主要用户自定义的函数符合这个signature,那么就可以用HandlerFunc(foo)将这个foo函数强制转换成HandlerFunc类型。又因为HandlerFunc类型实现了ServerHttp()接口,所以这个HandlerFunc对象(这又像C++的functor了)也就自动是一个Handler了。

注意到:抛开receiver,那么HandlerFunc的signature和ServerHttp是一样的。

示例

package main 

import (
    "fmt"
    "log"
    "net/http"
)

func msgHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "Hello, HandlerFunc!")
}

func main() {
    mux := http.NewServeMux()

    handler := http.HandlerFunc(msgHandler)

    mux.Handle("/hello",  handler)

    log.Println("Listening...")
    http.ListenAndServe(":8080", mux)
}

有struct改成func之后,之前的参数化功能(可定制msg)消失了。为此,可以对func进行改进。

改进:函数参数化

package main 

import (
    "fmt"
    "log"
    "net/http"
)

func msgHandler(msg string) http.Handler {
    return http.HandlerFunc(
        func(w http.ResponseWriter, r *http.Request) {
            fmt.Fprint(w, msg)
        })
}

func main() {
    mux := http.NewServeMux()

    handler1 := msgHandler("first handler")
    handler2 := msgHandler("second handler")

    mux.Handle("/first",  handler1)
    mux.Handle("/second",  handler2)

    log.Println("Listening...")
    http.ListenAndServe(":8080", mux)
}

ServeMux.HandleFunc()

Go总是尽力为用户提供方便。为了阐述这一点,先给出示例:

示例一

package main 

import (
    "fmt"
    "log"
    "net/http"
)

func msgHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "Hello, HandlerFunc!")
}

func main() {
    mux := http.NewServeMux()

    //handler := http.HandlerFunc(msgHandler)
    //mux.Handle("/hello",  handler)
    mux.HandleFunc("/hello", msgHandler)

    log.Println("Listening...")
    http.ListenAndServe(":8080", mux)
}

在这个例子中,把之前的两句合并为一句。为了说明其含义,给出ServeMux.HandleFunc()的定义。

ServeMux.HandleFunc()

// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    mux.Handle(pattern, HandlerFunc(handler))
}

对照这个源码,不难理解两句合并为一句的原理。故无须再啰嗦。

示例二

另一个优化示例。

package main 

import (
    "fmt"
    "log"
    "net/http"
)

func msgHandler(msg string) http.HandlerFunc/*http.Handler*/ {
    return http.HandlerFunc(
        func(w http.ResponseWriter, r *http.Request) {
            fmt.Fprint(w, msg)
        })
}

func main() {
    mux := http.NewServeMux()

    /*handler1 := msgHandler("first handler")
    handler2 := msgHandler("second handler")

    mux.Handle("/first",  handler1)
    mux.Handle("/second",  handler2)*/

    mux.HandleFunc("/first", msgHandler("first handler"))
    mux.HandleFunc("/second", msgHandler("second handler"))

    log.Println("Listening...")
    http.ListenAndServe(":8080", mux)
}

或者改为:

func msgHandler2(msg string) func(http.ResponseWriter, *http.Request) {
    return func(w http.ResponseWriter, r *http.Request) {
            fmt.Fprint(w, msg)
        }
}

func main() {
    //...
    mux.HandleFunc("/first", msgHandler2("first handler"))
    mux.HandleFunc("/second", msgHandler2("second handler"))
    //...
}

ServeMux

NewServeMux()

该函数(src/net/http/server.go)的定义如下:

// NewServeMux allocates and returns a new ServeMux.
func NewServeMux() *ServeMux { return new(ServeMux) }

也就是说,这个函数创建一个ServeMux对象,并返回该对象(的指针)。

DefaultServeMux

DefaultServeMux是http package中定义的一个ServeMux对象:

// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux

var defaultServeMux ServeMux

那么什么时候用到DefaultServeMux呢?——当用户代码没有定义ServeMux对象的时候。

示例代码:

package main 

import (
    "fmt"
    "log"
    "net/http"
)

func msgHandler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "Hello, HandlerFunc!")
}

func main() {
    //mux := http.NewServeMux()

    /*mux*/http.HandleFunc("/hello", msgHandler)

    log.Println("Listening...")
    http.ListenAndServe(":8080", nil/*mux*/)
}

可以看到,用DefaultServeMux会简化代码。

那么接下来,就把相关的函数的源码列出来,以此理解背后的原理。

HandleFunc

// 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)
}

// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    mux.Handle(pattern, HandlerFunc(handler))
}

ListenAndServe()

这个流程相当长,可以跳过前面,直接跑到最后一个函数。——中间函数罗列在这里,供参考。

func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

// ListenAndServe listens on the TCP network address srv.Addr and then
// calls Serve to handle requests on incoming connections.
// Accepted connections are configured to enable TCP keep-alives.
// If srv.Addr is blank, ":http" is used.
// ListenAndServe always returns a non-nil error.
func (srv *Server) ListenAndServe() error {
    addr := srv.Addr
    if addr == "" {
        addr = ":http"
    }
    ln, err := net.Listen("tcp", addr)
    if err != nil {
        return err
    }
    return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
}

// Serve accepts incoming connections on the Listener l, creating a
// new service goroutine for each. The service goroutines read requests and
// then call srv.Handler to reply to them.
//
// For HTTP/2 support, srv.TLSConfig should be initialized to the
// provided listener's TLS Config before calling Serve. If
// srv.TLSConfig is non-nil and doesn't include the string "h2" in
// Config.NextProtos, HTTP/2 support is not enabled.
//
// Serve always returns a non-nil error.
func (srv *Server) Serve(l net.Listener) error {
    defer l.Close()
    if fn := testHookServerServe; fn != nil {
        fn(srv, l)
    }
    var tempDelay time.Duration // how long to sleep on accept failure

    if err := srv.setupHTTP2_Serve(); err != nil {
        return err
    }

    // TODO: allow changing base context? can't imagine concrete
    // use cases yet.
    baseCtx := context.Background()
    ctx := context.WithValue(baseCtx, ServerContextKey, srv)
    ctx = context.WithValue(ctx, LocalAddrContextKey, l.Addr())
    for {
        rw, e := l.Accept()
        if e != nil {
            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
        }
        tempDelay = 0
        c := srv.newConn(rw)
        c.setState(c.rwc, StateNew) // before Serve can return
        go c.serve(ctx)
    }
}

// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
    c.remoteAddr = c.rwc.RemoteAddr().String()
    defer func() {
        if err := recover(); err != nil {
            const size = 64 << 10
            buf := make([]byte, size)
            buf = buf[:runtime.Stack(buf, false)]
            c.server.logf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf)
        }
        if !c.hijacked() {
            c.close()
            c.setState(c.rwc, StateClosed)
        }
    }()

    if tlsConn, ok := c.rwc.(*tls.Conn); ok {
        if d := c.server.ReadTimeout; d != 0 {
            c.rwc.SetReadDeadline(time.Now().Add(d))
        }
        if d := c.server.WriteTimeout; d != 0 {
            c.rwc.SetWriteDeadline(time.Now().Add(d))
        }
        if err := tlsConn.Handshake(); err != nil {
            c.server.logf("http: TLS handshake error from %s: %v", c.rwc.RemoteAddr(), err)
            return
        }
        c.tlsState = new(tls.ConnectionState)
        *c.tlsState = tlsConn.ConnectionState()
        if proto := c.tlsState.NegotiatedProtocol; validNPN(proto) {
            if fn := c.server.TLSNextProto[proto]; fn != nil {
                h := initNPNRequest{tlsConn, serverHandler{c.server}}
                fn(c.server, tlsConn, h)
            }
            return
        }
    }

    // HTTP/1.x from here on.

    c.r = &connReader{r: c.rwc}
    c.bufr = newBufioReader(c.r)
    c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)

    ctx, cancelCtx := context.WithCancel(ctx)
    defer cancelCtx()

    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 {
            if 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.
                io.WriteString(c.rwc, "HTTP/1.1 431 Request Header Fields Too Large\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\n431 Request Header Fields Too Large")
                c.closeWriteAndWait()
                return
            }
            if err == io.EOF {
                return // don't reply
            }
            if neterr, ok := err.(net.Error); ok && neterr.Timeout() {
                return // don't reply
            }
            var publicErr string
            if v, ok := err.(badRequestError); ok {
                publicErr = ": " + string(v)
            }
            io.WriteString(c.rwc, "HTTP/1.1 400 Bad Request\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\n400 Bad Request"+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
        }

        // 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.
        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)
    }
}

// initNPNRequest is an HTTP handler that initializes certain
// uninitialized fields in its *Request. Such partially-initialized
// Requests come from NPN protocol handlers.
type initNPNRequest struct {
    c *tls.Conn
    h serverHandler
}

// serverHandler delegates to either the server's Handler or
// DefaultServeMux and also handles "OPTIONS *" requests.
type serverHandler struct {
    srv *Server
}

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)
}

ServeMux struct

数据成员:

type ServeMux struct {
    mu    sync.RWMutex
    m     map[string]muxEntry
    hosts bool // whether any patterns contain hostnames
}

其中map是URI到Handler之间的映射关系。相关的函数或成员方法如下(godoc cmd/net/http ServeMux):

type ServeMux struct {
    // contains filtered or unexported fields
}
    ServeMux is an HTTP request multiplexer. It matches the URL of each
    incoming request against a list of registered patterns and calls the
    handler for the pattern that most closely matches the URL.

    Patterns name fixed, rooted paths, like "/favicon.ico", or rooted
    subtrees, like "/images/" (note the trailing slash). Longer patterns
    take precedence over shorter ones, so that if there are handlers
    registered for both "/images/" and "/images/thumbnails/", the latter
    handler will be called for paths beginning "/images/thumbnails/" and the
    former will receive requests for any other paths in the "/images/"
    subtree.

    Note that since a pattern ending in a slash names a rooted subtree, the
    pattern "/" matches all paths not matched by other registered patterns,
    not just the URL with Path == "/".

    If a subtree has been registered and a request is received naming the
    subtree root without its trailing slash, ServeMux redirects that request
    to the subtree root (adding the trailing slash). This behavior can be
    overridden with a separate registration for the path without the
    trailing slash. For example, registering "/images/" causes ServeMux to
    redirect a request for "/images" to "/images/", unless "/images" has
    been registered separately.

    Patterns may optionally begin with a host name, restricting matches to
    URLs on that host only. Host-specific patterns take precedence over
    general patterns, so that a handler might register for the two patterns
    "/codesearch" and "codesearch.google.com/" without also taking over
    requests for "https://www.google.com/".

    ServeMux also takes care of sanitizing the URL request path, redirecting
    any request containing . or .. elements or repeated slashes to an
    equivalent, cleaner URL.

func NewServeMux() *ServeMux
    NewServeMux allocates and returns a new 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.

func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string)
    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.

    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) ServeHTTP(w ResponseWriter, r *Request)
    ServeHTTP dispatches the request to the handler whose pattern most
    closely matches the request URL.

推荐阅读