Go-HTTP

u013344915 · · 3716 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

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文件,那么就可以用http://localhost:8080/about.html来访问这个页面。

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

自定义的Handler/Creating Custom Handlers

see 《Web Development with Go》Page 63.

用户可以创建自己的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)
}

/*
http://localhost:8080/first -> handler1
http://localhost:8080/second -> handler2
http://localhost:8080/second/item1 -> handler21
http://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 "http://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.

有疑问加站长微信联系(非本文作者)

本文来自:CSDN博客

感谢作者:u013344915

查看原文:Go-HTTP

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

3716 次点击  
加入收藏 微博
下一篇:Go-HTTP
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传