使用Go语言构建HTTP服务

发表时间: 2024-05-15 17:26

HTTP请求包(浏览器信息)

我们先来看看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 //客户端能接收的mine

Accept-Encoding:gzip,deflate,sdch //是否支持流压缩

Accept-Charset:UTF-8,*;q=0.5 //客户端字符编码集

/*空行,用于分割请求头和消息体 */

/*消息体,请求资源参数,例如POST传递的参数 */

package mainimport (  "fmt"  "net")func main() {    //监听    listener, err := net.Listen("tcp", ":8000")    if err != nil {        fmt.Println("net.Listen err = ", err)        return    }    //阻塞等待用户的连接    conn, err1 := listener.Accept()    if err1 != nil {        fmt.Println("listener.Accept err1 = ", err1)        return    }    defer conn.Close()    //接收客户端的数据    buf := make([]byte, 1024*4)    n, err2 := conn.Read(buf)    if n == 0 {        fmt.Println("Read err = ", err2)        return		}		fmt.Printf("#%v#", string(buf[:n]))}
D:\Go\study\src>go run http.go/* 用浏览器运行127.0.0.1:8000 * /#GET / HTTP/1.1 //请求行: 请求方法 请求URI HTTP协议/协议版本Host: 127.0.0.1:8000 //服务端的主机名Connection: keep-aliveUpgrade-Insecure-Requests: 1User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3 //浏览器信息Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 //客户端能接收的mineAccept-Encoding: gzip, deflate, br //是否支持流压缩Accept-Language: zh-CN,zh;q=0.9

客户端请求和服务端响应:

httpServer.go

package mainimport ("fmt""net/http")//服务端编写的业务逻辑处理程序func myHandler(w http.ResponseWriter, r *http.Request) {fmt.Fprintln(w, "Hello world")}func main() {http.HandleFunc("/go", myHandler)//在指定的地址进行监听, 开启一个HTTPhttp.ListenAndServe("127.0.0.1:8000", nil)}
D:\Go\study\src>go run httpServer.gohttp://127.0.0.1:8000/goHello world

响应报文格式

httpClient.go

package mainimport ("fmt""net")func main() {      //主动连接服务器      conn, err := net.Dial("tcp", ":8000")      if err != nil {          fmt.Println("net.Dial err = ", err)          return      }      defer conn.Close()      requestBuf := "#GET /go HTTP/1.1\r\nHost: 127.0.0.1:8000\r\nConnection: keep-alive\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: zh-CN,zh;q=0.9\r\n\r\n"      //先发请求包, 服务器才会回响应包      conn.Write([]byte(requestBuf))      //接收服务回复的响应包      buf := make([]byte, 1024*4)      n, err1 := conn.Read(buf)      if n == 0 {          fmt.Println("conn.Read err1 = ", err1)          return      }      //打印响应报文      fmt.Printf("#%v#", string(buf[:n]))      /*      #HTTP/1.1 200 OK      Date: Wed, 24 Oct 2018 01:56:51 GMT      Content-Length: 12      Content-Type: text/plain; charset=utf-8      Hello world      #      */}

编写http服务器

【实例】

package mainimport ("fmt""net/http")//w, 给客户端回复数据//r, 读取客户端发送的数据func HandConn(w http.ResponseWriter, r *http.Request) {    w.Write([]byte("Hello go ")) //给客户端回复数据    fmt.Println("r.Method = ", r.Method) //Method指定HTTP方法(GET、POST、PUT等)。对客户端,""代表GET。    fmt.Println("r.URL = ", r.URL) //URL在服务端表示被请求的URI,在客户端表示要访问的URL。    fmt.Println("r.Header = ", r.Header) //Header字段用来表示HTTP请求的头域。    fmt.Println("r.Body = ", r.Body) //Body是请求的主体。    // w.Write([]byte("Hello go ")) //给客户端回复数据}func main() {    //注册处理函数, 用户连接, 自动调用指定的处理函数    http.HandleFunc("/", HandConn)    //监听绑定    http.ListenAndServe(":8000", nil)}

Window平台下的cmd终端打印日志存在卡死的现象, 客户端的浏览器访问时无法响应结果, 需要使用回车键(Enter)将日志打印出来, 然后才能将结果响应给客户端的浏览器

解决的方法: 1 右键 -> 属性 -> 关闭快速编辑模式(Q)

2 将响应结果的语句放到打印日志语句的前面, 也就是打印日志语句放到最后面

编写HTTP客户端

package mainimport (    "fmt"    "net/http")func main() {    //resp, err := http.Get("http://www.baidu.com")    resp, err := http.Get("http://127.0.0.1:8000/go") //前面的http不能省略    if err != nil {        fmt.Println("http.Get err = ", err)        return    }    defer resp.Body.Close()    fmt.Println("Status = ", resp.Status)    fmt.Println("StatusCode = ", resp.StatusCode)    fmt.Println("Header = ", resp.Header)    //fmt.Println("Body = ", resp.Body)    buf := make([]byte, 4*1024)    var tmp string    for {        n, err := resp.Body.Read(buf)        if n == 0 {            fmt.Println("read err = ", err)            break    		}    		tmp += string(buf[:n])    }    fmt.Println("tmp = ", tmp)    /*    Status = 200 OK    StatusCode = 200    Header = map[Date:[Wed, 24 Oct 2018 03:15:22 GMT] Content-Length:[12] Content-Type:[text/plain; charset=utf-8]]    read err = EOF    tmp = Hello world    */}

关于 net/http 包:

net/http本身基于 goroutine 实现, 通过新建协程处理新的连接任务;

默认是长连接: net/http 客户端发起请求时 header 标记 HTTP/1.1;

连接可复用:默认创建连接池;

关于连接池使用:池中找不到空闲连接时, 会重新 new 一个连接, 而不会阻塞等待一个连接;

关于连接断开:如果对端关闭连接, 由于 Go Runtime 会在底层进行 epoll wait, 监听 close 事件并关闭相关 fd 资源, 上层应用可以被告知哪些连接已关闭, 从而进行相关的逻辑处理;

关于time_wait与close_wait:收到对方的 fin 请求, 内核会将连接置为close_wait状态; 主动发起关闭连接请求的一方, 会将连接置为time_wait状态。