http编程
# 1,概述
# 1,Web 工作方式
我们平时浏览网页的时候,会打开浏览器,输入网址后按下回车键,然后就会显示出你想要浏览的内容。在这个看似简单的用户行为背后,到底隐藏了些什么呢?
对于普通的上网过程,系统其实是这样做的:浏览器本身是一个客户端,当你输入 URL 的时候,首先浏览器会去请求 DNS 服务器,通过 DNS 获取相应的域名对应的 IP,然后通过 IP 地址找到 IP 对应的服务器后,要求建立 TCP 连接,等浏览器发送完 HTTP Request(请求)包后,服务器接收到请求包之后才开始处理请求包,服务器调用自身服务,返回 HTTP Response(响应)包;客户端收到来自服务器的响应后开始渲染这个 Response 包里的主体(body),等收到全部的内容随后断开与该服务器之间的 TCP 连接。
一个 Web 服务器也被称为 HTTP 服务器,它通过 HTTP 协议与客户端通信。这个客户端通常指的是 Web 浏览器 (其实手机端客户端内部也是浏览器实现的)。
Web 服务器的工作原理可以简单地归纳为:
- 客户机通过 TCP/IP 协议建立到服务器的 TCP 连接
- 客户端向服务器发送 HTTP 协议请求包,请求服务器里的资源文档
- 服务器向客户机发送 HTTP 协议应答包,如果请求的资源包含有动态语言的内容,那么服务器会调用动态语言的解释引擎负责处理 “动态内容”,并将处理得到的数据返回给客户端
- 客户机与服务器断开。由客户端解释 HTML 文档,在客户端屏幕上渲染图形结果
# 2,HTTP 协议
超文本传输协议 (HTTP,HyperText Transfer Protocol) 是互联网上应用最为广泛的一种网络协议,它详细规定了浏览器和万维网服务器之间互相通信的规则,通过因特网传送万维网文档的数据传送协议。
HTTP 协议通常承载于 TCP 协议之上,有时也承载于 TLS 或 SSL 协议层之上,这个时候,就成了我们常说的 HTTPS。如下图所示:
# 3,地址(URL)
URL 全称为 Unique Resource Location,用来表示网络资源,可以理解为网络文件路径。
URL 的格式如下:
http://host[":"port][abs_path]
http://192.168.31.1/html/index
2
URL 的长度有限制,不同的服务器的限制值不太相同,但是不能无限长。
# 2,HTTP 报文浅析
# 1,请求报文格式
# 1,测试代码
服务器测试代码:
package main
import (
"fmt"
"net"
)
func main() {
//监听
listener, err := net.Listen("tcp", "127.0.0.1:8000")
if err != nil {
fmt.Println("Listen err = ", err)
return
}
defer listener.Close()
//阻塞等待用户链接
conn, err := listener.Accept()
if err != nil {
fmt.Println("Accept err = ", err)
return
}
//接收用户的请求
buf := make([]byte, 1024)
n, err1 := conn.Read(buf)
if n == 0 {
fmt.Println("Read err1 = ", err1)
return
}
fmt.Printf("#%v#", string(buf[:n]))
defer conn.Close() //关闭当前用户链接
}
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
32
先把服务器跑起来,然后在浏览器当中访问。会得到如下返回信息:
# 2,请求报文格式说明
HTTP 请求报文由请求行、请求头部、空行、请求包体 4 个部分组成,如下图所示:
1,请求行 请求行由
方法字段
、URL
字段 和HTTP 协议版本
字段 3 个部分组成,他们之间使用空格隔开。常用的 HTTP 请求方法有 GET、POST。GET:
- 当客户端要从服务器中读取某个资源时,使用 GET 方法。GET 方法要求服务器将 URL 定位的资源放在响应报文的数据部分,回送给客户端,即向服务器请求某个资源。
- 使用 GET 方法时,请求参数和对应的值附加在 URL 后面,利用一个问号 (“?”) 代表 URL 的结尾与请求参数的开始,传递参数长度受限制,因此 GET 方法不适合用于上传数据。
- 通过 GET 方法来获取网页时,参数会显示在浏览器地址栏上,因此保密性很差。
POST:
- 当客户端给服务器提供信息较多时可以使用 POST 方法,POST 方法向服务器提交数据,比如完成表单数据的提交,将数据提交给服务器处理。
- GET 一般用于获取 / 查询资源信息,POST 会附带用户数据,一般用于更新资源信息。POST 方法将请求参数封装在 HTTP 请求数据中,而且长度没有限制,因为 POST 携带的数据,在 HTTP 的请求正文中,以名称 / 值的形式出现,可以传输大量数据。
2,请求头部 请求头部为请求报文添加了一些附加信息,由 “名 / 值” 对组成,每行一对,名和值之间使用冒号分隔。
请求头部通知服务器有关于客户端请求的信息,典型的请求头有:
请求头 含义 User-Agent 请求的浏览器类型 Accept 客户端可识别的响应内容类型列表,星号 “” 用于按范围将类型分组,用“ / ”指示可接受全部类型,用 “type/” 指示可接受 type 类型的所有子类型 Accept-Language 客户端可接受的自然语言 Accept-Encoding 客户端可接受的编码压缩格式 Accept-Charset 可接受的应答的字符集 Host 请求的主机名,允许多个域名同处一个 IP 地址,即虚拟主机 connection 连接方式 (close 或 keepalive) Cookie 存储于客户端扩展字段,向同一域名的服务端发送属于该域的 cookie 3,空行 最后一个请求头之后是一个空行,发送回车符和换行符,通知服务器以下不再有请求头。
4,请求包体 请求包体不在 GET 方法中使用,而是 POST 方法中使用。 POST 方法适用于需要客户填写表单的场合。与请求包体相关的最常使用的是包体类型 Content-Type 和包体长度 Content-Length。
# 2,响应报文格式
# 1,测试代码
服务器示例代码:
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)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
然后启动服务器程序,等待客户端连接。
客户端测试示例代码:
package main
import (
"fmt"
"net"
)
func main() {
//主动连接服务器
conn, err := net.Dial("tcp", ":8000")
if err != nil {
fmt.Println("Dial err = ", err)
}
defer conn.Close()
requstBuf := "GET /go HTTP/1.1\r\nAccept: image/gif, image/jpeg, image/pjpeg, application/x-ms-application, application/xaml+xml, application/x-ms-xbap, */*\r\nAccept-Language: zh-Hans-CN,zh-Hans;q=0.8,en-US;q=0.5,en;q=0.3\r\nUser-Agent: Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; .NET CLR 2.0.50727; .NET CLR 3.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(requstBuf))
//接收服务器恢复的响应包
buf := make([]byte, 1024*4)
n, err := conn.Read(buf)
if n == 0 {
fmt.Println("Read err = ", err)
return
}
//打印相应报文
fmt.Printf("#%v#", string(buf[:n]))
}
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
启动程序,测试 http 的成功响应报文:
启动程序,测试 http 的失败响应报文:
# 2,响应报文格式说明
HTTP 响应报文由状态行、响应头部、空行、响应包体 4 个部分组成,如下图所示:
1,状态行
状态行由 HTTP 协议版本字段、状态码和状态码的描述文本 3 个部分组成,他们之间使用空格隔开。
状态码由三位数字组成,第一位数字表示响应的类型,常用的状态码有五大类如下所示:
状态码 含义 1xx 表示服务器已接收了客户端请求,客户端可继续发送请求 2xx 表示服务器已成功接收到请求并进行处理 3xx 表示服务器要求客户端重定向 4xx 表示客户端的请求有非法内容 5xx 表示服务器未能正常处理客户端的请求而出现意外错误 常见的状态码举例:
状态码 含义 200 OK
客户端请求成功 400 Bad Request 请求报文有语法错误 401 Unauthorized 未授权 403 Forbidden
服务器拒绝服务 404 Not Found
请求的资源不存在 500 Internal Server Error
服务器内部错误 503 Server Unavailable 服务器临时不能处理客户端请求 (稍后可能可以) 2,响应头部
响应头可能包括:
响应头 含义 Location Location 响应报头域用于重定向接受者到一个新的位置 Server Server 响应报头域包含了服务器用来处理请求的软件信息及其版本 Vary 指示不可缓存的请求头列表 Connection 连接方式 3,空行
最后一个响应头部之后是一个空行,发送回车符和换行符,通知服务器以下不再有响应头部。
4,响应包体
服务器返回给客户端的文本信息。
# 3,http 编程。
Go 语言标准库内建提供了 net/http 包,涵盖了 HTTP 客户端和服务端的具体实现。使用 net/http 包,我们可以很方便地编写 HTTP 客户端或服务端的程序。
# 1,http 服务器端。
代码如下:
package main
import (
"fmt"
"net/http"
)
//w,给客户端回复数据
//req,读取客户端发送的数据
func HandConn(w http.ResponseWriter, req *http.Request) {
fmt.Println(req.RemoteAddr, "连接成功") //远程网络地址
fmt.Println(req.Method) //请求方法
fmt.Println(req.URL.Path)
fmt.Println(req.Header)
fmt.Println(req.Body)
w.Write([]byte("hello world")) //给客户端返回数据
}
func main() {
//注册处理函数,用户连接,自动调用指定的处理函数
http.HandleFunc("/eryajf", HandConn)
//监听绑定
http.ListenAndServe(":8000", nil)
}
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
运行之后,使用 postman 进行 get 请求:
# 2,客户端。
现在写一个客户端。
package main
import (
"fmt"
"net/http"
)
func main() {
resp, err := http.Get("http://127.0.0.1:8000/eryajf")
if err != nil {
fmt.Println("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)
}
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
32
33
34
首先运行上边的服务端,然后再运行下边的客户端,得到如下内容:
$ go run 5_http客户端.go
status = 200 OK
StatusCode = 200
Header = map[Date:[Sat, 29 Jun 2019 11:16:40 GMT] Content-Length:[11] Content-Type:[text/plain; charset=utf-8]]
read err = EOF
tmp = hello world
2
3
4
5
6