You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

346 lines
13 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  1. using Apewer;
  2. using Apewer.Internals;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.IO;
  6. using System.Net;
  7. using System.Net.Security;
  8. using System.Security;
  9. using System.Security.Cryptography.X509Certificates;
  10. using System.Text;
  11. namespace Apewer.Network
  12. {
  13. /// <summary></summary>
  14. public class HttpClient
  15. {
  16. private string _key = TextUtility.Guid();
  17. internal bool _locked = false;
  18. private TextSet _properties = new TextSet(true);
  19. private HttpRequest _request = new HttpRequest();
  20. private HttpResponse _response = new HttpResponse();
  21. private Exception _lastexception = null;
  22. private HttpWebRequest _lastrequest = null;
  23. private HttpWebResponse _lastresponse = null;
  24. /// <summary></summary>
  25. public HttpRequest Request { get { return _request; } }
  26. /// <summary></summary>
  27. public HttpResponse Response { get { return _response; } }
  28. /// <summary></summary>
  29. public string Key { get { return _properties["Key"]; } }
  30. /// <exception cref="System.ArgumentException"></exception>
  31. /// <exception cref="System.ArgumentNullException"></exception>
  32. /// <exception cref="System.InvalidOperationException"></exception>
  33. /// <exception cref="System.NotSupportedException"></exception>
  34. /// <exception cref="System.ObjectDisposedException"></exception>
  35. /// <exception cref="System.Security.SecurityException"></exception>
  36. /// <exception cref="System.Net.ProtocolViolationException"></exception>
  37. /// <exception cref="System.Net.WebException"></exception>
  38. private static HttpWebRequest CreateRequest(HttpRequest request)
  39. {
  40. var error = (Exception)null;
  41. if (request == null) throw new ArgumentNullException("Request", "参数无效。");
  42. var properties = request._properties;
  43. var certificates = request._certificates;
  44. var headers = request._headers;
  45. var cookies = request._cookies;
  46. var timeout = request._timeout;
  47. var url = properties["Url"];
  48. // 全局证书验证。
  49. var https = url.ToLower().StartsWith("https");
  50. if (https) SslUtility.ApproveValidation();
  51. // 创建对象。
  52. var http = (HttpWebRequest)WebRequest.Create(properties["Url"]);
  53. // 证书。
  54. foreach (var certificate in certificates)
  55. {
  56. if (certificate == null) continue;
  57. if (certificate.LongLength < 1L) continue;
  58. try
  59. {
  60. var x509 = new X509Certificate(certificate);
  61. http.ClientCertificates.Add(x509);
  62. }
  63. finally { }
  64. }
  65. // 请求超时。
  66. if (timeout > 0) http.Timeout = timeout;
  67. // 方法。
  68. http.Method = request._method.ToString();
  69. // 允许重定向。
  70. http.AllowAutoRedirect = request._redirect;
  71. // 头。
  72. foreach (var header in headers.Origin)
  73. {
  74. var key = header.Key.ToTrim(); ;
  75. var value = header.Value.ToTrim();
  76. if (TextUtility.IsBlank(key)) continue;
  77. if (TextUtility.IsBlank(value)) continue;
  78. try
  79. {
  80. switch (key.Lower())
  81. {
  82. case "accept":
  83. http.Accept = value;
  84. break;
  85. case "connection":
  86. http.Connection = value;
  87. break;
  88. case "content-length":
  89. http.ContentLength = NumberUtility.Int64(value);
  90. break;
  91. case "content-type":
  92. http.ContentType = value;
  93. break;
  94. case "date":
  95. // request.Date = ClockUtility.Parse(value);
  96. break;
  97. case "expect":
  98. http.Expect = value;
  99. break;
  100. #if !NET20
  101. case "host":
  102. http.Host = value;
  103. break;
  104. #endif
  105. case "if-modified-since":
  106. // request.IfModifiedSince = ClockUtility.Parse(value);
  107. break;
  108. case "referer":
  109. case "referrer":
  110. http.Referer = value;
  111. break;
  112. case "transfer-encoding":
  113. http.TransferEncoding = value;
  114. break;
  115. case "user-agent":
  116. http.UserAgent = value;
  117. break;
  118. default:
  119. http.Headers.Add(header.Key, header.Value);
  120. break;
  121. }
  122. }
  123. catch { }
  124. }
  125. if (!TextUtility.IsBlank(properties["TransferEncoding"])) http.TransferEncoding = properties["TransferEncoding"];
  126. if (!TextUtility.IsBlank(properties["ContentType"])) http.ContentType = properties["ContentType"];
  127. if (!TextUtility.IsBlank(properties["UserAgent"])) http.UserAgent = properties["UserAgent"];
  128. if (!TextUtility.IsBlank(properties["Referer"])) http.Referer = properties["Referer"];
  129. // Cookies。
  130. if (cookies != null) http.CookieContainer = cookies;
  131. // 对 POST 请求加入内容数据。
  132. if (request._method == HttpMethod.POST)
  133. {
  134. if (request._stream != null)
  135. {
  136. http.ContentLength = request._contentlength;
  137. var rs = http.GetRequestStream();
  138. try
  139. {
  140. BytesUtility.Read(request._stream, rs, request._progress);
  141. }
  142. catch { }
  143. }
  144. else if (request._data != null && request._data.LongLength > 0L)
  145. {
  146. http.ContentLength = request._data.Length;
  147. var rs = http.GetRequestStream();
  148. try
  149. {
  150. rs.Write(request._data, 0, request._data.Length);
  151. }
  152. catch (Exception exception) { error = exception; }
  153. }
  154. }
  155. if (error == null) return http;
  156. throw error;
  157. }
  158. /// <exception cref="System.ArgumentNullException"></exception>
  159. /// <exception cref="System.InvalidOperationException"></exception>
  160. /// <exception cref="System.NotSupportedException"></exception>
  161. /// <exception cref="System.ObjectDisposedException"></exception>
  162. /// <exception cref="System.Net.ProtocolViolationException"></exception>
  163. /// <exception cref="System.Net.WebException"></exception>
  164. private static HttpWebResponse CreateResponse(HttpWebRequest request, HttpResponse response)
  165. {
  166. var error = (Exception)null;
  167. // 发起请求。
  168. var http = (HttpWebResponse)request.GetResponse();
  169. // 读取头。
  170. foreach (var key in http.Headers.AllKeys) response._headers[key] = http.Headers[key];
  171. response._cached = http.IsFromCache;
  172. response._cookies = http.Cookies;
  173. response._contentlength = http.ContentLength;
  174. response._statuscode = http.StatusCode;
  175. response._properties["CharacterSet"] = http.CharacterSet;
  176. response._properties["ContentEncoding"] = http.ContentEncoding;
  177. response._properties["ContentType"] = http.ContentType;
  178. response._properties["LastModified"] = ClockUtility.Lucid(http.LastModified);
  179. response._properties["Method"] = http.Method;
  180. response._properties["ProtocolVersion"] = http.ProtocolVersion.ToString();
  181. response._properties["Url"] = http.ResponseUri.OriginalString;
  182. response._properties["Server"] = http.Server;
  183. response._properties["StatusDescription"] = http.StatusDescription;
  184. // 读取内容。
  185. var rs = http.GetResponseStream();
  186. if (response._stream == null)
  187. {
  188. var memory = new MemoryStream();
  189. try
  190. {
  191. BytesUtility.Read(rs, memory, response.ProgressCallback);
  192. }
  193. catch (Exception exception) { error = exception; }
  194. response._data = memory.ToArray();
  195. memory.Dispose();
  196. }
  197. else
  198. {
  199. BytesUtility.Read(rs, response._stream, response.ProgressCallback);
  200. }
  201. http.Close();
  202. if (error == null) return http;
  203. throw error;
  204. }
  205. /// <summary></summary>
  206. public Exception LatestException { get { return _lastexception; } }
  207. /// <summary></summary>
  208. public WebException WebException { get { return _lastexception as WebException; } }
  209. /// <summary></summary>
  210. public Exception Send()
  211. {
  212. var error = null as Exception;
  213. if (_request._locked || _response._locked)
  214. {
  215. error = new InvalidOperationException("存在已启动的请求,无法继续此次请求。");
  216. }
  217. else
  218. {
  219. _request._locked = true;
  220. _response._locked = true;
  221. try
  222. {
  223. _lastrequest = CreateRequest(_request);
  224. _lastresponse = CreateResponse(_lastrequest, _response);
  225. }
  226. catch (Exception exception)
  227. {
  228. error = exception;
  229. }
  230. _request._locked = false;
  231. _response._locked = false;
  232. }
  233. _lastexception = error;
  234. return error;
  235. }
  236. /// <summary>GET</summary>
  237. public static HttpClient SimpleGet(string url, int timeout = 30000)
  238. {
  239. var http = new HttpClient();
  240. http.Request.Url = url;
  241. http.Request.Timeout = timeout;
  242. http.Request.Method = HttpMethod.GET;
  243. http.Send();
  244. return http;
  245. }
  246. /// <summary>POST</summary>
  247. public static HttpClient SimplePost(string url, byte[] data, int timeout = 30000, string type = "application/octet-stream")
  248. {
  249. var http = new HttpClient();
  250. http.Request.Url = url;
  251. http.Request.Timeout = timeout;
  252. http.Request.Method = HttpMethod.POST;
  253. if (type.NotBlank()) http.Request.ContentType = type;
  254. http.Request.Data = data ?? BytesUtility.Empty;
  255. http.Send();
  256. return http;
  257. }
  258. /// <summary>POST text/plain</summary>
  259. public static HttpClient SimpleText(string url, string text, int timeout = 30000, string type = "text/plain")
  260. {
  261. return SimplePost(url, TextUtility.Bytes(text), timeout, type);
  262. }
  263. /// <summary>POST application/x-www-form-urlencoded</summary>
  264. public static HttpClient SimpleForm(string url, IDictionary<string, string> form, int timeout = 30000)
  265. {
  266. if (form == null) return SimplePost(url, BytesUtility.Empty, timeout, "application/x-www-form-urlencoded");
  267. var cache = new List<string>();
  268. foreach (var i in form)
  269. {
  270. var key = TextUtility.EncodeUrl(i.Key);
  271. var value = TextUtility.EncodeUrl(i.Value);
  272. cache.Add(key + "=" + value);
  273. }
  274. var text = string.Join("&", cache.ToArray());
  275. var data = TextUtility.Bytes(text);
  276. return SimplePost(url, data, timeout, "application/x-www-form-urlencoded");
  277. }
  278. /// <summary>POST application/x-www-form-urlencoded</summary>
  279. public static HttpClient SimpleForm(string url, TextSet form, int timeout = 30000)
  280. {
  281. return SimpleForm(url, form.Origin, timeout);
  282. }
  283. /// <summary>合并表单参数,不包含 Query 的 ? 符号。</summary>
  284. public static string MergeForm(IDictionary<string, string> form)
  285. {
  286. if (form == null) return "";
  287. if (form.Count < 1) return "";
  288. var cache = new List<string>();
  289. foreach (var i in form)
  290. {
  291. var key = TextUtility.EncodeUrl(i.Key);
  292. var value = TextUtility.EncodeUrl(i.Value);
  293. cache.Add(key + "=" + value);
  294. }
  295. var text = string.Join("&", cache.ToArray());
  296. return text;
  297. }
  298. /// <summary>合并表单参数,不包含 Query 的 ? 符号。</summary>
  299. public static string MergeForm(TextSet form)
  300. {
  301. if (form == null) return "";
  302. return MergeForm(form.Origin());
  303. }
  304. }
  305. }