Rumah pembangunan bahagian belakang Golang go语言网络编程、http处理流程详情

go语言网络编程、http处理流程详情

Nov 29, 2019 pm 03:14 PM
pergi bahasa

go语言网络编程、http处理流程详情

一、简介

go语言中的网络编程主要通过net包实现,net包提供了网络I/O接口,包括HTTP、TCP/IP、UDP、域名解析和Unix域socket等。和大多数语言一样go可以使用几行代码便可以启动一个服务器,但是得益于goroutine的配合go实现的服务器拥有强大并发处理能力。

二、socket编程

Socket又称"套接字",应用程序通常通过"套接字"向网络发出请求或者应答网络请求。

socket本质上就是在2台网络互通的电脑之间,架设一个通道,两台电脑通过这个通道来实现数据的互相传递。 我们知道网络 通信 都 是基于 ip+port 方能定位到目标的具体机器上的具体服务,操作系统有0-65535个端口,每个端口都可以独立对外提供服务,如果 把一个公司比做一台电脑 ,那公司的总机号码就相当于ip地址, 每个员工的分机号就相当于端口, 你想找公司某个人,必须 先打电话到总机,然后再转分机 。

go中socket编程实现起来非常方便,下面是处理流程

服务器端:

1、监听端口

2、接受客户端连接

3、创建goroutine处理连接

客户端:

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) //处理连接
    }
}
Salin selepas log masuk

客户端示例:

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]))
    }
}
Salin selepas log masuk

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

三、go中HTTP服务处理流程

简介

网络发展,很多网络应用都是构建再 HTTP 服务基础之上。HTTP 协议从诞生到现在,发展从1.0,1.1到2.0也不断再进步。除去细节,理解 HTTP 构建的网络应用只要关注两个端---客户端(clinet)和服务端(server),两个端的交互来自 clinet 的 request,以及server端的response。所谓的http服务器,主要在于如何接受 clinet 的 request,并向client返回response。接收request的过程中,最重要的莫过于路由(router),即实现一个Multiplexer器。Go中既可以使用内置的mutilplexer --- DefautServeMux,也可以自定义。Multiplexer路由的目的就是为了找到处理器函数(handler),后者将对request进行处理,同时构建response。

最后简化的请求处理流程为:

Clinet -> Requests ->  [Multiplexer(router) -> handler  -> Response -> Clinet
Salin selepas log masuk

因此,理解go中的http服务,最重要就是要理解Multiplexer和handler,Golang中的Multiplexer基于ServeMux结构,同时也实现了Handler接口。

对象说明:

1、hander函数: 具有func(w http.ResponseWriter, r *http.Requests)签名的函数

2、handler函数: 经过HandlerFunc结构包装的handler函数,它实现了ServeHTTP接口方法的函数。调用handler处理器的ServeHTTP方法时,即调用handler函数本身。

3、handler对象:实现了Handler接口ServeHTTP方法的结构。

handler处理器和handler对象的差别在于,一个是函数,另外一个是结构,它们都有实现了ServeHTTP方法。很多情况下它们的功能类似,下文就使用统称为handler。

Handler

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

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}
Salin selepas log masuk

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

ServeMux

源码部分:

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

type muxEntry struct {
    explicit bool
    h        Handler
    pattern  string
}
Salin selepas log masuk

ServeMux结构中最重要的字段为m,这是一个map,key是一些url模式,value是一个muxEntry结构,后者里定义存储了具体的url模式和handler。

当然,所谓的ServeMux也实现了ServeHTTP接口,也算是一个handler,不过ServeMux的ServeHTTP方法不是用来处理request和respone,而是用来找到路由注册的handler,后面再做解释。

Server

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

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

查看server的结构如下:

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     
}
Salin selepas log masuk

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()
Salin selepas log masuk

示例:

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
Salin selepas log masuk

路由注册

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

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.HandleFunc(pattern, handler)
}
Salin selepas log masuk

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
Salin selepas log masuk

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))
}
Salin selepas log masuk

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)
}
Salin selepas log masuk

我们最开始写的例子中
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)})
}
Salin selepas log masuk

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)
    }
}
Salin selepas log masuk

监听开启之后,一旦客户端请求到底,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方法
Salin selepas log masuk

使用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)
}
Salin selepas log masuk

这里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)
}
Salin selepas log masuk

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)
}
Salin selepas log masuk

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()))
}
Salin selepas log masuk

使用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)
}
Salin selepas log masuk

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))
}
Salin selepas log masuk

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))
}
Salin selepas log masuk

Atas ialah kandungan terperinci go语言网络编程、http处理流程详情. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

Kenyataan Laman Web ini
Kandungan artikel ini disumbangkan secara sukarela oleh netizen, dan hak cipta adalah milik pengarang asal. Laman web ini tidak memikul tanggungjawab undang-undang yang sepadan. Jika anda menemui sebarang kandungan yang disyaki plagiarisme atau pelanggaran, sila hubungi admin@php.cn

Alat AI Hot

Undresser.AI Undress

Undresser.AI Undress

Apl berkuasa AI untuk mencipta foto bogel yang realistik

AI Clothes Remover

AI Clothes Remover

Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

Undress AI Tool

Undress AI Tool

Gambar buka pakaian secara percuma

Clothoff.io

Clothoff.io

Penyingkiran pakaian AI

Video Face Swap

Video Face Swap

Tukar muka dalam mana-mana video dengan mudah menggunakan alat tukar muka AI percuma kami!

Alat panas

Notepad++7.3.1

Notepad++7.3.1

Editor kod yang mudah digunakan dan percuma

SublimeText3 versi Cina

SublimeText3 versi Cina

Versi Cina, sangat mudah digunakan

Hantar Studio 13.0.1

Hantar Studio 13.0.1

Persekitaran pembangunan bersepadu PHP yang berkuasa

Dreamweaver CS6

Dreamweaver CS6

Alat pembangunan web visual

SublimeText3 versi Mac

SublimeText3 versi Mac

Perisian penyuntingan kod peringkat Tuhan (SublimeText3)

Apakah masalah dengan thread giliran di crawler colly go? Apakah masalah dengan thread giliran di crawler colly go? Apr 02, 2025 pm 02:09 PM

Masalah Threading Giliran di GO Crawler Colly meneroka masalah menggunakan Perpustakaan Colly Crawler dalam bahasa Go, pemaju sering menghadapi masalah dengan benang dan permintaan beratur. � ...

Perpustakaan apa yang digunakan untuk operasi nombor terapung di GO? Perpustakaan apa yang digunakan untuk operasi nombor terapung di GO? Apr 02, 2025 pm 02:06 PM

Perpustakaan yang digunakan untuk operasi nombor terapung dalam bahasa Go memperkenalkan cara memastikan ketepatannya ...

Di Go, mengapa rentetan percetakan dengan fungsi println dan rentetan () mempunyai kesan yang berbeza? Di Go, mengapa rentetan percetakan dengan fungsi println dan rentetan () mempunyai kesan yang berbeza? Apr 02, 2025 pm 02:03 PM

Perbezaan antara percetakan rentetan dalam bahasa Go: perbezaan kesan menggunakan fungsi println dan rentetan () sedang ...

Bagaimana menyelesaikan masalah penukaran jenis user_id semasa menggunakan aliran redis untuk melaksanakan beratur mesej dalam bahasa Go? Bagaimana menyelesaikan masalah penukaran jenis user_id semasa menggunakan aliran redis untuk melaksanakan beratur mesej dalam bahasa Go? Apr 02, 2025 pm 04:54 PM

Masalah menggunakan redisstream untuk melaksanakan beratur mesej dalam bahasa Go menggunakan bahasa Go dan redis ...

Apakah perbezaan antara struktur definisi kata kunci `var` dan` type` dalam bahasa Go? Apakah perbezaan antara struktur definisi kata kunci `var` dan` type` dalam bahasa Go? Apr 02, 2025 pm 12:57 PM

Dua cara untuk menentukan struktur dalam bahasa Go: perbezaan antara VAR dan jenis kata kunci. Apabila menentukan struktur, pergi bahasa sering melihat dua cara menulis yang berbeza: pertama ...

Perpustakaan mana yang dibangunkan oleh syarikat besar atau disediakan oleh projek sumber terbuka yang terkenal? Perpustakaan mana yang dibangunkan oleh syarikat besar atau disediakan oleh projek sumber terbuka yang terkenal? Apr 02, 2025 pm 04:12 PM

Perpustakaan mana yang dibangunkan oleh syarikat besar atau projek sumber terbuka yang terkenal? Semasa pengaturcaraan di GO, pemaju sering menghadapi beberapa keperluan biasa, ...

Apa yang perlu saya lakukan jika label struktur tersuai di Goland tidak dipaparkan? Apa yang perlu saya lakukan jika label struktur tersuai di Goland tidak dipaparkan? Apr 02, 2025 pm 05:09 PM

Apa yang perlu saya lakukan jika label struktur tersuai di Goland tidak dipaparkan? Apabila menggunakan Goland untuk Pembangunan Bahasa GO, banyak pemaju akan menghadapi tag struktur tersuai ...

Apabila menggunakan sql.open, mengapa tidak melaporkan ralat apabila DSN berlalu kosong? Apabila menggunakan sql.open, mengapa tidak melaporkan ralat apabila DSN berlalu kosong? Apr 02, 2025 pm 12:54 PM

Apabila menggunakan SQL.Open, mengapa DSN tidak melaporkan ralat? Dalam bahasa Go, sql.open ...

See all articles