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.
有疑问加站长微信联系(非本文作者)