这是我参与「第五届青训营 」伴学笔记创作活动的第 6 天
先通过两个简单的例子简单认识一下web服务
获取单个URL
-
获取单个URL的内容,并打印其源文本
// Fetch prints the content found at a URL.
package main
import (
"fmt"
"io/ioutil"
"net/http"
"os"
)
func main() {
for _, url := range os.Args[1:] { //os.Args 获取命令行参数
resp, err := http.Get(url)
if err != nil {
fmt.Fprintf(os.Stderr, "fetch: %v\n", err)
os.Exit(1)
}
b, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
fmt.Fprintf(os.Stderr, "fetch: reading %s: %v\n", url, err)
os.Exit(1)
}
fmt.Printf("%s", b)
}
}
过程:
a. resp, err := http.Get(url) 创建HTTP请求
b. 请求过程没有error,则,在resp结构体中得到访问的请求结构
c. resp.Body 包含一个可读的服务器响应流
d. ioutil.ReadAll()从resp.Body中读取全部内容
e. resp.Body.Close()关闭Body流,防止资源泄露
f. fmt.Printf()标准输出流
中间有很多需要判断
err!=nil的时候
注意
fmt.Printf的P是大写。由于写惯了Java,我写过几次fmt.print而喜提报错,百思不得其解;去搜报错原因,才发现只是大小写错了😢
- 若请求失败,则会调用os.Exit()终止进程,返回status = 1的错误码
- reap.Status变量可以获取HTTP协议的状态码
并发获取多个URL
打印获取内容的大小和经过的事件,不会打印获取的内容
package main
import (
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"time"
)
func main() {
start := time.Now()
ch := make(chan string) //创建channel
for _, url := range os.Args[1:] {
go fetch(url, ch) // start a goroutine
}
for range os.Args[1:] {
fmt.Println(<-ch) // receive from channel ch
//若是 ch <- 则代表发送给ch
}
fmt.Printf("%.2fs elapsed\n", time.Since(start).Seconds())
}
func fetch(url string, ch chan<- string) {
start := time.Now()
resp, err := http.Get(url)
if err != nil {
ch <- fmt.Sprint(err)
// 将错误信息发送给 ch
return
}
nbytes, err := io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close() // 关闭流
if err != nil {
ch <- fmt.Sprintf("while reading %s: %v", url, err)
return
}
secs := time.Since(start).Seconds()
ch <- fmt.Sprintf("%.2fs %7d %s", secs, nbytes, url)
}
Web工作方式
一些涉及到计网的东西就不赘述了
建立TCP连接后,浏览器发送HTTP Request(请求)包,服务器收到请求包后开始处理,然后返回HTTP Response(响应)包
浏览器收到响应包后开始渲染Response包里的主体(body),收到全部内容后断开连接
HTTP 请求包
包括请求行、请求头和消息体三部分
- Request line
包括请求方法(GET/POST等等),请求URL,协议(HTTP/HTTPS等)和协议版本 - Request header
包括服务器主机名,浏览器信息,和其他一些标头 - 有一行空行用于分割请求头和消息体
- Request body
请求的资源参数
更多详情参考 HTTP | MDN (mozilla.org)
HTTP 响应包
- 状态行
由 协议版本号, 状态码(类似404), 状态消息三部分组成。 - 响应头
- 一行空行用于分割响应头和响应体
- 消息体(响应体)
搭建一个简单的Web服务器
上述代码,go build 之后,然后执行 web.exe, 这个时候就已经在 9090 端口监听 http 链接请求了
在浏览器输入 http://localhost:9090
可以看到浏览器页面输出了 Hello astaxie!
过程分析
- 创建Listen Socket,监听指定的端口,等待客户端请求
- Listen Socket接收客户端请求,得到 Client Socket, 然后通过 Client Socket 与客户端通信
- 处理客户端的请求,首先从 Client Socket 读取 HTTP 请求的协议头,如果是 POST 方法,还可能要读取客户端提交的数据,然后交给相应的 handler 处理请求,handler 处理完毕准备好客户端需要的数据,通过 Client Socket 写给客户端(类似于MVC框架)
ListenAndServe()函数
Go中通过ListenAndServe(address string,handler Handler)函数来监听端口 && 接收客户端请求 && 分配handler
- 调用ListenAndServe函数后,会在其中创建一个
Server结构体的实例 - 然后执行
Server.ListenAndServe()方法,在其中创建一个服务器Listener - 在返回Server.ListenAndServe()方法时,将Listener传给Server.Serve()方法并调用
Server.Serve()方法 - 在真正的Server.Serve()方法中用
readRequest()方法读取客户端请求,对请求进行处理,设置状态码,关闭请求等等
参考资料
《Go Web 编程》 | Go 技术论坛 (learnku.com)
Go源码分析——http.ListenAndServe()是如何工作的_爱神CODE的博客-CSDN博客_listenandserve