WebClient.cs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using System.Net.Sockets;
  5. using System.IO;
  6. using System.Threading;
  7. using CommonLang;
  8. using CommonNetwork;
  9. using CommonNetwork.Sockets;
  10. using CommonLang.IO;
  11. using CommonLang.Log;
  12. using System.Net;
  13. using CommonLang.Net;
  14. using System.Net.Security;
  15. using System.Security.Cryptography.X509Certificates;
  16. using System.Security.Authentication;
  17. namespace CommonNetwork.Http
  18. {
  19. public delegate void HttpConnectHandler(WebClient www);
  20. public delegate void HttpPostHandler(string result);
  21. public delegate void HttpGetHandler(byte[] result);
  22. public class HttpRequest
  23. {
  24. public const string METHOD_GET = "GET";
  25. public const string METHOD_POST = "POST";
  26. public const string CONTENT_TYPE_OCTET_STREAM = "application/octet-stream";
  27. public const string CONTENT_TYPE_WWW_FORM_URLENCODED = "application/x-www-form-urlencoded";
  28. public const string CONTENT_TYPE_TEXT_XML = "text/xml";
  29. public string Method = METHOD_GET;
  30. public string ContentType = CONTENT_TYPE_OCTET_STREAM;
  31. public string Referer;
  32. public string Accept = "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2";
  33. public string AcceptLanguage = "zh-CN";
  34. public string AcceptEncoding = "identity";
  35. public string UserAgent = "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.95 Safari/537.36";
  36. public string Connection = "keep-alive";
  37. public string CacheControl = "no-cache";
  38. public SslProtocols SslProtocol = SslProtocols.Ssl3;
  39. public X509CertificateCollection Certificates = null;
  40. public RemoteCertificateValidationCallback OnRemoteCertificateValidation = null;
  41. public LocalCertificateSelectionCallback OnLocalCertificateValidation = null;
  42. private Properties _params;
  43. public Properties Params
  44. {
  45. get
  46. {
  47. if (_params == null)
  48. {
  49. _params = new Properties();
  50. }
  51. return _params;
  52. }
  53. }
  54. public byte[] Content;
  55. }
  56. public class HttpResponse
  57. {
  58. public Properties Params { get; internal set; }
  59. public string Status { get; internal set; }
  60. public string ContentType { get; internal set; }
  61. public int ContentLength { get; internal set; }
  62. public string Location { get; internal set; }
  63. public bool IsGzip { get; internal set; }
  64. public bool IsChunk { get; internal set; }
  65. private Stream input;
  66. public Stream InputStream
  67. {
  68. get { return input; }
  69. internal set
  70. {
  71. if (this.IsChunk)
  72. {
  73. this.input = new ChunkInputStream(this, value);
  74. }
  75. else
  76. {
  77. this.input = value;
  78. }
  79. }
  80. }
  81. public override string ToString()
  82. {
  83. var sb = new StringBuilder();
  84. if (Params != null)
  85. {
  86. foreach (var e in Params)
  87. {
  88. sb.Append(e.Key + " : " + e.Value + WebClient.BR);
  89. }
  90. }
  91. return sb.ToString();
  92. }
  93. public byte[] ReadContentToEnd()
  94. {
  95. if (IsChunk)
  96. {
  97. byte[] data = IOUtil.ReadToEnd(input);
  98. return data;
  99. }
  100. else
  101. {
  102. byte[] data = new byte[ContentLength];
  103. IOUtil.ReadToEnd(input, data, 0, data.Length);
  104. return data;
  105. }
  106. }
  107. internal class ChunkInputStream : Stream
  108. {
  109. private readonly HttpResponse response;
  110. private readonly Stream baseStream;
  111. private int current_chunk_pos = 0;
  112. private int current_chunk_size = -1;
  113. public override bool CanRead { get { return baseStream.CanRead; } }
  114. public override bool CanSeek { get { return false; } }
  115. public override bool CanWrite { get { return false; } }
  116. public override long Length { get { return 0; } }
  117. public override long Position { get { return 0; } set { } }
  118. internal ChunkInputStream(HttpResponse rsp, Stream s)
  119. {
  120. this.response = rsp;
  121. this.baseStream = s;
  122. }
  123. public override int Read(byte[] buffer, int offset, int count)
  124. {
  125. if (current_chunk_pos >= current_chunk_size)
  126. {
  127. try
  128. {
  129. var line = WebClient.ReadLine(baseStream);
  130. current_chunk_size = Convert.ToInt32(line.Trim(), 16);
  131. current_chunk_pos = 0;
  132. }
  133. catch (Exception err)
  134. {
  135. throw new Exception("Maybe exists 'chunk-ext' field.", err);
  136. }
  137. }
  138. if (current_chunk_size == 0)
  139. {
  140. return 0;
  141. }
  142. int total = current_chunk_size - current_chunk_pos;
  143. count = Math.Min(total, count);
  144. int readed = baseStream.Read(buffer, offset, count);
  145. if (readed > 0)
  146. {
  147. current_chunk_pos += readed;
  148. if (current_chunk_pos >= current_chunk_size)
  149. {
  150. WebClient.ReadLine(baseStream);
  151. }
  152. }
  153. return readed;
  154. }
  155. public override long Seek(long offset, SeekOrigin origin)
  156. {
  157. throw new NotImplementedException();
  158. }
  159. public override void SetLength(long value)
  160. {
  161. throw new NotImplementedException();
  162. }
  163. public override void Flush()
  164. {
  165. throw new NotImplementedException();
  166. }
  167. public override void Write(byte[] buffer, int offset, int count)
  168. {
  169. throw new NotImplementedException();
  170. }
  171. }
  172. }
  173. public class WebClient : IDisposable
  174. {
  175. private static Logger _log;
  176. private static Logger log
  177. {
  178. get
  179. {
  180. if (_log == null) _log = LoggerFactory.GetLogger("WebClient");
  181. return _log;
  182. }
  183. }
  184. private TcpClient mSocket = null;
  185. private Uri url;
  186. public HttpRequest Request = new HttpRequest();
  187. public HttpResponse Response { get; private set; }
  188. public Exception Error { get; private set; }
  189. public int TimeoutMS { get; set; }
  190. public WebClient(Uri url)
  191. {
  192. this.url = url;
  193. this.TimeoutMS = 30000;
  194. }
  195. public void Dispose()
  196. {
  197. try
  198. {
  199. if (mSocket != null)
  200. {
  201. mSocket.Close();
  202. }
  203. }
  204. catch (System.Exception e) { log.Error(e.Message, e); }
  205. }
  206. public Stream Connect()
  207. {
  208. _connect(null);
  209. if (Response != null)
  210. {
  211. return Response.InputStream;
  212. }
  213. return null;
  214. }
  215. public void ConnectAsync(HttpConnectHandler handler)
  216. {
  217. var ts = new ThreadStart(() => { this._connect(handler); });
  218. var tr = new Thread(ts);
  219. tr.IsBackground = true;
  220. tr.Start();
  221. }
  222. //-----------------------------------------------------------------------------------------------------------------
  223. #region Internal
  224. /// <summary>
  225. ///
  226. /// </summary>
  227. /// <param name="forceIPv6">强制IPv6</param>
  228. /// <param name="location"></param>
  229. /// <param name="timeoutMS"></param>
  230. private static TcpClient _connect_remote(bool forceIPv6, Uri location, int timeoutMS)
  231. {
  232. if (location.HostNameType == UriHostNameType.Dns)
  233. {
  234. Console.WriteLine("dns : " + location + " " + location.HostNameType.ToString());
  235. var ips = Dns.GetHostEntry(location.Host);
  236. // 如果只包含IPv6地址,表示当前环境IPv6 only
  237. var family = IPUtil.IsOnlyIPv6(ips) ? AddressFamily.InterNetworkV6 : AddressFamily.InterNetwork;
  238. var mSocket = new TcpClient(forceIPv6 ? AddressFamily.InterNetworkV6 : family);
  239. mSocket.SendTimeout = timeoutMS;
  240. mSocket.ReceiveTimeout = timeoutMS;
  241. if (family != AddressFamily.InterNetworkV6 && forceIPv6)
  242. {
  243. //首次是IPV6地址,优先选择V6地址//
  244. foreach (var ip in ips.AddressList)
  245. {
  246. if (ip.AddressFamily == AddressFamily.InterNetworkV6)
  247. {
  248. mSocket.Connect(ip, location.Port);
  249. return mSocket;
  250. }
  251. }
  252. //强转V4地址到V6地址//
  253. foreach (var ip in ips.AddressList)
  254. {
  255. if (ip.AddressFamily == AddressFamily.InterNetwork)
  256. {
  257. var ipv6 = IPUtil.MapToIPv6(ip);
  258. Console.WriteLine("ipv4 to ipv6 : " + ip + " - " + ipv6);
  259. mSocket.Connect(ipv6, location.Port);
  260. return mSocket;
  261. }
  262. }
  263. mSocket.Connect(ips.AddressList, location.Port);
  264. }
  265. else
  266. {
  267. mSocket.Connect(ips.AddressList, location.Port);
  268. }
  269. return mSocket;
  270. }
  271. else
  272. {
  273. Console.WriteLine("ip : " + location + " " + location.HostNameType.ToString());
  274. var mSocket = new TcpClient(forceIPv6 ? AddressFamily.InterNetworkV6 : AddressFamily.InterNetwork);
  275. mSocket.SendTimeout = timeoutMS;
  276. mSocket.ReceiveTimeout = timeoutMS;
  277. if (location.HostNameType != UriHostNameType.IPv6 && forceIPv6)
  278. {
  279. var ipv6 = IPUtil.MapToIPv6(location.Host);
  280. Console.WriteLine("ipv4 to ipv6 : " + location.Host + " - " + ipv6);
  281. mSocket.Connect(ipv6, location.Port);
  282. }
  283. else
  284. {
  285. mSocket.Connect(location.Host, location.Port);
  286. }
  287. return mSocket;
  288. }
  289. }
  290. private void _connect(HttpConnectHandler handler)
  291. {
  292. try
  293. {
  294. bool isIpV6 = false;
  295. Uri location = url;
  296. do
  297. {
  298. mSocket = _connect_remote(isIpV6, location, TimeoutMS);
  299. if (!mSocket.Connected)
  300. {
  301. return;
  302. }
  303. Console.WriteLine("RemoteEndPoint AddressFamily : " + mSocket.Client.RemoteEndPoint.AddressFamily);
  304. if (mSocket.Client.RemoteEndPoint.AddressFamily == AddressFamily.InterNetworkV6)
  305. {
  306. isIpV6 = true;
  307. }
  308. Stream stream = null;
  309. if (location.Scheme == "http")
  310. {
  311. stream = mSocket.GetStream();
  312. }
  313. else if (location.Scheme == "https")
  314. {
  315. var sslStream = new SslStream(mSocket.GetStream(), true, Request.OnRemoteCertificateValidation, Request.OnLocalCertificateValidation);
  316. sslStream.ReadTimeout = TimeoutMS;
  317. sslStream.WriteTimeout = TimeoutMS;
  318. var certificates = Request.Certificates;
  319. if (certificates != null)
  320. {
  321. var store = new X509Store(StoreName.My);
  322. certificates = store.Certificates;
  323. }
  324. sslStream.AuthenticateAsClient(location.Host, certificates, Request.SslProtocol, false);
  325. if (!sslStream.IsAuthenticated)
  326. {
  327. return;
  328. }
  329. stream = sslStream;
  330. }
  331. else
  332. {
  333. throw new ArgumentException("url must start with HTTP or HTTPS.", "url");
  334. }
  335. log.Info("send request : " + location);
  336. this.Response = _send_request(stream, location, Request.Referer, Request);
  337. log.Info("response : " + Response.Status);
  338. // redirect 302
  339. if (!string.IsNullOrEmpty(this.Response.Location))
  340. {
  341. if (mSocket != null)
  342. {
  343. try { stream.Close(); } catch (System.Exception e) { log.Error(e.Message, e); }
  344. try { mSocket.Close(); } catch (System.Exception e) { log.Error(e.Message, e); }
  345. }
  346. log.Info(" redirect to : " + this.Response.Location);
  347. location = new Uri(this.Response.Location);
  348. continue;
  349. }
  350. else
  351. {
  352. this.Response.InputStream = stream;
  353. break;
  354. }
  355. }
  356. while (true);
  357. }
  358. catch (System.Exception e)
  359. {
  360. log.Error(e.Message, e);
  361. this.Response = null;
  362. this.Error = e;
  363. }
  364. finally
  365. {
  366. if (handler != null)
  367. {
  368. handler(this);
  369. }
  370. }
  371. }
  372. private static HttpResponse _send_request(Stream stream, Uri url, string referer, HttpRequest request)
  373. {
  374. // Send
  375. if (request.Method.Equals(HttpRequest.METHOD_GET))
  376. {
  377. StringBuilder header_text = new StringBuilder();
  378. header_text.Append(request.Method + " " + url.PathAndQuery + " HTTP/1.1" + BR);
  379. header_text.Append("Accept: " + request.Accept + BR);
  380. header_text.Append("Referer: " + referer + BR);
  381. header_text.Append("Accept-Language: " + request.AcceptLanguage + BR);
  382. header_text.Append("Accept-Encoding: " + request.AcceptEncoding + BR);
  383. header_text.Append("User-Agent: " + request.UserAgent + BR);
  384. header_text.Append("Host: " + url.Host + BR);
  385. header_text.Append("Connection: " + request.Connection + BR);
  386. header_text.Append("Cache-Control: " + request.CacheControl + BR);
  387. foreach (KeyValuePair<string, string> e in request.Params)
  388. {
  389. header_text.Append(e.Key + ": " + e.Value + BR);
  390. }
  391. header_text.Append(BR);
  392. // send HTTP GET //
  393. string req = header_text.ToString();
  394. byte[] header_bytes = Encoding.UTF8.GetBytes(req);
  395. IOUtil.WriteToEnd(stream, header_bytes, 0, header_bytes.Length);
  396. }
  397. else if (request.Method.Equals(HttpRequest.METHOD_POST))
  398. {
  399. byte[] body = _get_post_data(url, request);
  400. // send HTTP Post Head //
  401. StringBuilder header_text = new StringBuilder();
  402. header_text.Append(request.Method + " " + url.AbsolutePath + " HTTP/1.1" + BR);
  403. header_text.Append("Host: " + url.Host + BR);
  404. header_text.Append("Referer: " + referer + BR);
  405. header_text.Append("Content-Length: " + body.Length + BR);
  406. header_text.Append("Content-Type: " + request.ContentType + BR);
  407. header_text.Append("User-Agent: " + request.UserAgent + BR);
  408. header_text.Append("Connection: " + request.Connection + BR);
  409. header_text.Append("Cache-Control: " + request.CacheControl + BR);
  410. header_text.Append("Accept: " + request.Accept + BR);
  411. foreach (KeyValuePair<string, string> e in request.Params)
  412. {
  413. header_text.Append(e.Key + ": " + e.Value + BR);
  414. }
  415. header_text.Append(BR);
  416. string req = header_text.ToString();
  417. byte[] header_bytes = Encoding.UTF8.GetBytes(req);
  418. IOUtil.WriteToEnd(stream, header_bytes, 0, header_bytes.Length);
  419. IOUtil.WriteToEnd(stream, body, 0, body.Length);
  420. }
  421. HttpResponse response = new HttpResponse();
  422. response.Params = new Properties();
  423. String line = null;
  424. while ((line = ReadLine(stream)) != null)
  425. {
  426. if (line.Length == 0)
  427. {
  428. break;
  429. }
  430. else if (line.ToUpper().StartsWith("HTTP"))
  431. {
  432. response.Status = line;
  433. }
  434. else if (response.Params.ParseLine(line, ":"))
  435. {
  436. }
  437. string len = null;
  438. if (TryGetResponseValue(line, "Content-Length", out len))
  439. {
  440. response.ContentLength = int.Parse(len);
  441. }
  442. else if (TryGetResponseValue(line, "Content-Type", out len))
  443. {
  444. response.ContentType = len;
  445. }
  446. else if (TryGetResponseValue(line, "Location", out len))
  447. {
  448. response.Location = len;
  449. }
  450. else if (TryGetResponseValue(line, "Content-Encoding", out len))
  451. {
  452. if (len.IndexOf("gzip", StringComparison.OrdinalIgnoreCase) >= 0)
  453. {
  454. response.IsGzip = true;
  455. }
  456. }
  457. else if (TryGetResponseValue(line, "Transfer-Encoding", out len))
  458. {
  459. if (len.IndexOf("chunked", StringComparison.OrdinalIgnoreCase) >= 0)
  460. {
  461. response.IsChunk = true;
  462. }
  463. }
  464. }
  465. return response;
  466. }
  467. private static byte[] _get_post_data(Uri url, HttpRequest request)
  468. {
  469. if (request.Content == null)
  470. {
  471. string body = url.Query;
  472. if (body.StartsWith("?"))
  473. {
  474. body = body.Substring(1);
  475. }
  476. int body_length = Encoding.UTF8.GetByteCount(body);
  477. byte[] body_bytes = Encoding.UTF8.GetBytes(body);
  478. return body_bytes;
  479. }
  480. else
  481. {
  482. return request.Content;
  483. }
  484. }
  485. #endregion
  486. //-----------------------------------------------------------------------------------------------------------------
  487. #region STATIC
  488. public static string DownloadString(Uri url, Encoding enc = null)
  489. {
  490. if (enc == null)
  491. {
  492. enc = CUtils.UTF8;
  493. }
  494. using (WebClient client = new WebClient(url))
  495. {
  496. client.Request.Method = HttpRequest.METHOD_GET;
  497. client.Request.Referer = url.AbsoluteUri;
  498. client.Connect();
  499. byte[] data = client.Response.ReadContentToEnd();
  500. string ret = enc.GetString(data);
  501. return ret;
  502. }
  503. }
  504. public static byte[] Get(Uri url)
  505. {
  506. using (WebClient client = new WebClient(url))
  507. {
  508. client.Request.Method = HttpRequest.METHOD_GET;
  509. client.Request.Referer = url.AbsoluteUri;
  510. client.Connect();
  511. byte[] data = client.Response.ReadContentToEnd();
  512. return data;
  513. }
  514. }
  515. public static void GetAsync(Uri url, HttpGetHandler handler)
  516. {
  517. WebClient client = new WebClient(url);
  518. client.Request.Method = HttpRequest.METHOD_GET;
  519. client.Request.Referer = url.AbsoluteUri;
  520. client.ConnectAsync((www) =>
  521. {
  522. try
  523. {
  524. if (www.Response != null)
  525. {
  526. byte[] data = client.Response.ReadContentToEnd();
  527. handler.Invoke(data);
  528. }
  529. else
  530. {
  531. handler.Invoke(null);
  532. }
  533. }
  534. catch (Exception err)
  535. {
  536. log.Error(err.Message, err);
  537. handler.Invoke(null);
  538. }
  539. finally
  540. {
  541. www.Dispose();
  542. }
  543. });
  544. }
  545. public static string Post(Uri url, string referer = null, Encoding enc = null)
  546. {
  547. if (enc == null)
  548. {
  549. enc = CUtils.UTF8;
  550. }
  551. if (referer == null)
  552. {
  553. referer = url.AbsoluteUri;
  554. }
  555. using (WebClient client = new WebClient(url))
  556. {
  557. client.Request.Method = HttpRequest.METHOD_POST;
  558. client.Request.ContentType = "application/x-www-form-urlencoded";
  559. client.Request.Referer = referer;
  560. client.Connect();
  561. byte[] data = client.Response.ReadContentToEnd();
  562. string ret = enc.GetString(data);
  563. return ret;
  564. }
  565. }
  566. public static void PostAsync(Uri url, string referer, Encoding enc, HttpPostHandler handler)
  567. {
  568. if (enc == null)
  569. {
  570. enc = CUtils.UTF8;
  571. }
  572. if (referer == null)
  573. {
  574. referer = url.AbsoluteUri;
  575. }
  576. WebClient client = new WebClient(url);
  577. client.Request.Method = HttpRequest.METHOD_POST;
  578. client.Request.ContentType = "application/x-www-form-urlencoded";
  579. client.Request.Referer = referer;
  580. client.ConnectAsync((www) =>
  581. {
  582. try
  583. {
  584. if (www.Response != null)
  585. {
  586. byte[] data = client.Response.ReadContentToEnd();
  587. string ret = enc.GetString(data);
  588. handler.Invoke(ret);
  589. }
  590. else
  591. {
  592. handler.Invoke(null);
  593. }
  594. }
  595. catch (Exception err)
  596. {
  597. log.Error(err.Message, err);
  598. handler.Invoke(null);
  599. }
  600. finally
  601. {
  602. www.Dispose();
  603. }
  604. });
  605. }
  606. public static void PostAsync(Uri url, Encoding enc, HttpPostHandler handler)
  607. {
  608. PostAsync(url, null, CUtils.UTF8, handler);
  609. }
  610. public static void PostAsync(Uri url, HttpPostHandler handler)
  611. {
  612. PostAsync(url, CUtils.UTF8, handler);
  613. }
  614. #endregion
  615. //----------------------------------------------------------------------------------------------
  616. #region UTILS
  617. public static readonly string BR = "\r\n";
  618. public static readonly char[] SPLIT = new char[] { ':' };
  619. public static string FormatPath(string path)
  620. {
  621. path = path.Replace('\\', '/');
  622. return path;
  623. }
  624. private static bool TryGetResponseValue(string line, string key, out string value)
  625. {
  626. if (line.ToLower().StartsWith(key.ToLower()))
  627. {
  628. string[] kv = line.Split(SPLIT, 2);
  629. value = kv[1].Trim();
  630. return true;
  631. }
  632. value = null;
  633. return false;
  634. }
  635. public static string ReadLine(Stream input)
  636. {
  637. byte _r = (byte)'\r';
  638. byte _n = (byte)'\n';
  639. using (var ms = MemoryStreamObjectPool.AllocAutoRelease())
  640. {
  641. int a0 = 0;
  642. while (a0 >= 0)
  643. {
  644. int a1 = input.ReadByte();
  645. if (a1 < 0)
  646. {
  647. break;
  648. }
  649. ms.WriteByte((byte)a1);
  650. if (a0 == _r && a1 == _n)
  651. {
  652. break;
  653. }
  654. a0 = a1;
  655. }
  656. ms.Flush();
  657. return Encoding.ASCII.GetString(ms.GetBuffer(), 0, (int)(ms.Length - 2));
  658. }
  659. }
  660. #endregion
  661. }
  662. }