>백엔드 개발 >Golang >언어 네트워크 프로그래밍, http 처리 프로세스 세부 정보 이동

언어 네트워크 프로그래밍, http 처리 프로세스 세부 정보 이동

尚
앞으로
2019-11-29 15:14:002095검색

언어 네트워크 프로그래밍, http 처리 프로세스 세부 정보 이동

1. 소개

Go 언어 는 주로 net 패키지를 통해 구현됩니다. net 패키지는 HTTP, TCP/IP, UDP, 도메인 이름 확인 및 Unix 도메인을 포함한 네트워크 I/O 인터페이스를 제공합니다. 소켓 등 대부분의 언어와 마찬가지로 Go는 단 몇 줄의 코드만으로 서버를 시작할 수 있지만 goroutine 덕분에 Go로 구현된 서버는 강력한 동시 처리 기능을 갖추고 있습니다.

2. 소켓 프로그래밍

소켓은 "소켓"이라고도 합니다. 애플리케이션은 일반적으로 "소켓"을 통해 네트워크에 요청하거나 응답합니다.

Socket은 기본적으로 네트워크에 연결된 두 컴퓨터 사이에 채널을 설정합니다. 두 컴퓨터는 이 채널을 사용하여 서로 데이터를 전송합니다. 우리는 네트워크 통신이 대상 특정 시스템에서 특정 서비스를 찾기 위한 ip+포트를 기반으로 한다는 것을 알고 있습니다. 운영 체제에는 0-65535 포트가 있습니다. 회사를 컴퓨터에 비유하면 각 포트는 독립적으로 서비스를 제공할 수 있습니다. , 회사의 교환대 번호는 IP 주소와 동일하며 각 직원의 내선 번호는 포트와 동일합니다. 회사에서 누군가를 찾으려면 먼저 교환대에 전화한 다음 내선으로 연결해야 합니다.

Go의 소켓 프로그래밍은 구현하기가 매우 편리합니다.

1 , 연결 설정

2. 데이터 보내기 및 받기

3. 연결을 닫습니다.

서버 예:

package main

import (
    "fmt"
    "net"
)

func handle(conn net.Conn)  {    //处理连接方法
    defer conn.Close()  //关闭连接
    for{
        buf := make([]byte,100)
        n,err := conn.Read(buf)  //读取客户端数据
        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)  //发送数据
    }
}
func main()  {
    fmt.Println("start server....")
    listen,err := net.Listen("tcp","0.0.0.0:3000") //创建监听
    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)) //发送数据
        if err != nil {
            fmt.Println("send data error:", err)
            return
        }
        buf := make([]byte,512)
        n,err := conn.Read(buf)  //读取服务端端数据
        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)  //读取连接中数据
    
    // 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) //发送数据

    // Close closes the connection.
    // Any blocked Read or Write operations will be unblocked and return errors.
    Close() error   //关闭链接

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

    // RemoteAddr returns the remote network address.
    RemoteAddr() Addr //返回远程连接的地址

    // 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 //设置链接读取或者写超时时间

    // 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 //单独设置读取超时时间

    // 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//单独设置写超时时间
}

3. go 프로세스의 HTTP 서비스 처리

소개

네트워크 개발과 함께 많은 네트워크 애플리케이션이 HTTP 서비스를 기반으로 구축됩니다. HTTP 프로토콜은 처음부터 현재까지 1.0, 1.1, 2.0까지 지속적으로 발전해 왔습니다. 세부 사항과는 별개로, HTTP로 구축된 네트워크 애플리케이션을 이해하려면 두 끝, 즉 클라이언트(클라이언트)와 서버(서버)에만 주의하면 됩니다. 두 끝 사이의 상호 작용은 요청에서 비롯됩니다. 클라이언트와 서버의 응답. 소위 http 서버는 주로 클라이언트의 요청을 수락하고 클라이언트에 응답을 반환하는 방법에 따라 달라집니다. 요청을 수신하는 과정에서 가장 중요한 것은 라우터, 즉 Multiplexer의 구현입니다. Go에서는 내장된 mutilplexer인 DefautServeMux를 사용하거나 이를 사용자 정의할 수 있습니다. 멀티플렉서 라우팅의 목적은 요청을 처리하고 동시에 응답을 작성하는 핸들러 기능(핸들러)을 찾는 것입니다.

최종 단순화된 요청 처리 프로세스는 다음과 같습니다.

Clinet -> Requests ->  [Multiplexer(router) -> handler  -> Response -> Clinet
따라서 Go에서 http 서비스를 이해하려면 가장 중요한 것은 Golang의 Multiplexer와 Handler를 이해하는 것입니다. ServeMux 구조를 기반으로 하며 Handler 인터페이스도 구현합니다. 객체 설명:

1. 핸들러 함수: func(w http.ResponseWriter, r *http.Requests)의 서명이 있는 함수

2. 핸들러 함수: HandlerFunc 구조로 래핑된 핸들러 함수 ServeHTTP 인터페이스 메소드 기능. 핸들러의 ServeHTTP 메소드가 호출되면 핸들러 함수 자체가 호출됩니다.

3. 핸들러 객체: 핸들러 인터페이스의 ServeHTTP 메소드 구조를 구현합니다.

핸들러 프로세서와 핸들러 개체의 차이점은 하나는 함수이고 다른 하나는 둘 다 ServeHTTP 메서드를 구현한다는 것입니다. 대부분의 경우 해당 기능이 유사하므로 아래에서는 이를 핸들러라고 통칭합니다.

Handler

Golang에는 상속이 없으며 클래스 다형성은 인터페이스를 통해 달성될 수 있습니다. 소위 인터페이스는 함수 서명을 선언하는 정의입니다. 모든 구조가 인터페이스 함수 서명과 동일한 메서드를 구현하는 한 이는 인터페이스를 구현하는 것과 같습니다. Go의 http 서비스는 모두 핸들러를 기반으로 처리됩니다.

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

모든 구조는 ServeHTTP 메서드를 구현하는 한 이 구조를 핸들러 개체라고 부를 수 있습니다. ServeMux는 핸들러를 사용하고 ServeHTTP 메소드를 호출하여 요청을 처리하고 응답을 반환합니다.

ServeMux

소스 코드 부분:

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

type muxEntry struct {
    explicit bool
    h        Handler
    pattern  string
}

ServeMux 구조에서 가장 중요한 필드는 m이며 이는 맵이고 키는 일부 URL 패턴이며 값은 muxEntry 구조이며 특정 URL 패턴은 다음과 같습니다. 후자와 처리기에 정의되어 저장됩니다.

물론, 소위 ServeMux도 ServeHTTP 인터페이스를 구현하고 핸들러로 간주될 수 있습니다. 그러나 ServeMux의 ServeHTTP 메소드는 요청 및 응답을 처리하는 데 사용되지 않고 경로 등록을 위한 핸들러를 찾는 데 사용됩니다. 나중에 설명하겠습니다.

Server

ServeMux와 Handler 외에도 이해해야 할 또 다른 서버 구조가 있습니다. http.ListenAndServe의 소스 코드에서 볼 수 있듯이 서버 개체를 생성하고 서버 개체의 ListenAndServe 메서드를 호출합니다.

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

서버 구조를 다음과 같이 봅니다.

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结构存储了服务器处理请求常见的字段。其中Handler字段也保留Handler接口。如果Server接口没有提供Handler结构对象,那么会使用DefautServeMux做multiplexer,后面再做分析。

创建HTTP服务

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

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  
// 结果:Hello World

路由注册

net/http包暴露的注册路由的api很简单,http.HandleFunc选取了DefaultServeMux作为multiplexer:

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

DefaultServeMux是ServeMux的一个实例。当然http包也提供了NewServeMux方法创建一个ServeMux实例,默认则创建一个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)方法实际是定义在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是一个函数类型。同时实现了Handler接口的ServeHTTP方法。使用HandlerFunc类型包装一下路由定义的indexHandler函数,其目的就是为了让这个函数也实现ServeHTTP方法,即转变成一个handler处理器(函数)。

type HandlerFunc func(ResponseWriter, *Request)

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

我们最开始写的例子中
http.HandleFunc("/",Indexhandler)
这样 IndexHandler 函数也有了ServeHTTP方法。ServeMux的Handle方法,将会对pattern和handler函数做一个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方法中,会初始化监听地址Addr,同时调用Listen方法设置监听。最后将监听的TCP对象传入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 "h2" 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)
    }
}

监听开启之后,一旦客户端请求到底,go就开启一个协程处理请求,主要逻辑都在serve方法之中。

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

// 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&#39;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&#39;re
                // responding to them and hanging up
                // while they&#39;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&#39;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&#39;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&#39;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&#39;re in shutdown mode. We might&#39;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定义了函数退出时,连接关闭相关的处理。然后就是读取连接的网络数据,并处理读取完毕时候的状态。接下来就是调用serverHandler{c.server}.ServeHTTP(w, w.req)方法处理请求了。最后就是请求处理完毕的逻辑。

serverHandler是一个重要的结构,它近有一个字段,即Server结构,同时它也实现了Handler接口方法ServeHTTP,并在该接口方法中做了一个重要的事情,初始化multiplexer路由多路复用器。

如果server对象没有指定Handler,则使用默认的DefaultServeMux作为路由Multiplexer。并调用初始化Handler的ServeHTTP方法。

// serverHandler delegates to either the server&#39;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方法其实也是定义在ServeMux结构中的,相关代码如下:

// 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方法通过调用其Handler方法寻找注册到路由上的handler函数,并调用该函数的ServeHTTP方法,本例则是IndexHandler函数。

 mux的Handler方法对URL简单的处理,然后调用handler方法,后者会创建一个锁,同时调用match方法返回一个handler和pattern。 在match方法中,mux的m字段是map[string]muxEntry图,后者存储了pattern和handler处理器函数,因此通过迭代m寻找出注册路由的patten模式与实际url匹配的handler函数并返回。 

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

上述函数运行结束即`serverHandler{c.server}.ServeHTTP(w, w.req)`运行结束。接下来就是对请求处理完毕之后上希望和连接断开的相关逻辑。 至此,Golang中一个完整的http服务介绍完毕,包括注册路由,开启监听,处理连接,路由处理函数。

总结

多数的web应用基于HTTP协议,客户端和服务器通过request-response的方式交互。一个server并不可少的两部分莫过于路由注册和连接处理。Golang通过一个ServeMux实现了的multiplexer路由多路复用器来管理路由。同时提供一个Handler接口提供ServeHTTP用来实现handler处理其函数,后者可以处理实际request并构造response。 

ServeMux和handler处理器函数的连接桥梁就是Handler接口。ServeMux的ServeHTTP方法实现了寻找注册路由的handler的函数,并调用该handler的ServeHTTP方法。

ServeHTTP方法就是真正处理请求和构造响应的地方。 回顾go的http包实现http服务的流程,可见大师们的编码设计之功力。学习有利提高自身的代码逻辑组织能力。更好的学习方式除了阅读,就是实践,接下来,我们将着重讨论来构建http服务。尤其是构建http中间件函数。

四、HTTP客户端工具

net/http不仅提供了服务端处理,还提供了客户端处理功能。

http包中提供了Get、Post、Head、PostForm方法实现HTTP请求:

//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请求示例

package main

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

func main() {

    resp, err := http.Get("http://www.baidu.com")
    if err != nil {
        // 错误处理
        log.Println(err)
        return
    }

    defer resp.Body.Close() //关闭链接

    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设置请求头、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") //设置请求头信息

    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请求示例

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请求示例

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

위 내용은 언어 네트워크 프로그래밍, http 처리 프로세스 세부 정보 이동의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 cnblogs.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제