这周的作业是阅读《Golang web 应用开发》,Golang web应用开发有很多相关内容,表单、访问数据库、web服务、安全加密、设计web框架等等,本文就其中的一个重点,web基础,用go搭建一个简单的web服务来谈谈自己学习到的知识和理解
1.1 http协议,web的运行原理
想必学习过计算机网络的同学都会清楚web也就是浏览器的工作方式,浏览网页的时候,会打开浏览器,输入网址后按下回车键,然后就会显示出你想要浏览的内容
这其中蕴含的过程简单回忆一下:输入URL的时候,首先浏览器会去请求DNS服务器,通过DNS获取相应的域名对应的IP,然后通过IP地址找到IP对应的服务器后,要求建立TCP连接,等浏览器发送完HTTP Request(请求)包后,服务器接收到请求包之后才开始处理请求包,服务器调用自身服务,返回HTTP Response(响应)包;客户端收到来自服务器的响应后开始渲染这个Response包里的主体(body),等收到全部的内容随后断开与该服务器之间的TCP连接
其中URL解析和DNS解析,URL按照规定的格式进行解析,集中式域名解析和分布式域名解析,一种是一个一个访问,一种是深度迭代访问DNS服务器,这些都不是重点
当今网络的请求和响应数据传输,都是遵循最常用的http协议,http协议也是web工作的核心,HTTP是一种让Web服务器与浏览器(客户端)通过Internet发送与接收数据的协议,它建立在TCP协议之上,一般采用TCP的80端口。它是一个请求、响应协议–客户端发出一个请求,服务器响应这个请求。在HTTP中,客户端总是通过建立一个连接与发送一个HTTP请求来发起一个事务。服务器不能主动去与客户端联系,也不能给客户端发出一个回调连接。客户端与服务器端都可以提前中断一个连接。例如,当浏览器下载一个文件时,你可以通过点击“停止”键来中断文件的下载,关闭与服务器的HTTP连接。HTTP协议是无状态的。
1.2 如何用go搭建一个web服务器
我们从最基础最简单的那种开始学起,Go语言里面提供了一个完善的net/http包,学会使用这个包就可以搭建起一个基础可运行的web服务器,同时可以对Web的路由,静态文件,模版,cookie等数据进行设置和操作。
package main
import (
"fmt"
"net/http"
"strings"
"log"
)
func sayhelloName(w http.ResponseWriter, r *http.Request) {
r.ParseForm() //解析参数,默认是不会解析的
fmt.Println(r.Form) //这些信息是输出到服务器端的打印信息
fmt.Println("path", r.URL.Path)
fmt.Println("scheme", r.URL.Scheme)
fmt.Println(r.Form["url_long"])
for k, v := range r.Form {
fmt.Println("key:", k)
fmt.Println("val:", strings.Join(v, ""))
}
fmt.Fprintf(w, "Hello astaxie!") //这个写入到w的是输出到客户端的
}
func main() {
http.HandleFunc("/", sayhelloName) //设置访问的路由
err := http.ListenAndServe(":9090", nil) //设置监听的端口
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}
build代码后,执行web.exe(已经在9090端口监听http链接请求),在浏览器输入http://localhost:9090查看,Hello astaxie!浏览器的页面输出,通过http的两个函数就可以写一个简单的web服务器了
1.3 接下来介绍底层原理,go如何让web服务器正常工作起来
先来看服务器的几个概念,Request:是用户请求的信息 Response:服务器反馈给客户端的信息 Conn:用户的每次请求链接 Handler:处理请求和生成返回信息的方法
先来看http包运行机制
- 创建Listen Socket, 监听指定的端口, 等待客户端请求到来。
- Listen Socket接受客户端的请求, 得到Client Socket, 接下来通过Client Socket与客户端通信。
- 处理客户端的请求, 首先从Client Socket读取HTTP请求的协议头, 如果是POST方法, 还可能要读取客户端提交的数据, 然后交给相应的handler处理请求, handler处理完毕准备好客户端需要的数据, 通过Client Socket写给客户端。
Go是通过一个函数ListenAndServe
来处理监听端口、接收客户端请求和分配handler这三件事情的,这个底层其实这样处理的:初始化一个server对象,然后调用了net.Listen("tcp", addr)
,也就是底层用TCP协议搭建了一个服务,然后监控我们设置的端口。内部源码摘录如下
func (srv *Server) Serve(l net.Listener) error {
defer l.Close()
var tempDelay time.Duration // how long to sleep on accept failure
for {
rw, e := l.Accept()
if e != nil {
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
}
log.Printf("http: Accept error: %v; retrying in %v", e, tempDelay)
time.Sleep(tempDelay)
continue
}
return e
}
tempDelay = 0
c, err := srv.newConn(rw)
if err != nil {
continue
}
go c.serve()
}
}
我们仔细分析分析上面的代码
-
监控之后如何接收客户端的请求呢?
上面代码执行监控端口之调用srv.Serve(net.Listener),这个函数就是用来处理用户的请求信息的,函数里有个for{},首先通过Listener接受请求,然后创建一个Conn,最后单独开一个goroutine,把请求的参数扔给Conn服务,go c.serve(),go的高并发也是体现在这里,go的每一次请求都是一个新的goroutine服务
-
如何分配到具体的函数来处理请求呢?
conn首先会解析request:
c.readRequest()
,然后获取相应的handler:handler := c.server.Handler
,也就是调用函数ListenAndServe
时候的第二个参数,我们前面例子传递的是nil时,默认获取
handler = DefaultServeMux
,这个变量的作用是一个路由器,它用来匹配url跳转到其相应的handle函数,我们调用的代码里面第一句调用了http.HandleFunc("/", sayhelloName)
这里就是有过这种设置。这个作用就是注册了请求/
的路由规则,当请求uri为"/",路由就会转到函数sayhelloName,DefaultServeMux会调用ServeHTTP方法,这个方法内部其实就是调用sayhelloName本身,最后通过写入response的信息反馈到客户端。
整个函数的运行流程,具体每一块的作用如下图
问题解答后,我们这样就大概知道go如何让web跑起来,至少是如何处理接受和处理请求信息的