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.

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