简单服务器 Demo
在 Go 编写一个服务器非常简单:
func main() {
http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) {
writer.Write([]byte("hello, world!"))
})
http.ListenAndServe(":8080", nil)
}
之后便可以访问:
~ curl localhost:8080/
hello, world!
也可以用自定义类型实现 http.Handler 接口的方式:
func main() {
helloStr := "hello, world!"
http.Handle("/hello", helloHandler(helloStr))
http.ListenAndServe(":8080", nil)
}
type helloHandler string
func (h helloHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
writer.Write([]byte(h))
}
上述代码中,向 http.Handle 方法传入了请求路径和相应处理请求的 handler ,请求到达时服务器会根据请求路径找到相应的 handler 处理请求。
http.ResponseWriter 接口
ResponseWriter 接口用于让 handler 构建一个 http 响应,其方法如下:
Header() Header
Write([]byte) (int, error)
WriteHeader(statusCode int)
Header()
Header 代表了 HTTP 首部中的键值对,其实际是一个哈希表:
type Header map[string][]string
Header 包含的方法主要是对响应头键值对的增删改查,以 Add 方法为例:
func (h Header) Add(key, value string) {
textproto.MIMEHeader(h).Add(key, value)
}
它将 Header 转为 MIMEHeader 类型,实际调用了 MIMEHeader 的 Add 方法:
func (h MIMEHeader) Add(key, value string) {
key = CanonicalMIMEHeaderKey(key)
h[key] = append(h[key], value)
}
Write([]byte)
该方法将数据写到 HTTP 连接中。
WriteHeader(statusCode int)
该方法发送一个包含状态码 statusCode 的 HTTP 响应头。 于 IANA 登记的状态码位于 http/status.go 中,这里仅列出几个常见的状态码:
package http
const (
StatusContinue = 100
StatusSwitchingProtocols = 101
StatusProcessing = 102
StatusEarlyHints = 103
StatusOK = 200
StatusCreated = 201
StatusAccepted = 202
StatusNonAuthoritativeInfo = 203
StatusNoContent = 204
StatusMultipleChoices = 300
StatusMovedPermanently = 301
StatusFound = 302
StatusSeeOther = 303
StatusNotModified = 304
StatusBadRequest = 400
StatusUnauthorized = 401
StatusPaymentRequired = 402
StatusForbidden = 403
StatusNotFound = 404
StatusMethodNotAllowed = 405
StatusNotAcceptable = 406
StatusInternalServerError = 500
StatusNotImplemented = 501
StatusBadGateway = 502
...
)
var statusText = map[int]string{
StatusContinue: "Continue",
StatusSwitchingProtocols: "Switching Protocols",
StatusProcessing: "Processing",
StatusEarlyHints: "Early Hints",
StatusOK: "OK",
StatusCreated: "Created",
StatusAccepted: "Accepted",
StatusNonAuthoritativeInfo: "Non-Authoritative Information",
StatusNoContent: "No Content",
StatusMultipleChoices: "Multiple Choices",
StatusMovedPermanently: "Moved Permanently",
StatusFound: "Found",
StatusSeeOther: "See Other",
StatusNotModified: "Not Modified",
StatusBadRequest: "Bad Request",
StatusUnauthorized: "Unauthorized",
StatusPaymentRequired: "Payment Required",
StatusForbidden: "Forbidden",
StatusNotFound: "Not Found",
StatusMethodNotAllowed: "Method Not Allowed",
StatusNotAcceptable: "Not Acceptable",
StatusInternalServerError: "Internal Server Error",
StatusNotImplemented: "Not Implemented",
StatusBadGateway: "Bad Gateway",
...
}
func StatusText(code int) string {
return statusText[code]
}
response 结构体
在 http 包的服务器实现中,传给 handler 的 ResponseWriter 的实际类型是 http 包下的 response ,换言之,response 实现了 ResponseWriter 接口。其结构如下:
type Response struct {
Status string
StatusCode int
Proto string
ProtoMajor int
ProtoMinor int
Header Header
Body io.ReadCloser
ContentLength int64
TransferEncoding []string
Close bool
Trailer Header
Request *Request
TLS *tls.ConnectionState
}
Header()
Header 方法将 handlerHeader 拷贝到 chunkWriter 的 header 中,然后将 handlerHeader 返回。
func (w *response) Header() Header {
if w.cw.header == nil && w.wroteHeader && !w.cw.wroteHeader {
w.cw.header = w.handlerHeader.Clone()
}
w.calledHeader = true
return w.handlerHeader
}
Write([]byte)
func (w *response) Write(data []byte) (n int, err error) {
return w.write(len(data), data, "")
}
调用了其 write 方法:
func (w *response) write(lenData int, dataB []byte, dataS string) (n int, err error) {
if w.conn.hijacked() {
if lenData > 0 {
caller := relevantCaller()
w.conn.server.logf("http: response.Write on hijacked connection from %s (%s:%d)", caller.Function, path.Base(caller.File), caller.Line)
}
return 0, ErrHijacked
}
if w.canWriteContinue.isSet() {
w.writeContinueMu.Lock()
w.canWriteContinue.setFalse()
w.writeContinueMu.Unlock()
}
if !w.wroteHeader {
w.WriteHeader(StatusOK)
}
if lenData == 0 {
return 0, nil
}
if !w.bodyAllowed() {
return 0, ErrBodyNotAllowed
}
w.written += int64(lenData)
if w.contentLength != -1 && w.written > w.contentLength {
return 0, ErrContentLength
}
if dataB != nil {
return w.w.Write(dataB)
} else {
return w.w.WriteString(dataS)
}
}
WriteHeader(statusCode int)
func (w *response) WriteHeader(code int) {
if w.conn.hijacked() {
caller := relevantCaller()
w.conn.server.logf("http: response.WriteHeader on hijacked connection from %s (%s:%d)", caller.Function, path.Base(caller.File), caller.Line)
return
}
if w.wroteHeader {
caller := relevantCaller()
w.conn.server.logf("http: superfluous response.WriteHeader call from %s (%s:%d)", caller.Function, path.Base(caller.File), caller.Line)
return
}
checkWriteHeaderCode(code)
w.wroteHeader = true
w.status = code
if w.calledHeader && w.cw.header == nil {
w.cw.header = w.handlerHeader.Clone()
}
if cl := w.handlerHeader.get("Content-Length"); cl != "" {
v, err := strconv.ParseInt(cl, 10, 64)
if err == nil && v >= 0 {
w.contentLength = v
} else {
w.conn.server.logf("http: invalid Content-Length of %q", cl)
w.handlerHeader.Del("Content-Length")
}
}
}
http.Request
与 http.ResponseWriter 不同的是,http.Request 是一个结构体而不是接口。
type Request struct {
Method string
URL *url.URL
Proto string
ProtoMajor int
ProtoMinor int
Header Header
Body io.ReadCloser
ContentLength int64
TransferEncoding []string
Close bool
Host string
Form url.Values
PostForm url.Values
MultipartForm *multipart.Form
Trailer Header
RemoteAddr string
RequestURI string
TLS *tls.ConnectionState
}
http.Handle
在声明对一个路径的处理器时,调用了:
http.Handle("/", helloHandler(handler))
其源码如下:
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }
该函数在 DefaultServeMux 中对路径和处理器进行注册,该函数将 pattern 和 handler 传给了 DefaultServeMux 的 Handle 方法。 ServeMux 是 HTTP 请求的多路转接器(multiplexer)。它会将每一个接收的请求的 URL 与一个注册模式的列表进行匹配,并调用和 URL 最匹配的模式的处理器。 DefaultServeMux.Handle 实际上将模式放到一个哈希表中;如果路径以 '/' 结尾,则额外将其放到一个根据路径长度由长到短排序的数组中。例如,在匹配时如果不存在 /greetings/meet 路径的处理器,则依次尝试查找 /greetings/ 和 / 路径的处理器。
func (mux *ServeMux) Handle(pattern string, handler Handler) {
mux.mu.Lock()
defer mux.mu.Unlock()
if pattern == "" {
panic("http: invalid pattern")
}
if handler == nil {
panic("http: nil handler")
}
if _, exist := mux.m[pattern]; exist {
panic("http: multiple registrations for " + pattern)
}
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
}
}
例如,以下三个 handler 分别处理三个路径的请求:
func main() {
rootHandler := "hello"
http.Handle("/", helloHandler(rootHandler))
hiHandler := "hi"
http.Handle("/hi", helloHandler(hiHandler))
byeHandler := "bye"
http.Handle("/bye", helloHandler(byeHandler))
http.ListenAndServe(":8080", nil)
}
得到 mux.m 的内容如下:
http.ListenAndServe
地址的绑定和监听
在添加完各 handler 后,通过调用 http.ListenAndServe 函数开启服务器:
http.ListenAndServe(":8080", nil)
其源码如下:
func ListenAndServe(addr string, handler Handler) error {
server := &Server{Addr: addr, Handler: handler}
return server.ListenAndServe()
}
ListenAndServe 函数利用 TCP 地址 addr 和地址映射 handler 创建了一个 Server ,然后调用了其 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 源码如下:
func Listen(network, address string) (Listener, error) {
var lc ListenConfig
return lc.Listen(context.Background(), network, address)
}
其声明了一个 ListenConfig 后 调用其 Listen 方法:
func (lc *ListenConfig) Listen(ctx context.Context, network, address string) (Listener, error) {
addrs, err := DefaultResolver.resolveAddrList(ctx, "listen", network, address, nil)
if err != nil {
return nil, &OpError{Op: "listen", Net: network, Source: nil, Addr: nil, Err: err}
}
sl := &sysListener{
ListenConfig: *lc,
network: network,
address: address,
}
var l Listener
la := addrs.first(isIPv4)
switch la := la.(type) {
case *TCPAddr:
l, err = sl.listenTCP(ctx, la)
case *UnixAddr:
l, err = sl.listenUnix(ctx, la)
default:
return nil, &OpError{Op: "listen", Net: sl.network, Source: nil, Addr: la, Err: &AddrError{Err: "unexpected address type", Addr: address}}
}
if err != nil {
return nil, &OpError{Op: "listen", Net: sl.network, Source: nil, Addr: la, Err: err}
}
return l, nil
}
上述方法利用监听配置、网络协议类型、地址声明了一个 sysListener ,然后调用了其 listenTCP 方法得到 Listener :
func (sl *sysListener) listenTCP(ctx context.Context, laddr *TCPAddr) (*TCPListener, error) {
fd, err := internetSocket(ctx, sl.network, laddr, nil, syscall.SOCK_STREAM, 0, "listen", sl.ListenConfig.Control)
if err != nil {
return nil, err
}
return &TCPListener{fd: fd, lc: sl.ListenConfig}, nil
}
该方法调用了 internetSocket 函数创建一个套接字,sotype 即 socket 的类型,其传入的 sotype 参数为 syscall.SOCK_STREAM ,SOCK_STREAM 提供面向连接的稳定数据传输,即TCP协议。internetSocket 源码如下:
func internetSocket(ctx context.Context, net string, laddr, raddr sockaddr, sotype, proto int, mode string, ctrlFn func(string, string, syscall.RawConn) error) (fd *netFD, err error) {
if (runtime.GOOS == "aix" || runtime.GOOS == "windows" || runtime.GOOS == "openbsd") && mode == "dial" && raddr.isWildcard() {
raddr = raddr.toLocal(net)
}
family, ipv6only := favoriteAddrFamily(net, laddr, raddr, mode)
return socket(ctx, net, family, sotype, proto, ipv6only, laddr, raddr, ctrlFn)
}
socket 源码如下:
func socket(ctx context.Context, net string, family, sotype, proto int, ipv6only bool, laddr, raddr sockaddr, ctrlFn func(string, string, syscall.RawConn) error) (fd *netFD, err error) {
s, err := sysSocket(family, sotype, proto)
...
}
socket 调用了 sysSocket 获得了套接字的描述符:
func sysSocket(family, sotype, proto int) (int, error) {
syscall.ForkLock.RLock()
s, err := socketFunc(family, sotype, proto)
if err == nil {
syscall.CloseOnExec(s)
}
syscall.ForkLock.RUnlock()
if err != nil {
return -1, os.NewSyscallError("socket", err)
}
if err = syscall.SetNonblock(s, true); err != nil {
poll.CloseFunc(s)
return -1, os.NewSyscallError("setnonblock", err)
}
return s, nil
}
sysSocket 调用 socketFunc 取得了描述符。socketFunc 其实是 syscall.Socket :
var (
...
socketFunc func(int, int, int) (int, error) = syscall.Socket
...
)
其源码如下:
func Socket(domain, typ, proto int) (fd int, err error) {
if domain == AF_INET6 && SocketDisableIPv6 {
return -1, EAFNOSUPPORT
}
fd, err = socket(domain, typ, proto)
return
}
socket 函数源码如下,该函数真正进行了 socket 系统调用:
func socket(domain int, typ int, proto int) (fd int, err error) {
r0, _, e1 := rawSyscall(abi.FuncPCABI0(libc_socket_trampoline), uintptr(domain), uintptr(typ), uintptr(proto))
fd = int(r0)
if e1 != 0 {
err = errnoErr(e1)
}
return
}
socket 函数一路将描述符返回,直至 sock_posix.go 下的 socket 函数:
func socket(ctx context.Context, net string, family, sotype, proto int, ipv6only bool, laddr, raddr sockaddr, ctrlFn func(string, string, syscall.RawConn) error) (fd *netFD, err error) {
s, err := sysSocket(family, sotype, proto)
if err != nil {
return nil, err
}
if err = setDefaultSockopts(s, family, sotype, ipv6only); err != nil {
poll.CloseFunc(s)
return nil, err
}
if fd, err = newFD(s, family, sotype, net); err != nil {
poll.CloseFunc(s)
return nil, err
}
if laddr != nil && raddr == nil {
switch sotype {
case syscall.SOCK_STREAM, syscall.SOCK_SEQPACKET:
if err := fd.listenStream(laddr, listenerBacklog(), ctrlFn); err != nil {
fd.Close()
return nil, err
}
return fd, nil
case syscall.SOCK_DGRAM:
if err := fd.listenDatagram(laddr, ctrlFn); err != nil {
fd.Close()
return nil, err
}
return fd, nil
}
}
if err := fd.dial(ctx, laddr, raddr, ctrlFn); err != nil {
fd.Close()
return nil, err
}
return fd, nil
}
socket 函数利用调用 sysSocket 获取的文件描述符创建了一个网络文件句柄 fd ,之后调用该网络文件句柄的 listenStream 方法开始监听到来的数据流:
func (fd *netFD) listenStream(laddr sockaddr, backlog int, ctrlFn func(string, string, syscall.RawConn) error) error {
var err error
if err = setDefaultListenerSockopts(fd.pfd.Sysfd); err != nil {
return err
}
var lsa syscall.Sockaddr
if lsa, err = laddr.sockaddr(fd.family); err != nil {
return err
}
if ctrlFn != nil {
c, err := newRawConn(fd)
if err != nil {
return err
}
if err := ctrlFn(fd.ctrlNetwork(), laddr.String(), c); err != nil {
return err
}
}
if err = syscall.Bind(fd.pfd.Sysfd, lsa); err != nil {
return os.NewSyscallError("bind", err)
}
if err = listenFunc(fd.pfd.Sysfd, backlog); err != nil {
return os.NewSyscallError("listen", err)
}
if err = fd.init(); err != nil {
return err
}
lsa, _ = syscall.Getsockname(fd.pfd.Sysfd)
fd.setAddr(fd.addrFunc()(lsa), nil)
return nil
}
至此,ListenAndServe 完成了套接字的创建,开始对指定的 TCP 地址进行监听。
连接的接收和请求的映射
回到 ListenAndServe 方法:
func (srv *Server) ListenAndServe() error {
...
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}
return srv.Serve(ln)
}
在开启了对 TCP 地址的绑定和监听后,调用了 Server 的 Serve 方法,其源码如下:
func (srv *Server) Serve(l net.Listener) error {
...
for {
rw, err := l.Accept()
if err != nil {
select {
case <-srv.getDoneChan():
return ErrServerClosed
default:
}
if ne, ok := err.(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", err, tempDelay)
time.Sleep(tempDelay)
continue
}
return err
}
connCtx := ctx
if cc := srv.ConnContext; cc != nil {
connCtx = cc(connCtx, rw)
if connCtx == nil {
panic("ConnContext returned nil")
}
}
tempDelay = 0
c := srv.newConn(rw)
c.setState(c.rwc, StateNew, runHooks)
go c.serve(connCtx)
}
}
Listener 的 Accept 方法监听并返回了到来的连接。之后调用 src.newConn 封装为 conn (一个 conn 代表服务器侧的连接),设置一下连接的状态、运行一下钩子函数,之后新开启了一个 goroutine 运行该连接的 serve 方法,并将相应的 context 传入。
func (c *conn) serve(ctx context.Context) {
c.remoteAddr = c.rwc.RemoteAddr().String()
ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
defer func() {
if err := recover(); err != nil && err != ErrAbortHandler {
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, runHooks)
}
}()
if tlsConn, ok := c.rwc.(*tls.Conn); ok {
...
}
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() {
c.setState(c.rwc, StateActive, runHooks)
}
if err != nil {
...
}
req := w.req
if req.expectsContinue() {
if req.ProtoAtLeast(1, 1) && req.ContentLength != 0 {
req.Body = &expectContinueReader{readCloser: req.Body, resp: w}
w.canWriteContinue.setTrue()
}
} 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()
}
serverHandler{c.server}.ServeHTTP(w, w.req)
...
}
}
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)
}
如果在 main 函数中调用 ListenAndServe 时没有指定路径映射的 handler ,则默认使用 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)
}
我们看 DefaultServeMux 的 Handler 方法是如何分发请求的:
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
if r.Method == "CONNECT" {
if u, ok := mux.redirectToPathSlash(r.URL.Host, r.URL.Path, r.URL); ok {
return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
}
return mux.handler(r.Host, r.URL.Path)
}
host := stripHostPort(r.Host)
path := cleanPath(r.URL.Path)
if u, ok := mux.redirectToPathSlash(host, path, r.URL); ok {
return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
}
if path != r.URL.Path {
_, pattern = mux.handler(host, path)
u := &url.URL{Path: path, RawQuery: r.URL.RawQuery}
return RedirectHandler(u.String(), StatusMovedPermanently), pattern
}
return mux.handler(host, r.URL.Path)
}
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
mux.mu.RLock()
defer mux.mu.RUnlock()
if mux.hosts {
h, pattern = mux.match(host + path)
}
if h == nil {
h, pattern = mux.match(path)
}
if h == nil {
h, pattern = NotFoundHandler(), ""
}
return
}
match 真正包含了路径的匹配规则:
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
v, ok := mux.m[path]
if ok {
return v.h, v.pattern
}
for _, e := range mux.es {
if strings.HasPrefix(path, e.pattern) {
return e.h, e.pattern
}
}
return nil, ""
}
至此,可以知道默认的路由方法为:
- 如果路径为
/tree 但没有相应的 handler ,重定向至 /tree/ ; - 如果能精确匹配,则利用此
handler 处理; - 如果不能精确匹配,则匹配以
'/' 结尾的最长路径。
验证 DefaultServeMux 路由规则
建立如下路径处理器:
func main() {
http.Handle("/greetings/", helloHandler("greetings"))
http.Handle("/greetings/hello", helloHandler("hello"))
http.Handle("/greetings/hi", helloHandler("hi"))
http.Handle("/farewell/bye", helloHandler("bye"))
http.Handle("/farewell/good_night", helloHandler("good night"))
http.Handle("/farewell/", helloHandler("farewell"))
http.ListenAndServe(":8080", nil)
}
type helloHandler string
func (h helloHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
writer.Write([]byte(h))
}
- 如果路径为
/tree 但没有相应的 handler ,重定向至 /tree/ ; - 当访问
/greetings/ 或 /greetings/hello 时,能返回相应的字符串 "greetings" "hello" ; - 当访问
/greetings/bye 时,由于不能精确匹配该路径,DefaultServeMux 将调用 /greetings/ 处理请求; - 当访问
/hello 时,由于不能精确匹配该路径,DefaultServeMux 将调用 ’/‘ 处理请求,由于没有注册 '/' 下的处理器,返回 404 page not found 。
|