Go Web 基础--原理总结
Web的工作方式
对于普通的上网过程:
浏览器本身是一个客户端,当你输入URL的时候,首先浏 览器会去请求DNS服务器,通过DNS获取相应的域名对应的IP,然后通过IP地址找到IP对应的服务器 后,要求建立TCP连接,等浏览器发送完HTTP Request(请求)包后,服务器接收到请求包之后才开 始处理请求包,服务器调用自身服务,返回HTTP Response(响应)包;客户端收到来自服务器的响 应后开始渲染这个Response包里的主体(body),等收到全部的内容随后断开与该服务器之间的TCP 连接。
Web服务器的工作原理:
客户端通过TCP/IP协议建立到服务器的链接
客户端向服务器发送Http请求协议包,请求服务器里的资源
服务器向客户机发送HTTP协议应答包,如果请求的资源包含有动态语言的内容,那么服务器会调 用动态语言的解释引擎负责处理“动态内容”,并将处理得到的数据返回给客户端
客户机与服务器断开。由客户端解释HTML文档,在客户端屏幕上渲染图形结果
HTTP协议
HTTP是一种让Web服务器与浏览器(客户端)通过Internet发送与接收数据的协议,它建立在TCP协议 之上。它是一个请求、响应协议—客户端发出一个请求,服务器响应这个请 求。在HTTP中,客户端总是通过建立一个连接与发送一个HTTP请求来发起一个事务。服务器不能主动 去与客户端联系,也不能给客户端发出一个回调连接。客户端与服务器端都可以提前中断一个连接
Get与Post的区别
Get请求消息体为空,Post请求带有消息体
Get提交的数据会放在URL后,Post是将数据放在Http包的body中
GET提交的数据大小有限制(因为浏览器对URL的长度有限制),而POST方法提交的数据没有限制。
GET方式提交数据,会带来安全问题,比如一个登录页面,通过GET方式提交数据时,用户名和密 码将出现在URL上,如果页面可以被缓存或者其他人可以访问这台机器,就可以从历史记录获得该 用户的账号和密码。
基于Go语言创建一个服务器
示例代码中的关键字可以参考之前写的随笔!!!
package main import ( "fmt" "net/http" "strings" ) func GetInitInfo(w http.ResponseWriter, r *http.Request) { r.ParseForm() fmt.Println(r.Form) fmt.Println(r.URL.Path) fmt.Println(r.URL.Scheme) for k, v := range r.Form { fmt.Println("key:", k) fmt.Println("val:", strings.Join(v, "")) } fmt.Fprintf(w, "this is init information") } func main() { http.HandleFunc("/init", GetInitInfo) err := http.ListenAndServe("localhost:8080", nil) if err != nil { panic(err) } }
结果:
服务端几个必备的概念
Request:用户请求的信息,用来解析用户请求的信息,包括Post,Get,Cookie,Url等信息
Response:服务器需要反馈给客户端的信息
Conn:用户的每次请求链接
Handler:处理请求和生成返回信息的处理逻辑
分析Http包的运行机制
1.创建Listen Socket,监听指定的端口,等待客户端请求到来
2.Listen Socket 接收客户端的请求,得到Client Socket,接下来通过Client Socket与客户端通信
3.处理客户端的请求, 首先从Client Socket读取HTTP请求的协议头, 如果是POST方法, 还可 能要读取客户端提交的数据, 然后交给相应的handler处理请求, handler处理完毕准备好客户 端需要的数据, 通过Client Socket写给客户端。
三个值得探究的问题
1.如何监听端口?
2.如何接收客户端请求?
3.如何分配handler?
http包中的一段源码
func (srv *Server) Serve(l net.Listener) error {
if fn := testHookServerServe; fn != nil {
fn(srv, l) // call hook with unwrapped listener
}
origListener := l
l = &onceCloseListener{Listener: l}
defer l.Close()
if err := srv.setupHTTP2_Serve(); err != nil {
return err
}
if !srv.trackListener(&l, true) {
return ErrServerClosed
}
defer srv.trackListener(&l, false)
baseCtx := context.Background()
if srv.BaseContext != nil {
baseCtx = srv.BaseContext(origListener)
if baseCtx == nil {
panic("BaseContext returned a nil context")
}
}
var tempDelay time.Duration // how long to sleep on accept failure
ctx := context.WithValue(baseCtx, ServerContextKey, srv)
for {
rw, err := l.Accept()
if err != nil {
select {
case <-srv.getDoneChan():
return ErrServerClosed
default:
}
if ne, ok := err.(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", err, tempDelay)
time.Sleep(tempDelay)
continue
}
return err
}
connCtx := ctx
if cc := srv.ConnContext; cc != nil {
connCtx = cc(connCtx, rw)
if connCtx == nil {
panic("ConnContext returned nil")
}
}
tempDelay = 0
c := srv.newConn(rw)
c.setState(c.rwc, StateNew, runHooks) // before Serve can return
go c.serve(connCtx)
}
}
Go是通过一个函数 ListenAndServe 来处理这些事情的,这个底层其实这样处理的:初始化一个server对象,然后调用了 net.Listen("tcp", addr) ,也就是底层 用TCP协议搭建了一个服务,然后监控我们设置的端口。
调用了 srv.Serve(net.Listener) 函数,这个函数就是处理接收客户端的请求信息。这个函数里面起了 一个 for{} ,首先通过Listener接收请求,其次创建一个Conn,最后单独开了一个goroutine, 把这个请求的数据当做参数扔给这个conn去服务: go c.serve() 。这个就是高并发体现了,用户 的每一次请求都是在一个新的goroutine去服务,相互不影响。
conn首先会解析request: c.readRequest() ,然后 获取相应的handler: handler := c.server.Handler ,也就是我们刚才在调用函数 ListenAndServe 时候的第二个参数,我们前面例子传递的是nil,也就是为空,那么默认获取 handler = DefaultServeMux ,那么这个变量用来做什么的呢?对,这个变量就是一个路由器, 它用来匹配url跳转到其相应的handle函数,那么这个我们有设置过吗?有,我们调用的代码里面第一 句不是调用了 http.HandleFunc("/", GetInitInfo) 嘛。这个作用就是注册了请求 / 的路由规 则,当请求uri为”/init“,路由就会转到函数GetInitInfo,DefaultServeMux会调用ServeHTTP方 法,这个方法内部其实就是调用GetInitInfo本身,最后通过写入response的信息反馈到客户端。