WebClient.cs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716
  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)
  205. {
  206. log.Warn(e.Message, e);
  207. }
  208. }
  209. public Stream Connect()
  210. {
  211. _connect(null);
  212. if (Response != null)
  213. {
  214. return Response.InputStream;
  215. }
  216. return null;
  217. }
  218. public void ConnectAsync(HttpConnectHandler handler)
  219. {
  220. var ts = new ThreadStart(() => { this._connect(handler); });
  221. var tr = new Thread(ts);
  222. tr.IsBackground = true;
  223. tr.Start();
  224. }
  225. //-----------------------------------------------------------------------------------------------------------------
  226. #region Internal
  227. /// <summary>
  228. ///
  229. /// </summary>
  230. /// <param name="forceIPv6">强制IPv6</param>
  231. /// <param name="location"></param>
  232. /// <param name="timeoutMS"></param>
  233. private static TcpClient _connect_remote(bool forceIPv6, Uri location, int timeoutMS)
  234. {
  235. if (location.HostNameType == UriHostNameType.Dns)
  236. {
  237. Console.WriteLine("dns : " + location + " " + location.HostNameType.ToString());
  238. var ips = Dns.GetHostEntry(location.Host);
  239. // 如果只包含IPv6地址,表示当前环境IPv6 only
  240. var family = IPUtil.IsOnlyIPv6(ips) ? AddressFamily.InterNetworkV6 : AddressFamily.InterNetwork;
  241. var mSocket = new TcpClient(forceIPv6 ? AddressFamily.InterNetworkV6 : family);
  242. mSocket.SendTimeout = timeoutMS;
  243. mSocket.ReceiveTimeout = timeoutMS;
  244. if (family != AddressFamily.InterNetworkV6 && forceIPv6)
  245. {
  246. //首次是IPV6地址,优先选择V6地址//
  247. foreach (var ip in ips.AddressList)
  248. {
  249. if (ip.AddressFamily == AddressFamily.InterNetworkV6)
  250. {
  251. mSocket.Connect(ip, location.Port);
  252. return mSocket;
  253. }
  254. }
  255. //强转V4地址到V6地址//
  256. foreach (var ip in ips.AddressList)
  257. {
  258. if (ip.AddressFamily == AddressFamily.InterNetwork)
  259. {
  260. var ipv6 = IPUtil.MapToIPv6(ip);
  261. Console.WriteLine("ipv4 to ipv6 : " + ip + " - " + ipv6);
  262. mSocket.Connect(ipv6, location.Port);
  263. return mSocket;
  264. }
  265. }
  266. mSocket.Connect(ips.AddressList, location.Port);
  267. }
  268. else
  269. {
  270. mSocket.Connect(ips.AddressList, location.Port);
  271. }
  272. return mSocket;
  273. }
  274. else
  275. {
  276. Console.WriteLine("ip : " + location + " " + location.HostNameType.ToString());
  277. var mSocket = new TcpClient(forceIPv6 ? AddressFamily.InterNetworkV6 : AddressFamily.InterNetwork);
  278. mSocket.SendTimeout = timeoutMS;
  279. mSocket.ReceiveTimeout = timeoutMS;
  280. if (location.HostNameType != UriHostNameType.IPv6 && forceIPv6)
  281. {
  282. var ipv6 = IPUtil.MapToIPv6(location.Host);
  283. Console.WriteLine("ipv4 to ipv6 : " + location.Host + " - " + ipv6);
  284. mSocket.Connect(ipv6, location.Port);
  285. }
  286. else
  287. {
  288. mSocket.Connect(location.Host, location.Port);
  289. }
  290. return mSocket;
  291. }
  292. }
  293. private void _connect(HttpConnectHandler handler)
  294. {
  295. try
  296. {
  297. bool isIpV6 = false;
  298. Uri location = url;
  299. do
  300. {
  301. mSocket = _connect_remote(isIpV6, location, TimeoutMS);
  302. if (!mSocket.Connected)
  303. {
  304. return;
  305. }
  306. log.Debug("RemoteEndPoint AddressFamily : " + mSocket.Client.RemoteEndPoint.AddressFamily);
  307. if (mSocket.Client.RemoteEndPoint.AddressFamily == AddressFamily.InterNetworkV6)
  308. {
  309. isIpV6 = true;
  310. }
  311. Stream stream = null;
  312. if (location.Scheme == "http")
  313. {
  314. stream = mSocket.GetStream();
  315. }
  316. else if (location.Scheme == "https")
  317. {
  318. var sslStream = new SslStream(mSocket.GetStream(), true, Request.OnRemoteCertificateValidation, Request.OnLocalCertificateValidation);
  319. sslStream.ReadTimeout = TimeoutMS;
  320. sslStream.WriteTimeout = TimeoutMS;
  321. var certificates = Request.Certificates;
  322. if (certificates != null)
  323. {
  324. var store = new X509Store(StoreName.My);
  325. certificates = store.Certificates;
  326. }
  327. sslStream.AuthenticateAsClient(location.Host, certificates, Request.SslProtocol, false);
  328. if (!sslStream.IsAuthenticated)
  329. {
  330. return;
  331. }
  332. stream = sslStream;
  333. }
  334. else
  335. {
  336. log.Error("url must start with HTTP or HTTPS:" + url);
  337. break;
  338. }
  339. log.Info("send request : " + location);
  340. this.Response = _send_request(stream, location, Request.Referer, Request);
  341. log.Info("response : " + Response.Status);
  342. // redirect 302
  343. if (!string.IsNullOrEmpty(this.Response.Location))
  344. {
  345. if (mSocket != null)
  346. {
  347. try { stream.Close(); } catch (System.Exception e) { log.Warn(e.Message, e); }
  348. try { mSocket.Close(); } catch (System.Exception e) { log.Warn(e.Message, e); }
  349. }
  350. log.Info(" redirect to : " + this.Response.Location);
  351. location = new Uri(this.Response.Location);
  352. continue;
  353. }
  354. else
  355. {
  356. this.Response.InputStream = stream;
  357. break;
  358. }
  359. }
  360. while (true);
  361. }
  362. catch (System.Exception e)
  363. {
  364. log.Error(e.Message, e);
  365. this.Response = null;
  366. this.Error = e;
  367. }
  368. finally
  369. {
  370. if (handler != null)
  371. {
  372. handler(this);
  373. }
  374. }
  375. }
  376. private static HttpResponse _send_request(Stream stream, Uri url, string referer, HttpRequest request)
  377. {
  378. // Send
  379. if (request.Method.Equals(HttpRequest.METHOD_GET))
  380. {
  381. StringBuilder header_text = new StringBuilder();
  382. header_text.Append(request.Method + " " + url.PathAndQuery + " HTTP/1.1" + BR);
  383. header_text.Append("Accept: " + request.Accept + BR);
  384. header_text.Append("Referer: " + referer + BR);
  385. header_text.Append("Accept-Language: " + request.AcceptLanguage + BR);
  386. header_text.Append("Accept-Encoding: " + request.AcceptEncoding + BR);
  387. header_text.Append("User-Agent: " + request.UserAgent + BR);
  388. header_text.Append("Host: " + url.Host + BR);
  389. header_text.Append("Connection: " + request.Connection + BR);
  390. header_text.Append("Cache-Control: " + request.CacheControl + BR);
  391. foreach (KeyValuePair<string, string> e in request.Params)
  392. {
  393. header_text.Append(e.Key + ": " + e.Value + BR);
  394. }
  395. header_text.Append(BR);
  396. // send HTTP GET //
  397. string req = header_text.ToString();
  398. byte[] header_bytes = Encoding.UTF8.GetBytes(req);
  399. IOUtil.WriteToEnd(stream, header_bytes, 0, header_bytes.Length);
  400. }
  401. else if (request.Method.Equals(HttpRequest.METHOD_POST))
  402. {
  403. byte[] body = _get_post_data(url, request);
  404. // send HTTP Post Head //
  405. StringBuilder header_text = new StringBuilder();
  406. header_text.Append(request.Method + " " + url.AbsolutePath + " HTTP/1.1" + BR);
  407. header_text.Append("Host: " + url.Host + BR);
  408. header_text.Append("Referer: " + referer + BR);
  409. header_text.Append("Content-Length: " + body.Length + BR);
  410. header_text.Append("Content-Type: " + request.ContentType + BR);
  411. header_text.Append("User-Agent: " + request.UserAgent + BR);
  412. header_text.Append("Connection: " + request.Connection + BR);
  413. header_text.Append("Cache-Control: " + request.CacheControl + BR);
  414. header_text.Append("Accept: " + request.Accept + BR);
  415. foreach (KeyValuePair<string, string> e in request.Params)
  416. {
  417. header_text.Append(e.Key + ": " + e.Value + BR);
  418. }
  419. header_text.Append(BR);
  420. string req = header_text.ToString();
  421. byte[] header_bytes = Encoding.UTF8.GetBytes(req);
  422. IOUtil.WriteToEnd(stream, header_bytes, 0, header_bytes.Length);
  423. IOUtil.WriteToEnd(stream, body, 0, body.Length);
  424. }
  425. HttpResponse response = new HttpResponse();
  426. response.Params = new Properties();
  427. String line = null;
  428. while ((line = ReadLine(stream)) != null)
  429. {
  430. if (line.Length == 0)
  431. {
  432. break;
  433. }
  434. else if (line.ToUpper().StartsWith("HTTP"))
  435. {
  436. response.Status = line;
  437. }
  438. else if (response.Params.ParseLine(line, ":"))
  439. {
  440. }
  441. string len = null;
  442. if (TryGetResponseValue(line, "Content-Length", out len))
  443. {
  444. response.ContentLength = int.Parse(len);
  445. }
  446. else if (TryGetResponseValue(line, "Content-Type", out len))
  447. {
  448. response.ContentType = len;
  449. }
  450. else if (TryGetResponseValue(line, "Location", out len))
  451. {
  452. response.Location = len;
  453. }
  454. else if (TryGetResponseValue(line, "Content-Encoding", out len))
  455. {
  456. if (len.IndexOf("gzip", StringComparison.OrdinalIgnoreCase) >= 0)
  457. {
  458. response.IsGzip = true;
  459. }
  460. }
  461. else if (TryGetResponseValue(line, "Transfer-Encoding", out len))
  462. {
  463. if (len.IndexOf("chunked", StringComparison.OrdinalIgnoreCase) >= 0)
  464. {
  465. response.IsChunk = true;
  466. }
  467. }
  468. }
  469. return response;
  470. }
  471. private static byte[] _get_post_data(Uri url, HttpRequest request)
  472. {
  473. if (request.Content == null)
  474. {
  475. string body = url.Query;
  476. if (body.StartsWith("?"))
  477. {
  478. body = body.Substring(1);
  479. }
  480. int body_length = Encoding.UTF8.GetByteCount(body);
  481. byte[] body_bytes = Encoding.UTF8.GetBytes(body);
  482. return body_bytes;
  483. }
  484. else
  485. {
  486. return request.Content;
  487. }
  488. }
  489. #endregion
  490. //-----------------------------------------------------------------------------------------------------------------
  491. #region STATIC
  492. public static string DownloadString(Uri url, Encoding enc = null)
  493. {
  494. if (enc == null)
  495. {
  496. enc = CUtils.UTF8;
  497. }
  498. using (WebClient client = new WebClient(url))
  499. {
  500. client.Request.Method = HttpRequest.METHOD_GET;
  501. client.Request.Referer = url.AbsoluteUri;
  502. client.Connect();
  503. byte[] data = client.Response.ReadContentToEnd();
  504. string ret = enc.GetString(data);
  505. return ret;
  506. }
  507. }
  508. public static byte[] Get(Uri url)
  509. {
  510. using (WebClient client = new WebClient(url))
  511. {
  512. client.Request.Method = HttpRequest.METHOD_GET;
  513. client.Request.Referer = url.AbsoluteUri;
  514. client.Connect();
  515. byte[] data = client.Response.ReadContentToEnd();
  516. return data;
  517. }
  518. }
  519. public static void GetAsync(Uri url, HttpGetHandler handler)
  520. {
  521. WebClient client = new WebClient(url);
  522. client.Request.Method = HttpRequest.METHOD_GET;
  523. client.Request.Referer = url.AbsoluteUri;
  524. client.ConnectAsync((www) =>
  525. {
  526. try
  527. {
  528. if (www.Response != null)
  529. {
  530. byte[] data = client.Response.ReadContentToEnd();
  531. handler.Invoke(data);
  532. }
  533. else
  534. {
  535. handler.Invoke(null);
  536. }
  537. }
  538. catch (Exception err)
  539. {
  540. log.Error(err.Message, err);
  541. handler.Invoke(null);
  542. }
  543. finally
  544. {
  545. www.Dispose();
  546. }
  547. });
  548. }
  549. public static string Post(Uri url, string referer = null, Encoding enc = null)
  550. {
  551. if (enc == null)
  552. {
  553. enc = CUtils.UTF8;
  554. }
  555. if (referer == null)
  556. {
  557. referer = url.AbsoluteUri;
  558. }
  559. using (WebClient client = new WebClient(url))
  560. {
  561. client.Request.Method = HttpRequest.METHOD_POST;
  562. client.Request.ContentType = "application/x-www-form-urlencoded";
  563. client.Request.Referer = referer;
  564. client.Connect();
  565. byte[] data = client.Response.ReadContentToEnd();
  566. string ret = enc.GetString(data);
  567. return ret;
  568. }
  569. }
  570. public static void PostAsync(Uri url, string referer, Encoding enc, HttpPostHandler handler)
  571. {
  572. if (enc == null)
  573. {
  574. enc = CUtils.UTF8;
  575. }
  576. if (referer == null)
  577. {
  578. referer = url.AbsoluteUri;
  579. }
  580. WebClient client = new WebClient(url);
  581. client.Request.Method = HttpRequest.METHOD_POST;
  582. client.Request.ContentType = "application/x-www-form-urlencoded";
  583. client.Request.Referer = referer;
  584. client.ConnectAsync((www) =>
  585. {
  586. try
  587. {
  588. if (www.Response != null)
  589. {
  590. byte[] data = client.Response.ReadContentToEnd();
  591. string ret = enc.GetString(data);
  592. handler.Invoke(ret);
  593. }
  594. else
  595. {
  596. handler.Invoke(null);
  597. }
  598. }
  599. catch (Exception err)
  600. {
  601. log.Error(err.Message, err);
  602. handler.Invoke(null);
  603. }
  604. finally
  605. {
  606. www.Dispose();
  607. }
  608. });
  609. }
  610. public static void PostAsync(Uri url, Encoding enc, HttpPostHandler handler)
  611. {
  612. PostAsync(url, null, CUtils.UTF8, handler);
  613. }
  614. public static void PostAsync(Uri url, HttpPostHandler handler)
  615. {
  616. PostAsync(url, CUtils.UTF8, handler);
  617. }
  618. #endregion
  619. //----------------------------------------------------------------------------------------------
  620. #region UTILS
  621. public static readonly string BR = "\r\n";
  622. public static readonly char[] SPLIT = new char[] { ':' };
  623. public static string FormatPath(string path)
  624. {
  625. path = path.Replace('\\', '/');
  626. return path;
  627. }
  628. private static bool TryGetResponseValue(string line, string key, out string value)
  629. {
  630. if (line.ToLower().StartsWith(key.ToLower()))
  631. {
  632. string[] kv = line.Split(SPLIT, 2);
  633. value = kv[1].Trim();
  634. return true;
  635. }
  636. value = null;
  637. return false;
  638. }
  639. public static string ReadLine(Stream input)
  640. {
  641. byte _r = (byte)'\r';
  642. byte _n = (byte)'\n';
  643. using (var ms = MemoryStreamObjectPool.AllocAutoRelease())
  644. {
  645. int a0 = 0;
  646. while (a0 >= 0)
  647. {
  648. int a1 = input.ReadByte();
  649. if (a1 < 0)
  650. {
  651. break;
  652. }
  653. ms.WriteByte((byte)a1);
  654. if (a0 == _r && a1 == _n)
  655. {
  656. break;
  657. }
  658. a0 = a1;
  659. }
  660. ms.Flush();
  661. return Encoding.ASCII.GetString(ms.GetBuffer(), 0, (int)(ms.Length - 2));
  662. }
  663. }
  664. #endregion
  665. }
  666. }