基於byte[]的HTTP協議頭分析程式碼

smark發表於2013-12-18
最近需要為元件實現一個HTTP的擴充套件包,所以簡單地實現了HTTP協議分析。對於HTTP協議就不詳細解說了網上的資料太豐富了,這裡主要描述如何通過byte[]流分析出HTTP協議頭資訊。HTTP協議頭有兩個協議字元是比較重要的分別就是'\r\n'和':',前者要描述每個頭資訊的結束,而後則是屬性名和屬性值的分隔符號。

實現

由於並沒有使用Stream來處理所以在分析的時候就不能使用ReadLine來的方便,只能通過分析byte來解決。估計有朋友會問直接把byte[]打包成Stream就方便了,其實主要是使用場問題,有可能一次過來的byte[]包括多個http請求。所以包裝成stream用readline的方法成本高不划算。以下看下主體分析程式碼:

 1         public bool Import(byte[] data, ref int offset, ref int count)
 2         {
 3             byte[] buffer = mBuffer;
 4             while (count > 0)
 5             {
 6                 buffer[mHeaderLength] = data[offset];
 7                 mHeaderLength++;
 8                 offset++;
 9                 count--;
10                 if (mHeaderLength >= HEADER_BUFFER_LENGT)
11                     throw new NetTcpException("header data too long!");
12                 if (mBuffer[mHeaderLength - 1] == mWrap[1] && mBuffer[mHeaderLength - 2] == mWrap[0])
13                 {
14                     if (Action == null)
15                     {
16                         Action = Encoding.UTF8.GetString(buffer, mStartIndex, mHeaderLength - mStartIndex - 2);
17                         mStartIndex = mHeaderLength;
18                     }
19                     else
20                     {
21                         if (mBuffer[mHeaderLength - 3] == mWrap[1] && mBuffer[mHeaderLength - 4] == mWrap[0])
22                         {
23                             if (mLastPropertyName != null)
24                             {
25                                 this[mLastPropertyName] = Encoding.UTF8.GetString(buffer, mStartIndex, mHeaderLength - mStartIndex - 2);
26                             }
27                             return true;
28                         }
29                         else
30                         {
31                             if (mLastPropertyName != null)
32                             {
33                                 this[mLastPropertyName] = Encoding.UTF8.GetString(buffer, mStartIndex, mHeaderLength - mStartIndex - 2);
34                                 mStartIndex = mHeaderLength;
35                                 mLastPropertyName = null;
36                             }
37                         }
38                     }
39                 }
40                 else if (mBuffer[mHeaderLength - 1] == mNameEof[0] && mLastPropertyName == null)
41                 {
42                     mLastPropertyName = Encoding.UTF8.GetString(buffer, mStartIndex, mHeaderLength - mStartIndex - 1);
43                     mStartIndex = mHeaderLength;
44                 }
45 
46             }
47             return false;
48 
49         }

 程式碼比較簡單,通過一次遍歷buffer就把Http請求行為和相應用的頭屬性都分析出來的。由於一個byte[]有可能包括多個HTTP請求(特殊場景),所以引數上使用ref主要是通過外層這個byte[]是否有多個多header要處理。

單元測試

開發人員應該習慣寫單元測試,好處是很明顯就是實現的程式碼的可測性,如果可測性差那程式碼就要從設計上進行調整了。下面是針對以上程式碼的兩種單元測試,主要測試分析程式碼在不同情況下是否可以良好地工作。
 1 [TestMethod]
 2         public void HeaderImport()
 3         {
 4             string header = "Post\r\nname:henry\r\nemail:\r\n\r\n";
 5             byte[] data = Encoding.UTF8.GetBytes(header);
 6             int offset = 0;
 7             int count = data.Length;
 8             byte[] buffer = new byte[1024 * 4];
 9             HttpHeader hh = new HttpHeader(buffer);
10             if (hh.Import(data, ref offset, ref count))
11             {
12                 Assert.AreEqual(hh.RequestType, "Post");
13                 Assert.AreEqual(hh["name"], "henry");
14                 Assert.AreEqual(hh["email"], "");
15             }
16 
17         }
18 
19         [TestMethod]
20         public void HeaderImport1()
21         {
22             string header = "Post\r\nname:henry\r\nemail:henryfan@msn.com\r\n\r\n";
23             byte[] data = Encoding.UTF8.GetBytes(header);
24             int offset = 0;
25             int count = data.Length;
26             byte[] buffer = new byte[1024 * 4];
27             HttpHeader hh = new HttpHeader(buffer);
28 
29             if (hh.Import(data, ref offset, ref count))
30             {
31                 Assert.AreEqual(hh.RequestType, "Post");
32                 Assert.AreEqual(hh["name"], "henry");
33                 Assert.AreEqual(hh["email"], "henryfan@msn.com");
34                 hh = new HttpHeader(buffer);
35             }
36 
37 
38             header = "Get\r\nname:henry\r\n";
39             data = Encoding.UTF8.GetBytes(header);
40             offset = 0;
41             count = data.Length;
42             hh.Import(data, ref offset, ref count);
43 
44 
45             header = "email:henryfan@msn.com";
46             data = Encoding.UTF8.GetBytes(header);
47             offset = 0;
48             count = data.Length;
49             hh.Import(data, ref offset, ref count);
50 
51             header = "\r";
52             data = Encoding.UTF8.GetBytes(header);
53             offset = 0;
54             count = data.Length;
55             hh.Import(data, ref offset, ref count);
56 
57             header = "\n\r\n";
58             data = Encoding.UTF8.GetBytes(header);
59             offset = 0;
60             count = data.Length;
61 
62             if (hh.Import(data, ref offset, ref count))
63             {
64                 Assert.AreEqual(hh.RequestType, "Get");
65                 Assert.AreEqual(hh["name"], "henry");
66                 Assert.AreEqual(hh["email"], "henryfan@msn.com");
67             }
68 
69         }
70     }

 HttpHeader完整程式碼

  1   public class HttpHeader 
  2     {
  3 
  4         public const int HEADER_BUFFER_LENGT = 1024 * 4;
  5 
  6         public const string HEADER_CONTENT_LENGTH = "Content-Length";
  7 
  8         public const string HEADER_ACCEPT = "Accept";
  9 
 10         public const string HEADER_ACCEPT_ENCODING = "Accept-Encoding";
 11 
 12         public const string HEADER_ACCEPT_LANGUAGE = "Accept-Language";
 13 
 14         public const string HEADER_CONNNECTION = "Connection";
 15 
 16         public const string HEADER_COOKIE = "Cookie";
 17 
 18         public const string HEADER_HOST = "Host";
 19 
 20         public const string HEADER_USER_AGENT = "User-Agent";
 21 
 22         public const string HEADER_CONTENT_ENCODING = "Content-Encoding";
 23 
 24         public const string HEADER_CONTENT_TYPE="Content-Type";
 25 
 26         public const string HEADER_SERVER = "Server";
 27 
 28         private static byte[] mNameEof = Encoding.UTF8.GetBytes(":");
 29 
 30         private static byte[] mWrap = Encoding.UTF8.GetBytes("\r\n");
 31 
 32         public HttpHeader()
 33         {
 34 
 35         }
 36 
 37         public HttpHeader(byte[] buffer)
 38         {
 39             mBuffer = buffer;
 40         }
 41 
 42         private byte[] mBuffer;
 43 
 44         private int mHeaderLength = 0;
 45 
 46         private int mStartIndex = 0;
 47 
 48         private string mRequestType;
 49 
 50         private string mUrl;
 51 
 52         private string mAction;
 53 
 54         private long? mLength;
 55 
 56         private string mHttpVersion;
 57 
 58         private string mLastPropertyName = null;
 59 
 60         private Dictionary<string, string> mProperties = new Dictionary<string, string>();
 61 
 62         public string Accept
 63         {
 64             get
 65             {
 66                 return this[HEADER_ACCEPT];
 67             }
 68             set
 69             {
 70                 this[HEADER_ACCEPT] = value;
 71             }
 72         }
 73 
 74         public string AcceptEncoding
 75         {
 76             get
 77             {
 78                 return this[HEADER_ACCEPT_ENCODING];
 79             }
 80             set
 81             {
 82                 this[HEADER_ACCEPT_ENCODING] = value;
 83             }
 84         }
 85 
 86         public string AcceptLanguage
 87         {
 88             get
 89             {
 90                 return this[HEADER_ACCEPT_LANGUAGE];
 91             }
 92             set
 93             {
 94                 this[HEADER_ACCEPT_LANGUAGE] = value;
 95             }
 96         }
 97 
 98         public string Connection
 99         {
100             get
101             {
102                 return this[HEADER_CONNNECTION];
103             }
104             set
105             {
106                 this[HEADER_CONNNECTION] = value;
107             }
108         }
109 
110         public string Cookie
111         {
112             get
113             {
114                 return this[HEADER_COOKIE];
115             }
116             set
117             {
118                 this[HEADER_COOKIE] = value;
119             }
120         }
121 
122         public string Host
123         {
124             get
125             {
126                 return this[HEADER_HOST];
127             }
128             set
129             {
130                 this[HEADER_HOST] = value;
131             }
132         }
133 
134         public string UserAgent
135         {
136             get
137             {
138                 return this[HEADER_USER_AGENT];
139             }
140             set
141             {
142                 this[HEADER_USER_AGENT] = value;
143             }
144         }
145 
146         public string ContentEncoding
147         {
148             get
149             {
150                 return this[HEADER_CONTENT_ENCODING];
151             }
152             set
153             {
154                 this[HEADER_CONTENT_ENCODING] = value;
155             }
156         }
157 
158         public string ContentType
159         {
160             get
161             {
162                 return this[HEADER_CONTENT_TYPE];
163             }
164             set
165             {
166                 this[HEADER_CONTENT_TYPE] = value;
167             }
168         }
169 
170         public string Server
171         {
172             get
173             {
174                 return this[HEADER_SERVER];
175             }
176             set
177             {
178                 this[HEADER_SERVER] = value;
179             }
180         }
181 
182         public string Action
183         {
184             get
185             {
186                 return mAction;
187             }
188             set
189             {
190                 mAction = value;
191                 string[] values = mAction.Split(' ');
192                 if (values.Length > 0)
193                     mRequestType = values[0];
194                 if (values.Length > 1)
195                     mUrl = values[1];
196                 if (values.Length > 2)
197                     mHttpVersion = values[2];
198 
199             }
200         }
201 
202         public string HttpVersion
203         {
204             get
205             {
206                 return mHttpVersion;
207             }
208         }
209 
210         public string RequestType
211         {
212             get
213             {
214                 return mRequestType;
215             }
216         }
217 
218         public string Url
219         {
220             get
221             {
222                 return mUrl;
223             }
224         }
225 
226         public IDictionary<string, string> Properties
227         {
228             get
229             {
230                 return mProperties;
231             }
232         }
233 
234         public string this[string header]
235         {
236             get
237             {
238                 string value = null;
239                 mProperties.TryGetValue(header, out value);
240                 return value;
241             }
242             set
243             {
244                 mProperties[header] = value;
245             }
246 
247         }
248 
249         public long Length
250         {
251             get
252             {
253                 if (mLength == null)
254                 {
255                     string value = this["CONTENT_LENGTH"];
256                     if (value == null)
257                         mLength = 0;
258                     else
259                         mLength = long.Parse(value);
260                 }
261                 return mLength.Value;
262             }
263             set
264             {
265                 mProperties["CONTENT_LENGTH"] = value.ToString();
266             }
267         }
268 
269         public bool Import(byte[] data, ref int offset, ref int count)
270         {
271             byte[] buffer = mBuffer;
272             while (count > 0)
273             {
274                 buffer[mHeaderLength] = data[offset];
275                 mHeaderLength++;
276                 offset++;
277                 count--;
278                 if (mHeaderLength >= HEADER_BUFFER_LENGT)
279                     throw new NetTcpException("header data too long!");
280                 if (mBuffer[mHeaderLength - 1] == mWrap[1] && mBuffer[mHeaderLength - 2] == mWrap[0])
281                 {
282                     if (Action == null)
283                     {
284                         Action = Encoding.UTF8.GetString(buffer, mStartIndex, mHeaderLength - mStartIndex - 2);
285                         mStartIndex = mHeaderLength;
286                     }
287                     else
288                     {
289                         if (mBuffer[mHeaderLength - 3] == mWrap[1] && mBuffer[mHeaderLength - 4] == mWrap[0])
290                         {
291                             if (mLastPropertyName != null)
292                             {
293                                 this[mLastPropertyName] = Encoding.UTF8.GetString(buffer, mStartIndex, mHeaderLength - mStartIndex - 2);
294                             }
295                             return true;
296                         }
297                         else
298                         {
299                             if (mLastPropertyName != null)
300                             {
301                                 this[mLastPropertyName] = Encoding.UTF8.GetString(buffer, mStartIndex, mHeaderLength - mStartIndex - 2);
302                                 mStartIndex = mHeaderLength;
303                                 mLastPropertyName = null;
304                             }
305                         }
306                     }
307                 }
308                 else if (mBuffer[mHeaderLength - 1] == mNameEof[0] && mLastPropertyName == null)
309                 {
310                     mLastPropertyName = Encoding.UTF8.GetString(buffer, mStartIndex, mHeaderLength - mStartIndex - 1);
311                     mStartIndex = mHeaderLength;
312                 }
313 
314             }
315             return false;
316 
317         }
318 
319     }
View Code

 

相關文章