溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

golang中的網(wǎng)絡(luò)編程和http處理流程

發(fā)布時(shí)間:2020-06-22 10:17:33 來(lái)源:億速云 閱讀:165 作者:Leah 欄目:編程語(yǔ)言

今天就跟大家聊聊有關(guān)golang中的網(wǎng)絡(luò)編程和http處理流程,大部分知識(shí)點(diǎn)都是大家經(jīng)常用到的,為此分享給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧。

一、簡(jiǎn)介

go語(yǔ)言中的網(wǎng)絡(luò)編程主要通過(guò)net包實(shí)現(xiàn),net包提供了網(wǎng)絡(luò)I/O接口,包括HTTP、TCP/IP、UDP、域名解析和Unix域socket等。和大多數(shù)語(yǔ)言一樣go可以使用幾行代碼便可以啟動(dòng)一個(gè)服務(wù)器,但是得益于goroutine的配合go實(shí)現(xiàn)的服務(wù)器擁有強(qiáng)大并發(fā)處理能力。

二、socket編程

Socket又稱"套接字",應(yīng)用程序通常通過(guò)"套接字"向網(wǎng)絡(luò)發(fā)出請(qǐng)求或者應(yīng)答網(wǎng)絡(luò)請(qǐng)求。

socket本質(zhì)上就是在2臺(tái)網(wǎng)絡(luò)互通的電腦之間,架設(shè)一個(gè)通道,兩臺(tái)電腦通過(guò)這個(gè)通道來(lái)實(shí)現(xiàn)數(shù)據(jù)的互相傳遞。 我們知道網(wǎng)絡(luò) 通信 都 是基于 ip+port 方能定位到目標(biāo)的具體機(jī)器上的具體服務(wù),操作系統(tǒng)有0-65535個(gè)端口,每個(gè)端口都可以獨(dú)立對(duì)外提供服務(wù),如果 把一個(gè)公司比做一臺(tái)電腦 ,那公司的總機(jī)號(hào)碼就相當(dāng)于ip地址, 每個(gè)員工的分機(jī)號(hào)就相當(dāng)于端口, 你想找公司某個(gè)人,必須 先打電話到總機(jī),然后再轉(zhuǎn)分機(jī) 。

go中socket編程實(shí)現(xiàn)起來(lái)非常方便,下面是處理流程

服務(wù)器端:

1、監(jiān)聽(tīng)端口

2、接受客戶端連接

3、創(chuàng)建goroutine處理連接

客戶端:

1、建立連接

2、收發(fā)數(shù)據(jù)

3、關(guān)閉連接

服務(wù)端示例:

package main

import (
    "fmt"
    "net"
)

func handle(conn net.Conn)  {    //處理連接方法
    defer conn.Close()  //關(guān)閉連接
    for{
        buf := make([]byte,100)
        n,err := conn.Read(buf)  //讀取客戶端數(shù)據(jù)
        if err!=nil {
            fmt.Println(err)
            return

        }
        fmt.Printf("read data size %d msg:%s", n, string(buf[0:n]))
        msg := []byte("hello,world\n")
        conn.Write(msg)  //發(fā)送數(shù)據(jù)
    }
}
func main()  {
    fmt.Println("start server....")
    listen,err := net.Listen("tcp","0.0.0.0:3000") //創(chuàng)建監(jiān)聽(tīng)
    if err != nil{
        fmt.Println("listen failed! msg :" ,err)
        return
    }
    for{
        conn,errs := listen.Accept()  //接受客戶端連接
        if errs != nil{
            fmt.Println("accept failed")
            continue
        }
        go handle(conn) //處理連接
    }
}

客戶端示例:

package main

import (
    "bufio"
    "fmt"
    "net"
    "os"
    "strings"
)

func main() {
    conn, err := net.Dial("tcp", "127.0.0.1:3000")
    if err != nil {
        fmt.Println("err dialing:", err.Error())
        return
    }
    defer conn.Close()
    inputReader := bufio.NewReader(os.Stdin)
    for {
        str, _ := inputReader.ReadString('\n')
        data := strings.Trim(str, "\n")
        if data == "quit" {   //輸入quit退出
            return
        }
        _, err := conn.Write([]byte(data)) //發(fā)送數(shù)據(jù)
        if err != nil {
            fmt.Println("send data error:", err)
            return
        }
        buf := make([]byte,512)
        n,err := conn.Read(buf)  //讀取服務(wù)端端數(shù)據(jù)
        fmt.Println("from server:", string(buf[:n]))
    }
}

conn示例還提供其他方法:

type Conn interface {
    // Read reads data from the connection.
    // Read can be made to time out and return an Error with Timeout() == true
    // after a fixed time limit; see SetDeadline and SetReadDeadline.
    Read(b []byte) (n int, err error)  //讀取連接中數(shù)據(jù)
    
    // Write writes data to the connection.
    // Write can be made to time out and return an Error with Timeout() == true
    // after a fixed time limit; see SetDeadline and SetWriteDeadline.
    Write(b []byte) (n int, err error) //發(fā)送數(shù)據(jù)

    // Close closes the connection.
    // Any blocked Read or Write operations will be unblocked and return errors.
    Close() error   //關(guān)閉鏈接

    // LocalAddr returns the local network address.
    LocalAddr() Addr //返回本地連接地址

    // RemoteAddr returns the remote network address.
    RemoteAddr() Addr //返回遠(yuǎn)程連接的地址

    // SetDeadline sets the read and write deadlines associated
    // with the connection. It is equivalent to calling both
    // SetReadDeadline and SetWriteDeadline.
    //
    // A deadline is an absolute time after which I/O operations
    // fail with a timeout (see type Error) instead of
    // blocking. The deadline applies to all future and pending
    // I/O, not just the immediately following call to Read or
    // Write. After a deadline has been exceeded, the connection
    // can be refreshed by setting a deadline in the future.
    //
    // An idle timeout can be implemented by repeatedly extending
    // the deadline after successful Read or Write calls.
    //
    // A zero value for t means I/O operations will not time out.
    SetDeadline(t time.Time) error //設(shè)置鏈接讀取或者寫超時(shí)時(shí)間

    // SetReadDeadline sets the deadline for future Read calls
    // and any currently-blocked Read call.
    // A zero value for t means Read will not time out.
    SetReadDeadline(t time.Time) error //單獨(dú)設(shè)置讀取超時(shí)時(shí)間

    // SetWriteDeadline sets the deadline for future Write calls
    // and any currently-blocked Write call.
    // Even if write times out, it may return n > 0, indicating that
    // some of the data was successfully written.
    // A zero value for t means Write will not time out.
    SetWriteDeadline(t time.Time) error//單獨(dú)設(shè)置寫超時(shí)時(shí)間
}

三、go中HTTP服務(wù)處理流程

簡(jiǎn)介

網(wǎng)絡(luò)發(fā)展,很多網(wǎng)絡(luò)應(yīng)用都是構(gòu)建再 HTTP 服務(wù)基礎(chǔ)之上。HTTP 協(xié)議從誕生到現(xiàn)在,發(fā)展從1.0,1.1到2.0也不斷再進(jìn)步。除去細(xì)節(jié),理解 HTTP 構(gòu)建的網(wǎng)絡(luò)應(yīng)用只要關(guān)注兩個(gè)端---客戶端(clinet)和服務(wù)端(server),兩個(gè)端的交互來(lái)自 clinet 的 request,以及server端的response。所謂的http服務(wù)器,主要在于如何接受 clinet 的 request,并向client返回response。接收request的過(guò)程中,最重要的莫過(guò)于路由(router),即實(shí)現(xiàn)一個(gè)Multiplexer器。Go中既可以使用內(nèi)置的mutilplexer --- DefautServeMux,也可以自定義。Multiplexer路由的目的就是為了找到處理器函數(shù)(handler),后者將對(duì)request進(jìn)行處理,同時(shí)構(gòu)建response。

最后簡(jiǎn)化的請(qǐng)求處理流程為:

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

因此,理解go中的http服務(wù),最重要就是要理解Multiplexer和handler,Golang中的Multiplexer基于ServeMux結(jié)構(gòu),同時(shí)也實(shí)現(xiàn)了Handler接口。

對(duì)象說(shuō)明:

1、hander函數(shù): 具有func(w http.ResponseWriter, r *http.Requests)簽名的函數(shù)

2、handler函數(shù): 經(jīng)過(guò)HandlerFunc結(jié)構(gòu)包裝的handler函數(shù),它實(shí)現(xiàn)了ServeHTTP接口方法的函數(shù)。調(diào)用handler處理器的ServeHTTP方法時(shí),即調(diào)用handler函數(shù)本身。

3、handler對(duì)象:實(shí)現(xiàn)了Handler接口ServeHTTP方法的結(jié)構(gòu)。

handler處理器和handler對(duì)象的差別在于,一個(gè)是函數(shù),另外一個(gè)是結(jié)構(gòu),它們都有實(shí)現(xiàn)了ServeHTTP方法。很多情況下它們的功能類似,下文就使用統(tǒng)稱為handler。

Handler

Golang沒(méi)有繼承,類多態(tài)的方式可以通過(guò)接口實(shí)現(xiàn)。所謂接口則是定義聲明了函數(shù)簽名,任何結(jié)構(gòu)只要實(shí)現(xiàn)了與接口函數(shù)簽名相同的方法,就等同于實(shí)現(xiàn)了接口。go的http服務(wù)都是基于handler進(jìn)行處理。

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

任何結(jié)構(gòu)體,只要實(shí)現(xiàn)了ServeHTTP方法,這個(gè)結(jié)構(gòu)就可以稱之為handler對(duì)象。ServeMux會(huì)使用handler并調(diào)用其ServeHTTP方法處理請(qǐng)求并返回響應(yīng)。

ServeMux

源碼部分:

type ServeMux struct {
    mu    sync.RWMutex
    m     map[string]muxEntry
    hosts bool 
}

type muxEntry struct {
    explicit bool
    h        Handler
    pattern  string
}

ServeMux結(jié)構(gòu)中最重要的字段為m,這是一個(gè)map,key是一些url模式,value是一個(gè)muxEntry結(jié)構(gòu),后者里定義存儲(chǔ)了具體的url模式和handler。

當(dāng)然,所謂的ServeMux也實(shí)現(xiàn)了ServeHTTP接口,也算是一個(gè)handler,不過(guò)ServeMux的ServeHTTP方法不是用來(lái)處理request和respone,而是用來(lái)找到路由注冊(cè)的handler,后面再做解釋。

Server

除了ServeMux和Handler,還有一個(gè)結(jié)構(gòu)Server需要了解。從http.ListenAndServe的源碼可以看出,它創(chuàng)建了一個(gè)server對(duì)象,并調(diào)用server對(duì)象的ListenAndServe方法:

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

查看server的結(jié)構(gòu)如下:

type Server struct {
    Addr         string        
    Handler      Handler       
    ReadTimeout  time.Duration 
    WriteTimeout time.Duration 
    TLSConfig    *tls.Config   

    MaxHeaderBytes int

    TLSNextProto map[string]func(*Server, *tls.Conn, Handler)

    ConnState func(net.Conn, ConnState)
    ErrorLog *log.Logger
    disableKeepAlives int32     nextProtoOnce     sync.Once 
    nextProtoErr      error     
}

server結(jié)構(gòu)存儲(chǔ)了服務(wù)器處理請(qǐng)求常見(jiàn)的字段。其中Handler字段也保留Handler接口。如果Server接口沒(méi)有提供Handler結(jié)構(gòu)對(duì)象,那么會(huì)使用DefautServeMux做multiplexer,后面再做分析。

創(chuàng)建HTTP服務(wù)

創(chuàng)建一個(gè)http服務(wù),大致需要經(jīng)歷兩個(gè)過(guò)程,首先需要注冊(cè)路由,即提供url模式和handler函數(shù)的映射,其次就是實(shí)例化一個(gè)server對(duì)象,并開(kāi)啟對(duì)客戶端的監(jiān)聽(tīng)。

http.HandleFunc("/", indexHandler)
http.ListenAndServe("127.0.0.1:8000", nil)
或
server := &Server{Addr: addr, Handler: handler}

server.ListenAndServe()

示例:

package main
import (
"fmt"
"net/http"
)

func Hello(w http.ResponseWriter, r *http.Request) {
fmt.Println("Hello World.")
fmt.Fprintf(w, "Hello World.\n")
}

func main() {
http.HandleFunc("/", Hello)
err := http.ListenAndServe("0.0.0.0:6000", nil)
if err != nil {
fmt.Println("http listen failed.")
}
}

//curl http://127.0.0.1:6000  
// 結(jié)果:Hello World

路由注冊(cè)

net/http包暴露的注冊(cè)路由的api很簡(jiǎn)單,http.HandleFunc選取了DefaultServeMux作為multiplexer:

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

DefaultServeMux是ServeMux的一個(gè)實(shí)例。當(dāng)然http包也提供了NewServeMux方法創(chuàng)建一個(gè)ServeMux實(shí)例,默認(rèn)則創(chuàng)建一個(gè)DefaultServeMux:

// NewServeMux allocates and returns a new ServeMux.
func NewServeMux() *ServeMux { return new(ServeMux) }

// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux

var defaultServeMux ServeMux

DefaultServeMux的HandleFunc(pattern, handler)方法實(shí)際是定義在ServeMux下的:

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

HandlerFunc是一個(gè)函數(shù)類型。同時(shí)實(shí)現(xiàn)了Handler接口的ServeHTTP方法。使用HandlerFunc類型包裝一下路由定義的indexHandler函數(shù),其目的就是為了讓這個(gè)函數(shù)也實(shí)現(xiàn)ServeHTTP方法,即轉(zhuǎn)變成一個(gè)handler處理器(函數(shù))。

type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}

我們最開(kāi)始寫的例子中
http.HandleFunc("/",Indexhandler)
這樣 IndexHandler 函數(shù)也有了ServeHTTP方法。ServeMux的Handle方法,將會(huì)對(duì)pattern和handler函數(shù)做一個(gè)map映射:

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)})
}

Server的ListenAndServe方法中,會(huì)初始化監(jiān)聽(tīng)地址Addr,同時(shí)調(diào)用Listen方法設(shè)置監(jiān)聽(tīng)。最后將監(jiān)聽(tīng)的TCP對(duì)象傳入Serve方法:

// 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 "h3" in
// Config.NextProtos, HTTP/2 support is not enabled.
//
// Serve always returns a non-nil error. After Shutdown or Close, the
// returned error is ErrServerClosed.
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
    }

    srv.trackListener(l, true)
    defer srv.trackListener(l, false)

    baseCtx := context.Background() // base is always background, per Issue 16220
    ctx := context.WithValue(baseCtx, ServerContextKey, srv)
    for {
        rw, e := l.Accept()
        if e != nil {
            select {
            case <-srv.getDoneChan():
                return ErrServerClosed
            default:
            }
            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)
    }
}

監(jiān)聽(tīng)開(kāi)啟之后,一旦客戶端請(qǐng)求到底,go就開(kāi)啟一個(gè)協(xié)程處理請(qǐng)求,主要邏輯都在serve方法之中。

serve方法比較長(zhǎng),其主要職能就是,創(chuàng)建一個(gè)上下文對(duì)象,然后調(diào)用Listener的Accept方法用來(lái) 獲取連接數(shù)據(jù)并使用newConn方法創(chuàng)建連接對(duì)象。最后使用goroutine協(xié)程的方式處理連接請(qǐng)求。因?yàn)槊恳粋€(gè)連接都開(kāi)起了一個(gè)協(xié)程,請(qǐng)求的上下文都不同,同時(shí)又保證了go的高并發(fā)。serve也是一個(gè)長(zhǎng)長(zhǎng)的方法:

// Serve a new connection.
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)
        }
    }()

    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.

    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() {
            // If we read any bytes off the wire, we're active.
            c.setState(c.rwc, StateActive)
        }
        if err != nil {
            const errorHeaders = "\r\nContent-Type: text/plain; charset=utf-8\r\nConnection: close\r\n\r\n"

            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.
                const publicErr = "431 Request Header Fields Too Large"
                fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+publicErr)
                c.closeWriteAndWait()
                return
            }
            if isCommonNetReadError(err) {
                return // don't reply
            }

            publicErr := "400 Bad Request"
            if v, ok := err.(badRequestError); ok {
                publicErr = publicErr + ": " + string(v)
            }

            fmt.Fprintf(c.rwc, "HTTP/1.1 "+publicErr+errorHeaders+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
        }

        c.curReq.Store(w)

        if requestBodyRemains(req.Body) {
            registerOnHitEOF(req.Body, w.conn.r.startBackgroundRead)
        } else {
            if w.conn.bufr.Buffered() > 0 {
                w.conn.r.closeNotifyFromPipelinedRequest()
            }
            w.conn.r.startBackgroundRead()
        }

        // 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.
        // But we're not going to implement HTTP pipelining because it
        // was never deployed in the wild and the answer is HTTP/2.
        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)
        c.curReq.Store((*response)(nil))

        if !w.conn.server.doKeepAlives() {
            // We're in shutdown mode. We might've replied
            // to the user without "Connection: close" and
            // they might think they can send another
            // request, but such is life with HTTP/1.1.
            return
        }

        if d := c.server.idleTimeout(); d != 0 {
            c.rwc.SetReadDeadline(time.Now().Add(d))
            if _, err := c.bufr.Peek(4); err != nil {
                return
            }
        }
        c.rwc.SetReadDeadline(time.Time{})
    }
}

serve方法

使用defer定義了函數(shù)退出時(shí),連接關(guān)閉相關(guān)的處理。然后就是讀取連接的網(wǎng)絡(luò)數(shù)據(jù),并處理讀取完畢時(shí)候的狀態(tài)。接下來(lái)就是調(diào)用serverHandler{c.server}.ServeHTTP(w, w.req)方法處理請(qǐng)求了。最后就是請(qǐng)求處理完畢的邏輯。

serverHandler是一個(gè)重要的結(jié)構(gòu),它近有一個(gè)字段,即Server結(jié)構(gòu),同時(shí)它也實(shí)現(xiàn)了Handler接口方法ServeHTTP,并在該接口方法中做了一個(gè)重要的事情,初始化multiplexer路由多路復(fù)用器。

如果server對(duì)象沒(méi)有指定Handler,則使用默認(rèn)的DefaultServeMux作為路由Multiplexer。并調(diào)用初始化Handler的ServeHTTP方法。

// 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)
}

這里DefaultServeMux的ServeHTTP方法其實(shí)也是定義在ServeMux結(jié)構(gòu)中的,相關(guān)代碼如下:

// Find a handler on a handler map given a path string.
// Most-specific (longest) pattern wins.
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
    // Check for exact match first.
    v, ok := mux.m[path]
    if ok {
        return v.h, v.pattern
    }

    // Check for longest valid match.
    var n = 0
    for k, v := range mux.m {
        if !pathMatch(k, path) {
            continue
        }
        if h == nil || len(k) > n {
            n = len(k)
            h = v.h
            pattern = v.pattern
        }
    }
    return
}
func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {

    // CONNECT requests are not canonicalized.
    if r.Method == "CONNECT" {
        return mux.handler(r.Host, r.URL.Path)
    }

    // All other requests have any port stripped and path cleaned
    // before passing to mux.handler.
    host := stripHostPort(r.Host)
    path := cleanPath(r.URL.Path)
    if path != r.URL.Path {
        _, pattern = mux.handler(host, path)
        url := *r.URL
        url.Path = path
        return RedirectHandler(url.String(), StatusMovedPermanently), pattern
    }

    return mux.handler(host, r.URL.Path)
}

// handler is the main implementation of Handler.
// The path is known to be in canonical form, except for CONNECT methods.
func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
    mux.mu.RLock()
    defer mux.mu.RUnlock()

    // Host-specific pattern takes precedence over generic ones
    if mux.hosts {
        h, pattern = mux.match(host + path)
    }
    if h == nil {
        h, pattern = mux.match(path)
    }
    if h == nil {
        h, pattern = NotFoundHandler(), ""
    }
    return
}

// ServeHTTP dispatches the request to the handler whose
// pattern most closely matches the request URL.
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)
}

mux的ServeHTTP方法通過(guò)調(diào)用其Handler方法尋找注冊(cè)到路由上的handler函數(shù),并調(diào)用該函數(shù)的ServeHTTP方法,本例則是IndexHandler函數(shù)。

mux的Handler方法對(duì)URL簡(jiǎn)單的處理,然后調(diào)用handler方法,后者會(huì)創(chuàng)建一個(gè)鎖,同時(shí)調(diào)用match方法返回一個(gè)handler和pattern。 在match方法中,mux的m字段是map[string]muxEntry圖,后者存儲(chǔ)了pattern和handler處理器函數(shù),因此通過(guò)迭代m尋找出注冊(cè)路由的patten模式與實(shí)際url匹配的handler函數(shù)并返回。

返回的結(jié)構(gòu)一直傳遞到mux的ServeHTTP方法,接下來(lái)調(diào)用handler函數(shù)的ServeHTTP方法,即IndexHandler函數(shù),然后把response寫到http.RequestWirter對(duì)象返回給客戶端。

上述函數(shù)運(yùn)行結(jié)束即`serverHandler{c.server}.ServeHTTP(w, w.req)`運(yùn)行結(jié)束。接下來(lái)就是對(duì)請(qǐng)求處理完畢之后上希望和連接斷開(kāi)的相關(guān)邏輯。 至此,Golang中一個(gè)完整的http服務(wù)介紹完畢,包括注冊(cè)路由,開(kāi)啟監(jiān)聽(tīng),處理連接,路由處理函數(shù)。

總結(jié)

多數(shù)的web應(yīng)用基于HTTP協(xié)議,客戶端和服務(wù)器通過(guò)request-response的方式交互。一個(gè)server并不可少的兩部分莫過(guò)于路由注冊(cè)和連接處理。Golang通過(guò)一個(gè)ServeMux實(shí)現(xiàn)了的multiplexer路由多路復(fù)用器來(lái)管理路由。同時(shí)提供一個(gè)Handler接口提供ServeHTTP用來(lái)實(shí)現(xiàn)handler處理其函數(shù),后者可以處理實(shí)際request并構(gòu)造response。

ServeMux和handler處理器函數(shù)的連接橋梁就是Handler接口。ServeMux的ServeHTTP方法實(shí)現(xiàn)了尋找注冊(cè)路由的handler的函數(shù),并調(diào)用該handler的ServeHTTP方法。

ServeHTTP方法就是真正處理請(qǐng)求和構(gòu)造響應(yīng)的地方。 回顧go的http包實(shí)現(xiàn)http服務(wù)的流程,可見(jiàn)大師們的編碼設(shè)計(jì)之功力。學(xué)習(xí)有利提高自身的代碼邏輯組織能力。更好的學(xué)習(xí)方式除了閱讀,就是實(shí)踐,接下來(lái),我們將著重討論來(lái)構(gòu)建http服務(wù)。尤其是構(gòu)建http中間件函數(shù)。

四、HTTP客戶端工具

net/http不僅提供了服務(wù)端處理,還提供了客戶端處理功能。

http包中提供了Get、Post、Head、PostForm方法實(shí)現(xiàn)HTTP請(qǐng)求:

//GET
func Get(url string) (resp *Response, err error) {
    return DefaultClient.Get(url)
}

//POST
func Post(url string, contentType string, body io.Reader) (resp *Response, err error) {
    return DefaultClient.Post(url, contentType, body)
}

//HEAD
func Head(url string) (resp *Response, err error) {
    return DefaultClient.Head(url)
}

//POSTFORM

func PostForm(url string, data url.Values) (resp *Response, err error) {
    return DefaultClient.PostForm(url, data)
}

GET請(qǐng)求示例

package main

import (
    "fmt"
    "net/http"
    "log"
    "reflect"
    "bytes"
)

func main() {

    resp, err := http.Get("http://www.baidu.com")
    if err != nil {
        // 錯(cuò)誤處理
        log.Println(err)
        return
    }

    defer resp.Body.Close() //關(guān)閉鏈接

    headers := resp.Header

    for k, v := range headers {
        fmt.Printf("k=%v, v=%v\n", k, v) //所有頭信息
    }

    fmt.Printf("resp status %s,statusCode %d\n", resp.Status, resp.StatusCode)

    fmt.Printf("resp Proto %s\n", resp.Proto)

    fmt.Printf("resp content length %d\n", resp.ContentLength)

    fmt.Printf("resp transfer encoding %v\n", resp.TransferEncoding)

    fmt.Printf("resp Uncompressed %t\n", resp.Uncompressed)

    fmt.Println(reflect.TypeOf(resp.Body)) 
    buf := bytes.NewBuffer(make([]byte, 0, 512))
    length, _ := buf.ReadFrom(resp.Body)
    fmt.Println(len(buf.Bytes()))
    fmt.Println(length)
    fmt.Println(string(buf.Bytes()))
}

使用http.Do設(shè)置請(qǐng)求頭、cookie等

package main

import (
    "net/http"
    "strings"
    "io/ioutil"
    "log"
    "fmt"
)

func main() {
    client := &http.Client{}
    req, err := http.NewRequest("POST", "http://www.baidu.com",
        strings.NewReader("name=xxxx&passwd=xxxx"))
    if err != nil {
        fmt.Println(err)
        return
    }

    req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8") //設(shè)置請(qǐng)求頭信息

    resp, err := client.Do(req)

    defer resp.Body.Close()

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        log.Println(err)
        return
    }
    var res string
    res = string(body[:])
    fmt.Println(res)
}

POST請(qǐng)求示例

package main

import (
    "net/http"
    "strings"
    "fmt"
    "io/ioutil"
)

func main() {
    resp, err := http.Post("http://www.baidu.com",
        "application/x-www-form-urlencoded",
        strings.NewReader("username=xxx&password=xxxx"))
    if err != nil {
        fmt.Println(err)
        return
    }

    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Println(string(body))
}

PostForm請(qǐng)求示例

package main

import (
    "net/http"
    "fmt"
    "io/ioutil"
    "net/url"
)

func main() {

    postParam := url.Values{
        "name":      {"wd"},
        "password": {"1234"},
    }

    resp, err := http.PostForm("https://cn.bing.com/", postParam)
    if err != nil {
        fmt.Println(err)
        return
    }

    defer resp.Body.Close()
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Println(string(body))
}

以上就是golang中的網(wǎng)絡(luò)編程和http處理流程,看完之后是否有所收獲呢?如果想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊,感謝各位的閱讀。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI