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.

1219 lines
44 KiB

4 years ago
4 years ago
  1. using Apewer.Models;
  2. using Apewer.Source;
  3. using System;
  4. using System.Collections;
  5. using System.Collections.Generic;
  6. using System.Collections.Specialized;
  7. using System.IO;
  8. using System.Net;
  9. using System.Text;
  10. #if NETFX
  11. using System.Web;
  12. #endif
  13. #if NETCORE
  14. using Microsoft.AspNetCore.Http;
  15. #endif
  16. namespace Apewer.Web
  17. {
  18. /// <summary></summary>
  19. public static class WebUtility
  20. {
  21. /// <summary>按 & 拆分多个参数。</summary>
  22. public static StringPairs ParseParameters(string query, bool decode = true)
  23. {
  24. var input = query;
  25. var list = new StringPairs();
  26. if (!string.IsNullOrEmpty(input))
  27. {
  28. if (input[0] == '?') input = input.Substring(1);
  29. var args = input.Split('&');
  30. foreach (var arg in args)
  31. {
  32. var equals = arg.IndexOf("=");
  33. var left = equals < 0 ? arg : arg.Substring(0, equals);
  34. var right = equals < 0 ? "" : arg.Substring(equals + 1);
  35. if (decode)
  36. {
  37. left = TextUtility.DecodeUrl(left);
  38. right = TextUtility.DecodeUrl(right);
  39. }
  40. list.Add(left, right);
  41. }
  42. }
  43. return list;
  44. }
  45. /// <summary>获取 URL 查询段,不存在的段为 NULL 值。可要求解码。</summary>
  46. public static string GetSegmental(Uri url, int index = 3, bool decode = false)
  47. {
  48. try
  49. {
  50. if (url == null) return null;
  51. if (index < 0) return null;
  52. var sus = url.AbsolutePath.Split('/');
  53. if (sus.Length > index)
  54. {
  55. var result = sus[index];
  56. if (decode) result = TextUtility.DecodeUrl(result);
  57. return result;
  58. }
  59. }
  60. catch { }
  61. return null;
  62. }
  63. /// <summary>获取参数并解码,可要求修剪参数值。</summary>
  64. public static string GetParameter(string encoded, bool trim, params string[] names)
  65. {
  66. if (string.IsNullOrEmpty(encoded)) return null;
  67. if (names == null || names.Length < 1) return null;
  68. // Names 转为小写,加强适配。
  69. var lowerNames = new List<string>(names.Length);
  70. foreach (var name in names)
  71. {
  72. var lower = TextUtility.ToLower(name);
  73. if (string.IsNullOrEmpty(lower)) continue;
  74. lowerNames.Add(lower);
  75. }
  76. if (lowerNames.Count < 1) return null;
  77. // 分参数对比。
  78. var parameters = ParseParameters(encoded);
  79. var matched = false;
  80. foreach (var parameter in parameters)
  81. {
  82. var left = parameter.Key;
  83. var right = parameter.Value;
  84. if (trim) right = TextUtility.ToLower(right);
  85. var lowerLeft = TextUtility.ToLower(left);
  86. if (lowerNames.Contains(right))
  87. {
  88. matched = true;
  89. if (!string.IsNullOrEmpty(right)) return right;
  90. }
  91. }
  92. return matched ? "" : null;
  93. }
  94. /// <summary>获取参数并解码,默认不修剪参数值。</summary>
  95. public static string GetParameter(string encoded, params string[] names) => GetParameter(encoded, false, names);
  96. /// <summary>指定可能的参数名,从 URL 中获取参数值并解码,默认不修剪参数值。</summary>
  97. public static string GetParameter(Uri url, params string[] names) => url == null ? null : GetParameter(url.Query, false, names);
  98. /// <summary>获取参数,默认不修剪参数值。</summary>
  99. public static string GetParameter(List<KeyValuePair<string, string>> parameters, params string[] names) => GetParameter(parameters, false, names);
  100. /// <summary>指定可能的参数名,从 URL 中获取参数值并解码,可要求修剪参数值。</summary>
  101. public static string GetParameter(Uri url, bool trim, params string[] names) => url == null ? null : GetParameter(url.Query, trim, names);
  102. /// <summary>获取参数,可要求修剪参数值。</summary>
  103. public static string GetParameter(List<KeyValuePair<string, string>> parameters, bool trim, params string[] names)
  104. {
  105. if (parameters == null || parameters.Count < 1) return null;
  106. if (names == null || names.Length < 1) return null;
  107. var lowerNames = new List<string>(names.Length);
  108. foreach (var name in names)
  109. {
  110. var lower = TextUtility.ToLower(name);
  111. if (string.IsNullOrEmpty(lower)) continue;
  112. lowerNames.Add(lower);
  113. }
  114. foreach (var parameter in parameters)
  115. {
  116. var lowerKey = TextUtility.ToLower(parameter.Key);
  117. if (lowerNames.Contains(lowerKey))
  118. {
  119. var value = parameter.Value;
  120. if (trim) value = TextUtility.ToLower(value);
  121. if (!string.IsNullOrEmpty(value)) return value;
  122. }
  123. }
  124. return null;
  125. }
  126. /// <summary>修剪 IP 地址,去除无效的部分。</summary>
  127. public static string TrimIP(string text)
  128. {
  129. var trimmed = TextUtility.Trim(text);
  130. if (string.IsNullOrEmpty(trimmed)) return "";
  131. var ip = TextUtility.Trim(trimmed.Split(':')[0]);
  132. return NetworkUtility.IsIP(ip) ? ip : "";
  133. }
  134. private static string GetClientIP(string directIP, StringPairs headers)
  135. {
  136. // 获取直连 IP。
  137. var ip = directIP;
  138. if (NetworkUtility.FromLAN(ip)) // 直连 IP 来自内网。
  139. {
  140. // 请求可能由内网服务器反向代理,检测 Forwarded IP,取最后一个 IP。
  141. foreach (var header in headers)
  142. {
  143. if (header.Key != "x-forwarded-for") continue;
  144. var fips = new List<string>();
  145. var split = header.Value.Split(',', ' ');
  146. foreach (var para in split)
  147. {
  148. var fip = TrimIP(para);
  149. if (!string.IsNullOrEmpty(fip)) fips.Add(fip);
  150. }
  151. if (fips.Count > 0) return fips[fips.Count - 1];
  152. break;
  153. }
  154. }
  155. else // 直连 IP 来自公网。
  156. {
  157. // 请求可能由阿里云 CDN 反向代理,检测 Ali-CDN-Real-IP。
  158. foreach (var header in headers)
  159. {
  160. if (header.Key != "ali-cdn-real-ip") continue;
  161. var alicdn_ip = TrimIP(header.Value);
  162. if (!string.IsNullOrEmpty(alicdn_ip)) return alicdn_ip;
  163. break;
  164. }
  165. }
  166. return ip;
  167. }
  168. /// <summary>请求来自于可信客户端。</summary>
  169. public static bool FromTrusted(string clientIP, params string[] addresses)
  170. {
  171. var cip = TrimIP(clientIP);
  172. if (!NetworkUtility.IsIP(cip)) return false;
  173. // 解析可信地址。
  174. if (addresses == null) return false;
  175. foreach (var address in addresses)
  176. {
  177. if (NetworkUtility.IsIP(address))
  178. {
  179. if (address == cip) return true;
  180. continue;
  181. }
  182. var tip = NetworkUtility.Resolve(address);
  183. if (NetworkUtility.IsIP(tip))
  184. {
  185. if (tip == cip) return true;
  186. }
  187. }
  188. return false;
  189. }
  190. /// <summary>获取 HTTP 方法。</summary>
  191. public static Network.HttpMethod GetMethod(string method)
  192. {
  193. if (!string.IsNullOrEmpty(method))
  194. {
  195. var upper = TextUtility.ToUpper(method);
  196. if (upper.Contains("OPTIONS")) return Network.HttpMethod.OPTIONS;
  197. else if (upper.Contains("POST")) return Network.HttpMethod.POST;
  198. else if (upper.Contains("GET")) return Network.HttpMethod.GET;
  199. else if (upper.Contains("CONNECT")) return Network.HttpMethod.CONNECT;
  200. else if (upper.Contains("DELETE")) return Network.HttpMethod.DELETE;
  201. else if (upper.Contains("HEAD")) return Network.HttpMethod.HEAD;
  202. else if (upper.Contains("PATCH")) return Network.HttpMethod.PATCH;
  203. else if (upper.Contains("PUT")) return Network.HttpMethod.PUT;
  204. else if (upper.Contains("TRACE")) return Network.HttpMethod.TRACE;
  205. }
  206. return Network.HttpMethod.NULL;
  207. }
  208. /// <summary>获取 X-Forwarded-For,不存在时返回 NULL 值。</summary>
  209. public static string GetForwardedIP(StringPairs headers)
  210. {
  211. if (headers == null) return "";
  212. var value = headers.GetValue("x-forwarded-for", true);
  213. if (string.IsNullOrEmpty(value)) return "";
  214. var split = value.Split(',');
  215. var ips = new List<string>();
  216. foreach (var para in split)
  217. {
  218. var ip = TrimIP(para);
  219. if (string.IsNullOrEmpty(ip)) continue;
  220. ips.Add(ip);
  221. }
  222. if (ips.Count > 0) return string.Join(", ", ips.ToArray());
  223. return "";
  224. }
  225. /// <summary>获取 User Agent。</summary>
  226. public static string GetUserAgent(StringPairs headers) => headers == null ? null : headers.GetValue("user-agent");
  227. /// <summary>获取查询字符串。</summary>
  228. public static string QueryString(ApiRequest request, string name)
  229. {
  230. if (request == null) return null;
  231. if (request.Parameters == null) return null;
  232. if (string.IsNullOrEmpty(name)) return null;
  233. return request.Parameters.GetValue(name, true);
  234. }
  235. /// <summary>获取表单。</summary>
  236. public static string Form(ApiRequest request, string name)
  237. {
  238. if (request == null) return null;
  239. if (string.IsNullOrEmpty(request.PostText)) return null;
  240. if (string.IsNullOrEmpty(name)) return null;
  241. var ps = ParseParameters(request.PostText, true);
  242. return ps.GetValue(name, true);
  243. }
  244. #region ApiController
  245. /// <summary>以 POST 转移请求到其它 URL。</summary>
  246. public static string Transfer(ApiController controller, string url, string application = null, string function = null)
  247. {
  248. if (controller == null || controller.Request == null || controller.Response == null) return "ApiControllser 无效。";
  249. if (url.IsEmpty()) return "ApiController 无效。";
  250. var s = Json.NewObject();
  251. s.SetProperty("random", TextUtility.NewGuid());
  252. s.SetProperty("application", application.IsEmpty() ? controller.Request.Application : application);
  253. s.SetProperty("function", function.IsEmpty() ? controller.Request.Function : function);
  254. s.SetProperty("data", controller.Request.Data);
  255. s.SetProperty("session", controller.Request.Session);
  256. s.SetProperty("ticket", controller.Request.Ticket);
  257. s.SetProperty("page", controller.Request.Page);
  258. var c = new Network.HttpClient();
  259. c.Request.Url = url;
  260. c.Request.Method = Network.HttpMethod.POST;
  261. // TODO 设置 Request 的 Cookies。
  262. c.Request.Data = s.ToString().GetBytes();
  263. var e = c.Send();
  264. if (e != null) return e.Message;
  265. // TODO 解析 Response 的 Cookies。
  266. var r = Json.Parse(c.Response.Data.GetString());
  267. if (r == null || !r.Available)
  268. {
  269. controller.Response.Error("请求失败。");
  270. return "请求失败。";
  271. }
  272. if (r["status"] != "ok")
  273. {
  274. controller.Response.Error(r["message"]);
  275. return r["message"];
  276. }
  277. controller.Response.Data.Reset(r.GetProperty("data"));
  278. return null;
  279. }
  280. /// <summary>创建新控制器实例并运行。</summary>
  281. /// <typeparam name="T">新控制器的类型。</typeparam>
  282. /// <param name="current">当前控制器。</param>
  283. public static T Run<T>(ApiController current) where T : ApiController, new()
  284. {
  285. if (current == null) return null;
  286. var target = new T();
  287. target.Request = current.Request;
  288. target.Response = current.Response;
  289. target.AfterInitialized?.Invoke();
  290. return target;
  291. }
  292. /// <summary>创建新控制器实例并运行。</summary>
  293. /// <param name="current">当前控制器。</param>
  294. /// <param name="controller">新控制器的类型。</param>
  295. public static ApiController Run(ApiController current, Type controller)
  296. {
  297. if (current == null || controller == null) return null;
  298. if (!RuntimeUtility.IsInherits(controller, typeof(ApiController))) return null;
  299. var target = ApiProcessor.CreateController(controller);
  300. if (target == null) return null;
  301. target.Request = current.Request;
  302. target.Response = current.Response;
  303. target.AfterInitialized?.Invoke();
  304. return target;
  305. }
  306. #endregion
  307. #region ApiRequest
  308. /// <summary>获取 URL 查询段,不存在的段为 NULL 值。可要求解码。</summary>
  309. public static string GetSegmentalUrl(ApiRequest request, int index = 3, bool decode = false)
  310. {
  311. return request == null ? null : GetSegmental(request.Url, index, decode);
  312. }
  313. /// <summary>获取参数,指定可能的参数名,从 URL 中获取参数时将解码,默认不修剪参数值。</summary>
  314. public static string GetParameter(ApiRequest request, params string[] names) => GetParameter(request, false, names);
  315. /// <summary>获取参数,指定可能的参数名,从 URL 中获取参数时将解码,可要求修剪参数值。</summary>
  316. public static string GetParameter(ApiRequest request, bool trim, params string[] names)
  317. {
  318. if (request == null) return null;
  319. if (names == null || names.Length < 1) return null;
  320. var dedupNames = new List<string>(names.Length);
  321. var lowerNames = new List<string>(names.Length);
  322. foreach (var name in names)
  323. {
  324. if (string.IsNullOrEmpty(name)) continue;
  325. if (dedupNames.Contains(name)) continue;
  326. else dedupNames.Add(name);
  327. var lower = TextUtility.ToLower(name);
  328. if (lowerNames.Contains(lower)) continue;
  329. else lowerNames.Add(lower);
  330. }
  331. var matched = false;
  332. // POST 优先。
  333. var data = request.Data;
  334. if (data != null && data.IsObject)
  335. {
  336. var properties = data.GetProperties();
  337. if (properties != null)
  338. {
  339. // Json 区分大小写,先全字匹配。
  340. foreach (var property in properties)
  341. {
  342. if (!property.IsProperty) continue;
  343. var name = property.Name;
  344. if (!dedupNames.Contains(name)) continue;
  345. var value = property.Value;
  346. if (value == null) continue;
  347. matched = true;
  348. var text = value.ToString();
  349. if (trim) text = TextUtility.Trim(text);
  350. if (!string.IsNullOrEmpty(text)) return text;
  351. }
  352. // 以小写模糊匹配。
  353. foreach (var property in properties)
  354. {
  355. if (!property.IsProperty) continue;
  356. var name = TextUtility.ToLower(property.Name);
  357. if (!lowerNames.Contains(name)) continue;
  358. var value = property.Value;
  359. if (value == null) continue;
  360. matched = true;
  361. var text = value.ToString();
  362. if (trim) text = TextUtility.Trim(text);
  363. if (!string.IsNullOrEmpty(text)) return text;
  364. }
  365. }
  366. }
  367. // 从已解析的 Get 参数中搜索。
  368. if (request.Parameters != null)
  369. {
  370. var value = GetParameter(request.Parameters, trim, names);
  371. if (!string.IsNullOrEmpty(value)) return value;
  372. if (value != null) matched = true;
  373. }
  374. return matched ? "" : null;
  375. }
  376. #endregion
  377. #region ApiResponse
  378. /// <summary>设置响应。</summary>
  379. public static string Respond(ApiResponse response, Json data, bool lower = true)
  380. {
  381. if (response == null) return "Response 对象无效。";
  382. if (data != null)
  383. {
  384. if (lower) data = Json.ToLower(data);
  385. response.Data.Reset(data);
  386. }
  387. return null;
  388. }
  389. /// <summary>设置响应,当发生错误时设置响应。返回错误信息。</summary>
  390. public static string Respond(ApiResponse response, IList list, bool lower = true, int depth = -1, bool force = false)
  391. {
  392. if (response == null) return "Response 对象无效。";
  393. if (list == null)
  394. {
  395. var error = "列表对象无效。";
  396. response.Error(error);
  397. return error;
  398. }
  399. var json = Json.Parse(list, lower, depth, force);
  400. if (json == null || !json.Available)
  401. {
  402. var error = "列表无法序列化。";
  403. response.Error(error);
  404. return error;
  405. }
  406. if (response.Data == null) response.Data = Json.NewObject();
  407. response.Data.SetProperty("count", list.Count);
  408. response.Data.SetProperty("list", Json.Parse(list, lower, depth, force));
  409. return null;
  410. }
  411. /// <summary>设置响应,当发生错误时设置响应。返回错误信息。</summary>
  412. public static string Respond(ApiResponse response, Record record, bool lower = true)
  413. {
  414. if (response == null) return "Response 对象无效。";
  415. if (record == null)
  416. {
  417. var error = "记录无效。";
  418. response.Error(error);
  419. return error;
  420. }
  421. var json = Json.Parse(record, lower);
  422. if (json == null || !json.Available)
  423. {
  424. var error = "记录无法序列化。";
  425. response.Error(error);
  426. return error;
  427. }
  428. if (response.Data == null) response.Data = Json.NewObject();
  429. response.Data.Reset(json);
  430. return null;
  431. }
  432. /// <summary>设置 status 为 error,并设置 message 的内容。</summary>
  433. public static void SetError(ApiResponse response, string message = "未知错误。")
  434. {
  435. if (response == null) return;
  436. response.Type = ApiFormat.Json;
  437. response.Status = "error";
  438. response.Message = message ?? TextUtility.EmptyString;
  439. }
  440. /// <summary>设置 status 为 error,并设置 message 的内容。</summary>
  441. public static void SetError(ApiResponse response, Exception exception)
  442. {
  443. if (response == null) return;
  444. response.Exception = exception;
  445. response.Type = ApiFormat.Json;
  446. response.Status = "error";
  447. try
  448. {
  449. response.Message = exception == null ? "无效异常。" : exception.Message;
  450. response.Data.Reset(Json.NewObject());
  451. response.Data["message"] = exception.Message;
  452. response.Data["helplink"] = exception.HelpLink;
  453. response.Data["source"] = exception.Source;
  454. response.Data["stacktrace"] = Json.Parse(exception.StackTrace.Split('\n'), true);
  455. }
  456. catch { }
  457. }
  458. /// <summary>输出 UTF-8 文本。</summary>
  459. public static void SetText(ApiResponse response, string content, string type = "text/plain; charset=utf-8")
  460. {
  461. if (response == null) return;
  462. response.Type = ApiFormat.Text;
  463. response.TextString = content;
  464. response.TextType = type ?? "text/plain; charset=utf-8";
  465. }
  466. /// <summary>输出字节数组。</summary>
  467. public static void SetBinary(ApiResponse response, byte[] content, string type = "application/octet-stream")
  468. {
  469. if (response == null) return;
  470. response.Type = ApiFormat.Binary;
  471. response.BinaryStream = null;
  472. response.BinaryBytes = content;
  473. response.BinaryPath = null;
  474. response.BinaryType = type ?? "application/octet-stream";
  475. }
  476. /// <summary>输出二进制。</summary>
  477. public static void SetBinary(ApiResponse response, Stream content, string type = "application/octet-stream")
  478. {
  479. if (response == null) return;
  480. response.Type = ApiFormat.Binary;
  481. response.BinaryStream = content;
  482. response.BinaryBytes = null;
  483. response.BinaryPath = null;
  484. response.BinaryType = type ?? "application/octet-stream";
  485. }
  486. /// <summary>输出二进制。</summary>
  487. public static void SetBinary(ApiResponse response, string path, string type = "application/octet-stream")
  488. {
  489. if (response == null) return;
  490. response.Type = ApiFormat.Binary;
  491. response.BinaryStream = null;
  492. response.BinaryBytes = null;
  493. response.BinaryPath = path;
  494. response.BinaryType = type ?? "application/octet-stream";
  495. }
  496. /// <summary>输出文件。</summary>
  497. public static void SetFile(ApiResponse response, Stream stream, string name, string type = "application/octet-stream")
  498. {
  499. if (response == null) return;
  500. response.Type = ApiFormat.File;
  501. response.FileStream = stream;
  502. response.FileName = name;
  503. response.FileType = type ?? "application/octet-stream";
  504. }
  505. /// <summary>重定向。</summary>
  506. public static void SetRedirect(ApiResponse response, string url)
  507. {
  508. if (response == null) return;
  509. response.Type = ApiFormat.Redirect;
  510. response.RedirectUrl = url;
  511. }
  512. #endregion
  513. #region Application & Path
  514. /// <summary>获取程序目录的路径。</summary>
  515. public static string AppDirectory
  516. {
  517. get
  518. {
  519. // AppDomain.CurrentDomain.BaseDirectory
  520. // AppDomain.CurrentDomain.SetupInformation.ApplicationBase
  521. return AppDomain.CurrentDomain.BaseDirectory;
  522. }
  523. }
  524. /// <summary>MapPath。</summary>
  525. public static string MapPath(params string[] names)
  526. {
  527. var list = new List<string>();
  528. if (names != null)
  529. {
  530. var invalid = StorageUtility.InvalidPathChars;
  531. foreach (var name in names)
  532. {
  533. if (string.IsNullOrEmpty(name)) continue;
  534. var split = name.Split('/', '\\');
  535. foreach (var s in split)
  536. {
  537. var sb = new StringBuilder();
  538. foreach (var c in s)
  539. {
  540. if (Array.IndexOf(invalid, "c") < 0) sb.Append(c);
  541. }
  542. var t = sb.ToString();
  543. if (!string.IsNullOrEmpty(t)) list.Add(t);
  544. }
  545. }
  546. }
  547. return list.Count > 1 ? StorageUtility.CombinePath(list.ToArray()) : list[0];
  548. }
  549. #endregion
  550. #region Request - GetMethod
  551. /// <summary>获取 HTTP 方法。</summary>
  552. public static Network.HttpMethod GetMethod(HttpListenerRequest request)
  553. {
  554. if (request == null) return Network.HttpMethod.NULL;
  555. return GetMethod(request.HttpMethod);
  556. }
  557. #if NETFX || NETCORE
  558. /// <summary>获取 HTTP 方法。</summary>
  559. public static Network.HttpMethod GetMethod(HttpRequest request)
  560. {
  561. if (request == null) return Network.HttpMethod.NULL;
  562. #if NETFX
  563. return GetMethod(request.HttpMethod);
  564. #else
  565. return GetMethod(request.Method);
  566. #endif
  567. }
  568. #endif
  569. #endregion
  570. #region Request - Client IP
  571. /// <summary>获取直连端的 IP。</summary>
  572. public static string GetDirectIP(HttpListenerRequest request)
  573. {
  574. if (request == null) return null;
  575. var ip = request.UserHostAddress;
  576. return TrimIP(ip);
  577. }
  578. /// <summary>获取客户端 IP。</summary>
  579. public static string GetClientIP(HttpListenerRequest request)
  580. {
  581. if (request == null) return null;
  582. var headers = GetHeaders(request, true);
  583. var directIP = GetDirectIP(request);
  584. return GetClientIP(directIP, headers);
  585. }
  586. #if NETFX || NETCORE
  587. /// <summary>获取直连端的 IP。</summary>
  588. public static string GetDirectIP(HttpRequest request)
  589. {
  590. if (request == null) return null;
  591. #if NETFX
  592. var ip = request.UserHostAddress;
  593. #else
  594. if (request.HttpContext == null || request.HttpContext.Connection == null) return null;
  595. var ip = request.HttpContext.Connection.RemoteIpAddress.ToString();
  596. #endif
  597. return TrimIP(ip);
  598. }
  599. /// <summary>获取客户端 IP。</summary>
  600. public static string GetClientIP(HttpRequest request)
  601. {
  602. if (request == null) return null;
  603. var headers = GetHeaders(request, true);
  604. var directIP = GetDirectIP(request);
  605. return GetClientIP(directIP, headers);
  606. }
  607. #endif
  608. #endregion
  609. #region Request - URL
  610. /// <summary>获取请求的 URL,失败时返回 NULL 值。</summary>
  611. public static Uri GetUrl(HttpListenerRequest request)
  612. {
  613. if (request == null) return null;
  614. return request.Url;
  615. }
  616. #if NETFX || NETCORE
  617. /// <summary>获取请求的 URL,失败时返回 NULL 值。</summary>
  618. public static Uri GetUrl(HttpRequest request)
  619. {
  620. if (request == null) return null;
  621. #if NETFX
  622. return request.Url;
  623. #else
  624. var context = request.HttpContext;
  625. if (context == null) return null;
  626. var https = request.IsHttps;
  627. var port = context.Connection.LocalPort;
  628. var query = request.QueryString == null ? null : request.QueryString.Value;
  629. var sb = new StringBuilder();
  630. sb.Append(https ? "https://" : "http://");
  631. sb.Append(request.Host.Host ?? "");
  632. if ((https && port != 443) || (!https && port != 80))
  633. {
  634. sb.Append(":");
  635. sb.Append(port);
  636. }
  637. sb.Append(request.Path);
  638. if (!string.IsNullOrEmpty(query))
  639. {
  640. sb.Append("?");
  641. sb.Append(query);
  642. }
  643. var url = sb.ToString();
  644. var uri = new Uri(url);
  645. return uri;
  646. #endif
  647. }
  648. #endif
  649. #endregion
  650. #region Request - Parameters
  651. /// <summary>解析 URL 的查询字符串,获取所有已解码的参数。</summary>
  652. public static StringPairs ParseUrlParameters(HttpListenerRequest request)
  653. {
  654. if (request == null) return new StringPairs();
  655. if (request.Url == null) return new StringPairs();
  656. return ParseParameters(request.Url.Query);
  657. }
  658. #if NETFX || NETCORE
  659. /// <summary>解析 URL 的查询字符串,获取所有已解码的参数。</summary>
  660. public static StringPairs ParseUrlParameters(HttpRequest request)
  661. {
  662. if (request == null) return new StringPairs();
  663. #if NETFX
  664. if (request.Url == null) return new StringPairs();
  665. return ParseParameters(request.Url.Query);
  666. #else
  667. var list = new StringPairs();
  668. if (request.Query == null) return list;
  669. var keys = request.Query.Keys;
  670. list.Capacity = keys.Count;
  671. foreach (var key in keys)
  672. {
  673. list.Add(new KeyValuePair<string, string>(key, request.Query[key]));
  674. }
  675. list.Capacity = list.Count;
  676. return list;
  677. #endif
  678. }
  679. #endif
  680. #endregion
  681. #region Request - Headers
  682. /// <summary>获取 HTTP 头。</summary>
  683. public static StringPairs GetHeaders(HttpListenerRequest request, bool lowerKey = false)
  684. {
  685. var sp = new StringPairs();
  686. if (request != null && request.Headers != null)
  687. {
  688. foreach (var key in request.Headers.AllKeys)
  689. {
  690. try
  691. {
  692. var v = request.Headers[key];
  693. if (string.IsNullOrEmpty(v)) continue;
  694. var k = lowerKey ? key.ToLower() : key;
  695. sp.Add(k, v);
  696. }
  697. catch { }
  698. }
  699. }
  700. return sp;
  701. }
  702. /// <summary>获取 User Agent。</summary>
  703. public static string GetUserAgent(HttpListenerRequest request) => GetUserAgent(GetHeaders(request));
  704. #if NETFX || NETCORE
  705. /// <summary>获取 HTTP 头。</summary>
  706. public static StringPairs GetHeaders(HttpRequest request, bool lowerKey = false)
  707. {
  708. var sp = new StringPairs();
  709. if (request != null && request.Headers != null)
  710. {
  711. #if NETFX
  712. foreach (var key in request.Headers.AllKeys)
  713. #else
  714. foreach (var key in request.Headers.Keys)
  715. #endif
  716. {
  717. try
  718. {
  719. var v = request.Headers[key];
  720. if (string.IsNullOrEmpty(v)) continue;
  721. var k = lowerKey ? key.ToLower() : key;
  722. sp.Add(k, v);
  723. }
  724. catch { }
  725. }
  726. }
  727. return sp;
  728. }
  729. /// <summary>获取 X-Forwarded-For。</summary>
  730. public static string GetForwardedIP(HttpRequest request) => GetForwardedIP(GetHeaders(request));
  731. /// <summary>获取 User Agent。</summary>
  732. public static string GetUserAgent(HttpRequest request) => GetUserAgent(GetHeaders(request));
  733. #endif
  734. #endregion
  735. #region Response - Redirect
  736. /// <summary>将客户端重新定向到新 URL。</summary>
  737. /// <remarks>响应 302 状态。</remarks>
  738. public static void Redirect(HttpListenerResponse response, string url)
  739. {
  740. if (response == null) return;
  741. try { response.Redirect(url); } catch { }
  742. }
  743. #if NETFX
  744. /// <summary>将客户端重新定向到新 URL。</summary>
  745. /// <remarks>响应 302 状态。</remarks>
  746. public static void Redirect(HttpResponse response, string url)
  747. {
  748. if (response == null) return;
  749. try { response.Redirect(url, true); } catch { }
  750. }
  751. #endif
  752. #if NETCORE
  753. /// <summary>将客户端重新定向到新 URL。</summary>
  754. /// <remarks>默认响应 302 状态。可指定 permanent = true 以响应 301 状态。</remarks>
  755. public static void Redirect(HttpResponse response, string url, bool permanent = false)
  756. {
  757. if (response == null) return;
  758. try { response.Redirect(url, permanent); } catch { }
  759. }
  760. #endif
  761. #endregion
  762. #region Response - Stop
  763. /// <summary>停止并关闭响应流。可指定向发送缓冲区的数据。</summary>
  764. public static void Stop(HttpListenerResponse response, bool flush = true)
  765. {
  766. if (response == null) return;
  767. try { if (flush) response.OutputStream.Flush(); } catch { }
  768. try { response.Close(); } catch { }
  769. }
  770. #if NETFX || NETCORE
  771. /// <summary>停止并关闭响应流。可指定向发送缓冲区的数据。</summary>
  772. public static void Stop(HttpResponse response, bool flush = true)
  773. {
  774. if (response == null) return;
  775. #if NETFX
  776. try { if (flush) response.Flush(); } catch { }
  777. try { response.Close(); } catch { }
  778. // try { response.End(); } catch { }
  779. #endif
  780. }
  781. #endif
  782. #endregion
  783. #region Response - Headers
  784. /// <summary>从 Response 头中移除 Server 和 X-Powered-By 属性。</summary>
  785. public static void RemoveServer(HttpListenerResponse response)
  786. {
  787. if (response == null) return;
  788. var keys = new List<string>(response.Headers.AllKeys);
  789. if (keys.Contains("Server")) response.Headers.Remove("Server");
  790. if (keys.Contains("X-Powered-By")) response.Headers.Remove("X-Powered-By");
  791. }
  792. /// <summary>添加 Header 属性,返回错误信息。</summary>
  793. public static string AddHeader(HttpListenerResponse response, string name, string value)
  794. {
  795. if (response == null || string.IsNullOrEmpty(name) || string.IsNullOrEmpty(value)) return "参数无效。";
  796. try
  797. {
  798. response.AddHeader(name, value);
  799. return null;
  800. }
  801. catch (Exception ex) { return ex.Message; }
  802. }
  803. /// <summary>添加多个 Headers 属性。</summary>
  804. public static void AddHeaders(HttpListenerResponse response, StringPairs headers)
  805. {
  806. if (response == null || headers == null) return;
  807. foreach (var key in headers.GetAllKeys())
  808. {
  809. var values = headers.GetValues(key);
  810. foreach (var value in values) AddHeader(response, key, value);
  811. }
  812. }
  813. #if NETFX || NETCORE
  814. /// <summary>从 Response 头中移除 Server 和 X-Powered-By 属性。</summary>
  815. public static void RemoveServer(HttpResponse response)
  816. {
  817. if (response == null) return;
  818. #if NETFX
  819. var keys = new List<string>(response.Headers.AllKeys);
  820. #else
  821. var keys = new List<string>(response.Headers.Keys);
  822. #endif
  823. if (keys.Contains("Server")) response.Headers.Remove("Server");
  824. if (keys.Contains("X-Powered-By")) response.Headers.Remove("X-Powered-By");
  825. }
  826. /// <summary>添加 Header 属性,返回错误信息。</summary>
  827. public static string AddHeader(HttpResponse response, string name, string value)
  828. {
  829. if (response == null || string.IsNullOrEmpty(name) || string.IsNullOrEmpty(value)) return "参数无效。";
  830. try
  831. {
  832. #if NET20
  833. response.AddHeader(name, value);
  834. #else
  835. response.Headers.Add(name, value);
  836. #endif
  837. return null;
  838. }
  839. catch (Exception ex) { return ex.Message; }
  840. }
  841. /// <summary>添加多个 Headers 属性。</summary>
  842. public static void AddHeaders(HttpResponse response, StringPairs headers)
  843. {
  844. if (response == null || headers == null) return;
  845. foreach (var key in headers.GetAllKeys())
  846. {
  847. var values = headers.GetValues(key);
  848. foreach (var value in values) AddHeader(response, key, value);
  849. }
  850. }
  851. #endif
  852. #endregion
  853. #region Response - Cache Control
  854. /// <summary>设置缓存时间,单位为秒,最大为 2592000 秒(30 天)。</summary>
  855. public static void SetCacheControl(HttpListenerResponse response, int seconds = 0)
  856. {
  857. if (response == null) return;
  858. var s = seconds;
  859. if (s < 0) s = 0;
  860. if (s > 2592000) s = 2592000;
  861. if (s > 0)
  862. {
  863. AddHeader(response, "Cache-Control", $"public, max-age={s}, s-maxage={s}");
  864. }
  865. else
  866. {
  867. AddHeader(response, "Cache-Control", "no-cache, no-store, must-revalidate");
  868. AddHeader(response, "Pragma", "no-cache");
  869. }
  870. }
  871. #if NETFX || NETCORE
  872. /// <summary>设置缓存时间,单位为秒,最大为 2592000 秒(30 天)。</summary>
  873. public static void SetCacheControl(HttpResponse response, int seconds = 0)
  874. {
  875. if (response == null) return;
  876. var s = seconds;
  877. if (s < 0) s = 0;
  878. if (s > 2592000) s = 2592000;
  879. #if NETFX
  880. if (s > 0)
  881. {
  882. response.CacheControl = "public";
  883. response.Cache.SetCacheability(HttpCacheability.Public);
  884. response.Cache.SetMaxAge(TimeSpan.FromSeconds(seconds));
  885. response.Cache.SetProxyMaxAge(TimeSpan.FromSeconds(seconds));
  886. response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
  887. }
  888. else
  889. {
  890. var minutes = s < 60 ? 0 : (s / 60);
  891. response.CacheControl = "no-cache";
  892. response.Cache.SetCacheability(HttpCacheability.NoCache);
  893. response.Cache.SetNoStore();
  894. AddHeader(response, "Pragma", "no-cache");
  895. }
  896. #else
  897. if (s > 0)
  898. {
  899. AddHeader(response, "Cache-Control", $"public, max-age={s}, s-maxage={s}");
  900. }
  901. else
  902. {
  903. AddHeader(response, "Cache-Control", "no-cache, no-store, must-revalidate");
  904. AddHeader(response, "Pragma", "no-cache");
  905. }
  906. #endif
  907. }
  908. #endif
  909. #endregion
  910. #region Cookies
  911. // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
  912. // https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Set-Cookie
  913. // <cookie-name> 可以是除了控制字符 (CTLs)、空格 (spaces) 或制表符 (tab)之外的任何 US-ASCII 字符。
  914. // 同时不能包含以下分隔字符: ( ) < > @ , ; : \ " / [ ] ? = { }.
  915. // <cookie-value> 是可选的,如果存在的话,那么需要包含在双引号里面。
  916. // 支持除了控制字符(CTLs)、空格(whitespace)、双引号(double quotes)、逗号(comma)、分号(semicolon)以及反斜线(backslash)之外的任意 US-ASCII 字符。
  917. // 关于编码:许多应用会对 cookie 值按照URL编码(URL encoding)规则进行编码,但是按照 RFC 规范,这不是必须的。
  918. // 不过满足规范中对于 <cookie-value> 所允许使用的字符的要求是有用的。
  919. /// <summary>获取 Cookies。</summary>
  920. public static StringPairs GetCookies(HttpListenerRequest request)
  921. {
  922. var sp = new StringPairs();
  923. if (request != null && request.Cookies != null)
  924. {
  925. for (var i = 0; i < request.Cookies.Count; i++)
  926. {
  927. try
  928. {
  929. var cookie = request.Cookies[i];
  930. var key = cookie.Name;
  931. var value = cookie.Value ?? "";
  932. sp.Add(new KeyValuePair<string, string>(key, value));
  933. }
  934. catch { }
  935. }
  936. }
  937. return sp;
  938. }
  939. /// <summary>设置响应的 Cookies。</summary>
  940. public static string SetCookies(HttpListenerResponse response, IEnumerable<KeyValuePair<string, string>> cookies)
  941. {
  942. // experimentation_subject_id
  943. if (cookies == null) return "参数 Response 无效。";
  944. foreach (var kvp in cookies)
  945. {
  946. var error = SetCookie(response, kvp.Key, kvp.Value);
  947. if (error.NotEmpty()) return error;
  948. }
  949. return null;
  950. }
  951. /// <summary>设置响应的 Cookie。</summary>
  952. public static string SetCookie(HttpListenerResponse response, string key, string value)
  953. {
  954. if (response == null) return null;
  955. var k = key.SafeTrim();
  956. var v = value.SafeTrim();
  957. if (k.IsEmpty()) return "参数 Key 无效。";
  958. try
  959. {
  960. var cookie = new Cookie(k, v);
  961. var now = DateTime.Now;
  962. cookie.Expires = v.IsEmpty() ? now.AddDays(-1) : now.AddYears(1);
  963. response.SetCookie(cookie);
  964. return null;
  965. }
  966. catch (Exception ex)
  967. {
  968. return ex.Message;
  969. }
  970. }
  971. #if NETFX || NETCORE
  972. /// <summary>获取 Cookies。</summary>
  973. public static StringPairs GetCookies(HttpRequest request)
  974. {
  975. var sp = new StringPairs();
  976. if (request != null && request.Cookies != null)
  977. {
  978. #if NETFX
  979. foreach (var key in request.Cookies.AllKeys)
  980. {
  981. try
  982. {
  983. var cookie = request.Cookies[key];
  984. var value = cookie.Value ?? "";
  985. sp.Add(new KeyValuePair<string, string>(key, value));
  986. }
  987. catch { }
  988. }
  989. #else
  990. foreach (var key in request.Cookies.Keys)
  991. {
  992. try
  993. {
  994. var value = request.Cookies[key] ?? "";
  995. sp.Add(new KeyValuePair<string, string>(key, value));
  996. }
  997. catch { }
  998. }
  999. #endif
  1000. }
  1001. return sp;
  1002. }
  1003. /// <summary>设置响应的 Cookies。</summary>
  1004. public static string SetCookies(HttpResponse response, IEnumerable<KeyValuePair<string, string>> cookies)
  1005. {
  1006. // experimentation_subject_id
  1007. if (cookies == null) return "参数 Response 无效。";
  1008. foreach (var kvp in cookies)
  1009. {
  1010. var error = SetCookie(response, kvp.Key, kvp.Value);
  1011. if (error.NotEmpty()) return error;
  1012. }
  1013. return null;
  1014. }
  1015. /// <summary>设置响应的 Cookie。</summary>
  1016. public static string SetCookie(HttpResponse response, string key, string value)
  1017. {
  1018. if (response == null) return null;
  1019. var k = key.SafeTrim();
  1020. var v = value.SafeTrim();
  1021. if (k.IsEmpty()) return "参数 Key 无效。";
  1022. try
  1023. {
  1024. #if NETFX
  1025. var cookie = new HttpCookie(k, v);
  1026. var now = DateTime.Now;
  1027. cookie.Expires = v.IsEmpty() ? now.AddDays(-1) : now.AddYears(1);
  1028. response.SetCookie(cookie);
  1029. #else
  1030. // var options = new CookieOptions();
  1031. // response.Cookies.Append(key, value, options);
  1032. response.Cookies.Append(key, value);
  1033. #endif
  1034. return null;
  1035. }
  1036. catch (Exception ex)
  1037. {
  1038. return ex.Message;
  1039. }
  1040. }
  1041. #endif
  1042. #endregion
  1043. }
  1044. }