HTTP 编程
概述
Web 工作方式
对于普通的上网过程,浏览器本身是一个客户端,当我们输入URL的时候,首先浏览器会去请求 DNS 服务器,通过DNS获取相应的域名对应的IP,然后通过IP 地址找到IP 对应的服务器后,要求建立 TCP 连接,等浏览器发送完 HTTP Request(请求)包后,服务器接收到请求包之后才开始处理请求包,服务器调用自身服务,返回HTTPResponse(响应)包:客户端收到来自服务器的响应后开始渲染这个Response 包里的主体(body),等收到全部的内容随后断开与该服务器之间的TCP连接。
一个 Web 服务器也被称为HTTP 服务器,它通过 HTTP 协议与客户端通信。这个客户端通常指的是 Web浏览器(其实手机端客户端内部也是浏览器实现的)。
Web 服务器的工作原理可以简单地归纳为:
- 客户机通过 TCP/IP 协议建立到服务器的 TCP 连接
- 客户端向服务器发送HTTP 协议请求包,请求服务器里的资源文档
- 服务器向客户机发送HTTP协议应答包,如果请求的资源包含有动态语言的内容,那么服务器会调用动态语言的解释引擎负责处理“动态内容”,并将处理得到的数据返回给客户端
- 客户机与服务器断开。由客户端解释HTML文档,在客户端屏幕上渲染图形结果。
HTTP 协议
超文本传输协议(HTTP,HypcrTcxt Transfer Protocol)是互联网上应用最为广泛的一种网络协议,它详细规定了浏览器和万维网服务器之间互相通信的规则,通过因特网传送万维网文档的数据传送协议。
HTTP 协议通常承载于 TCP 协议之上,有时也承载于 TLS 或 SSL 协议层之上,这个时候,中就成了我们常说的HTTPS。如下图所示:
地址URL
URL 全称为 Unique Resource Location,用来表示网络资源,可以理解为网络文件路径。
格式如下:
http://host[":"port][abs_path]
http://192.168.31.1/html/index
URL的长度有限制,不同的服务器的限制值不太相同,但是不能无限长。
http请求包格式
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 listener.Close()
//接受客户端的数据
buf := make([]byte, 1024*4)
n, err2 := conn.Read(buf)
if n == 0 {
fmt.Println("conn.Read.err2=", err2)
return
}
fmt.Printf("#%v#", string(buf[:n]))
}
格式:
HTTP 请求报文由请求行、请求头部、空行、请求包体4个部分组成,如下图所示:
请求行
请求行由方法字段、URL字段 和HTTP协议版本字段 3个部分组成,他们之间使用空格隔开。常用的HTTP 请求方法有GET、POST。
GET:
- 当客户端要从服务器中读取某个资源时,使用 GET 方法。GET 方法要求服务器将 URL定位的资源放在响应报文的数据部分,回送给客户端,即向服务器请求某个资源。
- 使用 GET 方法时,请求参数和对应的值附加在 URL 后面,利用一个问号(“?)代表 URI的结尾与请求参数的开始,传递参数长度受限制,因此 GET 方法不适合用于上传数据。
- 通过 GET方法来获取网页时,参数会显示在浏览器地址栏上,因此保密性很差。
- 注意:报文GET 后面为我们要的资源
POST:
- 当客户端给服务器提供信息较多时可以使用POST 方法,POST方法向服务器提交数据,比如完成表单数据的提交,将数据提交给服务器处理。
- GET 一般用于获取/查询资源信息,POST 会附带用户数据,一般用于更新资源信息。POST 方法将请求参数封装在HTTP 请求数据中,而且长度没有限制,因为POST携带的数据,在HTTP的请求正文中,以名称/值的形式出现,可以传输大量数据。
请求头
- 请求头部为请求报文添加了一些附加信息,由“名/值”对组成,每行一对,名和值之问使用冒号分隔。
- 请求头部通知服务器有关于客户端请求的信息,典型的请求头有:
空行
最后一个请求头之后是一个空行,发送回车符和换行符,通知服务器以下不再有请求头。
请求包体
请求包体不在 GET 方法中使用,而是 POST 方法中使用。
POST方法适用于需要客户填写表单的场合。与请求包体相关的最常使用的是包体类型Content-Type 和包体长度 Content-Length。
响应报文
测试代码
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\nCache-Control: max-age=0\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3\r\nAccept-Encoding: gzip, deflate, \r\nAccept-Language: zh-CN,zh;q=0.9,ja;q=0.8,en;q=0.7\r\n\r\n"
//先发请求包,服务器回响应包
conn.Write([]byte(requestBuf))
buf := make([]byte, 1024*4)
n, err1 := conn.Read(buf)
if err1 != nil {
fmt.Println("conn.Read.err1=", err1)
return
}
//打印响应报文
fmt.Printf("#%v#", string(buf[:n]))
}
http响应格式
HTTP 响应报文由状态行、响应头部、空行、响应包体4个部分组成,如下图所示:
状态行
状态行由 HTTP 协议版本字段、状态码和状态码的描述文本3个部分组成,他们之间使用空格隔开。
状态码:
状态码由三位数字组成,第一位数字表示响应的类型,常用的状态码有五大类如下所示:
常见的有:
响应头部
与请求头部类似,为响应报文添加了一些附加信息,响应头可能包括: