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

2023-06-01 00:00:00 流程 详情 网络编程

一、简介

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

三、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

因此,理解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)}

任何结构体,只要实现了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}

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

查看server的结构如下:

type Server struct {    Addr         stringHandler      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 mainimport ("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 = &defaultServeMuxvar 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 ErrServerCloseddefault:}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 << 10buf := 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定义了函数退出时,连接关闭相关的处理。然后就是读取连接的网络数据,并处理读取完毕时候的状态。接下来就是调用serverHandler{c.server}.ServeHTTP(w, w.req)方法处理请求了。最后就是请求处理完毕的逻辑。

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

如果server对象没有指定Handler,则使用默认的DefaultServeMux作为路由Multiplexer。并调用初始化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方法其实也是定义在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.hpattern = 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请求:

//GETfunc Get(url string) (resp *Response, err error) {    return DefaultClient.Get(url)}//POSTfunc Post(url string, contentType string, body io.Reader) (resp *Response, err error) {    return DefaultClient.Post(url, contentType, body)}//HEADfunc Head(url string) (resp *Response, err error) {    return DefaultClient.Head(url)}//POSTFORMfunc PostForm(url string, data url.Values) (resp *Response, err error) {    return DefaultClient.PostForm(url, data)}

GET请求示例

package mainimport (    "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 mainimport (    "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 mainimport (    "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 mainimport (    "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))}

以上就是go语言网络编程、http处理流程详情的详细内容,更多请关注其它相关文章!

相关文章