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.

541 lines
20 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  1. using Apewer.Network;
  2. using Apewer.Source;
  3. using System;
  4. using System.Collections;
  5. using System.Collections.Generic;
  6. using System.Diagnostics;
  7. using System.IO;
  8. using System.Text;
  9. namespace Apewer.Web
  10. {
  11. /// <summary></summary>
  12. public static class ApiUtility
  13. {
  14. #region Text
  15. /// <summary>修剪 IP 地址,去除无效的部分。</summary>
  16. public static string TrimIP(string text)
  17. {
  18. var trimmed = TextUtility.Trim(text);
  19. if (string.IsNullOrEmpty(trimmed)) return "";
  20. var ip = TextUtility.Trim(trimmed.Split(':')[0]);
  21. return NetworkUtility.IsIP(ip) ? ip : "";
  22. }
  23. /// <summary>按 &amp; 拆分多个参数。</summary>
  24. public static StringPairs Parameters(string query, bool decode = true)
  25. {
  26. var input = query;
  27. var list = new StringPairs();
  28. if (!string.IsNullOrEmpty(input))
  29. {
  30. if (input[0] == '?') input = input.Substring(1);
  31. var args = input.Split('&');
  32. foreach (var arg in args)
  33. {
  34. var equals = arg.IndexOf("=");
  35. var left = equals < 0 ? arg : arg.Substring(0, equals);
  36. var right = equals < 0 ? "" : arg.Substring(equals + 1);
  37. if (decode)
  38. {
  39. left = TextUtility.DecodeUrl(left);
  40. right = TextUtility.DecodeUrl(right);
  41. }
  42. list.Add(left, right);
  43. }
  44. }
  45. return list;
  46. }
  47. /// <summary>获取参数并解码,可要求修剪参数值。</summary>
  48. public static string Parameter(string encoded, bool trim, params string[] names)
  49. {
  50. if (string.IsNullOrEmpty(encoded)) return null;
  51. if (names == null || names.Length < 1) return null;
  52. // Names 转为小写,加强适配。
  53. var lowerNames = new List<string>(names.Length);
  54. foreach (var name in names)
  55. {
  56. var lower = TextUtility.Lower(name);
  57. if (string.IsNullOrEmpty(lower)) continue;
  58. lowerNames.Add(lower);
  59. }
  60. if (lowerNames.Count < 1) return null;
  61. // 分参数对比。
  62. var parameters = Parameters(encoded);
  63. var matched = false;
  64. foreach (var parameter in parameters)
  65. {
  66. var left = parameter.Key;
  67. var right = parameter.Value;
  68. if (trim) right = TextUtility.Lower(right);
  69. var lowerLeft = TextUtility.Lower(left);
  70. if (lowerNames.Contains(right))
  71. {
  72. matched = true;
  73. if (!string.IsNullOrEmpty(right)) return right;
  74. }
  75. }
  76. return matched ? "" : null;
  77. }
  78. /// <summary>获取参数,可要求修剪参数值。</summary>
  79. public static string Parameter(StringPairs parameters, bool trim, params string[] names)
  80. {
  81. if (parameters == null || parameters.Count < 1) return null;
  82. if (names == null || names.Length < 1) return null;
  83. var lowerNames = new List<string>(names.Length);
  84. foreach (var name in names)
  85. {
  86. var lower = TextUtility.Lower(name);
  87. if (string.IsNullOrEmpty(lower)) continue;
  88. lowerNames.Add(lower);
  89. }
  90. foreach (var parameter in parameters)
  91. {
  92. var lowerKey = TextUtility.Lower(parameter.Key);
  93. if (lowerNames.Contains(lowerKey))
  94. {
  95. var value = parameter.Value;
  96. if (trim) value = TextUtility.Lower(value);
  97. if (!string.IsNullOrEmpty(value)) return value;
  98. }
  99. }
  100. return null;
  101. }
  102. /// <summary>获取 User Agent。</summary>
  103. public static string UserAgent(StringPairs headers) => headers == null ? null : headers.GetValue("user-agent");
  104. // 从 Uri 对象中解析路径片段。
  105. private static string[] Segmentals(Uri url)
  106. {
  107. if (url == null) return null;
  108. if (string.IsNullOrEmpty(url.AbsolutePath)) return null;
  109. var segmentals = url.AbsolutePath.Split('/');
  110. return segmentals;
  111. }
  112. // 获取已经解析的路径片段。
  113. private static string Segmental(string[] segmentals, int index = 3, bool decode = false)
  114. {
  115. if (segmentals == null || segmentals.Length < 1) return null;
  116. if (index < 1 || index >= segmentals.Length) return null;
  117. var segmental = segmentals[index];
  118. if (decode) segmental = TextUtility.DecodeUrl(segmental);
  119. return segmental;
  120. }
  121. #endregion
  122. #region Headers
  123. /// <summary></summary>
  124. public static HttpMethod Method(string method)
  125. {
  126. if (!string.IsNullOrEmpty(method))
  127. {
  128. var upper = TextUtility.Upper(method);
  129. if (upper.Contains("OPTIONS")) return HttpMethod.OPTIONS;
  130. else if (upper.Contains("POST")) return HttpMethod.POST;
  131. else if (upper.Contains("GET")) return HttpMethod.GET;
  132. else if (upper.Contains("CONNECT")) return HttpMethod.CONNECT;
  133. else if (upper.Contains("DELETE")) return HttpMethod.DELETE;
  134. else if (upper.Contains("HEAD")) return HttpMethod.HEAD;
  135. else if (upper.Contains("PATCH")) return HttpMethod.PATCH;
  136. else if (upper.Contains("PUT")) return HttpMethod.PUT;
  137. else if (upper.Contains("TRACE")) return HttpMethod.TRACE;
  138. }
  139. return HttpMethod.NULL;
  140. }
  141. /// <summary>获取 X-Forwarded-For,不存在时返回 NULL 值。</summary>
  142. public static string[] GetForwardedIP(StringPairs headers)
  143. {
  144. if (headers != null)
  145. {
  146. var value = headers.GetValue("x-forwarded-for", true);
  147. if (!string.IsNullOrEmpty(value))
  148. {
  149. var fips = new List<string>();
  150. var split = value.Split(',', ' ');
  151. foreach (var para in split)
  152. {
  153. var fip = TrimIP(para);
  154. if (!string.IsNullOrEmpty(fip)) fips.Add(fip);
  155. }
  156. return fips.ToArray();
  157. }
  158. }
  159. return new string[0];
  160. }
  161. #endregion
  162. #region ApiController
  163. /// <summary>设置控制器属性。</summary>
  164. public static void SetProperties(ApiController controller, ApiRequest request, ApiResponse response, ApiOptions options)
  165. {
  166. if (controller == null) return;
  167. controller.Request = request;
  168. controller.Response = response;
  169. controller._options = options;
  170. }
  171. /// <summary>获取由控制器构造函数指定的初始化程序。</summary>
  172. public static Func<ApiController, bool> GetInitialier(this ApiController controller) => controller == null ? null : controller._func;
  173. /// <summary>获取由控制器构造函数指定的默认程序。</summary>
  174. public static Action<ApiController> GetDefault(this ApiController controller) => controller == null ? null : controller._default;
  175. /// <summary>获取选项。</summary>
  176. public static ApiOptions GetOptions(this ApiController controller) => controller == null ? null : controller._options;
  177. /// <summary>以 POST 转移请求到其它 URL。</summary>
  178. private static string Transfer(ApiController controller, string url, string application = null, string function = null)
  179. {
  180. if (controller == null || controller.Request == null || controller.Response == null) return "ApiControllser 无效。";
  181. if (url.IsEmpty()) return "ApiController 无效。";
  182. var s = Json.NewObject();
  183. s.SetProperty("random", TextUtility.Guid());
  184. s.SetProperty("application", application.IsEmpty() ? controller.Request.Application : application);
  185. s.SetProperty("function", function.IsEmpty() ? controller.Request.Function : function);
  186. s.SetProperty("data", controller.Request.Data);
  187. s.SetProperty("session", controller.Request.Session);
  188. s.SetProperty("ticket", controller.Request.Ticket);
  189. s.SetProperty("page", controller.Request.Page);
  190. var c = new Network.HttpClient();
  191. c.Request.Url = url;
  192. c.Request.Method = Network.HttpMethod.POST;
  193. // TODO 设置 Request 的 Cookies。
  194. c.Request.Data = s.ToString().Bytes();
  195. var e = c.Send();
  196. if (e != null) return e.Message;
  197. // TODO 解析 Response 的 Cookies。
  198. var r = Json.From(c.Response.Data.Text());
  199. if (r == null || !r.Available)
  200. {
  201. controller.Response.Error("请求失败。");
  202. return "请求失败。";
  203. }
  204. if (r["status"] != "ok")
  205. {
  206. controller.Response.Error(r["message"]);
  207. return r["message"];
  208. }
  209. controller.Response.Data.Reset(r.GetProperty("data"));
  210. return null;
  211. }
  212. /// <summary>创建指定类型的控制器,并引用当前控制器的 Request 和 Response。</summary>
  213. public static T Create<T>(this ApiController current) where T : ApiController, new()
  214. {
  215. var controller = new T();
  216. if (current != null)
  217. {
  218. controller.Request = current.Request;
  219. controller.Response = current.Response;
  220. }
  221. return controller;
  222. }
  223. /// <summary>使用默认控制器处理请求。</summary>
  224. public static void UseDefault(this ApiController current)
  225. {
  226. if (current == null) return;
  227. var options = GetOptions(current);
  228. if (options == null) return;
  229. var type = options.Default;
  230. if (type == null) return;
  231. var controller = null as ApiController;
  232. try
  233. {
  234. controller = (ApiController)Activator.CreateInstance(type);
  235. SetProperties(controller, current.Request, current.Response, options);
  236. }
  237. catch
  238. {
  239. return;
  240. }
  241. controller.GetInitialier()?.Invoke(controller);
  242. }
  243. #endregion
  244. #region ApiRequest
  245. /// <summary>获取 URL 路径段,不存在的段为 NULL 值。可要求解码。</summary>
  246. public static string Segmental(this ApiRequest request, int index = 3, bool decode = false)
  247. {
  248. if (request == null) return null;
  249. if (request._segmentals == null) request._segmentals = Segmentals(request.Url);
  250. return Segmental(request._segmentals, index, decode);
  251. }
  252. /// <summary>获取参数,指定可能的参数名,默认将修剪参数值。</summary>
  253. /// <remarks>当从 URL 中获取参数时将解码。</remarks>
  254. public static string Parameter(this ApiRequest request, params string[] names) => Parameter(request, true, names);
  255. /// <summary>获取参数,指定可能的参数名,可要求修剪参数值。</summary>
  256. /// <remarks>当从 URL 中获取参数时将解码。</remarks>
  257. public static string Parameter(ApiRequest request, bool trim, params string[] names)
  258. {
  259. if (request == null) return null;
  260. if (names == null || names.Length < 1) return null;
  261. var dedupNames = new List<string>(names.Length);
  262. var lowerNames = new List<string>(names.Length);
  263. foreach (var name in names)
  264. {
  265. if (string.IsNullOrEmpty(name)) continue;
  266. if (dedupNames.Contains(name)) continue;
  267. else dedupNames.Add(name);
  268. var lower = TextUtility.Lower(name);
  269. if (lowerNames.Contains(lower)) continue;
  270. else lowerNames.Add(lower);
  271. }
  272. var matched = false;
  273. // POST 优先。
  274. var data = request.Data;
  275. if (data != null && data.IsObject)
  276. {
  277. var properties = data.GetProperties();
  278. if (properties != null)
  279. {
  280. // Json 区分大小写,先全字匹配。
  281. foreach (var property in properties)
  282. {
  283. if (!property.IsProperty) continue;
  284. var name = property.Name;
  285. if (!dedupNames.Contains(name)) continue;
  286. var value = property.Value;
  287. if (value == null) continue;
  288. matched = true;
  289. var text = value.ToString();
  290. if (trim) text = TextUtility.Trim(text);
  291. if (!string.IsNullOrEmpty(text)) return text;
  292. }
  293. // 以小写模糊匹配。
  294. foreach (var property in properties)
  295. {
  296. if (!property.IsProperty) continue;
  297. var name = TextUtility.Lower(property.Name);
  298. if (!lowerNames.Contains(name)) continue;
  299. var value = property.Value;
  300. if (value == null) continue;
  301. matched = true;
  302. var text = value.ToString();
  303. if (trim) text = TextUtility.Trim(text);
  304. if (!string.IsNullOrEmpty(text)) return text;
  305. }
  306. }
  307. }
  308. // 从已解析的 Get 参数中搜索。
  309. if (request.Parameters != null)
  310. {
  311. var value = Parameter(request.Parameters, trim, names);
  312. if (!string.IsNullOrEmpty(value)) return value;
  313. if (value != null) matched = true;
  314. }
  315. return matched ? "" : null;
  316. }
  317. #endregion
  318. #region ApiResponse
  319. internal static ApiModel Model(ApiResponse response, string type, ApiModel model)
  320. {
  321. if (response == null && model == null) return null;
  322. if (!string.IsNullOrEmpty(type)) model.ContentType = type;
  323. response.Model = model;
  324. return model;
  325. }
  326. /// <summary>设置响应。</summary>
  327. public static string Respond(ApiResponse response, Json data, bool lower = true)
  328. {
  329. if (response == null) return "Response 对象无效。";
  330. if (data != null)
  331. {
  332. if (lower) data = Json.Lower(data);
  333. response.Data = data;
  334. }
  335. return null;
  336. }
  337. /// <summary>设置响应,当发生错误时设置响应。返回错误信息。</summary>
  338. public static string Respond(ApiResponse response, IList list, bool lower = true, int depth = -1, bool force = false)
  339. {
  340. if (response == null) return "Response 对象无效。";
  341. if (list == null)
  342. {
  343. var error = "列表对象无效。";
  344. response.Error(error);
  345. return error;
  346. }
  347. var json = Json.From(list, lower, depth, force);
  348. if (json == null || !json.Available)
  349. {
  350. var error = "列表无法序列化。";
  351. response.Error(error);
  352. return error;
  353. }
  354. if (response.Data == null) response.Data = Json.NewObject();
  355. response.Data.SetProperty("count", list.Count);
  356. response.Data.SetProperty("list", Json.From(list, lower, depth, force));
  357. return null;
  358. }
  359. /// <summary>设置响应,当发生错误时设置响应。返回错误信息。</summary>
  360. public static string Respond(ApiResponse response, IRecord record, bool lower = true)
  361. {
  362. if (response == null) return "Response 对象无效。";
  363. if (record == null)
  364. {
  365. var error = "记录无效。";
  366. response.Error(error);
  367. return error;
  368. }
  369. var json = Json.From(record, lower);
  370. if (json == null || !json.Available)
  371. {
  372. var error = "记录无法序列化。";
  373. response.Error(error);
  374. return error;
  375. }
  376. if (response.Data == null) response.Data = Json.NewObject();
  377. response.Data.Reset(json);
  378. return null;
  379. }
  380. /// <summary>设置 status 为 error,并设置 message 的内容。</summary>
  381. public static void Error(ApiResponse response, string message = "未知错误。")
  382. {
  383. if (response == null) return;
  384. response.Model = null;
  385. response.Status = "error";
  386. response.Message = message ?? TextUtility.Empty;
  387. }
  388. /// <summary>设置 status 为 exception,并设置 message 的内容。</summary>
  389. public static void Exception(ApiResponse response, Exception exception)
  390. {
  391. if (response == null) return;
  392. response.Model = null;
  393. response.Status = "exception";
  394. try
  395. {
  396. response.Message = exception == null ? "无效异常。" : exception.Message;
  397. response.Data = ToJson(exception);
  398. }
  399. catch { }
  400. }
  401. /// <summary></summary>
  402. private static Json ToJson(Exception exception)
  403. {
  404. if (exception == null) return null;
  405. var json = Json.NewObject();
  406. try
  407. {
  408. var ex = exception;
  409. json.SetProperty("type", exception.GetType().FullName);
  410. try
  411. {
  412. json.SetProperty("message", $"ex.Message({ex.GetType().FullName})");
  413. json.SetProperty("helplink", ex.HelpLink);
  414. json.SetProperty("source", ex.Source);
  415. json.SetProperty("stack", Json.From((ex.StackTrace ?? "").Replace("\r", "").Split('\n'), false));
  416. }
  417. catch { }
  418. // WebException 附加数据。
  419. var webex = ex as System.Net.WebException;
  420. if (webex != null)
  421. {
  422. json.SetProperty("data", Json.From(webex.Data));
  423. }
  424. }
  425. catch { }
  426. return json;
  427. }
  428. #endregion
  429. #region ApiModel
  430. /// <summary>初始化 ApiMode 的属性。</summary>
  431. public static void Initialize(ApiModel model, ApiRequest request, ApiResponse response, ApiOptions options, ApiProvider provider)
  432. {
  433. if (model == null) return;
  434. model._request = request;
  435. model._response = response;
  436. model._options = options;
  437. model._provider = provider;
  438. }
  439. #endregion
  440. #region Application & Path
  441. /// <summary>MapPath。</summary>
  442. public static string MapPath(params string[] names)
  443. {
  444. var list = new List<string>();
  445. if (names != null)
  446. {
  447. var invalid = StorageUtility.InvalidPathChars;
  448. foreach (var name in names)
  449. {
  450. if (string.IsNullOrEmpty(name)) continue;
  451. var split = name.Split('/', '\\');
  452. foreach (var s in split)
  453. {
  454. var sb = new StringBuilder();
  455. foreach (var c in s)
  456. {
  457. if (Array.IndexOf(invalid, "c") < 0) sb.Append(c);
  458. }
  459. var t = sb.ToString();
  460. if (!string.IsNullOrEmpty(t)) list.Add(t);
  461. }
  462. }
  463. }
  464. return list.Count > 1 ? StorageUtility.CombinePath(list.ToArray()) : "";
  465. }
  466. #endregion
  467. }
  468. }