一、简介
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编程实现起来非常方便,下面是处理流程
服务器端:
- 监听端口
- 接受客户端连接
- 创建goroutine处理连接
客户端:
- 建立连接
- 收发数据
- 关闭连接
服务端示例:
<span style="color: #800000;">"<span style="color: #800000;">fmt<span style="color: #800000;">"
<span style="color: #800000;">"<span style="color: #800000;">net<span style="color: #800000;">"<span style="color: #000000;">
)
func handle(conn net.Conn) { <span style="color: #008000;">//<span style="color: #008000;">处理连接方法
defer conn.Close() <span style="color: #008000;">//<span style="color: #008000;">关闭连接
<span style="color: #0000ff;">for<span style="color: #000000;">{
buf := make([]<span style="color: #0000ff;">byte,<span style="color: #800080;">100<span style="color: #000000;">)
n,err := conn.Read(buf) <span style="color: #008000;">//<span style="color: #008000;">读取客户端数据
<span style="color: #0000ff;">if err!=<span style="color: #000000;">nil {
fmt.Println(err)
<span style="color: #0000ff;">return<span style="color: #000000;">
}
fmt.Printf(</span><span style="color: #800000;">"</span><span style="color: #800000;">read data size %d msg:%s</span><span style="color: #800000;">"</span>,n,<span style="color: #0000ff;">string</span>(buf[<span style="color: #800080;">0</span><span style="color: #000000;">:n]))
msg :</span>= []<span style="color: #0000ff;">byte</span>(<span style="color: #800000;">"</span><span style="color: #800000;">hello,world\n</span><span style="color: #800000;">"</span><span style="color: #000000;">)
conn.Write(msg) </span><span style="color: #008000;">//</span><span style="color: #008000;">发送数据</span>
<span style="color: #000000;"> }
}
func main() {
fmt.Println(<span style="color: #800000;">"<span style="color: #800000;">start server....<span style="color: #800000;">"<span style="color: #000000;">)
listen,err := net.Listen(<span style="color: #800000;">"<span style="color: #800000;">tcp<span style="color: #800000;">",<span style="color: #800000;">"<span style="color: #800000;">0.0.0.0:3000<span style="color: #800000;">") <span style="color: #008000;">//<span style="color: #008000;">创建监听
<span style="color: #0000ff;">if err !=<span style="color: #000000;"> nil{
fmt.Println(<span style="color: #800000;">"<span style="color: #800000;">listen Failed! msg :<span style="color: #800000;">"<span style="color: #000000;">,err)
<span style="color: #0000ff;">return<span style="color: #000000;">
}
<span style="color: #0000ff;">for<span style="color: #000000;">{
conn,errs := listen.Accept() <span style="color: #008000;">//<span style="color: #008000;">接受客户端连接
<span style="color: #0000ff;">if errs !=<span style="color: #000000;"> nil{
fmt.Println(<span style="color: #800000;">"<span style="color: #800000;">accept Failed<span style="color: #800000;">"<span style="color: #000000;">)
<span style="color: #0000ff;">continue<span style="color: #000000;">
}
go handle(conn) <span style="color: #008000;">//<span style="color: #008000;">处理连接
<span style="color: #000000;"> }
}
客户端示例:
<span style="color: #800000;">"<span style="color: #800000;">bufio<span style="color: #800000;">"
<span style="color: #800000;">"<span style="color: #800000;">fmt<span style="color: #800000;">"
<span style="color: #800000;">"<span style="color: #800000;">net<span style="color: #800000;">"
<span style="color: #800000;">"<span style="color: #800000;">os<span style="color: #800000;">"
<span style="color: #800000;">"<span style="color: #800000;">strings<span style="color: #800000;">"<span style="color: #000000;">
)
func main() {
conn,err := net.Dial(<span style="color: #800000;">"<span style="color: #800000;">tcp<span style="color: #800000;">",<span style="color: #800000;">"<span style="color: #800000;">127.0.0.1:3000<span style="color: #800000;">"<span style="color: #000000;">)
<span style="color: #0000ff;">if err !=<span style="color: #000000;"> nil {
fmt.Println(<span style="color: #800000;">"<span style="color: #800000;">err dialing:<span style="color: #800000;">"<span style="color: #000000;">,err.Error())
<span style="color: #0000ff;">return<span style="color: #000000;">
}
defer conn.Close()
inputReader :=<span style="color: #000000;"> bufio.NewReader(os.Stdin)
<span style="color: #0000ff;">for<span style="color: #000000;"> {
str,_ := inputReader.ReadString(<span style="color: #800000;">'<span style="color: #800000;">\n<span style="color: #800000;">'<span style="color: #000000;">)
data := strings.Trim(str,<span style="color: #800000;">"<span style="color: #800000;">\n<span style="color: #800000;">"<span style="color: #000000;">)
<span style="color: #0000ff;">if data == <span style="color: #800000;">"<span style="color: #800000;">quit<span style="color: #800000;">" { <span style="color: #008000;">//<span style="color: #008000;">输入quit退出
<span style="color: #0000ff;">return<span style="color: #000000;">
}
_,err := conn.Write([]<span style="color: #0000ff;">byte(data)) <span style="color: #008000;">//<span style="color: #008000;">发送数据
<span style="color: #0000ff;">if err !=<span style="color: #000000;"> nil {
fmt.Println(<span style="color: #800000;">"<span style="color: #800000;">send data error:<span style="color: #800000;">"<span style="color: #000000;">,err)
<span style="color: #0000ff;">return<span style="color: #000000;">
}
buf := make([]<span style="color: #0000ff;">byte,<span style="color: #800080;">512<span style="color: #000000;">)
n,err := conn.Read(buf) <span style="color: #008000;">//<span style="color: #008000;">读取服务端端数据
fmt.Println(<span style="color: #800000;">"<span style="color: #800000;">from server:<span style="color: #800000;">",<span style="color: #0000ff;">string<span style="color: #000000;">(buf[:n]))
}
}
conn示例还提供其他方法:
</span></span><span style="color: #008000;">//</span><span style="color: #008000;"> Write writes data to the connection.
</span><span style="color: #008000;">//</span><span style="color: #008000;"> Write can be made to time out and return an Error with Timeout() == true
</span><span style="color: #008000;">//</span><span style="color: #008000;"> after a fixed time limit; see SetDeadline and SetWriteDeadline.</span>
Write(b []<span style="color: #0000ff;">byte</span>) (n <span style="color: #0000ff;">int</span><span style="color: #000000;">,err error) <span style="color: #008000;">//发送数据
</span></span><span style="color: #008000;">//</span><span style="color: #008000;"> Close closes the connection.
</span><span style="color: #008000;">//</span><span style="color: #008000;"> Any blocked Read or Write operations will be unblocked and return errors.</span>
<span style="color: #000000;"> Close() error <span style="color: #008000;">//关闭链接
</span></span><span style="color: #008000;">//</span><span style="color: #008000;"> LocalAddr returns the local network address.</span>
<span style="color: #000000;"> LocalAddr() Addr<span style="color: #008000;"> //返回本地连接地址
</span></span><span style="color: #008000;">//</span><span style="color: #008000;"> RemoteAddr returns the remote network address.</span>
<span style="color: #000000;"> RemoteAddr() Addr <span style="color: #008000;">//返回远程连接的地址
</span></span><span style="color: #008000;">//</span><span style="color: #008000;"> SetDeadline sets the read and write deadlines associated
</span><span style="color: #008000;">//</span><span style="color: #008000;"> with the connection. It is equivalent to calling both
</span><span style="color: #008000;">//</span><span style="color: #008000;"> SetReadDeadline and SetWriteDeadline.
</span><span style="color: #008000;">//</span>
<span style="color: #008000;">//</span><span style="color: #008000;"> A deadline is an absolute time after which I/O operations
</span><span style="color: #008000;">//</span><span style="color: #008000;"> fail with a timeout (see type Error) instead of
</span><span style="color: #008000;">//</span><span style="color: #008000;"> blocking. The deadline applies to all future and pending
</span><span style="color: #008000;">//</span><span style="color: #008000;"> I/O,not just the immediately following call to Read or
</span><span style="color: #008000;">//</span><span style="color: #008000;"> Write. After a deadline has been exceeded,the connection
</span><span style="color: #008000;">//</span><span style="color: #008000;"> can be refreshed by setting a deadline in the future.
</span><span style="color: #008000;">//</span>
<span style="color: #008000;">//</span><span style="color: #008000;"> An idle timeout can be implemented by repeatedly extending
</span><span style="color: #008000;">//</span><span style="color: #008000;"> the deadline after successful Read or Write calls.
</span><span style="color: #008000;">//</span>
<span style="color: #008000;">//</span><span style="color: #008000;"> A zero value for t means I/O operations will not time out.</span>
<span style="color: #000000;"> SetDeadline(t time.Time) error <span style="color: #008000;">//设置链接读取或者写超时时间
</span></span><span style="color: #008000;">//</span><span style="color: #008000;"> SetReadDeadline sets the deadline for future Read calls
</span><span style="color: #008000;">//</span><span style="color: #008000;"> and any currently-blocked Read call.
</span><span style="color: #008000;">//</span><span style="color: #008000;"> A zero value for t means Read will not time out.</span>
<span style="color: #000000;"> SetReadDeadline(t time.Time) error <span style="color: #008000;">//单独设置读取超时时间
</span></span><span style="color: #008000;">//</span><span style="color: #008000;"> SetWriteDeadline sets the deadline for future Write calls
</span><span style="color: #008000;">//</span><span style="color: #008000;"> and any currently-blocked Write call.
</span><span style="color: #008000;">//</span><span style="color: #008000;"> Even if write times out,it may return n > 0,indicating that
</span><span style="color: #008000;">//</span><span style="color: #008000;"> some of the data was successfully written.
</span><span style="color: #008000;">//</span><span style="color: #008000;"> A zero value for t means Write will not time out.</span>
<span style="color: #000000;"> SetWriteDeadline(t time.Time) error<span style="color: #008000;">//单独设置写超时时间
}
三、go中HTTP服务处理流程
简介
网络发展,很多网络应用都是构建再 HTTP 服务基础之上。HTTP 协议从诞生到现在,发展从1.0,1.1到2.0也不断再进步。除去细节,理解 HTTP 构建的网络应用只要关注两个端---客户端(clinet)和服务端(server),两个端的交互来自 clinet 的 request,以及server端的response。所谓的http服务器,主要在于如何接受 clinet 的 request,并向client返回response。接收request的过程中,最重要的莫过于路由(router@H_301_285@),即实现一个
Multiplexer@H_301_285@器。Go中既可以使用内置的mutilplexer ---
DefautServeMux@H_301_285@,也可以自定义。Multiplexer路由的目的就是为了找到处理器函数(
handler@H_301_285@),后者将对request进行处理,同时构建response。
最后简化的请求处理流程为:
因此,理解go中的http服务,最重要就是要理解Multiplexer和handler,Golang中的Multiplexer基于ServeMux@H_301_285@结构,同时也实现了
Handler@H_301_285@接口。
对象说明:
- hander函数: 具有
func(w http.ResponseWriter,r *http.Requests)@H_301_285@签名的函数
- handler函数: 经过
HandlerFunc@H_301_285@结构包装的
handler函数@H_301_285@,它实现了ServeHTTP接口方法的函数。调用handler处理器的ServeHTTP方法时,即调用handler函数本身。
- handler对象:实现了Handler接口ServeHTTP方法的结构。
handler处理器和handler对象的差别在于,一个是函数,另外一个是结构,它们都有实现了ServeHTTP方法。很多情况下它们的功能类似,下文就使用统称为handler。
Handler
Golang没有继承,类多态的方式可以通过接口实现。所谓接口则是定义声明了函数签名,任何结构只要实现了与接口函数签名相同的方法,就等同于实现了接口。go的http服务都是基于handler进行处理。
任何结构体,只要实现了ServeHTTP方法,这个结构就可以称之为handler对象。ServeMux会使用handler并调用其ServeHTTP方法处理请求并返回响应。
ServeMux
源码部分:
<span style="color: #0000ff;">explicit <span style="color: #0000ff;">bool<span style="color: #000000;">
h Handler
pattern <span style="color: #0000ff;">string<span style="color: #000000;">
}
ServeMux结构中最重要的字段为m@H_301_285@,这是一个map,key是一些url模式,value是一个muxEntry结构,后者里定义存储了具体的url模式和handler。当然,所谓的ServeMux也实现了ServeHTTP接口,也算是一个handler,不过ServeMux的ServeHTTP方法不是用来处理request和respone,而是用来找到路由注册的handler,后面再做解释。
Server
除了ServeMux和Handler,还有一个结构Server需要了解。从http.ListenAndServe@H_301_285@的源码可以看出,它创建了一个server对象,并调用server对象的ListenAndServe方法:
查看server的结构如下:
MaxHeaderBytes </span><span style="color: #0000ff;">int</span><span style="color: #000000;">
TLSNextProto map[</span><span style="color: #0000ff;">string</span>]func(*Server,*<span style="color: #000000;">tls.Conn,Handler)
ConnState func(net.Conn,ConnState)
ErrorLog </span>*<span style="color: #000000;">log.Logger
disableKeepAlives int32 nextProtoOnce sync.Once
nextProtoErr error
}
server结构存储了服务器处理请求常见的字段。其中Handler字段也保留Handler接口。如果Server接口没有提供Handler结构对象,那么会使用DefautServeMux做multiplexer,后面再做分析。
创建HTTP服务
创建一个http服务,大致需要经历两个过程,首先需要注册路由,即提供url模式和handler函数的映射,其次就是实例化一个server对象,并开启对客户端的监听。
示例:
fmt.Println(<span style="color: #800000;">"<span style="color: #800000;">Hello World.<span style="color: #800000;">"<span style="color: #000000;">)
fmt.Fprintf(w,<span style="color: #800000;">"<span style="color: #800000;">Hello World.\n<span style="color: #800000;">"<span style="color: #000000;">)
}
func main() {
http.HandleFunc(<span style="color: #800000;">"<span style="color: #800000;">/<span style="color: #800000;">"<span style="color: #000000;">,Hello)
err := http.ListenAndServe(<span style="color: #800000;">"<span style="color: #800000;">0.0.0.0:6000<span style="color: #800000;">"<span style="color: #000000;">,nil)
<span style="color: #0000ff;">if err !=<span style="color: #000000;"> nil {
fmt.Println(<span style="color: #800000;">"<span style="color: #800000;">http listen Failed.<span style="color: #800000;">"<span style="color: #000000;">)
}
}
<span style="color: #008000;">//<span style="color: #008000;">curl <span style="color: #008000; text-decoration: underline;">http://127.0.0.1<span style="color: #008000;">:6000
<span style="color: #008000;">//<span style="color: #008000;"> 结果:Hello World
路由注册
net/http包暴露的注册路由的api很简单,http.HandleFunc选取了DefaultServeMux作为multiplexer:
DefaultServeMux是ServeMux的一个实例。当然http包也提供了NewServeMux方法创建一个ServeMux实例,默认则创建一个DefaultServeMux:
<span style="color: #0000ff;">var DefaultServeMux = &<span style="color: #000000;">defaultServeMux <span style="color: #0000ff;">var defaultServeMux ServeMux
DefaultServeMux的HandleFunc(pattern,handler)方法实际是定义在ServeMux下的:
HandlerFunc是一个函数类型。同时实现了Handler接口的ServeHTTP方法。使用HandlerFunc类型包装一下路由定义的indexHandler函数,其目的就是为了让这个函数也实现ServeHTTP方法,即转变成一个handler处理器(函数)。
func (f HandlerFunc) ServeHTTP(w ResponseWriter,r *<span style="color: #000000;">Request) {
f(w,r)
}
我们最开始写的例子中http.HandleFunc("/",Indexhandler)这样 IndexHandler 函数也有了ServeHTTP方法。ServeMux的Handle方法,将会对pattern和handler函数做一个map映射:
Server的ListenAndServe方法中,会初始化监听地址Addr,同时调用Listen方法设置监听。最后将监听的TCP对象传入Serve方法:
<span style="color: #0000ff;">if</span> err := srv.setupHTTP2_Serve(); err !=<span style="color: #000000;"> nil {
</span><span style="color: #0000ff;">return</span><span style="color: #000000;"> err
}
srv.trackListener(l,</span><span style="color: #0000ff;">true</span><span style="color: #000000;">)
defer srv.trackListener(l,</span><span style="color: #0000ff;">false</span><span style="color: #000000;">)
baseCtx :</span>= context.Background() <span style="color: #008000;">//</span><span style="color: #008000;"> base is always background,per Issue 16220</span>
ctx :=<span style="color: #000000;"> context.WithValue(baseCtx,ServerContextKey,srv)
</span><span style="color: #0000ff;">for</span><span style="color: #000000;"> {
rw,e :</span>=<span style="color: #000000;"> l.Accept()
</span><span style="color: #0000ff;">if</span> e !=<span style="color: #000000;"> nil {
</span><span style="color: #0000ff;">select</span><span style="color: #000000;"> {
</span><span style="color: #0000ff;">case</span> <-<span style="color: #000000;">srv.getDoneChan():
</span><span style="color: #0000ff;">return</span><span style="color: #000000;"> ErrServerClosed
</span><span style="color: #0000ff;">default</span><span style="color: #000000;">:
}
</span><span style="color: #0000ff;">if</span> ne,ok := e.(net.Error); ok &&<span style="color: #000000;"> ne.Temporary() {
</span><span style="color: #0000ff;">if</span> tempDelay == <span style="color: #800080;">0</span><span style="color: #000000;"> {
tempDelay </span>= <span style="color: #800080;">5</span> *<span style="color: #000000;"> time.Millisecond
} </span><span style="color: #0000ff;">else</span><span style="color: #000000;"> {
tempDelay </span>*= <span style="color: #800080;">2</span><span style="color: #000000;">
}
</span><span style="color: #0000ff;">if</span> max := <span style="color: #800080;">1</span> * time.Second; tempDelay ><span style="color: #000000;"> max {
tempDelay </span>=<span style="color: #000000;"> max
}
srv.logf(</span><span style="color: #800000;">"</span><span style="color: #800000;">http: Accept error: %v; retrying in %v</span><span style="color: #800000;">"</span><span style="color: #000000;">,e,tempDelay)
time.Sleep(tempDelay)
</span><span style="color: #0000ff;">continue</span><span style="color: #000000;">
}
</span><span style="color: #0000ff;">return</span><span style="color: #000000;"> e
}
tempDelay </span>= <span style="color: #800080;">0</span><span style="color: #000000;">
c :</span>=<span style="color: #000000;"> srv.newConn(rw)
c.setState(c.rwc,StateNew) </span><span style="color: #008000;">//</span><span style="color: #008000;"> before Serve can return</span>
<span style="color: #000000;"> go c.serve(ctx)
}
}
监听开启之后,一旦客户端请求到底,go就开启一个协程处理请求,主要逻辑都在serve方法之中。
serve方法比较长,其主要职能就是,创建一个上下文对象,然后调用Listener的Accept方法用来 获取连接数据并使用newConn方法创建连接对象。最后使用goroutine协程的方式处理连接请求。因为每一个连接都开起了一个协程,请求的上下文都不同,同时又保证了go的高并发。serve也是一个长长的方法:
方法使用defer定义了函数退出时,连接关闭相关的处理。然后就是读取连接的网络数据,并处理读取完毕时候的状态。接下来就是调用serverHandler{c.server}.ServeHTTP(w,w.req)@H_301_285@方法处理请求了。最后就是请求处理完毕的逻辑。serverHandler是一个重要的结构,它近有一个字段,即Server结构,同时它也实现了Handler接口方法ServeHTTP,并在该接口方法中做了一个重要的事情,初始化multiplexer路由多路复用器。如果server对象没有指定Handler,则使用默认的DefaultServeMux作为路由Multiplexer。并调用初始化Handler的ServeHTTP方法。
handler :=<span style="color: #000000;"> sh.srv.Handler
<span style="color: #0000ff;">if handler ==<span style="color: #000000;"> nil {
handler =<span style="color: #000000;"> DefaultServeMux
}
<span style="color: #0000ff;">if req.RequestURI == <span style="color: #800000;">"<span style="color: #800000;"><span style="color: #800000;">" && req.Method == <span style="color: #800000;">"<span style="color: #800000;">OPTIONS<span style="color: #800000;">"<span style="color: #000000;"> {
handler =<span style="color: #000000;"> globalOptionsHandler{}
}
handler.ServeHTTP(rw,req)
}
这里DefaultServeMux的ServeHTTP方法其实也是定义在ServeMux结构中的,相关代码如下:
</span><span style="color: #008000;">//</span><span style="color: #008000;"> Check for longest valid match.</span>
<span style="color: #0000ff;">var</span> n = <span style="color: #800080;">0</span>
<span style="color: #0000ff;">for</span> k,v :=<span style="color: #000000;"> range mux.m {
</span><span style="color: #0000ff;">if</span> !<span style="color: #000000;">pathMatch(k,path) {
</span><span style="color: #0000ff;">continue</span><span style="color: #000000;">
}
</span><span style="color: #0000ff;">if</span> h == nil || len(k) ><span style="color: #000000;"> n {
n </span>=<span style="color: #000000;"> len(k)
h </span>=<span style="color: #000000;"> v.h
pattern </span>=<span style="color: #000000;"> v.pattern
}
}
</span><span style="color: #0000ff;">return</span><span style="color: #000000;">
}
func (mux
</span><span style="color: #008000;">//</span><span style="color: #008000;"> CONNECT requests are not canonicalized.</span>
<span style="color: #0000ff;">if</span> r.Method == <span style="color: #800000;">"</span><span style="color: #800000;">CONNECT</span><span style="color: #800000;">"</span><span style="color: #000000;"> {
</span><span style="color: #0000ff;">return</span><span style="color: #000000;"> mux.handler(r.Host,r.URL.Path)
}
</span><span style="color: #008000;">//</span><span style="color: #008000;"> All other requests have any port stripped and path cleaned
</span><span style="color: #008000;">//</span><span style="color: #008000;"> before passing to mux.handler.</span>
host :=<span style="color: #000000;"> stripHostPort(r.Host)
path :</span>=<span style="color: #000000;"> cleanPath(r.URL.Path)
</span><span style="color: #0000ff;">if</span> path !=<span style="color: #000000;"> r.URL.Path {
_,pattern </span>=<span style="color: #000000;"> mux.handler(host,path)
url :</span>= *<span style="color: #000000;">r.URL
url.Path </span>=<span style="color: #000000;"> path
</span><span style="color: #0000ff;">return</span><span style="color: #000000;"> RedirectHandler(url.String(),StatusMovedPermanently),pattern
}
</span><span style="color: #0000ff;">return</span><span style="color: #000000;"> mux.handler(host,r.URL.Path)
}
<span style="color: #008000;">//<span style="color: #008000;"> handler is the main implementation of Handler.<span style="color: #008000;">//<span style="color: #008000;"> The path is known to be in canonical form,except for CONNECT methods.
func (mux *ServeMux) handler(host,path <span style="color: #0000ff;">string) (h Handler,pattern <span style="color: #0000ff;">string<span style="color: #000000;">) {
mux.mu.RLock()
defer mux.mu.RUnlock()
</span><span style="color: #008000;">//</span><span style="color: #008000;"> Host-specific pattern takes precedence over generic ones</span>
<span style="color: #0000ff;">if</span><span style="color: #000000;"> mux.hosts {
h,pattern </span>= mux.match(host +<span style="color: #000000;"> path)
}
</span><span style="color: #0000ff;">if</span> h ==<span style="color: #000000;"> nil {
h,pattern </span>=<span style="color: #000000;"> mux.match(path)
}
</span><span style="color: #0000ff;">if</span> h ==<span style="color: #000000;"> nil {
h,pattern </span>= NotFoundHandler(),<span style="color: #800000;">""</span><span style="color: #000000;">
}
</span><span style="color: #0000ff;">return</span><span style="color: #000000;">
}
<span style="color: #008000;">//<span style="color: #008000;"> ServeHTTP dispatches the request to the handler whose<span style="color: #008000;">//<span style="color: #008000;"> pattern most closely matches the request URL.
func (mux ServeMux) ServeHTTP(w ResponseWriter,r <span style="color: #000000;">Request) {
<span style="color: #0000ff;">if r.RequestURI == <span style="color: #800000;">"<span style="color: #800000;">*<span style="color: #800000;">"<span style="color: #000000;"> {
<span style="color: #0000ff;">if r.ProtoAtLeast(<span style="color: #800080;">1,<span style="color: #800080;">1<span style="color: #000000;">) {
w.Header().Set(<span style="color: #800000;">"<span style="color: #800000;">Connection<span style="color: #800000;">",<span style="color: #800000;">"<span style="color: #800000;">close<span style="color: #800000;">"<span style="color: #000000;">)
}
w.WriteHeader(StatusBadRequest)
<span style="color: #0000ff;">return<span style="color: #000000;">
}
h,_ :=<span style="color: #000000;"> mux.Handler(r)
h.ServeHTTP(w,r)
}
mux的ServeHTTP方法通过调用其Handler方法寻找注册到路由上的handler函数,并调用该函数的ServeHTTP方法,本例则是IndexHandler函数。 mux的Handler方法对URL简单的处理,然后调用handler方法,后者会创建一个锁,同时调用match方法返回一个handler和pattern。 在match方法中,mux的m字段是函数,因此通过迭代m寻找出注册路由的patten模式与实际url匹配的handler函数并返回。 返回的结构一直传递到mux的ServeHTTP方法,接下来调用handler函数的ServeHTTP方法,即IndexHandler函数,然后把response写到http.RequestWirter对象返回给客户端。 上述函数运行结束即`serverHandler{c.server}.ServeHTTP(w,w.req)`运行结束。接下来就是对请求处理完毕之后上希望和连接断开的相关逻辑。 至此,Golang中一个完整的http服务介绍完毕,包括注册路由,开启监听,处理连接,路由处理函数。
总结
四、HTTP客户端工具
net/http不仅提供了服务端处理,还提供了客户端处理功能。
http包中提供了Get、Post、Head、PostForm方法实现HTTP请求:
func Post(url <span style="color: #0000ff;">string,contentType <span style="color: #0000ff;">string,body io.Reader) (resp *<span style="color: #000000;">Response,err error) {
<span style="color: #0000ff;">return<span style="color: #000000;"> DefaultClient.Post(url,contentType,body)
}
<span style="color: #008000;">//<span style="color: #008000;">HEAD
func Head(url <span style="color: #0000ff;">string) (resp *<span style="color: #000000;">Response,err error) {
<span style="color: #0000ff;">return<span style="color: #000000;"> DefaultClient.Head(url)
}
<span style="color: #008000;">//<span style="color: #008000;">POSTFORM
<span style="color: #000000;">
func PostForm(url <span style="color: #0000ff;">string,data url.Values) (resp *<span style="color: #000000;">Response,err error) {
<span style="color: #0000ff;">return<span style="color: #000000;"> DefaultClient.PostForm(url,data)
}
GET请求示例
<span style="color: #800000;">"<span style="color: #800000;">fmt<span style="color: #800000;">"
<span style="color: #800000;">"<span style="color: #800000;">net/http<span style="color: #800000;">"
<span style="color: #800000;">"<span style="color: #800000;">log<span style="color: #800000;">"
<span style="color: #800000;">"<span style="color: #800000;">reflect<span style="color: #800000;">"
<span style="color: #800000;">"<span style="color: #800000;">bytes<span style="color: #800000;">"<span style="color: #000000;">
)
func main() {
resp,err :</span>= http.Get(<span style="color: #800000;">"</span><span style="color: #800000;">http://www.baidu.com</span><span style="color: #800000;">"</span><span style="color: #000000;">)
</span><span style="color: #0000ff;">if</span> err !=<span style="color: #000000;"> nil {
</span><span style="color: #008000;">//</span><span style="color: #008000;"> <a href="/tag/cuowu/" target="_blank" class="keywords">错误</a>处理</span>
<span style="color: #000000;"> log.Println(err)
<span style="color: #0000ff;">return<span style="color: #000000;">
}
defer resp.Body.Close() <span style="color: #008000;">//<a href="/tag/guanbi/" target="_blank" class="keywords">关闭</a><a href="/tag/lianjie/" target="_blank" class="keywords">链接</a></span>
headers :</span>=<span style="color: #000000;"> resp.Header
</span><span style="color: #0000ff;">for</span> k,v :=<span style="color: #000000;"> range headers {
fmt.Printf(</span><span style="color: #800000;">"</span><span style="color: #800000;">k=%v,v=%v\n</span><span style="color: #800000;">"</span><span style="color: #000000;">,k,v) <span style="color: #008000;">//所有头信息</span>
}
fmt.Printf(</span><span style="color: #800000;">"</span><span style="color: #800000;">resp status %s,statusCode %d\n</span><span style="color: #800000;">"</span><span style="color: #000000;">,resp.Status,resp.StatusCode)
fmt.Printf(</span><span style="color: #800000;">"</span><span style="color: #800000;">resp Proto %s\n</span><span style="color: #800000;">"</span><span style="color: #000000;">,resp.Proto)
fmt.Printf(</span><span style="color: #800000;">"</span><span style="color: #800000;">resp content length %d\n</span><span style="color: #800000;">"</span><span style="color: #000000;">,resp.ContentLength)
fmt.Printf(</span><span style="color: #800000;">"</span><span style="color: #800000;">resp transfer encoding %v\n</span><span style="color: #800000;">"</span><span style="color: #000000;">,resp.TransferEncoding)
fmt.Printf(</span><span style="color: #800000;">"</span><span style="color: #800000;">resp Uncompressed %t\n</span><span style="color: #800000;">"</span><span style="color: #000000;">,resp.Uncompressed)
fmt.Println(reflect.TypeOf(resp.Body)) </span>
buf := bytes.NewBuffer(make([]<span style="color: #0000ff;">byte</span>,<span style="color: #800080;">0</span>,<span style="color: #800080;">512</span><span style="color: #000000;">))
length,_ :</span>=<span style="color: #000000;"> buf.ReadFrom(resp.Body)
fmt.Println(len(buf.Bytes()))
fmt.Println(length)
fmt.Println(</span><span style="color: #0000ff;">string</span><span style="color: #000000;">(buf.Bytes()))
}
使用http.Do设置请求头、cookie等
<span style="color: #800000;">"<span style="color: #800000;">net/http<span style="color: #800000;">"
<span style="color: #800000;">"<span style="color: #800000;">strings<span style="color: #800000;">"
<span style="color: #800000;">"<span style="color: #800000;">io/IoUtil<span style="color: #800000;">"
<span style="color: #800000;">"<span style="color: #800000;">log<span style="color: #800000;">"
<span style="color: #800000;">"<span style="color: #800000;">fmt<span style="color: #800000;">"<span style="color: #000000;">
)
func main() {
client := &<span style="color: #000000;">http.Client{}
req,err := http.NewRequest(<span style="color: #800000;">"<span style="color: #800000;">POST<span style="color: #800000;">",<span style="color: #800000;">"<span style="color: #800000;">http://www.baidu.com<span style="color: #800000;">"<span style="color: #000000;">,strings.NewReader(<span style="color: #800000;">"<span style="color: #800000;">name=xxxx&passwd=xxxx<span style="color: #800000;">"<span style="color: #000000;">))
<span style="color: #0000ff;">if err !=<span style="color: #000000;"> nil {
fmt.Println(err)
<span style="color: #0000ff;">return<span style="color: #000000;">
}
req.Header.Set(</span><span style="color: #800000;">"</span><span style="color: #800000;">Content-Type</span><span style="color: #800000;">"</span>,<span style="color: #800000;">"</span><span style="color: #800000;">application/x-www-form-urlencoded; charset=UTF-8</span><span style="color: #800000;">"</span>) <span style="color: #008000;">//</span><span style="color: #008000;">设置请求头信息</span>
<span style="color: #000000;">
resp,err :=<span style="color: #000000;"> client.Do(req)
defer resp.Body.Close()
body,err :</span>=<span style="color: #000000;"> <a href="/tag/IoU/" target="_blank" class="keywords">IoU</a>til.ReadAll(resp.Body)
</span><span style="color: #0000ff;">if</span> err !=<span style="color: #000000;"> nil {
log.Println(err)
</span><span style="color: #0000ff;">return</span><span style="color: #000000;">
}
</span><span style="color: #0000ff;">var</span> res <span style="color: #0000ff;">string</span><span style="color: #000000;">
res </span>= <span style="color: #0000ff;">string</span><span style="color: #000000;">(body[:])
fmt.Println(res)
}
POST请求示例
<span style="color: #800000;">"<span style="color: #800000;">net/http<span style="color: #800000;">"
<span style="color: #800000;">"<span style="color: #800000;">strings<span style="color: #800000;">"
<span style="color: #800000;">"<span style="color: #800000;">fmt<span style="color: #800000;">"
<span style="color: #800000;">"<span style="color: #800000;">io/IoUtil<span style="color: #800000;">"<span style="color: #000000;">
)
func main() {
resp,err := http.Post(<span style="color: #800000;">"<span style="color: #800000;">http://www.baidu.com<span style="color: #800000;">"<span style="color: #000000;">,<span style="color: #800000;">"<span style="color: #800000;">application/x-www-form-urlencoded<span style="color: #800000;">"<span style="color: #000000;">,strings.NewReader(<span style="color: #800000;">"<span style="color: #800000;">username=xxx&password=xxxx<span style="color: #800000;">"<span style="color: #000000;">))
<span style="color: #0000ff;">if err !=<span style="color: #000000;"> nil {
fmt.Println(err)
<span style="color: #0000ff;">return<span style="color: #000000;">
}
defer resp.Body.Close()
body,err :</span>=<span style="color: #000000;"> <a href="/tag/IoU/" target="_blank" class="keywords">IoU</a>til.ReadAll(resp.Body)
</span><span style="color: #0000ff;">if</span> err !=<span style="color: #000000;"> nil {
fmt.Println(err)
</span><span style="color: #0000ff;">return</span><span style="color: #000000;">
}
fmt.Println(</span><span style="color: #0000ff;">string</span><span style="color: #000000;">(body))
}
PostForm请求示例
<span style="color: #800000;">"<span style="color: #800000;">net/http<span style="color: #800000;">"
<span style="color: #800000;">"<span style="color: #800000;">fmt<span style="color: #800000;">"
<span style="color: #800000;">"<span style="color: #800000;">io/IoUtil<span style="color: #800000;">"
<span style="color: #800000;">"<span style="color: #800000;">net/url<span style="color: #800000;">"<span style="color: #000000;">
)
func main() {
postParam :</span>=<span style="color: #000000;"> url.Values{
</span><span style="color: #800000;">"</span><span style="color: #800000;">name</span><span style="color: #800000;">"</span>: {<span style="color: #800000;">"</span><span style="color: #800000;">wd</span><span style="color: #800000;">"</span><span style="color: #000000;">},</span><span style="color: #800000;">"</span><span style="color: #800000;">password</span><span style="color: #800000;">"</span>: {<span style="color: #800000;">"</span><span style="color: #800000;">1234</span><span style="color: #800000;">"</span><span style="color: #000000;">},}
resp,err :</span>= http.PostForm(<span style="color: #800000;">"</span><span style="color: #800000;">https://cn.bing.com/</span><span style="color: #800000;">"</span><span style="color: #000000;">,postParam)
</span><span style="color: #0000ff;">if</span> err !=<span style="color: #000000;"> nil {
fmt.Println(err)
</span><span style="color: #0000ff;">return</span><span style="color: #000000;">
}
defer resp.Body.Close()
body,err :</span>=<span style="color: #000000;"> <a href="/tag/IoU/" target="_blank" class="keywords">IoU</a>til.ReadAll(resp.Body)
</span><span style="color: #0000ff;">if</span> err !=<span style="color: #000000;"> nil {
fmt.Println(err)
</span><span style="color: #0000ff;">return</span><span style="color: #000000;">
}
fmt.Println(</span><span style="color: #0000ff;">string</span><span style="color: #000000;">(body))
}