Go Web编程(一)—— 搭建简单Web服务器 | 青训营笔记

145 阅读4分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 6 天

先通过两个简单的例子简单认识一下web服务

获取单个URL

  1. 获取单个URL的内容,并打印其源文本

    net包
    Go语言标准库文档中文版(studygolang.com)

// 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而喜提报错,百思不得其解;去搜报错原因,才发现只是大小写错了😢

  1. 若请求失败,则会调用os.Exit()终止进程,返回status = 1的错误码
  2. 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 请求包

包括请求行、请求头和消息体三部分

  1. Request line
    包括请求方法(GET/POST等等),请求URL,协议(HTTP/HTTPS等)和协议版本
  2. Request header
    包括服务器主机名,浏览器信息,和其他一些标头
  3. 有一行空行用于分割请求头和消息体
  4. Request body
    请求的资源参数

更多详情参考 HTTP | MDN (mozilla.org)

HTTP 响应包

  1. 状态行
    由 协议版本号, 状态码(类似404), 状态消息三部分组成。
  2. 响应头
  3. 一行空行用于分割响应头和响应体
  4. 消息体(响应体)

搭建一个简单的Web服务器

image.png

上述代码,go build 之后,然后执行 web.exe, 这个时候就已经在 9090 端口监听 http 链接请求了

在浏览器输入 http://localhost:9090

可以看到浏览器页面输出了 Hello astaxie!

过程分析

  1. 创建Listen Socket,监听指定的端口,等待客户端请求
  2. Listen Socket接收客户端请求,得到 Client Socket, 然后通过 Client Socket 与客户端通信
  3. 处理客户端的请求,首先从 Client Socket 读取 HTTP 请求的协议头,如果是 POST 方法,还可能要读取客户端提交的数据,然后交给相应的 handler 处理请求,handler 处理完毕准备好客户端需要的数据,通过 Client Socket 写给客户端(类似于MVC框架)

ListenAndServe()函数

Go中通过ListenAndServe(address string,handler Handler)函数来监听端口 && 接收客户端请求 && 分配handler

  1. 调用ListenAndServe函数后,会在其中创建一个Server结构体的实例
  2. 然后执行Server.ListenAndServe()方法,在其中创建一个服务器Listener
  3. 在返回Server.ListenAndServe()方法时,将Listener传给Server.Serve()方法并调用Server.Serve()方法
  4. 在真正的Server.Serve()方法中用readRequest()方法读取客户端请求,对请求进行处理,设置状态码,关闭请求等等

参考资料

《Go Web 编程》 | Go 技术论坛 (learnku.com)

前言 · Go语言圣经 (studygolang.com)

Go源码分析——http.ListenAndServe()是如何工作的_爱神CODE的博客-CSDN博客_listenandserve