一、簡介
#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) //处理连接 } }
客戶端範例:
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//单独设置写超时时间 }
三、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 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'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.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)) }
以上是go語言網路程式設計、http處理流程詳情的詳細內容。更多資訊請關注PHP中文網其他相關文章!