基于byte[]的HTTP协议头分析代码
最近需要为组件实现一个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完整代码
View Code1 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 }
访问Beetlex的Github