HTTP 协议简介
什么是HTTP协议
HTTP
是 Hypertext Transfer Protocol
的缩写,也就是超文本标记协议,多数的 HTTP 协议运行在 TCP 上,新型的 HTTP3 协议运行在 UDP 上。
简单来说,HTTP 协议就是一个 客户端与服务端
通信的协议,客户端发送数据给服务端,那么服务端要认识这些数据表示什么,服务端返回给客户端的数据,客户端也要认识这些数据
HTTP发展
HTTP其实诞生也不过30多年,但是对人类的影响是巨大的,随着技术的不断发展进步,HTTP协议也在不断地完善和改进
HTTP/0.9
最古老的 HTTP 协议并不是 HTTP/1.0,而是 HTTP/0.9,但是 HTTP/0.9 并没有成为标准,也没有进行大规模的使用。
HTTP/0.9 的组成极其简单,对于客户端,仅仅支持 GET
方法,服务端也只能返回 HTML
文本内容
HTTP/0.9 协议不能满足实际需求,也仅仅在实验室中使用
HTTP/1.0
1996年, HTTP/1.0 公布,HTTP/1.0 在 HTTP/0.9 的基础上进行了多方面的扩展,完善了请求行信息,添加了请求头部,响应内容也支持头部信息,和目前的协议格式基本一致
并且开始支持三个请求方法:
POST
: 向服务端发送数据GET
: 用来请求对应的资源HEAD
: 用于获取响应头部信息,与GET
十分类似,但是没有响应主体
HTTP/1.0 默认使用的是短连接,也就是说,每一次向服务端请求一次资源,便需要重新创建一个新的 socket,向服务端请求数据,然后关闭 socket。
HTTP/1.1
在 HTTP/1.0 发布没几个月,1997年,HTTP/1.1 也正式公布了,并且成为第一个标准化的 HTTP 协议。
HTTP/1.1 中默认开启长连接,也就是指定头部的 Connection:keep-alive ,使用长连接的好处就是可以减少 socket 创建的消耗,因为创建一个 socket 需要进行系统调用,这个需要消耗比较多的资源。
此外HTTP/1.1中增加了一系列其他的请求方法,比如 PUT
,DELETE
,OPTIONS
等等,并且可以使用头部的 Cache-Control
控制资源的缓存。
HTTP/1.1 在互联网中广泛使用,现在仍然有很多网站在使用 HTTP/1.1
,我们可以使用 wireshark
进行抓包分析
HTTP/2
HTTP/1.1 得到广泛使用,但是随着使用时间的越来越长,也暴露出了不少了的问题,比如说发送的内容都是明文字符,容易被第三方截获造成消息的泄露,在 HTTP/2 中采用了 SSL/TLS
协议,对数据进行加密,提高数据传输过程中的安全性,不过数据的加密需要较多的CPU资源和时间。
HTTP/1.1 在 socket
利用率方面还是有所缺陷,HTTP/2 中提出了 多路复用
机制,能够同时发送多个 HTTP 请求,然后服务端返回多个响应,如下图:
由于HTTP头部信息内容比较大,并且在很多时候发送的HTTP请求头部都有一部分的重复,HTTP/2中采用头部压缩技术 HPACK
,使用字典对头部信息进行保存,这样下一次发送的时候就可以只发送对应的键就好了,大大压缩的头部消息。
HTTP/2 还支持 服务端推送
,服务端在客户端请求资源的时候可以推断下一次客户端可能需要的资源,发送给客户端缓存到本地,下一次使用的时候可以直接使用缓存中的资源,当然客户端也可以拒绝服务端推送的资源。
实现简单的HTTP客户端和服务端
其实服务端和客户端的沟通就是通过 socket 进行连接,然后通过发送的数据和解析数据,客户端和服务端便可以互相理解,从而做出对应的动作,这里为了简单,使用 HTTP/1.1 协议作为讲解,请求网站 http://httpbin.org
客户端
根据我们上面的理解,客户端请求数据的时候有一个请求行和一个请求头部,我们只需要满足这个条件,便可以模拟浏览器发送一个 HTTP
请求了。
首先我们先与服务端建立连接
1 | // 使用host:port形式 |
然后构建HTTP请求数据
1 | msg := strings.Builder{} |
接着将数据发送到服务端
1 | _, err = conn.Write([]byte(msg.String())) |
最后读取服务端返回的数据即可
1 | buf := bufio.NewReader(conn) |
服务端
对于服务端来说,我们需要解析客户端请求的数据,然后进一步做出响应,这里为了简单,仅仅向客户端显示一个 Hello World
字符串。
首先我们需要监听一个端口
1 | listener, err := net.Listen("tcp", ":8888") |
然后接收客户端的请求,得到一个与客户端的连接
1 | for { |
然后向连接中写入HTTP响应格式的数据即可
1 | func handle(conn net.Conn){ |
使用curl
命令请求便可以获取到对应的响应信息
1 | $ curl http://localhost:8888 |
生活杂笔,学习杂记,偶尔随便写写东西。