Go HTTP服务器源码浅析

简单服务器 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)) // 用 helloStr 处理 /hello 请求
	http.ListenAndServe(":8080", nil)
}

type helloHandler string

// Implement http.Handler.
func (h helloHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
	writer.Write([]byte(h))
}

上述代码中,向 http.Handle 方法传入了请求路径和相应处理请求的 handler,请求到达时服务器会根据请求路径找到相应的 handler 处理请求。

http.ResponseWriter 接口

ResponseWriter 接口用于让 handler 构建一个 http 响应,其方法如下:

// 返回将要被 WriteHeader 发送的响应头。
Header() Header

// 将数据写到 Http 连接中。
// 如果调用该方法时 WriteHeader 还未被调用,Write 将先调用 WriteHeader(http.StatusOK)。
Write([]byte) (int, error)

// 该方法发送一个包含状态码 statusCode 的 HTTP 响应头
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 类型,实际调用了 MIMEHeaderAdd 方法:

func (h MIMEHeader) Add(key, value string) {
	// 将 MIME 头部的键值转换为规范格式,如 "accept-encoding" 转换为 "Accept-Encoding"。
	key = CanonicalMIMEHeaderKey(key)
	h[key] = append(h[key], value)
}
Write([]byte)

该方法将数据写到 HTTP 连接中。

WriteHeader(statusCode int)

该方法发送一个包含状态码 statusCode 的 HTTP 响应头。
于 IANA 登记的状态码位于 http/status.go 中,这里仅列出几个常见的状态码:

package http

// HTTP status codes as registered with IANA.
const (
	StatusContinue           = 100 // RFC 7231, 6.2.1
	StatusSwitchingProtocols = 101 // RFC 7231, 6.2.2
	StatusProcessing         = 102 // RFC 2518, 10.1
	StatusEarlyHints         = 103 // RFC 8297

	StatusOK                   = 200 // RFC 7231, 6.3.1
	StatusCreated              = 201 // RFC 7231, 6.3.2
	StatusAccepted             = 202 // RFC 7231, 6.3.3
	StatusNonAuthoritativeInfo = 203 // RFC 7231, 6.3.4
	StatusNoContent            = 204 // RFC 7231, 6.3.5

	StatusMultipleChoices   = 300 // RFC 7231, 6.4.1
	StatusMovedPermanently  = 301 // RFC 7231, 6.4.2
	StatusFound             = 302 // RFC 7231, 6.4.3
	StatusSeeOther          = 303 // RFC 7231, 6.4.4
	StatusNotModified       = 304 // RFC 7232, 4.1

	StatusBadRequest                   = 400 // RFC 7231, 6.5.1
	StatusUnauthorized                 = 401 // RFC 7235, 3.1
	StatusPaymentRequired              = 402 // RFC 7231, 6.5.2
	StatusForbidden                    = 403 // RFC 7231, 6.5.3
	StatusNotFound                     = 404 // RFC 7231, 6.5.4
	StatusMethodNotAllowed             = 405 // RFC 7231, 6.5.5
	StatusNotAcceptable                = 406 // RFC 7231, 6.5.6
	
	StatusInternalServerError           = 500 // RFC 7231, 6.6.1
	StatusNotImplemented                = 501 // RFC 7231, 6.6.2
	StatusBadGateway                    = 502 // RFC 7231, 6.6.3
	...
)

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 包的服务器实现中,传给 handlerResponseWriter 的实际类型是 http 包下的 response ,换言之,response 实现了 ResponseWriter 接口。其结构如下:

// response 代表服务器的 HTTP 响应.
type Response struct {
    Status     string // 例如"200 OK"
    StatusCode int    // 例如200
    Proto      string // 例如"HTTP/1.0"
    ProtoMajor int    // 例如1
    ProtoMinor int    // 例如0
    
    // Header保管头域的键值对。
    // 如果回复中有多个头的键相同,Header中保存为该键对应用逗号分隔串联起来的这些头的值
    // (参见RFC 2616 Section 4.2)
    // 被本结构体中的其他字段复制保管的头(如ContentLength)会从Header中删掉。
    //
    // Header中的键都是规范化的,参见CanonicalHeaderKey函数
    Header Header
    
    // Body代表回复的主体。
    // Client类型和Transport类型会保证Body字段总是非nil的,即使回复没有主体或主体长度为0。
    // 关闭主体是调用者的责任。
    // 如果服务端采用"chunked"传输编码发送的回复,Body字段会自动进行解码。
    Body io.ReadCloser
    
    // ContentLength记录相关内容的长度。
    // 其值为-1表示长度未知(采用chunked传输编码)
    // 除非对应的Request.Method是"HEAD",其值>=0表示可以从Body读取的字节数
    ContentLength int64
    
    // TransferEncoding按从最外到最里的顺序列出传输编码,空切片表示"identity"编码。
    TransferEncoding []string
    
    // Close记录头域是否指定应在读取完主体后关闭连接。(即Connection头)
    // 该值是给客户端的建议,Response.Write方法的ReadResponse函数都不会关闭连接。
    Close bool
    
    // Trailer字段保存和头域相同格式的trailer键值对,和Header字段相同类型
    Trailer Header
    
    // Request是用来获取此回复的请求
    // Request的Body字段是nil(因为已经被用掉了)
    // 这个字段是被Client类型发出请求并获得回复后填充的
    Request *Request
    
    // TLS包含接收到该回复的TLS连接的信息。 对未加密的回复,本字段为nil。
    // 返回的指针是被(同一TLS连接接收到的)回复共享的,不应被修改。
    TLS *tls.ConnectionState
}
Header()

Header 方法将 handlerHeader 拷贝到 chunkWriterheader 中,然后将 handlerHeader 返回。

func (w *response) Header() Header {
	if w.cw.header == nil && w.wroteHeader && !w.cw.wroteHeader {
		// 在逻辑写和物理写之间访问响应头,需要对逻辑写入的状态进行快照。
		// 即若 w.cw 中的 header 并未设置,则拷贝一份放到 w.cw 中。
		// response 的 WriteHeader 方法会将 handlerHeader 拷贝到 w.cw 中。
		// 由于 w.cw 中的 header 仅包内可见,且没有修改方法,
		// 后续 w.cw.header 将免于再被修改。
		w.cw.header = w.handlerHeader.Clone()
	}
	w.calledHeader = true	// 标记 handler 通过 Header 方法访问了 handlerHeader。
	return w.handlerHeader
}
Write([]byte)
func (w *response) Write(data []byte) (n int, err error) {
	return w.write(len(data), data, "")
}

调用了其 write 方法:

// either dataB or dataS is non-zero.
func (w *response) write(lenData int, dataB []byte, dataS string) (n int, err error) {
	// 该连接是否已经被劫持
	// 因为被劫持的连接交由相应的 handler 接管,返回 ErrHijacked
	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
	}

	// canWriteContinue 指示是否能将 100 Continue 写到连接中。
	if w.canWriteContinue.isSet() {
		// 请求体 reader 想要写出 100 Continue 但还未写出。
		// 告诉它不要写出。持有 writeContinueMu 是为了确保当前没有在写出数据。
		w.writeContinueMu.Lock()
		w.canWriteContinue.setFalse()
		w.writeContinueMu.Unlock()
	}

	// 如果还没写入响应码,则将 StatusOK 写入。
	if !w.wroteHeader {
		w.WriteHeader(StatusOK)
	}
	if lenData == 0 {
		return 0, nil
	}
	// 根据状态码判断是否允许写响应体。
	// 当状态码为 100~199, 204, 304 时不允许有响应体。
	if !w.bodyAllowed() {
		return 0, ErrBodyNotAllowed
	}

	w.written += int64(lenData) // 即使最终没有写出,仍计入 written
	// 如果写出的数据大于响应头 Content-Length,则不写出。
	if w.contentLength != -1 && w.written > w.contentLength {
		return 0, ErrContentLength
	}
	if dataB != nil {
		return w.w.Write(dataB)	// 调用 response 的 bufio.Writer 写入数据。
	} else {
		return w.w.WriteString(dataS)
	}
}
WriteHeader(statusCode int)
func (w *response) WriteHeader(code int) {
	// 如果连接被劫持,即连接已被一个 handler 接管,则不写入头部。
	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()	// 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) // 100 <= code <= 999?
	w.wroteHeader = true
	w.status = code

	if w.calledHeader && w.cw.header == nil {
		w.cw.header = w.handlerHeader.Clone()
	}
	// 如果设置了 Content-Length,则将该 header 的值放到 response.contentLength 中
	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 是一个结构体而不是接口。

// Request 代表了一个服务器收到的请求,或一个 client 将要发出的请求。
type Request struct {

    // Method指定HTTP方法(GET、POST、PUT等)。对客户端,""代表GET。
    Method string
    
    // URL在服务端表示被请求的URI,在客户端表示要访问的URL。
    //
    // 在服务端,URL字段是解析请求行的URI(保存在RequestURI字段)得到的,
    // 对大多数请求来说,除了Path和RawQuery之外的字段都是空字符串。
    // (参见RFC 2616, Section 5.1.2)
    //
    // 在客户端,URL的Host字段指定了要连接的服务器,
    // 而Request的Host字段(可选地)指定要发送的HTTP请求的Host头的值。
    URL *url.URL
    
    // 接收到的请求的协议版本。本包生产的Request总是使用HTTP/1.1
    Proto      string // "HTTP/1.0"
    ProtoMajor int    // 1
    ProtoMinor int    // 0
    
    // Header字段用来表示HTTP请求的头域。如果头域(多行键值对格式)为:
    //	accept-encoding: gzip, deflate
    //	Accept-Language: en-us
    //	Connection: keep-alive
    // 则:
    //	Header = map[string][]string{
    //		"Accept-Encoding": {"gzip, deflate"},
    //		"Accept-Language": {"en-us"},
    //		"Connection": {"keep-alive"},
    //	}
    // HTTP规定头域的键名(头名)是大小写敏感的,请求的解析器通过规范化头域的键名来实现这点。
    // 在客户端的请求,可能会被自动添加或重写Header中的特定的头,参见Request.Write方法。
    Header Header
    
    // Body是请求的主体。
    //
    // 在客户端,如果Body是nil表示该请求没有主体买入GET请求。
    // Client的Transport字段会负责调用Body的Close方法。
    //
    // 在服务端,Body字段总是非nil的;但在没有主体时,读取Body会立刻返回EOF。
    // Server会关闭请求的主体,ServeHTTP处理器不需要关闭Body字段。
    Body io.ReadCloser
    
    // ContentLength记录相关内容的长度。
    // 如果为-1,表示长度未知,如果>=0,表示可以从Body字段读取ContentLength字节数据。
    // 在客户端,如果Body非nil而该字段为0,表示不知道Body的长度。
    ContentLength int64
    
    // TransferEncoding按从最外到最里的顺序列出传输编码,空切片表示"identity"编码。
    // 本字段一般会被忽略。当发送或接受请求时,会自动添加或移除"chunked"传输编码。
    TransferEncoding []string
    
    // Close在服务端指定是否在回复请求后关闭连接,在客户端指定是否在发送请求后关闭连接。
    Close bool
    
    // 在服务端,Host指定URL会在其上寻找资源的主机。
    // 根据RFC 2616,该值可以是Host头的值,或者URL自身提供的主机名。
    // Host的格式可以是"host:port"。
    //
    // 在客户端,请求的Host字段(可选地)用来重写请求的Host头。
    // 如过该字段为"",Request.Write方法会使用URL字段的Host。
    Host string
    
    // Form是解析好的表单数据,包括URL字段的query参数和POST或PUT的表单数据。
    // 本字段只有在调用ParseForm后才有效。在客户端,会忽略请求中的本字段而使用Body替代。
    Form url.Values
    
    // PostForm是解析好的POST或PUT的表单数据。
    // 本字段只有在调用ParseForm后才有效。在客户端,会忽略请求中的本字段而使用Body替代。
    PostForm url.Values
    
    // MultipartForm是解析好的多部件表单,包括上传的文件。
    // 本字段只有在调用ParseMultipartForm后才有效。
    // 在客户端,会忽略请求中的本字段而使用Body替代。
    MultipartForm *multipart.Form
    
    // Trailer指定了会在请求主体之后发送的额外的头域。
    //
    // 在服务端,Trailer字段必须初始化为只有trailer键,所有键都对应nil值。
    // (客户端会声明哪些trailer会发送)
    // 在处理器从Body读取时,不能使用本字段。
    // 在从Body的读取返回EOF后,Trailer字段会被更新完毕并包含非nil的值。
    // (如果客户端发送了这些键值对),此时才可以访问本字段。
    //
    // 在客户端,Trail必须初始化为一个包含将要发送的键值对的映射。(值可以是nil或其终值)
    // ContentLength字段必须是0或-1,以启用"chunked"传输编码发送请求。
    // 在开始发送请求后,Trailer可以在读取请求主体期间被修改,
    // 一旦请求主体返回EOF,调用者就不可再修改Trailer。
    //
    // 很少有HTTP客户端、服务端或代理支持HTTP trailer。
    Trailer Header
    
    // RemoteAddr允许HTTP服务器和其他软件记录该请求的来源地址,一般用于日志。
    // 本字段不是ReadRequest函数填写的,也没有定义格式。
    // 本包的HTTP服务器会在调用处理器之前设置RemoteAddr为"IP:port"格式的地址。
    // 客户端会忽略请求中的RemoteAddr字段。
    RemoteAddr string
    
    // RequestURI是被客户端发送到服务端的请求的请求行中未修改的请求URI
    // (参见RFC 2616, Section 5.1)
    // 一般应使用URI字段,在客户端设置请求的本字段会导致错误。
    RequestURI string
    
    // TLS字段允许HTTP服务器和其他软件记录接收到该请求的TLS连接的信息
    // 本字段不是ReadRequest函数填写的。
    // 对启用了TLS的连接,本包的HTTP服务器会在调用处理器之前设置TLS字段,否则将设TLS为nil。
    // 客户端会忽略请求中的TLS字段。
    TLS *tls.ConnectionState
}

http.Handle

在声明对一个路径的处理器时,调用了:

http.Handle("/", helloHandler(handler))

其源码如下:

// Handle 函数对给定路径的处理器在 DefaultServeMux 中进行注册。
func Handle(pattern string, handler Handler) { DefaultServeMux.Handle(pattern, handler) }

该函数在 DefaultServeMux 中对路径和处理器进行注册,该函数将 patternhandler 传给了 DefaultServeMuxHandle 方法。
ServeMux 是 HTTP 请求的多路转接器(multiplexer)。它会将每一个接收的请求的 URL 与一个注册模式的列表进行匹配,并调用和 URL 最匹配的模式的处理器。
DefaultServeMux.Handle 实际上将模式放到一个哈希表中;如果路径以 '/' 结尾,则额外将其放到一个根据路径长度由长到短排序的数组中。例如,在匹配时如果不存在 /greetings/meet 路径的处理器,则依次尝试查找 /greetings// 路径的处理器。

// Handle 为一个模式注册处理器。
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")
	}
	// m 是由 pattern 到 muxEntry 的映射,若已经存在则 panic。
	// muxEntry 是由处理器 Handler 和 pattern 组成的结构体。
	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
	// 如果路径以 '/' 结尾,则将新的 muxEntry 插入到 es 中
	if pattern[len(pattern)-1] == '/' {
		mux.es = appendSorted(mux.es, e)	
	}

	if pattern[0] != '/' {
		mux.hosts = true	// hosts:是否有模式指示了主机名
	}
}

例如,以下三个 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)

其源码如下:

// ListenAndServe 监听给出的 TCP 地址 addr,然后对到来的连接调用 Serve 方法,
// Serve 方法再利用入参 handler 映射来自该连接的请求。
// 入参 handler 用于对路径进行映射,如果设置为 nil,则 DefaultServeMux 将用于对路径进行映射。
func ListenAndServe(addr string, handler Handler) error {
	server := &Server{Addr: addr, Handler: handler}
	return server.ListenAndServe()
}

ListenAndServe 函数利用 TCP 地址 addr 和地址映射 handler 创建了一个 Server,然后调用了其 ListenAndServe 方法。

// ListenAndServe 方法监听 TCP 地址然后调用 Serve 方法处理到来的连接。
// 接受的连接被配置为开启 TCP 保活。
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 源码如下:

// Listen("tcp", ":8080")
func Listen(network, address string) (Listener, error) {
	var lc ListenConfig
	return lc.Listen(context.Background(), network, address)
}

其声明了一个 ListenConfig 后 调用其 Listen 方法:

// Listen announces on the local network address.、
func (lc *ListenConfig) Listen(ctx context.Context, network, address string) (Listener, error) {
	addrs, err := DefaultResolver.resolveAddrList(ctx, "listen", network, address, nil)	// 根据动作、协议、地址等解析返回 addrList 结构
	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)	// 找到第一个包含 IPv4 地址的 Addr
	switch la := la.(type) {
	case *TCPAddr:
		l, err = sl.listenTCP(ctx, la)	// 监听 TCP
	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} // l is non-nil interface containing nil pointer
	}
	return l, nil
}

上述方法利用监听配置、网络协议类型、地址声明了一个 sysListener,然后调用了其 listenTCP 方法得到 Listener

// 调用了 internetSocket 创建套接字,将其封装为一个 TCPListener 后返回。
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_STREAMSOCK_STREAM 提供面向连接的稳定数据传输,即TCP协议。internetSocket 源码如下:

// internetSocket 处理了一些特殊情况然后调用了 socket 创建套接字并返回之。
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() {
		// 将零地址映射为本地系统地址(127.0.0.1 或 ::1)
		raddr = raddr.toLocal(net)
	}
	family, ipv6only := favoriteAddrFamily(net, laddr, raddr, mode)	// 返回 syscall.AF_INET6,即 TCP/IP – IPv6 协议簇
	return socket(ctx, net, family, sotype, proto, ipv6only, laddr, raddr, ctrlFn)
}

socket 源码如下:

// socket 函数返回一个网络文件描述符,该描述符可以使用网络轮询器 poller 进行异步 I/O
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)	// 返回 s 即描述符的编号
	...
}

socket 调用了 sysSocket 获得了套接字的描述符:

// 包装了 socket 系统调用,并将返回的文件描述符标记为非阻塞的,并在 exec 系统调用时将其关闭。
func sysSocket(family, sotype, proto int) (int, error) {
	syscall.ForkLock.RLock()
	s, err := socketFunc(family, sotype, proto)	// 调用 socketFunc 取得了描述符
	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 (
	...
	// socket 系统调用的占位符
	socketFunc        func(int, int, int) (int, error)  = syscall.Socket
	...
)

其源码如下:

// 检查是否创建 AF_INET6 协议簇但套接字禁用了 IPv6.
func Socket(domain, typ, proto int) (fd int, err error) {
	if domain == AF_INET6 && SocketDisableIPv6 {
		return -1, EAFNOSUPPORT
	}
	// 调用 socket 创建套接字
	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))	// libc_socket_trampoline 是真正的 socket 系统调用
	fd = int(r0)
	if e1 != 0 {
		err = errnoErr(e1)
	}
	return
}

socket 函数一路将描述符返回,直至 sock_posix.go 下的 socket 函数:

// socket 函数返回一个网络文件描述符,该描述符可以使用网络轮询器 poller 进行异步 I/O
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)	// 返回 s 即描述符的编号
	if err != nil {
		return nil, err
	}
	// 设置默认套接字选项。
	if err = setDefaultSockopts(s, family, sotype, ipv6only); err != nil {
		// 如果设置失败,则调用 close 系统调用关闭文件描述符。
		poll.CloseFunc(s)
		return nil, err
	}
	// 利用套接字编号、协议簇、协议、网络类型创建一个描述符。
	// newFD(s, syscall.AF_INET6, syscall.SOCK_STREAM, "tcp")
	if fd, err = newFD(s, family, sotype, net); err != nil {
		// 如果创建失败,则调用 close 系统调用关闭文件描述符。
		poll.CloseFunc(s)
		return nil, err
	}

	// laddr 和 raddr 意即 local address 和 remote address
	// 当本地地址不为空且远程地址为空时
	if laddr != nil && raddr == nil {
		switch sotype {
		case syscall.SOCK_STREAM, syscall.SOCK_SEQPACKET:
			// listenStream 对描述符进行了设置、将描述符与监听地址进行了绑定、开启对地址的监听。
			// 另外,backlog 是保存客户端请求的队列长度。
			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
	}
	// 如果传入了 ctrlFn,则先调用一下
	if ctrlFn != nil {
		c, err := newRawConn(fd)
		if err != nil {
			return err
		}
		if err := ctrlFn(fd.ctrlNetwork(), laddr.String(), c); err != nil {
			return err
		}
	}
	// 将句柄与地址绑定,即 bind 系统调用
	if err = syscall.Bind(fd.pfd.Sysfd, lsa); err != nil {
		return os.NewSyscallError("bind", err)
	}
	// 开启监听,listenFunc 实际是 syscall.Listen,即 listen 系统调用
	if err = listenFunc(fd.pfd.Sysfd, backlog); err != nil {
		return os.NewSyscallError("listen", err)
	}
	// 对句柄进行初始化
	if err = fd.init(); err != nil {
		return err
	}
	// Getsockname 通过 getsockname 系统调用获取一个套接字的名字
	// 其利用套接字描述符获取该套接字的名字
	// 对于 TCP 连接的情况,如果不进行 bind 指定 IP 和端口,那么调用 connect 连接成功后,
	// 使用 getsockname 可以正确获得当前正在通信的 socket 的 IP 和端口地址。
	// 这里通过 Getsockname 和 setAddr 方法设置了 fd 的地址。
	lsa, _ = syscall.Getsockname(fd.pfd.Sysfd)
	fd.setAddr(fd.addrFunc()(lsa), nil)
	return nil
}

至此,ListenAndServe 完成了套接字的创建,开始对指定的 TCP 地址进行监听。

连接的接收和请求的映射

回到 ListenAndServe 方法:

// ListenAndServe 方法监听 TCP 地址然后调用 Serve 方法处理到来的连接。
// 接受的连接被配置为开启 TCP keep-alive。
func (srv *Server) ListenAndServe() error {
	...
	ln, err := net.Listen("tcp", addr)	// 由上文可知这里开启了对 TCP 地址的监听,该函数返回了一个用于监听面向流协议的 Listener.
	if err != nil {
		return err
	}
	return srv.Serve(ln)	// Serve 方法接收到来的连接并处理请求
}

在开启了对 TCP 地址的绑定和监听后,调用了 ServerServe 方法,其源码如下:

// Serve 方法从 listener 中接收到来的连接,并为每个连接创建一个 goroutine 处理请求
// 负责服务的 goroutine 读取请求并调用 srv.Handler 回复请求。
func (srv *Server) Serve(l net.Listener) error {
	...
	// 从该连接不断地读取请求
	for {
		rw, err := l.Accept()	// 调用 Accept 方法接收到来的连接,返回的 rw 是 Conn 对象,代表了一个面向流的连接
		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) // before Serve can return
		go c.serve(connCtx)	// 开启新的 goroutine 处理请求
	}
}

ListenerAccept 方法监听并返回了到来的连接。之后调用 src.newConn 封装为 conn (一个 conn 代表服务器侧的连接),设置一下连接的状态、运行一下钩子函数,之后新开启了一个 goroutine 运行该连接的 serve 方法,并将相应的 context 传入。

// Serve a new connection.
func (c *conn) serve(ctx context.Context) {
	c.remoteAddr = c.rwc.RemoteAddr().String()	// 远程地址的 IP 及端口号
	ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
	defer func() {
		// 从 panic 中恢复,即使具体某个 handler 处理请求时发生 panic,也不会导致整个服务器退出
		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 {
		...
	}

	// HTTP/1.x from here on.
	// 创建了一个子 context
	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 {
			// 错误处理,如请求太大、请求使用了不支持的编码类型等。
			...
		}

		// Expect 100 Continue support
		req := w.req
		if req.expectsContinue() {
			if req.ProtoAtLeast(1, 1) && req.ContentLength != 0 {
				// 包装请求体的 reader: req.Body
				// 将请求体 req.Body 传给 expectContinueReader,可以让 expectContinueReader 写出 100 Cotinue
				req.Body = &expectContinueReader{readCloser: req.Body, resp: w}
				// 指示可以将 100 Continue 写出。
				// 实际 100 Continue 由 expectContinueReader 写出
				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()
		}
		
		// 尽管 HTTP 管线可以同时处理多个请求,这里并没有实现此功能,而是就在此 goroutine 运行了请求处理器。
		// ServeHTTP 完成了地址到处理器的映射,并调用处理器进行处理。
		serverHandler{c.server}.ServeHTTP(w, w.req)
		// 后续进行了一些收尾工作
		...
	}
}

ServeHTTP 完成了地址到处理器的映射,并调用处理器进行处理。其实现如下:

// serverHandler 找到映射请求的 handler 并将请求转发给它。
func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
	handler := sh.srv.Handler	// 该 Handler 负责映射请求。
	// 如果没有设置 handler,则用默认的路由规则。
	if handler == nil {
		handler = DefaultServeMux
	}
	// 如果要处理的是获取选项的请求,则用 globalOptionsHandler 处理请求。
	if req.RequestURI == "*" && req.Method == "OPTIONS" {
		handler = globalOptionsHandler{}
	}
	
	...

	handler.ServeHTTP(rw, req)	// 利用该 handler 映射请求
}

如果在 main 函数中调用 ListenAndServe 时没有指定路径映射的 handler,则默认使用 DefaultServeMux,其 ServeHTTP 方法如下:

// ServeHTTP 将请求分发到跟请求 URL 最为匹配的模式的处理器
func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
	// 如果 URI 为 '*',则直接关闭连接并返回。
	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)	// 处理请求
}

我们看 DefaultServeMuxHandler 方法是如何分发请求的:

// Handler 返回给出路径的处理器,它参考了请求的方法、请求主机名、URL。
// 如果 path 不是在标准形式下,返回的将是一个内部生成的 handler,
// 该 handler 将请求重定向到标准形式的路径下。
// 如果主机名包含一个端口号,他在匹配 handler 时将被忽略。
// 如果没有符合请求路径的 handler,将返回一个 `page not found' handler。
// 该 Handler 方法主要对请求路径进行标准化,随后调用其 handler 方法进行映射。
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {

	// CONNECT requests are not canonicalized.
	if r.Method == "CONNECT" {
		// If r.URL.Path is /tree and its handler is not registered,
		// the /tree -> /tree/ redirect applies to CONNECT requests
		// but the path canonicalization does not.
		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)

	// 如果路径为 /tree 但没有相应的 handler,重定向至 /tree/.
	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()

	// 特定于主机的模式优先于通用模式
	// 本地测试下 host 为 localhost
	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 真正包含了路径的匹配规则:

// 从保存 handler 的哈希表中找出一个符合该路径的 handler。
// 最具体的模式(最长)将被返回。
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
	// 先查找完全符合的 handler。
	v, ok := mux.m[path]
	if ok {
		return v.h, v.pattern
	}

	// 查找最长的匹配路径。mux.es 中包含了所有以 '/' 结尾的模式。
	// 比如在注册时加入 '/hello/' 和 '/hi/',
	// 则 mux.es 中将保存为 ['/hello/', '/hi/']。
	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
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: http文件服务器是一款基于Python语言编写的文件共享服务器工具,可以用于在局域网内快速共享文件。该工具源码提供了完整的软件架构和程序代码,使用者可以通过对源代码进行二次开发或定制化实现自己的需求和功能。 该工具源码实现了基于HTTP协议的文件传输功能,可以通过浏览器访问文件服务器,方便地上传和下载文件。此外,该工具源码还提供了用户认证和权限控制功能,管理员可以管理文件上传和下载的权限,避免数据泄露和不当使用。 此外,该工具还提供了丰富的配置选项和模块化设计,可以方便地管理软件的设置和功能,适应不同的操作系统和网络环境。同时,该工具源码也提供了详细的使用说明和文档,让使用者可以轻松上手并快速部署文件服务器。 总的来说,http文件服务器工具源码是一款实用的文件共享工具,具有灵活的定制功能和丰富的扩展选项,适用于广泛的应用场景,包括企业文件共享、家庭网络文件传输等。 ### 回答2: HTTP文件服务器工具源码是一种开源的工具,可以帮助用户快速搭建一个基于HTTP协议的文件服务器。该工具具有易用性、稳定性、可扩展性和安全性等优点,可以满足不同用户的需求。 该工具的源码由多个文件组成,其中包括主程序文件、配置文件、日志文件、路由文件、静态文件、模板文件等。这些文件通过相互调用和协作,实现了HTTP服务器的各种功能。例如,主程序文件负责启动HTTP服务器并处理用户请求;配置文件存储了HTTP服务器的各项参数,包括端口号、路径、用户名、密码等;路由文件则定义了不同的URL路径和对应的处理函数;静态文件和模板文件则提供了HTTP服务器的静态文件和动态页面。 HTTP文件服务器工具源码的使用方法非常简单,用户只需要下载源码并按照指导进行配置和运行即可。同时,该工具还支持自定义扩展,用户可以基于源码开发自己的功能模块,从而实现更加个性化的HTTP服务器。在使用过程中,用户还可以根据需要对源码进行修改和优化,以满足自己的需求。 总之,HTTP文件服务器工具源码是一款优秀的开源工具,可以帮助用户快速搭建自己的HTTP文件服务器,并且具有高度的可定制化和扩展性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值