有人说用 Socket 请求 http 服务效率要比 HttpWebRequest 高很多, 但是又没有提供源码或者对比测试结果. 我对此很好奇, 到底能差多少? 所以决定自己写个类实现 Socket 请求 http 的功能.
下面的代码实现了基本的 http ,https 请求, 支持 gzip 解压, 分块传输.
经本人多次试验, 得出如下结论:
如果仅用 Socket 获取文本类型的内容, 且不考虑分块传输的情况, 那么 Socket 方式可比 HttpWebRequest 方式效率高 30%-40%.
如果考虑更多因素,需要做更多处理, 所以多数时候效率不如 HttpWebRequest, 当然与我写的代码效率有很大关系.
本文首发地址 http://blog.itnmg.net/socket-http/
欢迎共同探讨技术问题,提供建议, 请留言,我会尽快回复.

1 /* Name: Socket 实现 http 协议全功能版 2 * Version: v0.6 3 * Description: 此版本实现了 http 及 https 的 get 或 post 访问, 自动处理301,302跳转, 支持 gzip 解压, 分块传输. 4 * 支持的操作: 获取文本,图片,文件形式的内容. 5 * 使用方法: new HttpWebSocket(); 调用实例的 Get.... 方法. 6 * 声明: 本代码仅做技术探讨,可任意转载,请勿用于商业用途. 7 * 本人博客: http://blog.itnmg.net http://www.cnblogs.com/lhg-net 8 * 创建日期: 2013-01-15 9 * 修订日期: 2013-06-03 10 */ 11 using System; 12 using System.Collections.Generic; 13 using System.Net; 14 using System.Net.Sockets; 15 using System.Net.Security; 16 using System.Text; 17 using System.Text.RegularExpressions; 18 using System.IO; 19 using System.IO.Compression; 20 using System.Web; 21 using System.Drawing; 22 using System.Security.Cryptography.X509Certificates; 23 24 namespace ExtLibrary.Net 25 { 26 class HttpWebSocket 27 { 28 /// <summary> 29 /// 获取或设置请求与回应的超时时间,默认3秒. 30 /// </summary> 31 public int TimeOut 32 { 33 get; 34 set; 35 } 36 37 /// <summary> 38 /// 获取或设置请求cookie 39 /// </summary> 40 public List<string> Cookies 41 { 42 get; 43 set; 44 } 45 46 /// <summary> 47 /// 获取请求返回的 HTTP 头部内容 48 /// </summary> 49 public HttpHeader HttpHeaders 50 { 51 get; 52 internal set; 53 } 54 55 /// <summary> 56 /// 获取或设置错误信息分隔符 57 /// </summary> 58 private string ErrorMessageSeparate; 59 60 61 62 public HttpWebSocket() 63 { 64 this.TimeOut = 3; 65 this.Cookies = new List<string>(); 66 this.ErrorMessageSeparate = ";;"; 67 this.HttpHeaders = new HttpHeader(); 68 } 69 70 71 72 /// <summary> 73 /// get或post方式请求一个 http 或 https 地址.使用 Socket 方式 74 /// </summary> 75 /// <param name="url">请求绝对地址</param> 76 /// <param name="referer">请求来源地址,可为空</param> 77 /// <param name="postData">post请求参数. 设置空值为get方式请求</param> 78 /// <returns>返回图像</returns> 79 public Image GetImageUseSocket( string url, string referer, string postData = null ) 80 { 81 Image result = null; 82 MemoryStream ms = this.GetSocketResult( url, referer, postData ); 83 84 try 85 { 86 if ( ms != null ) 87 { 88 result = Image.FromStream( ms ); 89 } 90 } 91 catch ( Exception e ) 92 { 93 string ss = e.Message; 94 } 95 96 return result; 97 } 98 99 /// <summary> 100 /// get或post方式请求一个 http 或 https 地址.使用 Socket 方式 101 /// </summary> 102 /// <param name="url">请求绝对地址</param> 103 /// <param name="postData">post请求参数. 设置空值为get方式请求</param> 104 /// <returns>返回 html 内容,如果发生异常将返回上次http状态码及异常信息</returns> 105 public string GetHtmlUseSocket( string url, string postData = null ) 106 { 107 return this.GetHtmlUseSocket( url, null, postData ); 108 } 109 110 /// <summary> 111 /// get或post方式请求一个 http 或 https 地址.使用 Socket 方式 112 /// </summary> 113 /// <param name="url">请求绝对地址</param> 114 /// <param name="referer">请求来源地址,可为空</param> 115 /// <param name="postData">post请求参数. 设置空值为get方式请求</param> 116 /// <returns>返回 html 内容,如果发生异常将返回上次http状态码及异常信息</returns> 117 public string GetHtmlUseSocket( string url, string referer, string postData = null ) 118 { 119 string result = string.Empty; 120 121 try 122 { 123 MemoryStream ms = this.GetSocketResult( url, referer, postData ); 124 125 if ( ms != null ) 126 { 127 result = Encoding.GetEncoding( string.IsNullOrWhiteSpace( this.HttpHeaders.Charset ) ? "UTF-8" : this.HttpHeaders.Charset ).GetString( ms.ToArray() ); 128 } 129 } 130 catch ( SocketException se ) 131 { 132 result = this.HttpHeaders.ResponseStatusCode + this.ErrorMessageSeparate + se.ErrorCode.ToString() + this.ErrorMessageSeparate + se.SocketErrorCode.ToString( "G" ) + this.ErrorMessageSeparate + se.Message; 133 } 134 catch ( Exception e ) 135 { 136 result = this.HttpHeaders.ResponseStatusCode + this.ErrorMessageSeparate + e.Message; 137 } 138 139 return result; 140 } 141 142 /// <summary> 143 /// get或post方式请求一个 http 或 https 地址. 144 /// </summary> 145 /// <param name="url">请求绝对地址</param> 146 /// <param name="referer">请求来源地址,可为空</param> 147 /// <param name="postData">post请求参数. 设置空值为get方式请求</param> 148 /// <returns>返回的已解压的数据内容</returns> 149 private MemoryStream GetSocketResult( string url, string referer, string postData ) 150 { 151 if ( string.IsNullOrWhiteSpace( url ) ) 152 { 153 throw new UriFormatException( "'Url' cannot be empty." ); 154 } 155 156 MemoryStream result = null; 157 Uri uri = new Uri( url ); 158 159 if ( uri.Scheme == "http" ) 160 { 161 result = this.GetHttpResult( uri, referer, postData ); 162 } 163 else if ( uri.Scheme == "https" ) 164 { 165 result = this.GetSslResult( uri, referer, postData ); 166 } 167 else 168 { 169 throw new ArgumentException( "url must start with HTTP or HTTPS.", "url" ); 170 } 171 172 if ( !string.IsNullOrWhiteSpace( this.HttpHeaders.Location ) ) 173 { 174 result = GetSocketResult( this.HttpHeaders.Location, uri.AbsoluteUri, null ); 175 } 176 else 177 { 178 result = unGzip( result ); 179 } 180 181 return result; 182 } 183 184 /// <summary> 185 /// get或post方式请求一个 http 地址. 186 /// </summary> 187 /// <param name="uri">请求绝对地址</param> 188 /// <param name="referer">请求来源地址,可为空</param> 189 /// <param name="postData">post请求参数. 设置空值为get方式请求</param> 190 /// <param name="headText">输出包含头部内容的StringBuilder</param> 191 /// <returns>返回未解压的数据流</returns> 192 private MemoryStream GetHttpResult( Uri uri, string referer, string postData ) 193 { 194 MemoryStream result = new MemoryStream( 10240 ); 195 Socket HttpSocket = new Socket( AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp ); 196 HttpSocket.SendTimeout = this.TimeOut * 1000; 197 HttpSocket.ReceiveTimeout = this.TimeOut * 1000; 198 199 try 200 { 201 byte[] send = GetSendHeaders( uri, referer, postData ); 202 HttpSocket.Connect( uri.Host, uri.Port ); 203 204 if ( HttpSocket.Connected ) 205 { 206 HttpSocket.Send( send, SocketFlags.None ); 207 this.ProcessData( HttpSocket, ref result ); 208 } 209 210 result.Flush(); 211 } 212 finally 213 { 214 HttpSocket.Shutdown( SocketShutdown.Both ); 215 HttpSocket.Close(); 216 } 217 218 result.Seek( 0, SeekOrigin.Begin ); 219 220 return result; 221 } 222 223 /// <summary> 224 /// get或post方式请求一个 https 地址. 225 /// </summary> 226 /// <param name="uri">请求绝对地址</param> 227 /// <param name="referer">请求来源地址,可为空</param> 228 /// <param name="postData">post请求参数. 设置空值为get方式请求</param> 229 /// <param name="headText">输出包含头部内容的StringBuilder</param> 230 /// <returns>返回未解压的数据流</returns> 231 private MemoryStream GetSslResult( Uri uri, string referer, string postData ) 232 { 233 MemoryStream result = new MemoryStream( 10240 ); 234 StringBuilder sb = new StringBuilder( 1024 ); 235 236 byte[] send = GetSendHeaders( uri, referer, postData ); 237 TcpClient client = new TcpClient( uri.Host, uri.Port ); 238 239 try 240 { 241 SslStream sslStream = new SslStream( client.GetStream(), true 242 , new RemoteCertificateValidationCallback( ( sender, certificate, chain, sslPolicyErrors ) 243 => { 244 return sslPolicyErrors == SslPolicyErrors.None; 245 } 246 ), null ); 247 sslStream.ReadTimeout = this.TimeOut * 1000; 248 sslStream.WriteTimeout = this.TimeOut * 1000; 249 250 X509Store store = new X509Store( StoreName.My ); 251 252 sslStream.AuthenticateAsClient( uri.Host, store.Certificates, System.Security.Authentication.SslProtocols.Default, false ); 253 254 if ( sslStream.IsAuthenticated ) 255 { 256 sslStream.Write( send, 0, send.Length ); 257 sslStream.Flush(); 258 259 this.ProcessData( sslStream, ref result ); 260 } 261 262 result.Flush(); 263 } 264 finally 265 { 266 client.Close(); 267 } 268 269 result.Seek( 0, SeekOrigin.Begin ); 270 271 return result; 272 } 273 274 /// <summary> 275 /// 返回请求的头部内容 276 /// </summary> 277 /// <param name="uri">请求绝对地址</param> 278 /// <param name="referer">请求来源地址,可为空</param> 279 /// <param name="postData">post请求参数. 设置空值为get方式请求</param> 280 /// <returns>请求头部数据</returns> 281 private byte[] GetSendHeaders( Uri uri, string referer, string postData ) 282 { 283 string sendString = @"{0} {1} HTTP/1.1 284 Accept: text/html, application/xhtml+xml, */* 285 Referer: {2} 286 Accept-Language: zh-CN 287 User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0) 288 Accept-Encoding: gzip, deflate 289 Host: {3} 290 Connection: Keep-Alive 291 Cache-Control: no-cache 292 "; 293 294 sendString = string.Format( sendString, string.IsNullOrWhiteSpace( postData ) ? "GET" : "POST", uri.PathAndQuery 295 , string.IsNullOrWhiteSpace( referer ) ? uri.AbsoluteUri : referer, uri.Host ); 296 297 if ( this.Cookies != null && this.Cookies.Count > 0 ) 298 { 299 sendString += string.Format( "Cookie: {0}\r\n", string.Join( "; ", this.Cookies.ToArray() ) ); 300 } 301 302 if ( string.IsNullOrWhiteSpace( postData ) ) 303 { 304 sendString += "\r\n"; 305 } 306 else 307 { 308 int dlength = Encoding.UTF8.GetBytes( postData ).Length; 309 310 sendString += string.Format( @"Content-Type: application/x-www-form-urlencoded 311 Content-Length: {0} 312 313 {1} 314 ", postData.Length, postData ); 315 } 316 317 return Encoding.UTF8.GetBytes( sendString ); 318 ; 319 } 320 321 /// <summary> 322 /// 设置此类的字段 323 /// </summary> 324 /// <param name="headText">头部文本</param> 325 private void SetThisHeaders( string headText ) 326 { 327 if ( string.IsNullOrWhiteSpace( headText ) ) 328 { 329 throw new ArgumentNullException( "'WithHeadersText' cannot be empty." ); 330 } 331 332 //Match m = Regex.Match( withHeadersText,@".*(?=\r\n\r\n)", RegexOptions.Singleline | RegexOptions.IgnoreCase ); 333 334 //if ( m == null || string.IsNullOrWhiteSpace( m.Value ) ) 335 //{ 336 // throw new HttpParseException( "'SetThisHeaders' method has bug." ); 337 //} 338 339 string[] headers = headText.Split( new string[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries ); 340 341 if ( headers == null || headers.Length == 0 ) 342 { 343 throw new ArgumentException( "'WithHeadersText' param format error." ); 344 } 345 346 this.HttpHeaders = new HttpHeader(); 347 348 foreach ( string head in headers ) 349 { 350 if ( head.StartsWith( "HTTP", StringComparison.OrdinalIgnoreCase ) ) 351 { 352 string[] ts = head.Split( ' ' ); 353 if ( ts.Length > 1 ) 354 { 355 this.HttpHeaders.ResponseStatusCode = ts[1]; 356 } 357 } 358 else if ( head.StartsWith( "Set-Cookie:", StringComparison.OrdinalIgnoreCase ) ) 359 { 360 this.Cookies = this.Cookies ?? new List<string>(); 361 string tCookie = head.Substring( 11, head.IndexOf( ";" ) < 0 ? head.Length - 11 : head.IndexOf( ";" ) - 10 ).Trim(); 362 363 if ( !this.Cookies.Exists( f => f.Split( '=' )[0] == tCookie.Split( '=' )[0] ) ) 364 { 365 this.Cookies.Add( tCookie ); 366 } 367 } 368 else if ( head.StartsWith( "Location:", StringComparison.OrdinalIgnoreCase ) ) 369 { 370 this.HttpHeaders.Location = head.Substring( 9 ).Trim(); 371 } 372 else if ( head.StartsWith( "Content-Encoding:", StringComparison.OrdinalIgnoreCase ) ) 373 { 374 if ( head.IndexOf( "gzip", StringComparison.OrdinalIgnoreCase ) >= 0 ) 375 { 376 this.HttpHeaders.IsGzip = true; 377 } 378 } 379 else if ( head.StartsWith( "Content-Type:", StringComparison.OrdinalIgnoreCase ) ) 380 { 381 string[] types = head.Substring( 13 ).Split( new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries ); 382 383 foreach ( string t in types ) 384 { 385 if ( t.IndexOf( "charset=", StringComparison.OrdinalIgnoreCase ) >= 0 ) 386 { 387 this.HttpHeaders.Charset = t.Trim().Substring( 8 ); 388 } 389 else if ( t.IndexOf( '/' ) >= 0 ) 390 { 391 this.HttpHeaders.ContentType = t.Trim(); 392 } 393 } 394 } 395 else if ( head.StartsWith( "Content-Length:", StringComparison.OrdinalIgnoreCase ) ) 396 { 397 this.HttpHeaders.ContentLength = long.Parse( head.Substring( 15 ).Trim() ); 398 } 399 else if ( head.StartsWith( "Transfer-Encoding:", StringComparison.OrdinalIgnoreCase ) && head.EndsWith( "chunked", StringComparison.OrdinalIgnoreCase ) ) 400 { 401 this.HttpHeaders.IsChunk = true; 402 } 403 } 404 } 405 406 /// <summary> 407 /// 解压数据流 408 /// </summary> 409 /// <param name="data">数据流, 压缩或未压缩的.</param> 410 /// <returns>返回解压缩的数据流</returns> 411 private MemoryStream unGzip( MemoryStream data ) 412 { 413 if ( data == null ) 414 { 415 throw new ArgumentNullException( "data cannot be null.", "data" ); 416 } 417 418 data.Seek( 0, SeekOrigin.Begin ); 419 MemoryStream result = data; 420 421 if ( this.HttpHeaders.IsGzip ) 422 { 423 GZipStream gs = new GZipStream( data, CompressionMode.Decompress ); 424 result = new MemoryStream( 1024 ); 425 426 try 427 { 428 byte[] buffer = new byte[1024]; 429 int length = -1; 430 431 do 432 { 433 length = gs.Read( buffer, 0, buffer.Length ); 434 result.Write( buffer, 0, length ); 435 } 436 while ( length != 0 ); 437 438 gs.Flush(); 439 result.Flush(); 440 } 441 finally 442 { 443 gs.Close(); 444 } 445 } 446 447 return result; 448 } 449 450 451 /// <summary> 452 /// 处理请求返回的数据. 453 /// </summary> 454 /// <typeparam name="T">数据源类型</typeparam> 455 /// <param name="reader">数据源实例</param> 456 /// <param name="body">保存数据的流</param> 457 private void ProcessData<T>( T reader, ref MemoryStream body ) 458 { 459 byte[] data = new byte[10240]; 460 int bodyStart = -1;//数据部分起始位置 461 int readLength = 0; 462 463 bodyStart = GetHeaders( reader, ref data, ref readLength ); 464 465 if ( bodyStart >= 0 ) 466 { 467 if ( this.HttpHeaders.IsChunk ) 468 { 469 GetChunkData( reader, ref data, ref bodyStart, ref readLength, ref body ); 470 } 471 else 472 { 473 GetBodyData( reader, ref data, bodyStart, readLength, ref body ); 474 } 475 } 476 } 477 478 /// <summary> 479 /// 取得返回的http头部内容,并设置相关属性. 480 /// </summary> 481 /// <typeparam name="T">数据源类型</typeparam> 482 /// <param name="reader">数据源实例</param> 483 /// <param name="data">待处理的数据</param> 484 /// <param name="readLength">读取的长度</param> 485 /// <returns>数据内容的起始位置,返回-1表示未读完头部内容</returns> 486 private int GetHeaders<T>( T reader, ref byte[] data, ref int readLength ) 487 { 488 int result = -1; 489 StringBuilder sb = new StringBuilder( 1024 ); 490 491 do 492 { 493 readLength = this.ReadData( reader, ref data ); 494 495 if ( result < 0 ) 496 { 497 for ( int i = 0; i < data.Length; i++ ) 498 { 499 char c = (char)data[i]; 500 sb.Append( c ); 501 502 if ( c == '\n' && string.Concat( sb[sb.Length - 4], sb[sb.Length - 3], sb[sb.Length - 2], sb[sb.Length - 1] ).Contains( "\r\n\r\n" ) ) 503 { 504 result = i + 1; 505 this.SetThisHeaders( sb.ToString() ); 506 break; 507 } 508 } 509 } 510 511 if ( result >= 0 ) 512 { 513 break; 514 } 515 } 516 while ( readLength > 0 ); 517 518 return result; 519 } 520 521 /// <summary> 522 /// 取得未分块数据的内容 523 /// </summary> 524 /// <typeparam name="T">数据源类型</typeparam> 525 /// <param name="reader">数据源实例</param> 526 /// <param name="data">已读取未处理的字节数据</param> 527 /// <param name="startIndex">起始位置</param> 528 /// <param name="readLength">读取的长度</param> 529 /// <param name="body">保存块数据的流</param> 530 private void GetBodyData<T>( T reader, ref byte[] data, int startIndex, int readLength, ref MemoryStream body ) 531 { 532 int contentTotal = 0; 533 534 if ( startIndex < data.Length ) 535 { 536 int count = readLength - startIndex; 537 body.Write( data, startIndex, count ); 538 contentTotal += count; 539 } 540 541 int tlength = 0; 542 543 do 544 { 545 tlength = this.ReadData( reader, ref data ); 546 contentTotal += tlength; 547 body.Write( data, 0, tlength ); 548 549 if ( this.HttpHeaders.ContentLength > 0 && contentTotal >= this.HttpHeaders.ContentLength ) 550 { 551 break; 552 } 553 } 554 while ( tlength > 0 ); 555 } 556 557 /// <summary> 558 /// 取得分块数据 559 /// </summary> 560 /// <typeparam name="T">数据源类型</typeparam> 561 /// <param name="reader">Socket实例</param> 562 /// <param name="data">已读取未处理的字节数据</param> 563 /// <param name="startIndex">起始位置</param> 564 /// <param name="readLength">读取的长度</param> 565 /// <param name="body">保存块数据的流</param> 566 private void GetChunkData<T>( T reader, ref byte[] data, ref int startIndex, ref int readLength, ref MemoryStream body ) 567 { 568 int chunkSize = -1;//每个数据块的长度,用于分块数据.当长度为0时,说明读到数据末尾. 569 570 while ( true ) 571 { 572 chunkSize = this.GetChunkHead( reader, ref data, ref startIndex, ref readLength ); 573 this.GetChunkBody( reader, ref data, ref startIndex, ref readLength, ref body, chunkSize ); 574 575 if ( chunkSize <= 0 ) 576 { 577 break; 578 } 579 } 580 } 581 582 /// <summary> 583 /// 取得分块数据的数据长度 584 /// </summary> 585 /// <typeparam name="T">数据源类型</typeparam> 586 /// <param name="reader">Socket实例</param> 587 /// <param name="data">已读取未处理的字节数据</param> 588 /// <param name="startIndex">起始位置</param> 589 /// <param name="readLength">读取的长度</param> 590 /// <returns>块长度,返回0表示已到末尾.</returns> 591 private int GetChunkHead<T>( T reader, ref byte[] data, ref int startIndex, ref int readLength ) 592 { 593 int chunkSize = -1; 594 List<char> tChars = new List<char>();//用于临时存储块长度字符 595 596 if ( startIndex >= data.Length || startIndex >= readLength ) 597 { 598 readLength = this.ReadData( reader, ref data ); 599 startIndex = 0; 600 } 601 602 do 603 { 604 for ( int i = startIndex; i < readLength; i++ ) 605 { 606 char c = (char)data[i]; 607 608 if ( c == '\n' ) 609 { 610 try 611 { 612 chunkSize = Convert.ToInt32( new string( tChars.ToArray() ).TrimEnd( '\r' ), 16 ); 613 startIndex = i + 1; 614 } 615 catch ( Exception e ) 616 { 617 throw new Exception( "Maybe exists 'chunk-ext' field.", e ); 618 } 619 620 break; 621 } 622 623 tChars.Add( c ); 624 } 625 626 if ( chunkSize >= 0 ) 627 { 628 break; 629 } 630 631 startIndex = 0; 632 readLength = this.ReadData( reader, ref data ); 633 } 634 while ( readLength > 0 ); 635 636 return chunkSize; 637 } 638 639 /// <summary> 640 /// 取得分块传回的数据内容 641 /// </summary> 642 /// <typeparam name="T">数据源类型</typeparam> 643 /// <param name="reader">Socket实例</param> 644 /// <param name="data">已读取未处理的字节数据</param> 645 /// <param name="startIndex">起始位置</param> 646 /// <param name="readLength">读取的长度</param> 647 /// <param name="body">保存块数据的流</param> 648 /// <param name="chunkSize">块长度</param> 649 private void GetChunkBody<T>( T reader, ref byte[] data, ref int startIndex, ref int readLength, ref MemoryStream body, int chunkSize ) 650 { 651 if ( chunkSize <= 0 ) 652 { 653 return; 654 } 655 656 int chunkReadLength = 0;//每个数据块已读取长度 657 658 if ( startIndex >= data.Length || startIndex >= readLength ) 659 { 660 readLength = this.ReadData( reader, ref data ); 661 startIndex = 0; 662 } 663 664 do 665 { 666 int owing = chunkSize - chunkReadLength; 667 int count = Math.Min( readLength - startIndex, owing ); 668 669 body.Write( data, startIndex, count ); 670 chunkReadLength += count; 671 672 if ( owing <= count ) 673 { 674 startIndex += count + 2; 675 break; 676 } 677 678 startIndex = 0; 679 readLength = this.ReadData( reader, ref data ); 680 } 681 while ( readLength > 0 ); 682 } 683 684 /// <summary> 685 /// 从数据源读取数据 686 /// </summary> 687 /// <typeparam name="T">数据源类型</typeparam> 688 /// <param name="reader">数据源</param> 689 /// <param name="data">用于存储读取的数据</param> 690 /// <returns>读取的数据长度,无数据为-1</returns> 691 private int ReadData<T>( T reader, ref byte[] data ) 692 { 693 int result = -1; 694 695 if ( reader is Socket ) 696 { 697 result = (reader as Socket).Receive( data, SocketFlags.None ); 698 } 699 else if ( reader is SslStream ) 700 { 701 result = (reader as SslStream).Read( data, 0, data.Length ); 702 } 703 704 return result; 705 } 706 } 707 708 public class HttpHeader 709 { 710 /// <summary> 711 /// 获取请求回应状态码 712 /// </summary> 713 public string ResponseStatusCode 714 { 715 get; 716 internal set; 717 } 718 719 /// <summary> 720 /// 获取跳转url 721 /// </summary> 722 public string Location 723 { 724 get; 725 internal set; 726 } 727 728 /// <summary> 729 /// 获取是否由Gzip压缩 730 /// </summary> 731 public bool IsGzip 732 { 733 get; 734 internal set; 735 } 736 737 /// <summary> 738 /// 获取返回的文档类型 739 /// </summary> 740 public string ContentType 741 { 742 get; 743 internal set; 744 } 745 746 /// <summary> 747 /// 获取内容使用的字符集 748 /// </summary> 749 public string Charset 750 { 751 get; 752 internal set; 753 } 754 755 /// <summary> 756 /// 获取内容长度 757 /// </summary> 758 public long ContentLength 759 { 760 get; 761 internal set; 762 } 763 764 /// <summary> 765 /// 获取是否分块传输 766 /// </summary> 767 public bool IsChunk 768 { 769 get; 770 internal set; 771 } 772 } 773 }