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.

585 lines
21 KiB

  1. using Apewer;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Text;
  5. using System.Diagnostics;
  6. using Apewer.Models;
  7. using System.IO;
  8. #if NETFX
  9. using System.Web;
  10. #endif
  11. #if NETCORE
  12. using Microsoft.AspNetCore.Http;
  13. #endif
  14. namespace Apewer.Web
  15. {
  16. /// <summary></summary>
  17. public static class WebUtility
  18. {
  19. #region url
  20. /// <summary>按 & 拆分多个参数。</summary>
  21. public static StringPairs ParseParameters(string query, bool decode = true)
  22. {
  23. var input = query;
  24. var list = new StringPairs();
  25. if (!string.IsNullOrEmpty(input))
  26. {
  27. if (input[0] == '?') input = input.Substring(1);
  28. var args = input.Split('&');
  29. foreach (var arg in args)
  30. {
  31. var equals = arg.IndexOf("=");
  32. var left = equals < 0 ? arg : arg.Substring(0, equals);
  33. var right = equals < 0 ? "" : arg.Substring(equals + 1);
  34. if (decode)
  35. {
  36. left = TextUtility.DecodeUrl(left);
  37. right = TextUtility.DecodeUrl(right);
  38. }
  39. list.Add(left, right);
  40. }
  41. }
  42. return list;
  43. }
  44. /// <summary>获取 URL 查询段,不存在的段为 NULL 值。可要求解码。</summary>
  45. public static string GetSegmental(Uri url, int index = 3, bool decode = false)
  46. {
  47. try
  48. {
  49. if (url == null) return null;
  50. if (index < 0) return null;
  51. var sus = url.AbsolutePath.Split('/');
  52. if (sus.Length > index)
  53. {
  54. var result = sus[index];
  55. if (decode) result = TextUtility.DecodeUrl(result);
  56. return result;
  57. }
  58. }
  59. catch { }
  60. return null;
  61. }
  62. /// <summary>获取参数并解码。</summary>
  63. public static string GetParameter(string encoded, params string[] names)
  64. {
  65. if (string.IsNullOrEmpty(encoded)) return null;
  66. if (names == null || names.Length < 1) return null;
  67. // Names 转为小写,加强适配。
  68. var lowerNames = new List<string>();
  69. foreach (var name in names)
  70. {
  71. var lower = TextUtility.ToLower(name);
  72. if (string.IsNullOrEmpty(lower)) continue;
  73. lowerNames.Add(lower);
  74. }
  75. if (lowerNames.Count < 1) return null;
  76. // 分参数对比。
  77. var parameters = ParseParameters(encoded);
  78. var matched = false;
  79. foreach (var parameter in parameters)
  80. {
  81. var left = parameter.Key;
  82. var right = parameter.Value;
  83. var lowerLeft = TextUtility.ToLower(left);
  84. if (lowerNames.Contains(right))
  85. {
  86. matched = true;
  87. if (!string.IsNullOrEmpty(right)) return right;
  88. }
  89. }
  90. return matched ? "" : null;
  91. }
  92. /// <summary>指定可能的参数名,从 URL 中获取参数值并解码。</summary>
  93. public static string GetParameter(Uri url, params string[] names)
  94. {
  95. return url == null ? null : GetParameter(url.Query, names);
  96. }
  97. #endregion
  98. #if NETFX || NETCORE
  99. #region http
  100. /// <summary>获取直连端的 IP。</summary>
  101. public static string GetDirectIP(ApiRequest request)
  102. {
  103. if (request == null) return null;
  104. if (request.Context == null) return null;
  105. return GetDirectIP(request.Context.Request);
  106. }
  107. /// <summary>获取直连端的 IP。</summary>
  108. public static string GetDirectIP(HttpRequest request)
  109. {
  110. if (request == null) return null;
  111. #if NETFX
  112. return request.UserHostAddress;
  113. #else
  114. if (request.HttpContext == null || request.HttpContext.Connection == null) return null;
  115. return request.HttpContext.Connection.RemoteIpAddress.ToString();
  116. #endif
  117. }
  118. /// <summary>获取客户端 IP。</summary>
  119. public static string GetClientIP(ApiRequest request, bool withProxy = false)
  120. {
  121. if (request == null) return null;
  122. if (request.Context == null) return null;
  123. return GetClientIP(request.Context.Request, withProxy);
  124. }
  125. /// <summary>获取客户端 IP。</summary>
  126. #if NETFX
  127. public static string GetClientIP(System.Web.HttpRequest request, bool withProxy = false)
  128. #else
  129. public static string GetClientIP(Microsoft.AspNetCore.Http.HttpRequest request, bool withProxy = false)
  130. #endif
  131. {
  132. if (request == null) return null;
  133. var d = GetDirectIP(request);
  134. var f = GetForwardedIP(request);
  135. // return string.IsNullOrEmpty(f) ? "NULL" : $"{f},NULL";
  136. // return string.IsNullOrEmpty(f) ? $"{d}" : $"{f},{d}";
  137. // 没有代理,返回 Direct IP。
  138. if (string.IsNullOrEmpty(f)) return d;
  139. // 有代理,先加入 Forwarded IP,再加入 Direct IP。
  140. var ip = f;
  141. if (withProxy && !string.IsNullOrEmpty(d)) ip = ip + "," + d;
  142. return ip;
  143. }
  144. /// <summary>请求来自于可信客户端。</summary>
  145. public static bool FromTrusted(HttpRequest request, params string[] addresses)
  146. {
  147. if (request == null) return false;
  148. // 解析可信地址。
  149. var ips = new List<string>();
  150. if (addresses != null)
  151. {
  152. foreach (var address in addresses)
  153. {
  154. var ip = address;
  155. if (!NetworkUtility.IsIP(ip)) ip = NetworkUtility.Resolve(ip);
  156. if (!NetworkUtility.IsIP(ip)) continue;
  157. if (ips.Contains(ip)) continue;
  158. ips.Add(ip);
  159. }
  160. }
  161. // 检测代理 IP。
  162. var headers = GetHeaders(request);
  163. var proxyIP = headers.GetValue("Ali-CDN-Real-IP", true).SafeTrim();
  164. if (proxyIP.NotEmpty())
  165. {
  166. var split = proxyIP.Split(' ', '|', ',', ':');
  167. proxyIP = split.Length > 0 ? split[0] : null;
  168. }
  169. if (proxyIP.IsEmpty())
  170. {
  171. proxyIP = headers.GetValue("X-Forwarded-For", true).SafeTrim();
  172. if (proxyIP.NotEmpty())
  173. {
  174. var split = proxyIP.Split(' ', '|', ',', ':');
  175. proxyIP = split.Length > 0 ? split[0] : null;
  176. }
  177. }
  178. if (proxyIP.NotEmpty() && ips.Contains(proxyIP)) return true;
  179. // 检测直连 IP,
  180. var directIP = GetDirectIP(request);
  181. if (NetworkUtility.IsIP(directIP))
  182. {
  183. if (proxyIP.IsEmpty())
  184. {
  185. if (NetworkUtility.FromLAN(directIP)) return true;
  186. if (ips.Contains(directIP)) return true;
  187. }
  188. else
  189. {
  190. if (NetworkUtility.FromLAN(proxyIP) && NetworkUtility.FromLAN(directIP)) return true;
  191. }
  192. }
  193. return false;
  194. }
  195. /// <summary>请求来自于可信客户端。</summary>
  196. public static bool FromTrusted(HttpContext context, params string[] addresses)
  197. {
  198. if (context == null) return false;
  199. return FromTrusted(context.Request, addresses);
  200. }
  201. /// <summary>请求来自于可信客户端。</summary>
  202. public static bool FromTrusted(ApiRequest request, params string[] addresses)
  203. {
  204. if (request == null) return false;
  205. return FromTrusted(request.Context, addresses);
  206. }
  207. /// <summary>获取 HTTP 方法。</summary>
  208. public static Network.HttpMethod GetMethod(string method)
  209. {
  210. if (!string.IsNullOrEmpty(method))
  211. {
  212. var upper = TextUtility.ToUpper(method);
  213. if (upper.Contains("OPTIONS")) return Network.HttpMethod.OPTIONS;
  214. else if (upper.Contains("POST")) return Network.HttpMethod.POST;
  215. else if (upper.Contains("GET")) return Network.HttpMethod.GET;
  216. else if (upper.Contains("CONNECT")) return Network.HttpMethod.CONNECT;
  217. else if (upper.Contains("DELETE")) return Network.HttpMethod.DELETE;
  218. else if (upper.Contains("HEAD")) return Network.HttpMethod.HEAD;
  219. else if (upper.Contains("PATCH")) return Network.HttpMethod.PATCH;
  220. else if (upper.Contains("PUT")) return Network.HttpMethod.PUT;
  221. else if (upper.Contains("TRACE")) return Network.HttpMethod.TRACE;
  222. }
  223. return Network.HttpMethod.NULL;
  224. }
  225. /// <summary>获取 HTTP 方法。</summary>
  226. public static Network.HttpMethod GetMethod(HttpRequest request)
  227. {
  228. if (request == null) return Network.HttpMethod.NULL;
  229. #if NETFX
  230. return GetMethod(request.HttpMethod);
  231. #else
  232. return GetMethod(request.Method);
  233. #endif
  234. }
  235. #endregion
  236. #region request
  237. /// <summary>获取请求的 URL,失败时返回 NULL 值。</summary>
  238. public static Uri GetUrl(HttpRequest request)
  239. {
  240. if (request == null) return null;
  241. #if NETFX
  242. return request.Url;
  243. #else
  244. var context = request.HttpContext;
  245. if (context == null) return null;
  246. var https = request.IsHttps;
  247. var port = context.Connection.LocalPort;
  248. var query = request.QueryString == null ? null : request.QueryString.Value;
  249. var sb = new StringBuilder();
  250. sb.Append(https ? "https://" : "http://");
  251. sb.Append(request.Host.Host ?? "");
  252. if ((https && port != 443) || (!https && port != 80))
  253. {
  254. sb.Append(":");
  255. sb.Append(port);
  256. }
  257. sb.Append(request.Path);
  258. if (!string.IsNullOrEmpty(query))
  259. {
  260. sb.Append("?");
  261. sb.Append(query);
  262. }
  263. var url = sb.ToString();
  264. var uri = new Uri(url);
  265. return uri;
  266. #endif
  267. }
  268. /// <summary>获取 HTTP 头。</summary>
  269. public static StringPairs GetHeaders(HttpRequest request)
  270. {
  271. var sp = new StringPairs();
  272. if (request != null && request.Headers != null)
  273. {
  274. #if NETFX
  275. foreach (var key in request.Headers.AllKeys)
  276. #else
  277. foreach (var key in request.Headers.Keys)
  278. #endif
  279. {
  280. var value = request.Headers[key].ToString();
  281. sp.Add(new KeyValuePair<string, string>(key, value));
  282. }
  283. }
  284. return sp;
  285. }
  286. /// <summary>获取 X-Forwarded-For。</summary>
  287. public static string GetForwardedIP(StringPairs headers)
  288. {
  289. if (headers == null) return null;
  290. var value = headers.GetValue("x-forwarded-for");
  291. if (!string.IsNullOrEmpty(value)) value = value.Split(":")[0];
  292. return value;
  293. }
  294. /// <summary>获取 X-Forwarded-For。</summary>
  295. public static string GetForwardedIP(ApiRequest request)
  296. {
  297. if (request == null) return null;
  298. return GetForwardedIP(request.Headers);
  299. }
  300. /// <summary>获取 X-Forwarded-For。</summary>
  301. public static string GetForwardedIP(HttpRequest request) => GetForwardedIP(GetHeaders(request));
  302. /// <summary>获取 User Agent。</summary>
  303. public static string GetUserAgent(StringPairs headers)
  304. {
  305. if (headers == null) return null;
  306. return headers.GetValue("user-agent");
  307. }
  308. /// <summary>获取 User Agent。</summary>
  309. public static string GetUserAgent(HttpRequest request) => GetUserAgent(GetHeaders(request));
  310. #endregion
  311. #region response
  312. /// <summary>发送 302 状态,将客户端重新定向到新 URL。</summary>
  313. public static void RedirectResponse(HttpResponse response, string url)
  314. {
  315. if (response == null) return;
  316. try { response.Redirect(url, true); } catch { }
  317. }
  318. /// <summary>停止并关闭响应流。可指定向发送缓冲区的数据。</summary>
  319. public static void StopResponse(HttpResponse response, bool flush = true)
  320. {
  321. if (response == null) return;
  322. #if NETFX
  323. try { if (flush) response.Flush(); } catch { }
  324. try { response.Close(); } catch { }
  325. // try { response.End(); } catch { }
  326. #endif
  327. }
  328. #endregion
  329. #region api
  330. /// <summary>获取 URL 查询段,不存在的段为 NULL 值。可要求解码。</summary>
  331. public static string GetSegmentalUrl(ApiRequest request, int index = 3, bool decode = false)
  332. {
  333. return request == null ? null : GetSegmental(request.Url, index, decode);
  334. }
  335. /// <summary>获取参数。</summary>
  336. public static string GetParameter(List<KeyValuePair<string, string>> parameters, params string[] names)
  337. {
  338. if (parameters == null || parameters.Count < 1) return null;
  339. if (names == null || names.Length < 1) return null;
  340. var lowerNames = new List<string>();
  341. foreach (var name in names)
  342. {
  343. var lower = TextUtility.ToLower(name);
  344. if (string.IsNullOrEmpty(lower)) continue;
  345. lowerNames.Add(lower);
  346. }
  347. foreach (var parameter in parameters)
  348. {
  349. var lowerKey = TextUtility.ToLower(parameter.Key);
  350. if (lowerNames.Contains(lowerKey))
  351. {
  352. var value = parameter.Value;
  353. if (!string.IsNullOrEmpty(value)) return value;
  354. }
  355. }
  356. return null;
  357. }
  358. /// <summary>获取参数,指定可能的参数名,从 URL 中获取参数时将解码。</summary>
  359. public static string GetParameter(ApiRequest request, params string[] names)
  360. {
  361. if (request == null) return null;
  362. if (names == null || names.Length < 1) return null;
  363. var sameNames = new List<string>();
  364. var lowerNames = new List<string>();
  365. foreach (var name in names)
  366. {
  367. if (string.IsNullOrEmpty(name)) continue;
  368. if (!sameNames.Contains(name)) sameNames.Add(name);
  369. var lower = TextUtility.ToLower(name);
  370. if (string.IsNullOrEmpty(lower)) continue;
  371. if (!lowerNames.Contains(lower)) lowerNames.Add(lower);
  372. }
  373. // POST 优先。
  374. var data = request.Data;
  375. if (data != null && data.IsObject)
  376. {
  377. var properties = data.GetProperties();
  378. if (properties != null)
  379. {
  380. // Json 区分大小写,先全字匹配。
  381. foreach (var property in properties)
  382. {
  383. if (!property.IsProperty) continue;
  384. if (sameNames.Contains(property.Name) && property.Value != null)
  385. {
  386. var value = property.Value;
  387. if (value == null) continue;
  388. var text = value.ToString();
  389. if (!string.IsNullOrEmpty(text)) return text;
  390. }
  391. }
  392. // 以小写模糊匹配。
  393. foreach (var property in properties)
  394. {
  395. if (!property.IsProperty) continue;
  396. var lowerName = TextUtility.ToLower(property.Name);
  397. if (lowerNames.Contains(lowerName) && property.Value != null)
  398. {
  399. var value = property.Value;
  400. if (value == null) continue;
  401. var text = value.ToString();
  402. if (!string.IsNullOrEmpty(text)) return text;
  403. }
  404. }
  405. }
  406. }
  407. // 从已解析的 Parameters 中搜索。
  408. if (request.Parameters != null)
  409. {
  410. var value = GetParameter(request.Parameters);
  411. if (!string.IsNullOrEmpty(value)) return value;
  412. }
  413. // GET 参数。
  414. if (request.Parameters != null)
  415. {
  416. // 全字匹配。
  417. foreach (var kvp in request.Parameters)
  418. {
  419. if (string.IsNullOrEmpty(kvp.Key) || string.IsNullOrEmpty(kvp.Value)) continue;
  420. if (sameNames.Contains(kvp.Key)) return kvp.Value;
  421. }
  422. // 以小写模糊匹配。
  423. foreach (var kvp in request.Parameters)
  424. {
  425. var lowerKey = TextUtility.ToLower(kvp.Key);
  426. if (string.IsNullOrEmpty(lowerKey) || string.IsNullOrEmpty(kvp.Value)) continue;
  427. if (lowerNames.Contains(lowerKey)) return kvp.Value;
  428. }
  429. }
  430. return null;
  431. }
  432. /// <summary>转移 WebAPI 请求到其它服务器、应用或功能。</summary>
  433. public static string Transfer(ApiController controller, string url, string application = null, string function = null)
  434. {
  435. if (controller == null || controller.Request == null || controller.Response == null) return "ApiControllser 无效。";
  436. if (url.IsEmpty()) return "ApiControllser 无效。";
  437. var s = Json.NewObject();
  438. s.SetProperty("random", TextUtility.NewGuid());
  439. s.SetProperty("application", application.IsEmpty() ? controller.Request.Application : application);
  440. s.SetProperty("function", function.IsEmpty() ? controller.Request.Function : function);
  441. s.SetProperty("data", controller.Request.Data);
  442. s.SetProperty("session", controller.Request.Session);
  443. s.SetProperty("ticket", controller.Request.Ticket);
  444. s.SetProperty("page", controller.Request.Page);
  445. var c = Network.HttpClient.SimplePost(url, s.ToString().GetBytes());
  446. var r = Json.Parse(c.Response.Data.GetString());
  447. if (r == null || !r.Available)
  448. {
  449. controller.Response.Error("请求失败。");
  450. return "请求失败。";
  451. }
  452. if (r["status"] != "ok")
  453. {
  454. controller.Response.Error(r["message"]);
  455. return r["message"];
  456. }
  457. controller.Response.Data.Reset(r.GetProperty("data"));
  458. return null;
  459. }
  460. #endregion
  461. #region Log
  462. private static object LogLocker = new object();
  463. /// <summary>获取日志文件路径发生错误时返回 NULL 值。</summary>
  464. /// <remarks>d:\app\log\2020-02-02.log</remarks>
  465. /// <remarks>d:\www\app_data\log\2020-02-02.log</remarks>
  466. public static string GetLogPath()
  467. {
  468. // 找到 App_Data 目录。
  469. var appDir = KernelUtility.ApplicationBasePath;
  470. var dataDir = Path.Combine(appDir, "app_data");
  471. if (StorageUtility.DirectoryExists(dataDir)) appDir = dataDir;
  472. // 检查 Log 目录,不存在时创建,创建失败时返回。
  473. var logDir = Path.Combine(appDir, "log");
  474. if (!StorageUtility.AssureDirectory(logDir)) return null;
  475. // 文件不存在时创建新文件,无法创建时返回。
  476. var date = DateTime.Now.ToLucid(true, false, false, false);
  477. var filePath = Path.Combine(logDir, date + ".log");
  478. StorageUtility.CreateFile(filePath, 0, false);
  479. if (!StorageUtility.FileExists(filePath)) return null;
  480. // 返回 log 文件路径。
  481. return filePath;
  482. }
  483. /// <summary>写入日志。</summary>
  484. public static string WriteLog(params object[] content)
  485. {
  486. lock (LogLocker)
  487. {
  488. var path = GetLogPath();
  489. if (path.IsEmpty()) return "无法获取日志文件路径。";
  490. var text = TextUtility.Join(" | ", content);
  491. text = TextUtility.Merge(DateTimeUtility.NowLucid, " ", text, "\r\n");
  492. var bytes = TextUtility.ToBinary(text);
  493. if (!StorageUtility.AppendFile(path, bytes)) return "写日志文件失败。";
  494. return null;
  495. }
  496. }
  497. #endregion
  498. #endif
  499. }
  500. }