概述
Web工作方式
我们平时浏览网页的时候,会打开浏览器,输入网址后按下回车键,然后会显示你想要浏览的内容。在这个看似简单的用户行为背后,到底隐藏了些什么呢??
对于普通的上网过程,过程为:浏览器本身是一个客户端,当输入URL的时候,首先浏览器会去请求
DNS服务器。
通过DNS获取相应的域名对应的IP,然后通过IP地址找到IP对应的服务器后,要求建立TCP连接。
等浏览器发送完HTTPRequest(请求)包后,服务器接收到请求包开始处理请求包。服务器调用自身服
务,返回HTTPResponse(响应)包。
客户端收到来看服务器的响应后开始渲染这个Response包里的主体(body),等收到全部的内容随后断开与该服务器之间的TCP连接。
一个Web服务器也被称为HTTP服务器,它通过HTTP协议与客户端通信。这个客户端通常指的是Web浏览器(包括手机上的各种客户端))
Web服务器的工作原理可以简单的归纳为:
客户端通过TCP/IP协议建立到服务器的TCP连接
客户端向服务器发送HTTP协议请求包,请求服务器里的资源文档
服务器向客户端发送HTTP协议应答包
客户端与服务器断开连接,客户端解释HTML文档,在客户端屏幕上渲染图形结果
HTTP协议
HTTP协议的全称:超文本传输协议,HyperText Transfer Protocol。
HTTP协议是互联网上应用最为广泛的一种网络协议,详细规定了浏览器和万维网服务器之间互相通信的规则,通过因特网传递万维网文档的数据传送协议。
HTTP协议通常承载于TCP协议之上,有时也承载于TLS或SSL协议层之上,这个时候就成了常说的
HTTPS。
地址(URL)
URL全称为:Unique Resource Location,用来表示网络资源,可以理解为网络文件路径。
URL的格式如下:
1 2 http://host[":"port][/abs_path] http://192.168.1.1/html/index
URL的长度有限制,不同的服务器的限制值太太相同,但是不能无限长。
请求报文
HTTP请求报文由请求行,请求头部,请求包体等组成。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package mainimport ( "fmt" "net" ) func main () { listener, err := net.Listen("tcp" , ":8000" ) if err != nil { fmt.Println("Listen err = " , err) return } defer listener.Close() conn, err1 := listener.Accept() if err1 != nil { fmt.Println("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])) }
运行程序,用浏览器请求URL: http://127.0.0.1:8000/ ,程序后台打印结果:
1 2 3 4 5 6 7 8 9 #GET / HTTP/1.1 Host : 127.0.0.1:8000Connection : keep-aliveUpgrade-Insecure-Requests : 1User-Agent : Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML,like Gecko) Chrome/65.0.3325.146 Safari/537.36Accept :text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 Accept-Encoding : gzip, deflate, brAccept-Language : zh-CN,zh;q=0.9,en;q=0.8
响应数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport ( "fmt" "net/http" ) func myHandler (w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "hello world" ) } func main () { http.HandleFunc("/go" , myHandler) http.ListenAndServe("127.0.0.1:8000" , nil ) }
运行程序,用浏览器请求URL: http://127.0.0.1:8000/go ,程序会响应"hello world"在浏览器页面
上。
响应报文格式
服务端代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 package mainimport ( "fmt" "net/http" ) func myHandler (w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "hello world" ) } func main () { http.HandleFunc("/go" , myHandler) http.ListenAndServe("127.0.0.1:8000" , nil ) }
客户端代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package mainimport ( "fmt" "net" ) func main () { conn, err := net.Dial("tcp" , ":8000" ) if err != nil { fmt.Println("dial err = " , err) return } defer conn.Close() requestBuf := "GET /go HTTP/1.1\r\nAccept: image/gif, image/jpeg, image/pjpeg, application/x-ms-application, application/xaml+xml, application/xms-xbap, */*\r\nAccept-Language: zh-Hans-CN,zh-Hans;q=0.8,enUS;q=0.5,en;q=0.3\r\nUser-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR3.0.30729; .NET CLR 3.5.30729)\r\nAccept-Encoding: gzip, deflate\r\nHost:127.0.0.1:8000\r\nConnection: Keep-Alive\r\n\r\n" conn.Write([]byte (requestBuf)) buf := make ([]byte , 1024 *4 ) n, err1 := conn.Read(buf) if n == 0 { fmt.Println("Read err = " , err1) return } fmt.Printf("#%v#" , string (buf[:n])) }
先运行服务端程序,再运行客户端程序,客户端程序得到结果如下:
1 2 3 4 5 #HTTP/1.1 200 OK Date : Thu, 09 Apr 2020 13:07:35 GMTContent-Length : 12Content-Type : text/plain; charset=utf-8hello world
HTTP状态码
状态码由三位数据组成,第一位数据表示响应的类型,常用的状态码有五大类:
状态码
含义
1xx
表示服务器已接收到客户端的请求,客户端可继续发送请求
2xx
表示服务器已成功接收到请求,并进行处理
3xx
表示服务器要求客户端重定向
4xx
表示客户端的请求有非法内容
5xx
表示服务器未能正常处理客户端的请求而出现意外错误
常见的状态码举例:
1 2 3 4 5 6 7 8 200 OK:客户端请求成功 400 Bad Request:请求报文有语法错误 401 Unauthorized:未授权 403 Forbidden:服务器拒绝服务 404 Not Found:请求资源不存在 500 Internal Server Error:服务器内部错误 502 bad Gateway: 网关错误 503 Server Unavailable:服务器临时不能处理客户端请求
HTTP编程
Go请求标准库内建提供了net/http包,涵盖了HTTP客户端和服务器的具体实现。
使用net/http包可以很方便的编写HTTP客户端或服务端程序。
HTTP服务端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package mainimport ( "fmt" "net/http" ) func HandConn (w http.ResponseWriter, r *http.Request) { fmt.Println("r.Method = " , r.Method) fmt.Println("r.URL = " , r.URL) fmt.Println("r.Header = " , r.Header) fmt.Println("r.Body = " , r.Body) w.Write([]byte ("hello go" )) } func main () { http.HandleFunc("/go" , HandConn) http.ListenAndServe(":8000" , nil ) }
运行程序后,用浏览器打开URL: http://127.0.0.1:8000/go ,页面显示"hello go"
服务端运行结果:
1 2 3 4 5 6 7 r.Method = GET r.URL = /go r.Header = map[Connection:[keep-alive] Cache-Control:[max-age=0] UpgradeInsecure-Requests:[1] User-Agent:[Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.146 Safari/537.36] Accept :[text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8] Accept-Encoding:[gzip, deflate, br] Accept-Language:[zhCN,zh;q=0.9,en;q=0.8]] r.Body = {}
HTTP客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package mainimport ( "fmt" "net/http" ) func main () { resp, err := http.Get("http://127.0.0.1:8000/go" ) 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) 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) }
运行客户端程序,会自动请求"http://127.0.0.1:8000/go "这个地址,此时客户端后台打印:
1 2 3 4 5 Status = 200 OK StatusCode = 200 Header = map[Date:[Thu, 09 Apr 2020 13:25:54 GMT] Content-Length:[8] ContentType:[text/plain; charset=utf-8]] read err = EOF tmp = hello go
此时服务端后台打印结果:
1 2 3 4 r.Method = GET r.URL = /go r.Header = map [User-Agent:[Go-http-client/1.1 ] Accept-Encoding:[gzip]] r.Body = {}