vlambda博客
学习文章列表

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:123DEF:456

 

对应的数据就是这样:

 

Http协议简易分析

 

页面路径中?开始为参数,通过&分割参数,每个参数用key=val形式。一般框架都可以自动解析路径中的参数。

1.2. 响应协议

 

Http协议简易分析


和请求协议是一样。需要注意的就是这里的headers内的Content-Length,是比较重要的一个返回参数,用来标记返回消息的数据长度。同理Content-Type也就很重要,一般用来标记返回的数据是什么类型,比如application/json、image/jpeg等等,http是文本传输协议,主要指的是协议头,而内容就可以是二进制流,不需要做成字符串。

 

 

假设服务器返回了funCallback”这个字符串,字符串长度11

对应的主要数据就是这样:

Http协议简易分析

 

1.3. 测试代码

1.3.1. http服务

python写个简单的服务。

 

from urllib.parse import urlsplit,parse_qsfrom http.server import HTTPServer, BaseHTTPRequestHandler
class ProHeader(BaseHTTPRequestHandler): def do_GET(self): print("pageaddr: " + self.path) try: print("ABC: " + self.headers['ABC']) #获取headers中的ABC参数 123 print("DEF: " + self.headers['DEF']) #获取headers中的ABC参数 456 except 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

postget协议一样,区别就在于post发送时,第一行的请求方法由GET变成了POST

发送时headers内加上Content-LengthContent-Type这种,然后传输完协议头继续就可以传输数据内容了。

 

我们就简单写一个通过Post发送图像的例子测试一下。

2.1. http服务

服务器接收POST请求,然后通过Content-Length直接读发送的数据大小,然后存储成a.jpg就行。

 

from urllib.parse import urlsplit, parse_qs from http.server import HTTPServer, BaseHTTPRequestHandler 
class 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();