zoukankan      html  css  js  c++  java
  • 动手做webserver的核心之http解析

    简介

            webserver往小里说核心功能就是socket管理、url处理、http协议处理、业务dll管理等;下面简介绍一下http协议:超文本传输协议(HTTP)是一种通信协议,当时就是为web传输设计的一个基于tcp的协议;基于这个字面上理解,可以简单的点说就是用tcp来传输文本、数据的一种编解码格式。传输协议一般比如说定个长度+内容,或者以回车符作为结尾等方式。http协议是文本传输协议,所以也是采用回车符来结尾的方式来实现编码传输解析的; 这里使用分析工具来简单分析一下http的基本格式:

          从上图可以看出http的基本格式一般大体为成header和body,header的第一行是固定的status line,header与body之间用回车符+空行+回车符来分隔的

    GET

          webserver一般收到get请求如下:

    1  GET /yswenli/p/8858669.html HTTP/1.1 
    2  Host www.cnblogs.com
    3  User-Agent Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36  Accept image/webp,image/*,*/*;q=0.8 
    4  Referer https://www.cnblogs.com/yswenli/p/8858669.html 
    5  Accept-Encoding gzip, deflate, sdch  
    6  Accept-Language zh-CN,zh;q=0.8

          也就是说如果发起了一个get请求的时候,webserver只是收到一个一个http header,直接全部收取后解析就行

    POST

           如果是一个post请求呢?查看html相关内容,可以看post有很多方式,总体上分成三种,一种是默认的application/x-www-form-urlencoded:

    1 POST http://www.example.com HTTP/1.1
    2 Content-Type: application/x-www-form-urlencoded;charset=utf-8
    3 
    4 title=test&sub%5B%5D=1&sub%5B%5D=2&sub%5B%5D=3

          这种格式就是带有header和body两部分内容,解析的时候可以先按get收头部,然后再取body,body里面的参数取值是先urldecode再htmldecode就可以了。

          第二种就是json、xml、plaine等:

    1 POST http://www.example.com HTTP/1.1 
    2 Content-Type: application/json;charset=utf-8
    3 
    4 {"title":"test","sub":[1,2,3]}

          这种和上面类似,不过在取body的时候直接读就行~

          还有第三种multipart/form-data,这种模式格式比较复杂,它支持多键值对、多文件的方式,使用特定的boundary来分隔body

     1 POST http://www.example.com HTTP/1.1
     2 Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryrGKCBY7qhFd3TrwA
     3 
     4 ------WebKitFormBoundaryrGKCBY7qhFd3TrwA
     5 Content-Disposition: form-data; name="name"
     6 
     7 yswenli
     8 ------WebKitFormBoundaryrGKCBY7qhFd3TrwA
     9 Content-Disposition: form-data; name="file"; filename="chrome.png"
    10 Content-Type: image/png
    11 
    12 PNG ... content of chrome.png ... 
    13 ------WebKitFormBoundaryrGKCBY7qhFd3TrwA
    14 Content-Disposition: form-data; name="text"
    15 
    16 title
    17 ------WebKitFormBoundaryrGKCBY7qhFd3TrwA
    18 Content-Disposition: form-data; name="file"; filename="chrome2.png"
    19 Content-Type: image/png
    20 
    21 PNG ... content of chrome.png ...
    22 ------WebKitFormBoundaryrGKCBY7qhFd3TrwA--

          这种格式也是带有header和body两部分内容,解析的时候先按get收头部,然后再取body,body里面的内容先按boundry进行分割,然后再根据content-type进行判断取出文件内容。

    TCP转HTTP

          即然拿到相关数据格式,并研究分析了http的编码格式,下面就可以开始解码了。首先使用一个data来接收socket的tcp数据包,然后统一分析http包的header,直到截取到 +""+ 为止。

     1             using (MemoryStream ms = new MemoryStream(buffer))
     2             {
     3                 ms.Position = 0;
     4 
     5                 using (SAEA.Common.StreamReader streamReader = new SAEA.Common.StreamReader(ms))
     6                 {
     7                     while (true)
     8                     {
     9                         var str = streamReader.ReadLine();
    10                         if (str == string.Empty)
    11                         {
    12                             this.HeaderStr = _stringBuilder.ToString();
    13                             _stringBuilder.Clear();
    14                             break;
    15                         }
    16                         else if (str == null && string.IsNullOrEmpty(this.HeaderStr))
    17                         {
    18                             return false;
    19 
    20                         }
    21                         else
    22                             _stringBuilder.AppendLine(str);
    23                     }
    24                 }
    25             }

          上面使用了一个自定义的StreamReader,原因是自带的那个无法定位Stream的Position;若已成功解析出http的header,接下来就可以根据header来判断提交的方式是get还是post等。

     1         /// <summary>
     2         /// 解析http请求的数据
     3         /// </summary>
     4         /// <param name="data"></param>
     5         /// <param name="onUnpackage"></param>
     6         public void GetRequest(byte[] data, Action<RequestDataReader> onUnpackage)
     7         {
     8             lock (_locker)
     9             {
    10                 _cache.AddRange(data);
    11 
    12                 var buffer = _cache.ToArray();
    13 
    14                 if (!isAnalysis)
    15                 {
    16                     isAnalysis = _httpStringReader.Analysis(buffer);
    17                 }
    18                 if (isAnalysis)
    19                 {
    20                     //post需要处理body
    21                     if (_httpStringReader.Method == ConstString.POSTStr)
    22                     {
    23                         var contentLen = _httpStringReader.ContentLength;
    24                         var positon = _httpStringReader.Position;
    25                         var totlalLen = contentLen + positon;
    26                         if (buffer.Length == totlalLen)
    27                         {
    28                             _httpStringReader.AnalysisBody(buffer);
    29                             onUnpackage.Invoke(_httpStringReader);
    30                             Array.Clear(buffer, 0, buffer.Length);
    31                             buffer = null;
    32                             _cache.Clear();
    33                             _cache = null;
    34                         }
    35                     }
    36                     else
    37                     {
    38                         onUnpackage.Invoke(_httpStringReader);
    39                         Array.Clear(buffer, 0, buffer.Length);
    40                         buffer = null;
    41                         _cache.Clear();
    42                         _cache = null;
    43                     }
    44                 }
    45             }
    46         }

          已解析header的话,就可以根据上面说的相关post的http数据格式来解析body。

     1                switch (this.ContentType)
     2                 {
     3                     case ConstString.FORMENCTYPE1:
     4                         this.Forms = GetRequestForms(Encoding.UTF8.GetString(this.Body));
     5                         break;
     6                     case ConstString.FORMENCTYPE2:
     7                         //todo
     8                         using (MemoryStream ms = new MemoryStream(this.Body))
     9                         {
    10                             ms.Position = 0;
    11                             using (var sr = new SAEA.Common.StreamReader(ms))
    12                             {
    13                                 StringBuilder sb = new StringBuilder();
    14                                 var str = string.Empty;
    15                                 do
    16                                 {
    17                                     str = sr.ReadLine();
    18                                     if (str == null)
    19                                     {
    20                                         break;
    21                                     }
    22                                     else
    23                                     {
    24                                         sb.AppendLine(str);
    25                                         if (str.IndexOf(CT) > -1)
    26                                         {
    27                                             var filePart = GetRequestFormsWithMultiPart(sb.ToString());
    28 
    29                                             if (filePart != null)
    30                                             {
    31                                                 sr.ReadLine();
    32 
    33                                                 filePart.Data = sr.ReadData(sr.Position, this.Boundary);
    34                                                 if (filePart.Data != null)
    35                                                 {
    36                                                     filePart.Data = filePart.Data.Take(filePart.Data.Length - 2).ToArray();
    37                                                 }
    38                                                 if (this.PostFiles == null)
    39                                                     this.PostFiles = new List<FilePart>();
    40                                                 this.PostFiles.Add(filePart);
    41                                             }
    42                                             sb.Clear();
    43                                             sr.ReadLine();
    44                                         }
    45                                     }
    46                                 }
    47                                 while (true);
    48 
    49                             }
    50                         }
    51                         break;
    52                     default:
    53                         this.Json = Encoding.UTF8.GetString(this.Body);
    54                         break;
    55                 }

          至此,http的相关解析就完成了,详细的代码可参见:

          1.HCode主要功能收取tcp包、 

          2.RequestDataReader主要功能是收到的tcp包近http协议转成webrequest、

          3.HttpRequest主要功能是将转换的数据进行model赋值、

          4.HttpContext主要功能是映射到处理业务并返回http数据

    转载请标明本文来源:https://www.cnblogs.com/yswenli/p/9326453.html
    更多内容欢迎star/fork作者的github:https://github.com/yswenli/SAEA
    如果发现本文有什么问题和任何建议,也随时欢迎交流~

  • 相关阅读:
    关于格林尼治时间(GMT)和DateTime相互转换的分享
    Mybatis多表操作
    Mybatis动态SQL
    Mybatis连接池及事务
    Mybatis基本使用
    Mybatis基本入门搭建
    面试刷题29:mysql事务隔离实现原理?
    面试刷题28:如何写出安全的java代码?
    面试刷题27:程序员如何防护java界的新冠肺炎?
    面试刷题26:新冠攻击人类?什么攻击java平台?
  • 原文地址:https://www.cnblogs.com/yswenli/p/9326453.html
Copyright © 2011-2022 走看看