Http协议简易分析

什么TCP几层模型,什么几次握手就不扯了。让我们从代码层面来解析下http协议,主要就是用socket实现一个客户端的http,通过手动构造socket传输连接数据可以很清晰的明白http的协议内容。

  1. GET
    1. 请求协议

 

简单来说就是所有数据都是一行一行的文本(所以才叫文本协议吧),用\r\n换行,最后结束的时候多一个空行。

假设我们要发送get请:http://127.0.0.1:1234/ad1/ad2/?name=a&age=1

然后其中的headers中包含两个键值对:ABC:123、DEF:456

对应的数据就是这样:

 

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

    1. 响应协议

 

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

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

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

 

    1. 测试代码
      1. http服务

用python写个简单的服务。

from urllib.parse import urlsplit, parse_qs  

from 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. 客户端请求

这里直接用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个headers

            sb.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. 运行结果

服务器接收数据并解析数据头,结果如下:

客户端发送后,解析服务端的结果如下:

 

  1. POST

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

然后最重要的就是post可以发送大一些的数据,不像get只能在headers和地址中写字符串。 

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

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

    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()

    1. 客户端请求

客户端读取图像,将长度写入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();

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值