Http协议简易分析
什么TCP几层模型,什么几次握手就不扯了。让我们从代码层面来解析下http协议,主要就是用socket实现一个客户端的http,通过手动构造socket传输连接数据可以很清晰的明白http的协议内容。
1. GET
1.1. 请求协议
简单来说就是所有数据都是一行一行的文本(所以才叫文本协议吧),用\r\n换行,最后结束的时候多一个空行。
假设我们要发送get请:http://127.0.0.1:1234/ad1/ad2/?name=a&age=1
然后其中的headers中包含两个键值对:ABC:123、DEF:456
对应的数据就是这样:
页面路径中?开始为参数,通过&分割参数,每个参数用key=val形式。一般框架都可以自动解析路径中的参数。
1.2. 响应协议
和请求协议是一样。需要注意的就是这里的headers内的Content-Length,是比较重要的一个返回参数,用来标记返回消息的数据长度。同理Content-Type也就很重要,一般用来标记返回的数据是什么类型,比如application/json、image/jpeg等等,http是文本传输协议,主要指的是协议头,而内容就可以是二进制流,不需要做成字符串。
假设服务器返回了“funCallback”这个字符串,字符串长度11
对应的主要数据就是这样:
1.3. 测试代码
1.3.1. http服务
用python写个简单的服务。
from urllib.parse import urlsplit,parse_qsfrom http.server import HTTPServer, BaseHTTPRequestHandlerclass ProHeader(BaseHTTPRequestHandler):def do_GET(self):print("pageaddr: " + self.path)try:print("ABC: " + self.headers['ABC']) #获取headers中的ABC参数 123print("DEF: " + self.headers['DEF']) #获取headers中的ABC参数 456except Exception as e:print(e)rsp = "funCallback"rsp = rsp.encode("utf8")self.send_response(200)self.send_header("Content-Length", str(len(rsp)))self.end_headers()self.wfile.write(rsp)if __name__ == '__main__':httpd = HTTPServer(('', 1234), ProHeader)httpd.serve_forever()
1.3.2. 客户端请求
这里直接用C#的socket,按照协议组合数据,然后进行发送和接收。
Socket sokClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);sokClient.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 1234));StringBuilder sb = new StringBuilder();sb.Append("GET /ad1/ad2/?name=a&age=1 HTTP/1.1\r\n");//第一行//n个headerssb.Append("ABC:123\r\n");sb.Append("DEF:456\r\n");sb.Append("\r\n");//末尾空行//发送请求数据string datstr = sb.ToString();byte[] bs = Encoding.UTF8.GetBytes(datstr);sokClient.Send(bs, bs.Length, 0);//接收请求返回数据byte[] by = new byte[1];MemoryStream msHead = new MemoryStream();int ContentLength = 0;while (true){int ret = sokClient.Receive(by);if (ret <= 0) break;msHead.Write(by, 0, 1);string revdata = Encoding.UTF8.GetString(msHead.ToArray());if (revdata.Contains("\r\n\r\n")){//判断为末尾空行,获取返回的数据长度ContentLength = Convert.ToInt32(Regex.Match(revdata, "(?<=(Content-Length:)).*(?=\r)").Value.ToString());break;}}//根据ContentLength长度接收数据内容byte[] revDataBy = new byte[ContentLength];int revLen = 0;while (revLen < ContentLength){int read = sokClient.Receive(revDataBy, revLen, ContentLength - revLen, SocketFlags.None);revLen += read;}Console.Write("====head====\n");Console.WriteLine(Encoding.UTF8.GetString(msHead.ToArray()));Console.Write("\n====rev data====\n");Console.WriteLine(Encoding.UTF8.GetString(revDataBy));
1.3.3. 运行结果
服务器接收数据并解析数据头,结果如下:
客户端发送后,解析服务端的结果如下:
2. POST
post和get协议一样,区别就在于post发送时,第一行的请求方法由GET变成了POST。
发送时headers内加上Content-Length和Content-Type这种,然后传输完协议头继续就可以传输数据内容了。
我们就简单写一个通过Post发送图像的例子测试一下。
2.1. http服务
服务器接收POST请求,然后通过Content-Length直接读发送的数据大小,然后存储成a.jpg就行。
from urllib.parse import urlsplit, parse_qsfrom http.server import HTTPServer, BaseHTTPRequestHandlerclass ProHeader(BaseHTTPRequestHandler):def do_POST(self):jpgdat = self.rfile.read(int(self.headers['Content-Length']))pf = open("a.jpg","wb")pf.write(jpgdat)pf.close()if __name__ == '__main__':httpd = HTTPServer(('', 1234), ProHeader)httpd.serve_forever()
2.2. 客户端请求
客户端读取图像,将长度写入headers内的Content-Length,然后发送数据头和内容即可。
FileInfo fi = new FileInfo("D:\\1.jpg");byte[] jpgbuf = new byte[fi.Length];fi.OpenRead().Read(jpgbuf, 0, jpgbuf.Length);StringBuilder sb = new StringBuilder();sb.Append("POST /ad1 HTTP/1.1\r\n");//第一行sb.Append("Content-Length:" + jpgbuf.Length.ToString() + "\r\n"); //headers 设置标志数据长度sb.Append("\r\n");//末尾空行//发送请求数据string datstr = sb.ToString();byte[] bs = Encoding.UTF8.GetBytes(datstr);Socket sokClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);sokClient.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 1234));//将数据头和数据内容发送sokClient.Send(bs, bs.Length, 0);sokClient.Send(jpgbuf, jpgbuf.Length, 0);Console.ReadLine();
