net/http 包实现 HTTP Server

Go 中,实现一个最简单的 http server 非常容易,代码如下:

  1. package main
  2. import (
  3. "fmt"
  4. "net/http"
  5. )
  6.  
  7. func IndexHandlers(w http.ResponseWriter, r *http.Request){
  8. fmt.Fprintln(w, "hello, world")
  9. }
  10.  
  11. func main (){
  12. http.HandleFunc("/", IndexHandlers)
  13. err := http.ListenAndServe("127.0.0.1:8088", nil)
  14. if err != nil {
  15. fmt.Printf("listen error:[%v]", err.Error())
  16. }
  17. }

  

HTTP

通过上面这个简单的例子,来一点一点学习 Go 的 net/http 实现的 web 服务的原理

理解 HTTP 相关的网络应用,主要关注两个地方:客户端(client)和服务端(server)

两者的交互主要是 client 的 request 以及 server 的 response,主要就在于如何接受 client 的 request 并向 client 返回 response

接收 request 的过程中,最重要的莫过于路由(router),即实现一个 Multiplexer(多路复用的路由系统),Multiplexer 路由系统的目的就是为了找到处理器函数(handler),handler 将对 request 进行处理,同时构建 response

流程如下:

  1. Clinet -> Requests -> [Multiplexer(router) -> handler -> Response -> Clinet

  

理解 Go 中的 http 服务,最重要的就是要理解 Multiplexer 和 handler,下面对 handler 的几个重要概念进行说明

  • handler 函数:具有 func(w http.ResponseWriter, r *http.Requests) 签名的函数
  • handler 处理器(函数):经过 HandlerFunc() 函数包装处理后的 handler 函数,就成为实现了 ServeHTTP() 接口方法的函数,调用 handler 处理器的 ServeHTTP() 方法时,即调用 handler 函数本身
  • handler 对象:实现了 Handler 接口中 ServeHTTP() 方法的结构

Handler 接口

  1. type Handler interface {
  2. ServeHTTP(ResponseWriter, *Request)
  3. }

Go 没有继承,类多态的方式可以通过接口实现,所谓接口则是定义了声明了函数的签名,任何结构只要实现了与接口函数签名相同的方式,就等同于实现了接口,Go 的 http 服务都是基于 handler 进行处理

任何结构体,只要实现了 ServeHTTP() 方法,这个结构就可以称之为 handler 对象,ServeMux 会使用 handler 并调用其 ServeHTTP() 方法处理请求并返回响应

ServeMux 结构体

Go 中的 Multiplexer 是基于 ServeMux 结构体,DefaultServeMux是ServeMux的一个实例

Go 中既可以使用内置的 DefaultServeMux(默认的路由系统),也可以自定义

ServeMux 结构中最重要的字段是 m,这是一个 map,key是一些 url 模式,value 则是一个 muxEntry 结构体,定义存储了具体的 url 模式 和 Handler 接口

ServeMux 同时也实现了 Handler 接口,不过 ServeMux 的 ServeHTTP() 方法不是用来处理 request 和 构建 response 的,而是用来找到路由注册的 hanlder() 函数

ServeMux 的源码

  1. type ServeMux struct {
  2. mu sync.RWMutex
  3. m map[string]muxEntry
  4. hosts bool
  5. }
  6.  
  7. type muxEntry struct {
  8. explicit bool
  9. h Handler
  10. pattern string
  11. }

Server

除了 ServeMux 和 Handler,还有一个 Server 结构体需要了解,从 http.ListenAndServe 的源码可以看出,它创建了一个 server 对象,并调用 server 对象的 ListenAndServe() 方法

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

  

查看 Server 的结构体如下:

  1. type Server struct {
  2. Addr string
  3. Handler Handler
  4. ReadTimeout time.Duration
  5. WriteTimeout time.Duration
  6. TLSConfig *tls.Config
  7.  
  8. MaxHeaderBytes int
  9.  
  10. TLSNextProto map[string]func(*Server, *tls.Conn, Handler)
  11.  
  12. ConnState func(net.Conn, ConnState)
  13. ErrorLog *log.Logger
  14. disableKeepAlives int32 nextProtoOnce sync.Once
  15. nextProtoErr error
  16. }

Server 结构存储了服务器处理请求常见的字段,其中 Handler 字段也保留了 Handler 接口类型,如果 Server 接口没有提供 Handler 接口类型,那么会使用内置的 DefaultServeMux(默认的路由系统),后面再做分析

创建 HTTP 服务

创建一个 http 服务,大致需要经历两个过程,首先需要注册路由,即提供 url 模式 和 handler 函数的映射,其次就是实例化一个 Server 的对象,并开启对客户端的监听

再看 http 服务中的代码:

  1. //注册路由
  2. http.HandleFunc("/", indexHandler)
  3.  
  4. //实例化 Server 类型,并开启对客户端的监听
  5. http.ListenAndServe("127.0.0.1:8000", nil)
  6.  
  7. 或者:
  8.  
  9. server := &Server{Addr: addr, Handler: handler}
  10. server.ListenAndServe()

  

注册路由,开启服务监听,处理http请求的过程 源码分析

net/http 包提供的注册路由的 api 很简单,http.HandleFunc 选择了 DefaultServeMux 作为 Multiplexer

  1. func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
  2. DefaultServeMux.HandleFunc(pattern, handler)
  3. }

  

DefaultServeMux 是 ServeMux 结构体的实例,当然 http 包也提供了 NewServeMux() 方法创建一个 ServeMux 实例,默认则创建一个 DefaultServeMux

  1. // NewServeMux allocates and returns a new ServeMux.
  2. func NewServeMux() *ServeMux { return new(ServeMux) }
  3.  
  4. // DefaultServeMux is the default ServeMux used by Serve.
  5. var DefaultServeMux = &defaultServeMux
  6.  
  7. var defaultServeMux ServeMux

  

DefaultServeMux 的 HandlerFunc(parrern, handler) 方法实际是定义在 ServeMux 下的

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

  

HandlerFunc() 是一个函数,同时实现了 Handler 接口的 ServeHTTP() 方法,使用 HandlerFunc() 函数包装路由定义的 IndexHandlers() 函数,其目的就是为了让这个函数也实现 ServeHTTP() 方法,从而实现 Handler 接口,即转变成一个 handler 处理器(函数)

发生函数类型转换的源码:

  1. // HandleFunc registers the handler function for the given pattern.
  2. func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
  3. if handler == nil {
  4. panic("http: nil handler")
  5. }
  6. mux.Handle(pattern, HandlerFunc(handler))
  7. }

HandlerFunc 函数实现 Handler 接口的源码

  1. type HandlerFunc func(ResponseWriter, *Request)
  2.  
  3. // ServeHTTP calls f(w, r).
  4. func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
  5. f(w, r)
  6. }

  

最开始写的例子中

  1. http.HandleFunc("/",Indexhandler)

这样 IndexHandler() 函数实现了 Handler 接口,接下来,ServeMux 的 Handle() 方法将会对 pattern 和 IndexHandler() 函数做一个 map 映射

Handler() 函数的主要目的在于把 IndexHandler() 函数 和 pattern 模式绑定到 map[string]muxEntry 这样一个 map

  1. // Handle registers the handler for the given pattern.
  2. // If a handler already exists for pattern, Handle panics.
  3. func (mux *ServeMux) Handle(pattern string, handler Handler) {
  4. mux.mu.Lock()
  5. defer mux.mu.Unlock()
  6.  
  7. if pattern == "" {
  8. panic("http: invalid pattern " + pattern)
  9. }
  10. if handler == nil {
  11. panic("http: nil handler")
  12. }
  13. if mux.m[pattern].explicit {
  14. panic("http: multiple registrations for " + pattern)
  15. }
  16.  
  17. if mux.m == nil {
  18. mux.m = make(map[string]muxEntry)
  19. }
  20. mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern}
  21.  
  22. if pattern[0] != '/' {
  23. mux.hosts = true
  24. }
  25.  
  26. // Helpful behavior:
  27. // If pattern is /tree/, insert an implicit permanent redirect for /tree.
  28. // It can be overridden by an explicit registration.
  29. n := len(pattern)
  30. if n > 0 && pattern[n-1] == '/' && !mux.m[pattern[0:n-1]].explicit {
  31. // If pattern contains a host name, strip it and use remaining
  32. // path for redirect.
  33. path := pattern
  34. if pattern[0] != '/' {
  35. // In pattern, at least the last character is a '/', so
  36. // strings.Index can't be -1.
  37. path = pattern[strings.Index(pattern, "/"):]
  38. }
  39. url := &url.URL{Path: path}
  40. mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(url.String(), StatusMovedPermanently), pattern: pattern}
  41. }
  42. }

此时,pattern 和 IndexHandler() 的路由注册完成,接下来就是如何开启 Server 的监听,以接收客户端的请求

注册好路由之后,启动 web 服务还需要开启服务器监听,http 包中的 ListenAndServe() 方法中可以看到创建一个 Server 类型对象,并调用了 Server 类型对象的同名方法

  1. func ListenAndServe(addr string, handler Handler) error {
  2. server := &Server{Addr: addr, Handler: handler}
  3. return server.ListenAndServe()
  4. }
  5. // ListenAndServe listens on the TCP network address srv.Addr and then
  6. // calls Serve to handle requests on incoming connections.
  7. // Accepted connections are configured to enable TCP keep-alives.
  8. // If srv.Addr is blank, ":http" is used.
  9. // ListenAndServe always returns a non-nil error.
  10. func (srv *Server) ListenAndServe() error {
  11. addr := srv.Addr
  12. if addr == "" {
  13. addr = ":http"
  14. }
  15. ln, err := net.Listen("tcp", addr)
  16. if err != nil {
  17. return err
  18. }
  19. return srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})
  20. }

Server 的 ListenAndServe() 方法中,会初始化监听地址 Addr,同时调用 Listen() 方法设置监听,最后将监听的 TCP 对象传入 Serve() 方法

  1. // Serve accepts incoming connections on the Listener l, creating a
  2. // new service goroutine for each. The service goroutines read requests and
  3. // then call srv.Handler to reply to them.
  4. //
  5. // For HTTP/2 support, srv.TLSConfig should be initialized to the
  6. // provided listener's TLS Config before calling Serve. If
  7. // srv.TLSConfig is non-nil and doesn't include the string "h2" in
  8. // Config.NextProtos, HTTP/2 support is not enabled.
  9. //
  10. // Serve always returns a non-nil error. After Shutdown or Close, the
  11. // returned error is ErrServerClosed.
  12. func (srv *Server) Serve(l net.Listener) error {
  13. defer l.Close()
  14. if fn := testHookServerServe; fn != nil {
  15. fn(srv, l)
  16. }
  17. var tempDelay time.Duration // how long to sleep on accept failure
  18.  
  19. if err := srv.setupHTTP2_Serve(); err != nil {
  20. return err
  21. }
  22.  
  23. srv.trackListener(l, true)
  24. defer srv.trackListener(l, false)
  25.  
  26. baseCtx := context.Background() // base is always background, per Issue 16220
  27. ctx := context.WithValue(baseCtx, ServerContextKey, srv)
  28. for {
  29. rw, e := l.Accept()
  30. if e != nil {
  31. select {
  32. case <-srv.getDoneChan():
  33. return ErrServerClosed
  34. default:
  35. }
  36. if ne, ok := e.(net.Error); ok && ne.Temporary() {
  37. if tempDelay == 0 {
  38. tempDelay = 5 * time.Millisecond
  39. } else {
  40. tempDelay *= 2
  41. }
  42. if max := 1 * time.Second; tempDelay > max {
  43. tempDelay = max
  44. }
  45. srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)
  46. time.Sleep(tempDelay)
  47. continue
  48. }
  49. return e
  50. }
  51. tempDelay = 0
  52. c := srv.newConn(rw)
  53. c.setState(c.rwc, StateNew) // before Serve can return
  54. go c.serve(ctx)
  55. }
  56. }

监听开启之后,一旦有客户端请求过来,Go 就开启一个协程处理请求,主要逻辑都在 Serve() 方法中

Serve() 方法比较长,其主要职能就是,创建一个上下文对象,然后调用 Listener 的 Accept() 方法用来获取连接数据并使用 newConn() 方法创建连接对象,最后使用 goroutine 协程的方式处理连接请求,因为每一个连接都开启了一个协程,请求的上下文都不同,同时又保证了 Go 的高并发

Serve()方法的源码:

使用 defer 定义了函数退出时,连接关闭相关的处理,然后就是读取连接的网络数据,并处理读取完毕时候的状态,接下来就是调用 serverHandler{c.server}.ServeHTTP(w, w.req) 方法处理请求了,最后就是请求处理完毕的逻辑

  1. // Serve a new connection.
  2. func (c *conn) serve(ctx context.Context) {
  3. c.remoteAddr = c.rwc.RemoteAddr().String()
  4. ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
  5. defer func() {
  6. if err := recover(); err != nil && err != ErrAbortHandler {
  7. const size = 64 << 10
  8. buf := make([]byte, size)
  9. buf = buf[:runtime.Stack(buf, false)]
  10. c.server.logf("http: panic serving %v: %v\n%s", c.remoteAddr, err, buf)
  11. }
  12. if !c.hijacked() {
  13. c.close()
  14. c.setState(c.rwc, StateClosed)
  15. }
  16. }()
  17.  
  18. if tlsConn, ok := c.rwc.(*tls.Conn); ok {
  19. if d := c.server.ReadTimeout; d != 0 {
  20. c.rwc.SetReadDeadline(time.Now().Add(d))
  21. }
  22. if d := c.server.WriteTimeout; d != 0 {
  23. c.rwc.SetWriteDeadline(time.Now().Add(d))
  24. }
  25. if err := tlsConn.Handshake(); err != nil {
  26. c.server.logf("http: TLS handshake error from %s: %v", c.rwc.RemoteAddr(), err)
  27. return
  28. }
  29. c.tlsState = new(tls.ConnectionState)
  30. *c.tlsState = tlsConn.ConnectionState()
  31. if proto := c.tlsState.NegotiatedProtocol; validNPN(proto) {
  32. if fn := c.server.TLSNextProto[proto]; fn != nil {
  33. h := initNPNRequest{tlsConn, serverHandler{c.server}}
  34. fn(c.server, tlsConn, h)
  35. }
  36. return
  37. }
  38. }
  39.  
  40. // HTTP/1.x from here on.
  41.  
  42. ctx, cancelCtx := context.WithCancel(ctx)
  43. c.cancelCtx = cancelCtx
  44. defer cancelCtx()
  45.  
  46. c.r = &connReader{conn: c}
  47. c.bufr = newBufioReader(c.r)
  48. c.bufw = newBufioWriterSize(checkConnErrorWriter{c}, 4<<10)
  49.  
  50. for {
  51. w, err := c.readRequest(ctx)
  52. if c.r.remain != c.server.initialReadLimitSize() {
  53. // If we read any bytes off the wire, we're active.
  54. c.setState(c.rwc, StateActive)
  55. }
  56. if err != nil {
  57. const errorHeaders = "\r\nContent-Type: text/plain; charset=utf-8\r\nConnection: close\r\n\r\n"
  58.  
  59. if err == errTooLarge {
  60. // Their HTTP client may or may not be
  61. // able to read this if we're
  62. // responding to them and hanging up
  63. // while they're still writing their
  64. // request. Undefined behavior.
  65. const publicErr = "431 Request Header Fields Too Large"
  66. fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+publicErr)
  67. c.closeWriteAndWait()
  68. return
  69. }
  70. if isCommonNetReadError(err) {
  71. return // don't reply
  72. }
  73.  
  74. publicErr := "400 Bad Request"
  75. if v, ok := err.(badRequestError); ok {
  76. publicErr = publicErr + ": " + string(v)
  77. }
  78.  
  79. fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+publicErr)
  80. return
  81. }
  82.  
  83. // Expect 100 Continue support
  84. req := w.req
  85. if req.expectsContinue() {
  86. if req.ProtoAtLeast(1, 1) && req.ContentLength != 0 {
  87. // Wrap the Body reader with one that replies on the connection
  88. req.Body = &expectContinueReader{readCloser: req.Body, resp: w}
  89. }
  90. } else if req.Header.get("Expect") != "" {
  91. w.sendExpectationFailed()
  92. return
  93. }
  94.  
  95. c.curReq.Store(w)
  96.  
  97. if requestBodyRemains(req.Body) {
  98. registerOnHitEOF(req.Body, w.conn.r.startBackgroundRead)
  99. } else {
  100. if w.conn.bufr.Buffered() > 0 {
  101. w.conn.r.closeNotifyFromPipelinedRequest()
  102. }
  103. w.conn.r.startBackgroundRead()
  104. }
  105.  
  106. // HTTP cannot have multiple simultaneous active requests.[*]
  107. // Until the server replies to this request, it can't read another,
  108. // so we might as well run the handler in this goroutine.
  109. // [*] Not strictly true: HTTP pipelining. We could let them all process
  110. // in parallel even if their responses need to be serialized.
  111. // But we're not going to implement HTTP pipelining because it
  112. // was never deployed in the wild and the answer is HTTP/2.
  113. serverHandler{c.server}.ServeHTTP(w, w.req)
  114. w.cancelCtx()
  115. if c.hijacked() {
  116. return
  117. }
  118. w.finishRequest()
  119. if !w.shouldReuseConnection() {
  120. if w.requestBodyLimitHit || w.closedRequestBodyEarly() {
  121. c.closeWriteAndWait()
  122. }
  123. return
  124. }
  125. c.setState(c.rwc, StateIdle)
  126. c.curReq.Store((*response)(nil))
  127.  
  128. if !w.conn.server.doKeepAlives() {
  129. // We're in shutdown mode. We might've replied
  130. // to the user without "Connection: close" and
  131. // they might think they can send another
  132. // request, but such is life with HTTP/1.1.
  133. return
  134. }
  135.  
  136. if d := c.server.idleTimeout(); d != 0 {
  137. c.rwc.SetReadDeadline(time.Now().Add(d))
  138. if _, err := c.bufr.Peek(4); err != nil {
  139. return
  140. }
  141. }
  142. c.rwc.SetReadDeadline(time.Time{})
  143. }
  144. }

serverHandler 是一个重要的结构体类型,它只有一个字段,即 Server 结构体类型,同时 serverHandler 实现了 Handler 接口,并在该接口方法中做了一个重要的事情,初始化 Multiplexer 路由多路复用器,如果 Server 类型没有指定 handler 类型对象(实现了 Handler 接口的类型),则使用内置的 DefaultServeMux 作为 Multiplexer,并调用初始化 Handler 类型对象的 ServeHTTP() 方法

  1. // serverHandler delegates to either the server's Handler or
  2. // DefaultServeMux and also handles "OPTIONS *" requests.
  3. type serverHandler struct {
  4. srv *Server
  5. }
  6.  
  7. func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
  8. handler := sh.srv.Handler
  9. if handler == nil {
  10. handler = DefaultServeMux
  11. }
  12. if req.RequestURI == "*" && req.Method == "OPTIONS" {
  13. handler = globalOptionsHandler{}
  14. }
  15. handler.ServeHTTP(rw, req)
  16. }

这里 DefaultServeMux 的 ServeHTTP() 方法其实也是定义在 ServeMux 结构体中的,相关代码如下:

ServeMux 的 ServeHTTP() 方法通过调用其 handler() 方法寻找注册到路由上的 handler() 处理函数,并调用该函数的 ServeHTTP() 方法,在上面的例子中则是 IndexHander() 函数

ServeMux 的 handler() 方法对 URL 做了简单的处理,然后调用 handler() 函数,后者会创建一个锁,同时调用 match() 方法返回一个 handler 和 pattern

在 match() 方法中,ServeMux 的 m 字段是 map[string]muxEntry,后者存储了 pattern 和 handler 处理器函数,因此通过迭代 m 寻找出注册路由的 pattern 模式与实际 url 匹配的 handler() 函数并返回

返回的结构一直传递到 ServeMux 的 ServerHTTP() 方法,接下来调用 handler() 函数的 ServeHTTP() 方法,即 IndexHandler() 函数,然后把 response 写到 http.RequestWriter 对象返回给客户端

IndexHandler() 函数运行结束,即  serverHandler{c.server}.ServeHTTP(w, w.req) 运行结束,接下来就是对请求处理完毕之后,断开连接的相关逻辑

  1. // Find a handler on a handler map given a path string.
  2. // Most-specific (longest) pattern wins.
  3. func (mux *ServeMux) match(path string) (h Handler, pattern string) {
  4. // Check for exact match first.
  5. v, ok := mux.m[path]
  6. if ok {
  7. return v.h, v.pattern
  8. }
  9.  
  10. // Check for longest valid match.
  11. var n = 0
  12. for k, v := range mux.m {
  13. if !pathMatch(k, path) {
  14. continue
  15. }
  16. if h == nil || len(k) > n {
  17. n = len(k)
  18. h = v.h
  19. pattern = v.pattern
  20. }
  21. }
  22. return
  23. }
  24. func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
  25.  
  26. // CONNECT requests are not canonicalized.
  27. if r.Method == "CONNECT" {
  28. return mux.handler(r.Host, r.URL.Path)
  29. }
  30.  
  31. // All other requests have any port stripped and path cleaned
  32. // before passing to mux.handler.
  33. host := stripHostPort(r.Host)
  34. path := cleanPath(r.URL.Path)
  35. if path != r.URL.Path {
  36. _, pattern = mux.handler(host, path)
  37. url := *r.URL
  38. url.Path = path
  39. return RedirectHandler(url.String(), StatusMovedPermanently), pattern
  40. }
  41.  
  42. return mux.handler(host, r.URL.Path)
  43. }
  44.  
  45. // handler is the main implementation of Handler.
  46. // The path is known to be in canonical form, except for CONNECT methods.
  47. func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
  48. mux.mu.RLock()
  49. defer mux.mu.RUnlock()
  50.  
  51. // Host-specific pattern takes precedence over generic ones
  52. if mux.hosts {
  53. h, pattern = mux.match(host + path)
  54. }
  55. if h == nil {
  56. h, pattern = mux.match(path)
  57. }
  58. if h == nil {
  59. h, pattern = NotFoundHandler(), ""
  60. }
  61. return
  62. }
  63.  
  64. // ServeHTTP dispatches the request to the handler whose
  65. // pattern most closely matches the request URL.
  66. func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
  67. if r.RequestURI == "*" {
  68. if r.ProtoAtLeast(1, 1) {
  69. w.Header().Set("Connection", "close")
  70. }
  71. w.WriteHeader(StatusBadRequest)
  72. return
  73. }
  74. h, _ := mux.Handler(r)
  75. h.ServeHTTP(w, r)
  76. }

至此,Go 中一个完整的 http 服务介绍完毕,包括 注册路由,开启监听,处理连接,路由处理函数

多数的 web 应用基于 HTTP 协议,客户端和服务端通过 request 和 response 的方式交互,一个 server 必不可少的两部分莫过于路由注册和连接处理,Go 通过一个 ServeMux 实现了 Multiplexer 路由多路复用器来管理路由,同时提供一个 Handler 接口提供 ServeHTTP() 方法实现了包装 handler() 处理器函数,handler() 函数处理 request 并构造 response

ServeMux 和 handler() 处理器函数的连接桥梁就是 Handler 接口,ServeMux 的 ServeHTTP() 方法实现了寻找注册路由的 handler() 处理器函数,并调用该函数的 ServeHTTP() 方法,ServeHTTP() 方法就是真正处理请求和构造响应的地方

参考链接:https://www.cnblogs.com/zhaof/p/8569743.html

ending ~

Go net/http,web server的更多相关文章

  1. Simotion应用案例,使用Simotion web server调试,使用Project Generator创建项目,Simosim模拟运行运行项目

    Simotion web server simotion项目设计和调试过程中,web server功能越来越常用.例如Project generator生成的FBAxis, winder, print ...

  2. C#中自己动手创建一个Web Server(非Socket实现)

    目录 介绍 Web Server在Web架构系统中的作用 Web Server与Web网站程序的交互 HTTPListener与Socket两种方式的差异 附带Demo源码概述 Demo效果截图 总结 ...

  3. Web Server PROPFIND Method internal IP Discosure

    Title:Web Server PROPFIND Method internal IP Discosure  --2012-11-09 09:47 Nessus扫描出来一个安全缺陷,Web Serv ...

  4. 有关于web server架构的一个小疑问

    今天闲的时候trace route了yahoo和sina的域名,yahoo的如下: 1     1 ms     1 ms    <1 ms  172.21.127.1   2     3 ms ...

  5. WebSphere之Web Server插件

    WebSphere之Web Server插件 -----------------------------------2013/10/19 这里记录一下WebSphere与IHS的安装集成,和Web S ...

  6. 自己动手实现网络服务器(Web Server)——基于C#

    前言 最近在学习网络原理,突然萌发出自己实现一个网络服务器的想法,并且由于第三代小白机器人的开发需要,我把之前使用python.PHP写的那部分代码都迁移到了C#(别问我为什么这么喜欢C#),之前使用 ...

  7. 转载的web server实例

    asp.net—web server模拟网上购物 2014-05-08     我来说两句   来源:asp.net—web server模拟网上购物   收藏    我要投稿 在学vb的时候学到了a ...

  8. Web Server 与 App Server

    Web Server 常见的Web Server有Apache Server与Nginx. Apache Http Server是Apache软件基金会下的一个项目,是一款开源的HTTP服务器软件(它 ...

  9. CentOS 6.9安装类型选择(Basic Server/Web Server)

    Desktop :基本的桌面系统,包括常用的桌面软件,如文档查看工具. Minimal Desktop:基本的桌面系统,包含的软件更少. Minimal:基本的系统,不含有任何可选的软件包. Basi ...

随机推荐

  1. Navicat Premium 12 mysql show error: connection is being used

    错误原因:连接数满了. 解决方案:杀掉无用连接,释放资源.

  2. 小D课堂 - 零基础入门SpringBoot2.X到实战_第9节 SpringBoot2.x整合Redis实战_39、SpringBoot2.x整合redis实战讲解

    笔记 3.SpringBoot2.x整合redis实战讲解 简介:使用springboot-starter整合reids实战 1.官网:https://docs.spring.io/spring-bo ...

  3. Java 并发基础常见面试题总结

    1. 什么是线程和进程? 1.1. 何为进程? 进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的.系统运行一个程序即是一个进程从创建,运行到消亡的过程. 在 Java 中,当我们启 ...

  4. sqllite 学习-1

    C# SQLite 数据库操作学习: https://www.cnblogs.com/leemano/p/6578050.html SQLite 之 C#版 System.Data.SQLite 使用 ...

  5. 5.7.27版本mysql新增用户

    因为我们目前只有root,所以只能先用root登陆mysql,再新增用户: $ bin/mysql -u root -p Enter password: Welcome to the MySQL mo ...

  6. 3. Longest Substring Without Repeating Characters 无重复字符的最长子串

    1. 原始题目 给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度. 示例 1: 输入: "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 &quo ...

  7. LeetCode_278. First Bad Version

    278. First Bad Version Easy You are a product manager and currently leading a team to develop a new ...

  8. 推荐Pi(π)币,相当于比特币手机挖矿版

    我为什么推荐这个? 说实话,之所以发出来还是因为如果用我的邀请码注册,双方的挖矿速度都会增加些,我的邀请码:leneing,有问题可以咨询我. Pi币简介 1.在这里强烈推荐Pi币,相当于比特币手机挖 ...

  9. String类的构造函数,析构函数、拷贝构造函数和赋值函数

    (1)构造函数 String::String(const char *str) { if(str==NULL) { m_data = new char[1]; *m_data = ‘\0’; } el ...

  10. eclipse设置格式化tab为4个空格和idea一样