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 main
import (
"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-alive
Upgrade-Insecure-Requests: 1
User-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 //客户端能接收的mine
Accept-Encoding: gzip, deflate, br //是否支持流压缩
Accept-Language: zh-CN,zh;q=0.9
客户端请求和服务端响应:
httpServer.go
package main
import (
"fmt"
"net/http"
)
//服务端编写的业务逻辑处理程序
func myHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello world")
}
func main() {
http.HandleFunc("/go", myHandler)
//在指定的地址进行监听, 开启一个HTTP
http.ListenAndServe("127.0.0.1:8000", nil)
}
D:\Go\study\src>go run httpServer.go
http://127.0.0.1:8000/go
Hello world
响应报文格式
httpClient.go
package main
import (
"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 main
import (
"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 main
import (
"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状态。