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状态。