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.

1206 lines
43 KiB

  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.BinaryType = type ?? "application/octet-stream";
  474. }
  475. /// <summary>输出二进制。</summary>
  476. public static void SetBinary(ApiResponse response, Stream content, string type = "application/octet-stream")
  477. {
  478. if (response == null) return;
  479. response.Type = ApiFormat.Binary;
  480. response.BinaryStream = content;
  481. response.BinaryBytes = null;
  482. response.BinaryType = type ?? "application/octet-stream";
  483. }
  484. /// <summary>输出文件。</summary>
  485. public static void SetFile(ApiResponse response, Stream stream, string name, string type = "application/octet-stream")
  486. {
  487. if (response == null) return;
  488. response.Type = ApiFormat.File;
  489. response.FileStream = stream;
  490. response.FileName = name;
  491. response.FileType = type ?? "application/octet-stream";
  492. }
  493. /// <summary>重定向。</summary>
  494. public static void SetRedirect(ApiResponse response, string url)
  495. {
  496. if (response == null) return;
  497. response.Type = ApiFormat.Redirect;
  498. response.RedirectUrl = url;
  499. }
  500. #endregion
  501. #region Application & Path
  502. /// <summary>获取程序目录的路径。</summary>
  503. public static string AppDirectory
  504. {
  505. get
  506. {
  507. // AppDomain.CurrentDomain.BaseDirectory
  508. // AppDomain.CurrentDomain.SetupInformation.ApplicationBase
  509. return AppDomain.CurrentDomain.BaseDirectory;
  510. }
  511. }
  512. /// <summary>MapPath。</summary>
  513. public static string MapPath(params string[] names)
  514. {
  515. var list = new List<string>();
  516. if (names != null)
  517. {
  518. var invalid = StorageUtility.InvalidPathChars;
  519. foreach (var name in names)
  520. {
  521. if (string.IsNullOrEmpty(name)) continue;
  522. var split = name.Split('/', '\\');
  523. foreach (var s in split)
  524. {
  525. var sb = new StringBuilder();
  526. foreach (var c in s)
  527. {
  528. if (Array.IndexOf(invalid, "c") < 0) sb.Append(c);
  529. }
  530. var t = sb.ToString();
  531. if (!string.IsNullOrEmpty(t)) list.Add(t);
  532. }
  533. }
  534. }
  535. return list.Count > 1 ? StorageUtility.CombinePath(list.ToArray()) : list[0];
  536. }
  537. #endregion
  538. #region Request - GetMethod
  539. /// <summary>获取 HTTP 方法。</summary>
  540. public static Network.HttpMethod GetMethod(HttpListenerRequest request)
  541. {
  542. if (request == null) return Network.HttpMethod.NULL;
  543. return GetMethod(request.HttpMethod);
  544. }
  545. #if NETFX || NETCORE
  546. /// <summary>获取 HTTP 方法。</summary>
  547. public static Network.HttpMethod GetMethod(HttpRequest request)
  548. {
  549. if (request == null) return Network.HttpMethod.NULL;
  550. #if NETFX
  551. return GetMethod(request.HttpMethod);
  552. #else
  553. return GetMethod(request.Method);
  554. #endif
  555. }
  556. #endif
  557. #endregion
  558. #region Request - Client IP
  559. /// <summary>获取直连端的 IP。</summary>
  560. public static string GetDirectIP(HttpListenerRequest request)
  561. {
  562. if (request == null) return null;
  563. var ip = request.UserHostAddress;
  564. return TrimIP(ip);
  565. }
  566. /// <summary>获取客户端 IP。</summary>
  567. public static string GetClientIP(HttpListenerRequest request)
  568. {
  569. if (request == null) return null;
  570. var headers = GetHeaders(request, true);
  571. var directIP = GetDirectIP(request);
  572. return GetClientIP(directIP, headers);
  573. }
  574. #if NETFX || NETCORE
  575. /// <summary>获取直连端的 IP。</summary>
  576. public static string GetDirectIP(HttpRequest request)
  577. {
  578. if (request == null) return null;
  579. #if NETFX
  580. var ip = request.UserHostAddress;
  581. #else
  582. if (request.HttpContext == null || request.HttpContext.Connection == null) return null;
  583. var ip = request.HttpContext.Connection.RemoteIpAddress.ToString();
  584. #endif
  585. return TrimIP(ip);
  586. }
  587. /// <summary>获取客户端 IP。</summary>
  588. public static string GetClientIP(HttpRequest request)
  589. {
  590. if (request == null) return null;
  591. var headers = GetHeaders(request, true);
  592. var directIP = GetDirectIP(request);
  593. return GetClientIP(directIP, headers);
  594. }
  595. #endif
  596. #endregion
  597. #region Request - URL
  598. /// <summary>获取请求的 URL,失败时返回 NULL 值。</summary>
  599. public static Uri GetUrl(HttpListenerRequest request)
  600. {
  601. if (request == null) return null;
  602. return request.Url;
  603. }
  604. #if NETFX || NETCORE
  605. /// <summary>获取请求的 URL,失败时返回 NULL 值。</summary>
  606. public static Uri GetUrl(HttpRequest request)
  607. {
  608. if (request == null) return null;
  609. #if NETFX
  610. return request.Url;
  611. #else
  612. var context = request.HttpContext;
  613. if (context == null) return null;
  614. var https = request.IsHttps;
  615. var port = context.Connection.LocalPort;
  616. var query = request.QueryString == null ? null : request.QueryString.Value;
  617. var sb = new StringBuilder();
  618. sb.Append(https ? "https://" : "http://");
  619. sb.Append(request.Host.Host ?? "");
  620. if ((https && port != 443) || (!https && port != 80))
  621. {
  622. sb.Append(":");
  623. sb.Append(port);
  624. }
  625. sb.Append(request.Path);
  626. if (!string.IsNullOrEmpty(query))
  627. {
  628. sb.Append("?");
  629. sb.Append(query);
  630. }
  631. var url = sb.ToString();
  632. var uri = new Uri(url);
  633. return uri;
  634. #endif
  635. }
  636. #endif
  637. #endregion
  638. #region Request - Parameters
  639. /// <summary>解析 URL 的查询字符串,获取所有已解码的参数。</summary>
  640. public static StringPairs ParseUrlParameters(HttpListenerRequest request)
  641. {
  642. if (request == null) return new StringPairs();
  643. if (request.Url == null) return new StringPairs();
  644. return ParseParameters(request.Url.Query);
  645. }
  646. #if NETFX || NETCORE
  647. /// <summary>解析 URL 的查询字符串,获取所有已解码的参数。</summary>
  648. public static StringPairs ParseUrlParameters(HttpRequest request)
  649. {
  650. if (request == null) return new StringPairs();
  651. #if NETFX
  652. if (request.Url == null) return new StringPairs();
  653. return ParseParameters(request.Url.Query);
  654. #else
  655. var list = new StringPairs();
  656. if (request.Query == null) return list;
  657. var keys = request.Query.Keys;
  658. list.Capacity = keys.Count;
  659. foreach (var key in keys)
  660. {
  661. list.Add(new KeyValuePair<string, string>(key, request.Query[key]));
  662. }
  663. list.Capacity = list.Count;
  664. return list;
  665. #endif
  666. }
  667. #endif
  668. #endregion
  669. #region Request - Headers
  670. /// <summary>获取 HTTP 头。</summary>
  671. public static StringPairs GetHeaders(HttpListenerRequest request, bool lowerKey = false)
  672. {
  673. var sp = new StringPairs();
  674. if (request != null && request.Headers != null)
  675. {
  676. foreach (var key in request.Headers.AllKeys)
  677. {
  678. try
  679. {
  680. var v = request.Headers[key];
  681. if (string.IsNullOrEmpty(v)) continue;
  682. var k = lowerKey ? key.ToLower() : key;
  683. sp.Add(k, v);
  684. }
  685. catch { }
  686. }
  687. }
  688. return sp;
  689. }
  690. /// <summary>获取 User Agent。</summary>
  691. public static string GetUserAgent(HttpListenerRequest request) => GetUserAgent(GetHeaders(request));
  692. #if NETFX || NETCORE
  693. /// <summary>获取 HTTP 头。</summary>
  694. public static StringPairs GetHeaders(HttpRequest request, bool lowerKey = false)
  695. {
  696. var sp = new StringPairs();
  697. if (request != null && request.Headers != null)
  698. {
  699. #if NETFX
  700. foreach (var key in request.Headers.AllKeys)
  701. #else
  702. foreach (var key in request.Headers.Keys)
  703. #endif
  704. {
  705. try
  706. {
  707. var v = request.Headers[key];
  708. if (string.IsNullOrEmpty(v)) continue;
  709. var k = lowerKey ? key.ToLower() : key;
  710. sp.Add(k, v);
  711. }
  712. catch { }
  713. }
  714. }
  715. return sp;
  716. }
  717. /// <summary>获取 X-Forwarded-For。</summary>
  718. public static string GetForwardedIP(HttpRequest request) => GetForwardedIP(GetHeaders(request));
  719. /// <summary>获取 User Agent。</summary>
  720. public static string GetUserAgent(HttpRequest request) => GetUserAgent(GetHeaders(request));
  721. #endif
  722. #endregion
  723. #region Response - Redirect
  724. /// <summary>将客户端重新定向到新 URL。</summary>
  725. /// <remarks>响应 302 状态。</remarks>
  726. public static void Redirect(HttpListenerResponse response, string url)
  727. {
  728. if (response == null) return;
  729. try { response.Redirect(url); } catch { }
  730. }
  731. #if NETFX
  732. /// <summary>将客户端重新定向到新 URL。</summary>
  733. /// <remarks>响应 302 状态。</remarks>
  734. public static void Redirect(HttpResponse response, string url)
  735. {
  736. if (response == null) return;
  737. try { response.Redirect(url, true); } catch { }
  738. }
  739. #endif
  740. #if NETCORE
  741. /// <summary>将客户端重新定向到新 URL。</summary>
  742. /// <remarks>默认响应 302 状态。可指定 permanent = true 以响应 301 状态。</remarks>
  743. public static void Redirect(HttpResponse response, string url, bool permanent = false)
  744. {
  745. if (response == null) return;
  746. try { response.Redirect(url, permanent); } catch { }
  747. }
  748. #endif
  749. #endregion
  750. #region Response - Stop
  751. /// <summary>停止并关闭响应流。可指定向发送缓冲区的数据。</summary>
  752. public static void Stop(HttpListenerResponse response, bool flush = true)
  753. {
  754. if (response == null) return;
  755. try { if (flush) response.OutputStream.Flush(); } catch { }
  756. try { response.Close(); } catch { }
  757. }
  758. #if NETFX || NETCORE
  759. /// <summary>停止并关闭响应流。可指定向发送缓冲区的数据。</summary>
  760. public static void Stop(HttpResponse response, bool flush = true)
  761. {
  762. if (response == null) return;
  763. #if NETFX
  764. try { if (flush) response.Flush(); } catch { }
  765. try { response.Close(); } catch { }
  766. // try { response.End(); } catch { }
  767. #endif
  768. }
  769. #endif
  770. #endregion
  771. #region Response - Headers
  772. /// <summary>从 Response 头中移除 Server 和 X-Powered-By 属性。</summary>
  773. public static void RemoveServer(HttpListenerResponse response)
  774. {
  775. if (response == null) return;
  776. var keys = new List<string>(response.Headers.AllKeys);
  777. if (keys.Contains("Server")) response.Headers.Remove("Server");
  778. if (keys.Contains("X-Powered-By")) response.Headers.Remove("X-Powered-By");
  779. }
  780. /// <summary>添加 Header 属性,返回错误信息。</summary>
  781. public static string AddHeader(HttpListenerResponse response, string name, string value)
  782. {
  783. if (response == null || string.IsNullOrEmpty(name) || string.IsNullOrEmpty(value)) return "参数无效。";
  784. try
  785. {
  786. response.AddHeader(name, value);
  787. return null;
  788. }
  789. catch (Exception ex) { return ex.Message; }
  790. }
  791. /// <summary>添加多个 Headers 属性。</summary>
  792. public static void AddHeaders(HttpListenerResponse response, StringPairs headers)
  793. {
  794. if (response == null || headers == null) return;
  795. foreach (var key in headers.GetAllKeys())
  796. {
  797. var values = headers.GetValues(key);
  798. foreach (var value in values) AddHeader(response, key, value);
  799. }
  800. }
  801. #if NETFX || NETCORE
  802. /// <summary>从 Response 头中移除 Server 和 X-Powered-By 属性。</summary>
  803. public static void RemoveServer(HttpResponse response)
  804. {
  805. if (response == null) return;
  806. #if NETFX
  807. var keys = new List<string>(response.Headers.AllKeys);
  808. #else
  809. var keys = new List<string>(response.Headers.Keys);
  810. #endif
  811. if (keys.Contains("Server")) response.Headers.Remove("Server");
  812. if (keys.Contains("X-Powered-By")) response.Headers.Remove("X-Powered-By");
  813. }
  814. /// <summary>添加 Header 属性,返回错误信息。</summary>
  815. public static string AddHeader(HttpResponse response, string name, string value)
  816. {
  817. if (response == null || string.IsNullOrEmpty(name) || string.IsNullOrEmpty(value)) return "参数无效。";
  818. try
  819. {
  820. #if NET20
  821. response.AddHeader(name, value);
  822. #else
  823. response.Headers.Add(name, value);
  824. #endif
  825. return null;
  826. }
  827. catch (Exception ex) { return ex.Message; }
  828. }
  829. /// <summary>添加多个 Headers 属性。</summary>
  830. public static void AddHeaders(HttpResponse response, StringPairs headers)
  831. {
  832. if (response == null || headers == null) return;
  833. foreach (var key in headers.GetAllKeys())
  834. {
  835. var values = headers.GetValues(key);
  836. foreach (var value in values) AddHeader(response, key, value);
  837. }
  838. }
  839. #endif
  840. #endregion
  841. #region Response - Cache Control
  842. /// <summary>设置缓存时间,单位为秒,最大为 2592000 秒(30 天)。</summary>
  843. public static void SetCacheControl(HttpListenerResponse response, int seconds = 0)
  844. {
  845. if (response == null) return;
  846. var s = seconds;
  847. if (s < 0) s = 0;
  848. if (s > 2592000) s = 2592000;
  849. if (s > 0)
  850. {
  851. AddHeader(response, "Cache-Control", $"public, max-age={s}, s-maxage={s}");
  852. }
  853. else
  854. {
  855. AddHeader(response, "Cache-Control", "no-cache, no-store, must-revalidate");
  856. AddHeader(response, "Pragma", "no-cache");
  857. }
  858. }
  859. #if NETFX || NETCORE
  860. /// <summary>设置缓存时间,单位为秒,最大为 2592000 秒(30 天)。</summary>
  861. public static void SetCacheControl(HttpResponse response, int seconds = 0)
  862. {
  863. if (response == null) return;
  864. var s = seconds;
  865. if (s < 0) s = 0;
  866. if (s > 2592000) s = 2592000;
  867. #if NETFX
  868. if (s > 0)
  869. {
  870. response.CacheControl = "public";
  871. response.Cache.SetCacheability(HttpCacheability.Public);
  872. response.Cache.SetMaxAge(TimeSpan.FromSeconds(seconds));
  873. response.Cache.SetProxyMaxAge(TimeSpan.FromSeconds(seconds));
  874. response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
  875. }
  876. else
  877. {
  878. var minutes = s < 60 ? 0 : (s / 60);
  879. response.CacheControl = "no-cache";
  880. response.Cache.SetCacheability(HttpCacheability.NoCache);
  881. response.Cache.SetNoStore();
  882. AddHeader(response, "Pragma", "no-cache");
  883. }
  884. #else
  885. if (s > 0)
  886. {
  887. AddHeader(response, "Cache-Control", $"public, max-age={s}, s-maxage={s}");
  888. }
  889. else
  890. {
  891. AddHeader(response, "Cache-Control", "no-cache, no-store, must-revalidate");
  892. AddHeader(response, "Pragma", "no-cache");
  893. }
  894. #endif
  895. }
  896. #endif
  897. #endregion
  898. #region Cookies
  899. // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
  900. // https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Set-Cookie
  901. // <cookie-name> 可以是除了控制字符 (CTLs)、空格 (spaces) 或制表符 (tab)之外的任何 US-ASCII 字符。
  902. // 同时不能包含以下分隔字符: ( ) < > @ , ; : \ " / [ ] ? = { }.
  903. // <cookie-value> 是可选的,如果存在的话,那么需要包含在双引号里面。
  904. // 支持除了控制字符(CTLs)、空格(whitespace)、双引号(double quotes)、逗号(comma)、分号(semicolon)以及反斜线(backslash)之外的任意 US-ASCII 字符。
  905. // 关于编码:许多应用会对 cookie 值按照URL编码(URL encoding)规则进行编码,但是按照 RFC 规范,这不是必须的。
  906. // 不过满足规范中对于 <cookie-value> 所允许使用的字符的要求是有用的。
  907. /// <summary>获取 Cookies。</summary>
  908. public static StringPairs GetCookies(HttpListenerRequest request)
  909. {
  910. var sp = new StringPairs();
  911. if (request != null && request.Cookies != null)
  912. {
  913. for (var i = 0; i < request.Cookies.Count; i++)
  914. {
  915. try
  916. {
  917. var cookie = request.Cookies[i];
  918. var key = cookie.Name;
  919. var value = cookie.Value ?? "";
  920. sp.Add(new KeyValuePair<string, string>(key, value));
  921. }
  922. catch { }
  923. }
  924. }
  925. return sp;
  926. }
  927. /// <summary>设置响应的 Cookies。</summary>
  928. public static string SetCookies(HttpListenerResponse response, IEnumerable<KeyValuePair<string, string>> cookies)
  929. {
  930. // experimentation_subject_id
  931. if (cookies == null) return "参数 Response 无效。";
  932. foreach (var kvp in cookies)
  933. {
  934. var error = SetCookie(response, kvp.Key, kvp.Value);
  935. if (error.NotEmpty()) return error;
  936. }
  937. return null;
  938. }
  939. /// <summary>设置响应的 Cookie。</summary>
  940. public static string SetCookie(HttpListenerResponse response, string key, string value)
  941. {
  942. if (response == null) return null;
  943. var k = key.SafeTrim();
  944. var v = value.SafeTrim();
  945. if (k.IsEmpty()) return "参数 Key 无效。";
  946. try
  947. {
  948. var cookie = new Cookie(k, v);
  949. var now = DateTime.Now;
  950. cookie.Expires = v.IsEmpty() ? now.AddDays(-1) : now.AddYears(1);
  951. response.SetCookie(cookie);
  952. return null;
  953. }
  954. catch (Exception ex)
  955. {
  956. return ex.Message;
  957. }
  958. }
  959. #if NETFX || NETCORE
  960. /// <summary>获取 Cookies。</summary>
  961. public static StringPairs GetCookies(HttpRequest request)
  962. {
  963. var sp = new StringPairs();
  964. if (request != null && request.Cookies != null)
  965. {
  966. #if NETFX
  967. foreach (var key in request.Cookies.AllKeys)
  968. {
  969. try
  970. {
  971. var cookie = request.Cookies[key];
  972. var value = cookie.Value ?? "";
  973. sp.Add(new KeyValuePair<string, string>(key, value));
  974. }
  975. catch { }
  976. }
  977. #else
  978. foreach (var key in request.Cookies.Keys)
  979. {
  980. try
  981. {
  982. var value = request.Cookies[key] ?? "";
  983. sp.Add(new KeyValuePair<string, string>(key, value));
  984. }
  985. catch { }
  986. }
  987. #endif
  988. }
  989. return sp;
  990. }
  991. /// <summary>设置响应的 Cookies。</summary>
  992. public static string SetCookies(HttpResponse response, IEnumerable<KeyValuePair<string, string>> cookies)
  993. {
  994. // experimentation_subject_id
  995. if (cookies == null) return "参数 Response 无效。";
  996. foreach (var kvp in cookies)
  997. {
  998. var error = SetCookie(response, kvp.Key, kvp.Value);
  999. if (error.NotEmpty()) return error;
  1000. }
  1001. return null;
  1002. }
  1003. /// <summary>设置响应的 Cookie。</summary>
  1004. public static string SetCookie(HttpResponse response, string key, string value)
  1005. {
  1006. if (response == null) return null;
  1007. var k = key.SafeTrim();
  1008. var v = value.SafeTrim();
  1009. if (k.IsEmpty()) return "参数 Key 无效。";
  1010. try
  1011. {
  1012. #if NETFX
  1013. var cookie = new HttpCookie(k, v);
  1014. var now = DateTime.Now;
  1015. cookie.Expires = v.IsEmpty() ? now.AddDays(-1) : now.AddYears(1);
  1016. response.SetCookie(cookie);
  1017. #else
  1018. // var options = new CookieOptions();
  1019. // response.Cookies.Append(key, value, options);
  1020. response.Cookies.Append(key, value);
  1021. #endif
  1022. return null;
  1023. }
  1024. catch (Exception ex)
  1025. {
  1026. return ex.Message;
  1027. }
  1028. }
  1029. #endif
  1030. #endregion
  1031. }
  1032. }