大神的教程https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/03.0.md
https://www.yuque.com/liujunjie/pshhng/inyv2t
Web是基于http协议的一个服务,Go语言里面提供了一个完善的net/http包,通过http包可以很方便的搭建起来一个可以运行的Web服务。同时使用这个包能很简单地对Web的路由,静态文件,模版,cookie等数据进行设置和操作。
它通过HTTP协议与客户端通信
HTTP是一种让Web服务器与浏览器(客户端)通过Internet发送与接收数据的协议,它建立在TCP协议之上,一般采用TCP的80端口。
HTTP协议是无状态的,同一个客户端的这次请求和上次请求是没有对应关系的,对HTTP服务器来说,它并不知道这两个请求是否来自同一个客户端。为了解决这个问题, Web程序引入了Cookie机制来维护连接的可持续状态。
HTTP协议是建立在TCP协议之上的
我们先来看看Request包的结构, Request包分为3部分,第一部分叫Request line(请求行), 第二部分叫Request header(请求头),第三部分是body(主体)。header和body之间有个空行,请求包的例子所示:
GET /domains/example/ HTTP/1.1 //请求行: 请求方法 请求URI HTTP协议/协议版本
Host:www.iana.org //服务端的主机名
User-Agent:Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.94 Safari/537.4 //浏览器信息
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 //客户端能接收的MIME
Accept-Encoding:gzip,deflate,sdch //是否支持流压缩
Accept-Charset:UTF-8,*;q=0.5 //客户端字符编码集
//空行,用于分割请求头和消息体
//消息体,请求资源参数,例如POST传递的参数
我们再来看看HTTP的response包,他的结构如下:
HTTP/1.1 200 OK //状态行
Server: nginx/1.0.8 //服务器使用的WEB软件名及版本
Date:Date: Tue, 30 Oct 2012 04:14:25 GMT //发送时间
Content-Type: text/html //服务器发送信息的类型
Transfer-Encoding: chunked //表示发送HTTP包是分段发的
Connection: keep-alive //保持连接状态
Content-Length: 90 //主体内容长度
//空行 用来分割消息头和主体
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"... //消息体
客户机通过TCP/IP协议建立到服务器的TCP连接
浏览器会去请求DNS服务器,通过DNS获取相应的域名对应的IP,然后通过IP地址找到IP对应的服务器后,要求建立TCP连接,等浏览器发送完HTTP Request(请求)包后,服务器接收到请求包之后才开始处理请求包,服务器调用自身服务,返回HTTP Response(响应)包;客户端收到来自服务器的响应后开始渲染这个Response包里的主体(body),等收到全部的内容随后断开与该服务器之间的TCP连接。
DNS(Domain Name System)是“域名系统”的英文缩写,是一种组织成域层次结构的计算机和网络服务命名系统,它用于TCP/IP网络,它从事将主机名或域名转换为实际IP地址的工作。
(Uniform Resource Locator)是“统一资源定位符”的英文缩写,用于描述一个网络上的资源, 基本格式如下
scheme://host[:port#]/path/.../[?query-string][#anchor]
scheme 指定底层使用的协议(例如:http, https, ftp)
host HTTP服务器的IP地址或者域名
port# HTTP服务器的默认端口是80,这种情况下端口号可以省略。如果使用了别的端口,必须指明,例如 http://www.cnblogs.com:8080/
path 访问资源的路径
query-string 发送给http服务器的数据
anchor 锚
几个概念Request Response Conn Handler
Request:用户请求的信息,用来解析用户的请求信息,包括post、get、cookie、url等信息
Response:服务器需要反馈给客户端的信息
Conn:用户的每次请求链接
Handler:处理请求和生成返回信息的处理逻
如何监听端口?
go是通过一个函数ListenAndServe来处理这些事情的,这个底层其实这样处理的:初始化一个server对象,然后调用了net.Listen("tcp", addr),也就是底层用TCP协议搭建了一个服务,然后监控我们设置的端口。
如何接收客户端请求?
调用了srv.Serve(net.Listener)函数,这个函数就是处理接收客户端的请求信息。这个函数里面起了一个for{},首先通过Listener接收请求,其次创建一个Conn,最后单独开了一个goroutine,把这个请求的数据当做参数扔给这个conn去服务:go c.serve()。这个就是高并发体现了,用户的每一次请求都是在一个新的goroutine去服务,相互不影响。
如何分配handler?
conn首先会解析request:c.readRequest(),然后获取相应的handler:handler := c.server.Handler,也就是我们刚才在调用函数ListenAndServe时候的第二个参数,我们前面例子传递的是nil,也就是为空,那么默认获取handler = DefaultServeMux,那么这个变量用来做什么的呢?对,这个变量就是一个路由器,它用来匹配url跳转到其相应的handle函数,那么这个我们有设置过吗?有,我们调用的代码里面第一句不是调用了http.HandleFunc("/", sayhelloName)嘛。这个作用就是注册了请求/的路由规则,当请求uri为"/",路由就会转到函数sayhelloName,DefaultServeMux会调用ServeHTTP方法,这个方法内部其实就是调用sayhelloName本身,最后通过写入response的信息反馈到客户端。
设置路由对应的handle
// http.HandleFunc("/", sayhelloName)// Handle registers the handler for the given pattern.// If a handler already exists for pattern, Handle panics.func(mux*ServeMux)Handle(patternstring,handlerHandler) {mux.mu.Lock()//锁defermux.mu.Unlock()//解锁ifpattern==""{panic("http: invalid pattern") }ifhandler==nil{panic("http: nil handler") }if_,exist:=mux.m[pattern];exist{panic("http: multiple registrations for "+pattern) }ifmux.m==nil{mux.m=make(map[string]muxEntry) }e:=muxEntry{h:handler,pattern:pattern}mux.m[pattern]=eifpattern[len(pattern)-1]=='/'{mux.es=appendSorted(mux.es,e) }ifpattern[0]!='/'{mux.hosts=true}}
根据路由获取对应handle
//serverHandler{c.server}.ServeHTTP(w, w.req) 中调用 会判断 sh.srv.Handler是否为空,如果为空就赋值为//DefaultServeMux 为公共变量ServeMux,//http.HandleFunc("/", sayhelloName) //设置访问的路由 会将pattern设置到ServeMux的m map[string]muxEntrytypeserverHandlerstruct{srv*Server}func(shserverHandler)ServeHTTP(rwResponseWriter,req*Request) {handler:=sh.srv.Handlerifhandler==nil{handler=DefaultServeMux}ifreq.RequestURI=="*"&&req.Method=="OPTIONS"{handler=globalOptionsHandler{} }handler.ServeHTTP(rw,req)}typeServeMuxstruct{musync.RWMutexmmap[string]muxEntryes[]muxEntry// slice of entries sorted from longest to shortest.hostsbool// whether any patterns contain hostnames}
设置监听端
ln,err:=net.Listen("tcp",addr)c:=srv.newConn(rw)// A conn represents the server side of an HTTP connection.c.setState(c.rwc,StateNew)// before Serve can returngoc.serve(ctx)serverHandler{c.server}.ServeHTTP(w,w.req)
go c.serve(ctx) 是一个for循环里面,接收端口传过的信息
// 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.//// HTTP/2 support is only enabled if the Listener returns *tls.Conn// connections and they were configured with "h2" in the TLS// Config.NextProtos.//// Serve always returns a non-nil error and closes l.// After Shutdown or Close, the returned error is ErrServerClosed.func(srv*Server)Serve(lnet.Listener)error{iffn:=testHookServerServe;fn!=nil{fn(srv,l)// call hook with unwrapped listener}l=&onceCloseListener{Listener:l}deferl.Close()iferr:=srv.setupHTTP2_Serve();err!=nil{returnerr}if!srv.trackListener(&l,true) {returnErrServerClosed}defersrv.trackListener(&l,false)vartempDelaytime.Duration// how long to sleep on accept failurebaseCtx:=context.Background()// base is always background, per Issue 16220ctx:=context.WithValue(baseCtx,ServerContextKey,srv)for{rw,e:=l.Accept()ife!=nil{select{case<-srv.getDoneChan():returnErrServerCloseddefault: }ifne,ok:=e.(net.Error);ok&&ne.Temporary() {iftempDelay==0{tempDelay=5*time.Millisecond}else{tempDelay*=2}ifmax:=1*time.Second;tempDelay>max{tempDelay=max}srv.logf("http: Accept error: %v; retrying in %v",e,tempDelay)time.Sleep(tempDelay)continue}returne}tempDelay=0c:=srv.newConn(rw)c.setState(c.rwc,StateNew)// before Serve can returngoc.serve(ctx) }}
网友评论