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.

1079 lines
41 KiB

4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
11 months ago
4 years ago
11 months ago
4 years ago
11 months ago
4 years ago
3 years ago
11 months ago
3 years ago
11 months ago
3 years ago
4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
11 months ago
11 months ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
3 years ago
11 months ago
4 years ago
11 months ago
3 years ago
11 months ago
3 years ago
11 months ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
2 years ago
4 years ago
11 months ago
4 years ago
11 months ago
4 years ago
11 months ago
4 years ago
3 years ago
11 months ago
3 years ago
11 months ago
3 years ago
11 months ago
3 years ago
11 months ago
3 years ago
11 months ago
3 years ago
11 months ago
3 years ago
11 months ago
3 years ago
11 months ago
3 years ago
11 months ago
3 years ago
11 months ago
3 years ago
11 months ago
3 years ago
11 months ago
3 years ago
11 months ago
3 years ago
11 months ago
3 years ago
11 months ago
3 years ago
11 months ago
3 years ago
11 months ago
3 years ago
11 months ago
3 years ago
11 months ago
3 years ago
11 months ago
3 years ago
11 months ago
3 years ago
4 years ago
  1. using Apewer.Network;
  2. using Apewer.Source;
  3. using Newtonsoft.Json.Linq;
  4. using System;
  5. using System.Collections;
  6. using System.Collections.Generic;
  7. using System.Diagnostics;
  8. using System.IO;
  9. using System.Net;
  10. using System.Reflection;
  11. using System.Text;
  12. namespace Apewer.Web
  13. {
  14. /// <summary></summary>
  15. public static class ApiUtility
  16. {
  17. #region Text
  18. /// <summary>修剪 IP 地址,去除无效的部分。</summary>
  19. public static string TrimIP(string text)
  20. {
  21. var trimmed = TextUtility.Trim(text);
  22. if (string.IsNullOrEmpty(trimmed)) return "";
  23. var ip = TextUtility.Trim(trimmed.Split(':')[0]);
  24. return NetworkUtility.IsIP(ip) ? ip : "";
  25. }
  26. /// <summary>按 &amp; 拆分多个参数。</summary>
  27. public static StringPairs Parameters(string query, bool decode = true)
  28. {
  29. var input = query;
  30. var list = new StringPairs();
  31. if (!string.IsNullOrEmpty(input))
  32. {
  33. if (input[0] == '?') input = input.Substring(1);
  34. var args = input.Split('&');
  35. foreach (var arg in args)
  36. {
  37. var equals = arg.IndexOf("=");
  38. var left = equals < 0 ? arg : arg.Substring(0, equals);
  39. var right = equals < 0 ? "" : arg.Substring(equals + 1);
  40. if (decode)
  41. {
  42. left = TextUtility.DecodeUrl(left);
  43. right = TextUtility.DecodeUrl(right);
  44. }
  45. list.Add(left, right);
  46. }
  47. }
  48. return list;
  49. }
  50. /// <summary>获取参数并解码,可要求修剪参数值。</summary>
  51. public static string Parameter(string encoded, bool trim, params string[] names)
  52. {
  53. if (string.IsNullOrEmpty(encoded)) return null;
  54. if (names == null || names.Length < 1) return null;
  55. // Names 转为小写,加强适配。
  56. var lowerNames = new List<string>(names.Length);
  57. foreach (var name in names)
  58. {
  59. var lower = TextUtility.Lower(name);
  60. if (string.IsNullOrEmpty(lower)) continue;
  61. lowerNames.Add(lower);
  62. }
  63. if (lowerNames.Count < 1) return null;
  64. // 分参数对比。
  65. var parameters = Parameters(encoded);
  66. var matched = false;
  67. foreach (var parameter in parameters)
  68. {
  69. var left = parameter.Key;
  70. var right = parameter.Value;
  71. if (trim) right = TextUtility.Lower(right);
  72. var lowerLeft = TextUtility.Lower(left);
  73. if (lowerNames.Contains(right))
  74. {
  75. matched = true;
  76. if (!string.IsNullOrEmpty(right)) return right;
  77. }
  78. }
  79. return matched ? "" : null;
  80. }
  81. /// <summary>获取参数,可要求修剪参数值。</summary>
  82. public static string Parameter(StringPairs parameters, bool trim, params string[] names)
  83. {
  84. if (parameters == null || parameters.Count < 1) return null;
  85. if (names == null || names.Length < 1) return null;
  86. var lowerNames = new List<string>(names.Length);
  87. foreach (var name in names)
  88. {
  89. var lower = TextUtility.Lower(name);
  90. if (string.IsNullOrEmpty(lower)) continue;
  91. lowerNames.Add(lower);
  92. }
  93. foreach (var parameter in parameters)
  94. {
  95. var lowerKey = TextUtility.Lower(parameter.Key);
  96. if (lowerNames.Contains(lowerKey))
  97. {
  98. var value = parameter.Value;
  99. if (trim) value = TextUtility.Trim(value);
  100. if (!string.IsNullOrEmpty(value)) return value;
  101. }
  102. }
  103. return null;
  104. }
  105. /// <summary>获取 User Agent。</summary>
  106. public static string UserAgent(HttpHeaders headers) => headers == null ? null : headers.GetValue("user-agent");
  107. // 从 Uri 对象中解析路径片段。
  108. private static string[] Segmentals(Uri url)
  109. {
  110. if (url == null) return null;
  111. if (string.IsNullOrEmpty(url.AbsolutePath)) return null;
  112. var segmentals = url.AbsolutePath.Split('/');
  113. return segmentals;
  114. }
  115. // 获取已经解析的路径片段。
  116. private static string Segmental(string[] segmentals, int index = 3, bool decode = false)
  117. {
  118. if (segmentals == null || segmentals.Length < 1) return null;
  119. if (index < 1 || index >= segmentals.Length) return null;
  120. var segmental = segmentals[index];
  121. if (decode) segmental = TextUtility.DecodeUrl(segmental);
  122. return segmental;
  123. }
  124. #endregion
  125. #region Headers
  126. /// <summary></summary>
  127. public static HttpMethod Method(string method)
  128. {
  129. if (!string.IsNullOrEmpty(method))
  130. {
  131. var upper = TextUtility.Upper(method);
  132. if (upper.Contains("OPTIONS")) return HttpMethod.OPTIONS;
  133. else if (upper.Contains("POST")) return HttpMethod.POST;
  134. else if (upper.Contains("GET")) return HttpMethod.GET;
  135. else if (upper.Contains("CONNECT")) return HttpMethod.CONNECT;
  136. else if (upper.Contains("DELETE")) return HttpMethod.DELETE;
  137. else if (upper.Contains("HEAD")) return HttpMethod.HEAD;
  138. else if (upper.Contains("PATCH")) return HttpMethod.PATCH;
  139. else if (upper.Contains("PUT")) return HttpMethod.PUT;
  140. else if (upper.Contains("TRACE")) return HttpMethod.TRACE;
  141. }
  142. return HttpMethod.NULL;
  143. }
  144. /// <summary>获取 X-Forwarded-For,不存在时返回 NULL 值。</summary>
  145. public static string[] GetForwardedIP(HttpHeaders headers)
  146. {
  147. if (headers != null)
  148. {
  149. var value = headers.GetValue("x-forwarded-for");
  150. if (!string.IsNullOrEmpty(value))
  151. {
  152. var fips = new List<string>();
  153. var split = value.Split(',', ' ');
  154. foreach (var para in split)
  155. {
  156. var fip = TrimIP(para);
  157. if (!string.IsNullOrEmpty(fip)) fips.Add(fip);
  158. }
  159. return fips.ToArray();
  160. }
  161. }
  162. return new string[0];
  163. }
  164. #endregion
  165. #region Cookies
  166. // https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Set-Cookie
  167. // <cookie-name> 可以是除了控制字符 (CTLs)、空格 (spaces) 或制表符 (tab)之外的任何 US-ASCII 字符。
  168. // 同时不能包含以下分隔字符: ( ) < > @ , ; : \ " / [ ] ? = { }.
  169. // <cookie-value> 是可选的,如果存在的话,那么需要包含在双引号里面。
  170. // 支持除了控制字符(CTLs)、空格(whitespace)、双引号(double quotes)、逗号(comma)、分号(semicolon)以及反斜线(backslash)之外的任意 US-ASCII 字符。
  171. // 关于编码:许多应用会对 cookie 值按照URL编码(URL encoding)规则进行编码,但是按照 RFC 规范,这不是必须的。
  172. // 不过满足规范中对于 <cookie-value> 所允许使用的字符的要求是有用的。
  173. const string CookieChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&*+-<>^_`|~";
  174. /// <summary>修剪 Cookie 的 Name 文本。</summary>
  175. public static string CookieName(string name)
  176. {
  177. if (name.IsEmpty()) return null;
  178. var t = TextUtility.Restrict(name, CookieChars);
  179. while (t.StartsWith("$")) t = t.Substring(1);
  180. return t;
  181. }
  182. /// <summary>修剪 Cookie 的 Value 文本。</summary>
  183. public static string CookieValue(string value)
  184. {
  185. if (value.IsEmpty()) return value;
  186. var t = TextUtility.Restrict(value, CookieChars);
  187. if (t != value)
  188. {
  189. t = TextUtility.EncodeUrl(value);
  190. t = TextUtility.Restrict(t, CookieChars);
  191. }
  192. return t;
  193. }
  194. static string CookieExpires(DateTime dt)
  195. {
  196. // Expires=Wed, 21 Oct 2015 07:28:00 GMT;
  197. var utc = dt.ToUniversalTime();
  198. var sb = new StringBuilder();
  199. var week = utc.DayOfWeek.ToString().Substring(0, 3);
  200. sb.Append(week);
  201. sb.Append(", ");
  202. var day = utc.Day;
  203. if (day < 10) sb.Append("0");
  204. sb.Append(day);
  205. sb.Append(" ");
  206. var month = null as string;
  207. switch (utc.Month)
  208. {
  209. case 1: month = "Jan"; break;
  210. case 2: month = "Feb"; break;
  211. case 3: month = "Mar"; break;
  212. case 4: month = "Apr"; break;
  213. case 5: month = "May"; break;
  214. case 6: month = "Jun"; break;
  215. case 7: month = "Jul"; break;
  216. case 8: month = "Aug"; break;
  217. case 9: month = "Sep"; break;
  218. case 10: month = "Oct"; break;
  219. case 11: month = "Nov"; break;
  220. case 12: month = "Dec"; break;
  221. }
  222. sb.Append(month);
  223. sb.Append(" ");
  224. var year = utc.Year;
  225. if (year < 1000) sb.Append("0");
  226. if (year < 100) sb.Append("0");
  227. if (year < 10) sb.Append("0");
  228. sb.Append(year);
  229. sb.Append(" ");
  230. var time = ClockUtility.Lucid(utc, false, true, true, false);
  231. sb.Append(time);
  232. sb.Append(" GMT");
  233. return sb.ToString();
  234. }
  235. /// <summary>获取 Cookies 中第一个匹配到的值。</summary>
  236. public static string GetValue(this CookieCollection cookies, string name)
  237. {
  238. if (cookies == null || name.IsEmpty()) return null;
  239. var count = cookies.Count;
  240. for (var i = 0; i < count; i++)
  241. {
  242. var cookie = cookies[i];
  243. var n = cookie.Name;
  244. if (name == n) return cookie.Value;
  245. }
  246. var lower = TextUtility.Lower(name);
  247. for (var i = 0; i < count; i++)
  248. {
  249. var cookie = cookies[i];
  250. var n = TextUtility.Lower(cookie.Name);
  251. if (lower == n) return cookie.Value;
  252. }
  253. return null;
  254. }
  255. /// <summary>添加 Cookie 到集合。</summary>
  256. /// <param name="cookies">Cookie 集合。</param>
  257. /// <param name="name">名称。</param>
  258. /// <param name="value">值。</param>
  259. /// <param name="path">应用的路径。</param>
  260. /// <exception cref="CookieException"></exception>
  261. public static void Add(this CookieCollection cookies, string name, string value, string path = "/") => Add(cookies, name, value, path, null);
  262. /// <summary>添加 Cookie 到集合。</summary>
  263. /// <param name="cookies">Cookie 集合。</param>
  264. /// <param name="name">名称。</param>
  265. /// <param name="value">值。</param>
  266. /// <param name="path">应用的路径。</param>
  267. /// <param name="expires">过期时间。</param>
  268. /// <exception cref="CookieException"></exception>
  269. public static void Add(this CookieCollection cookies, string name, string value, DateTime expires, string path = "/") => Add(cookies, name, value, path, new Class<DateTime>(expires));
  270. /// <summary>添加 Cookie 到集合。</summary>
  271. /// <param name="cookies">Cookie 集合。</param>
  272. /// <param name="name">名称。</param>
  273. /// <param name="value">值。</param>
  274. /// <param name="path">应用的路径。</param>
  275. /// <param name="expires">过期时间。</param>
  276. /// <exception cref="CookieException"></exception>
  277. public static void Add(this CookieCollection cookies, string name, string value, string path, DateTime expires) => Add(cookies, name, value, path, new Class<DateTime>(expires));
  278. static void Add(this CookieCollection cookies, string name, string value, string path, Class<DateTime> expires)
  279. {
  280. if (cookies == null) return;
  281. name = CookieName(name);
  282. value = CookieName(value) ?? "";
  283. if (string.IsNullOrEmpty(name)) return;
  284. var cookie = new Cookie(name, value);
  285. cookie.Path = string.IsNullOrEmpty(path) ? "/" : path;
  286. if (expires != null) cookie.Expires = expires.Value;
  287. cookies.Add(cookie);
  288. }
  289. /// <summary>遍历 Cookie 集合。</summary>
  290. public static void ForEach(this CookieCollection cookies, Action<Cookie> action)
  291. {
  292. if (cookies == null || action == null) return;
  293. var count = cookies.Count;
  294. for (var i = 0; i < count; i++)
  295. {
  296. var cookie = cookies[i];
  297. if (cookie == null) continue;
  298. action.Invoke(cookie);
  299. }
  300. }
  301. internal static CookieCollection ParseCookies(HttpHeaders headers)
  302. {
  303. var cookies = new CookieCollection();
  304. if (headers == null) return cookies;
  305. var hvs = headers.GetValues("Cookie");
  306. foreach (var hv in hvs)
  307. {
  308. if (string.IsNullOrEmpty(hv)) continue;
  309. var lines = hv.Split(';');
  310. foreach (var line in lines)
  311. {
  312. var e = line.IndexOf("=");
  313. if (e < 0) continue;
  314. var name = TextUtility.Left(line, e, true);
  315. var value = TextUtility.Right(line, line.Length - e - 1, true);
  316. if (string.IsNullOrEmpty(name)) continue;
  317. if (string.IsNullOrEmpty(value)) continue;
  318. cookies.Add(name, value);
  319. }
  320. }
  321. return cookies;
  322. }
  323. /// <summary>生成 Set-Cookie 的值。</summary>
  324. public static string SetCookie(Cookie cookie)
  325. {
  326. if (cookie == null) return null;
  327. var n = CookieName(cookie.Name);
  328. if (n.IsEmpty()) return null;
  329. var v = CookieValue(cookie.Value);
  330. if (v == null) v = "";
  331. var sb = new StringBuilder();
  332. sb.Append(n);
  333. sb.Append("=");
  334. sb.Append(v);
  335. var expires = cookie.Expires;
  336. if (expires > DateTime.MinValue)
  337. {
  338. sb.Append("; Expires=");
  339. sb.Append(CookieExpires(expires));
  340. }
  341. var domain = cookie.Domain;
  342. if (!string.IsNullOrEmpty(domain))
  343. {
  344. sb.Append("; Domain=");
  345. sb.Append(domain);
  346. }
  347. var path = cookie.Path;
  348. sb.Append("; Path=");
  349. sb.Append(string.IsNullOrEmpty(path) ? "/" : path);
  350. var secure = cookie.Secure;
  351. if (secure)
  352. {
  353. sb.Append("; Secure");
  354. }
  355. return sb.ToString();
  356. }
  357. /// <summary>生成 Set-Cookie 的值。</summary>
  358. public static string[] SetCookie(CookieCollection cookies)
  359. {
  360. if (cookies == null) return null;
  361. var count = cookies.Count;
  362. var lines = new List<string>(count);
  363. for (var i = 0; i < count; i++)
  364. {
  365. var cookie = cookies[i];
  366. var value = SetCookie(cookie);
  367. if (value.IsEmpty()) continue;
  368. lines.Add(value);
  369. }
  370. return lines.ToArray();
  371. }
  372. #endregion
  373. #region ApiController
  374. /// <summary>设置控制器的 <see cref="ApiController.Context" /> 属性。</summary>
  375. /// <exception cref="ArgumentNullException" />
  376. public static void SetContext(ApiController controller, ApiContext context)
  377. {
  378. if (controller == null) throw new ArgumentNullException(nameof(controller));
  379. if (context == null) throw new ArgumentNullException(nameof(context));
  380. controller._context = context;
  381. }
  382. /// <summary>获取由控制器构造函数指定的初始化程序。</summary>
  383. public static Func<ApiController, bool> GetInitialier(this ApiController controller) => controller == null ? null : controller._func;
  384. /// <summary>获取由控制器构造函数指定的默认程序。</summary>
  385. public static Action<ApiController> GetDefault(this ApiController controller) => controller == null ? null : controller._default;
  386. /// <summary>以 POST 转移请求到其它 URL。</summary>
  387. private static string Transfer(ApiController controller, string url, string application = null, string function = null)
  388. {
  389. if (controller == null || controller.Request == null || controller.Response == null) return "ApiControllser 无效。";
  390. if (url.IsEmpty()) return "ApiController 无效。";
  391. var s = Json.NewObject();
  392. s.SetProperty("random", TextUtility.Key());
  393. s.SetProperty("application", application.IsEmpty() ? controller.Request.Application : application);
  394. s.SetProperty("function", function.IsEmpty() ? controller.Request.Function : function);
  395. s.SetProperty("data", controller.Request.Data);
  396. s.SetProperty("session", controller.Request.Session);
  397. s.SetProperty("ticket", controller.Request.Ticket);
  398. s.SetProperty("page", controller.Request.Page);
  399. var c = new HttpClient();
  400. c.Url = url;
  401. c.Method = HttpMethod.POST;
  402. // TODO 设置 Request 的 Cookies。
  403. c.RequestData = s.ToString().Bytes();
  404. var e = c.Send();
  405. if (e != null) return e.Message;
  406. // TODO 解析 Response 的 Cookies。
  407. var r = Json.From(c.ResponseData.Text());
  408. if (r == null || !r.Available)
  409. {
  410. controller.Response.Error("请求失败。");
  411. return "请求失败。";
  412. }
  413. if (r["status"] != "ok")
  414. {
  415. controller.Response.Error(r["message"]);
  416. return r["message"];
  417. }
  418. controller.Response.Data.Reset(r.GetProperty("data"));
  419. return null;
  420. }
  421. /// <summary>创建指定类型的控制器,并引用参照控制器的 Context。</summary>
  422. /// <exception cref="ArgumentNullException" />
  423. public static T Create<T>(this ApiController reference) where T : ApiController, new()
  424. {
  425. if (reference == null) throw new ArgumentNullException(nameof(reference));
  426. var controller = new T();
  427. controller._context = reference.Context;
  428. return controller;
  429. }
  430. /// <summary>使用默认控制器处理请求。</summary>
  431. public static void UseDefault(this ApiController current)
  432. {
  433. if (current == null) return;
  434. var options = current._context?.Options;
  435. if (options == null) return;
  436. var type = options.Default;
  437. if (type == null) return;
  438. var controller = null as ApiController;
  439. try
  440. {
  441. controller = (ApiController)Activator.CreateInstance(type);
  442. SetContext(controller, current.Context);
  443. }
  444. catch
  445. {
  446. return;
  447. }
  448. controller.GetInitialier()?.Invoke(controller);
  449. }
  450. #endregion
  451. #region ApiContext
  452. /// <summary>作为纯文本输出。</summary>
  453. /// <remarks>Content-Type: text/plain</remarks>
  454. public static void Text(ApiContext context, string text)
  455. {
  456. if (context == null) return;
  457. Model(context.Response, new ApiTextModel(text, "text/plain"));
  458. }
  459. /// <summary>设置 status 为 error,并设置 message 的内容。</summary>
  460. public static void Error(ApiContext context, string text)
  461. {
  462. if (context == null) return;
  463. Error(context.Response, text);
  464. }
  465. /// <summary>渲染为 JSON,并设置到 Response 的 Data 属性。</summary>
  466. public static void SetCamelJsonToResponseData(ApiContext context, object result)
  467. {
  468. if (context == null) return;
  469. if (context.Response == null) return;
  470. if (result == null) return;
  471. context.Response.Data = Json.From(result).Camel();
  472. }
  473. #endregion
  474. #region ApiRequest
  475. /// <summary>获取 URL 路径段,不存在的段为 NULL 值。可要求解码。</summary>
  476. public static string Segmental(this ApiRequest request, int index = 3, bool decode = false)
  477. {
  478. if (request == null) return null;
  479. if (request._segmentals == null) request._segmentals = Segmentals(request.Url);
  480. return Segmental(request._segmentals, index, decode);
  481. }
  482. /// <summary>获取参数,指定可能的参数名,默认将修剪参数值。</summary>
  483. /// <remarks>当从 URL 中获取参数时将解码。</remarks>
  484. public static string Parameter(this ApiRequest request, params string[] names) => Parameter(request, true, names);
  485. /// <summary>获取参数,指定可能的参数名,可要求修剪参数值。</summary>
  486. /// <remarks>当从 URL 中获取参数时将解码。</remarks>
  487. public static string Parameter(ApiRequest request, bool trim, params string[] names)
  488. {
  489. if (request == null) return null;
  490. if (names == null || names.Length < 1) return null;
  491. var dedupNames = new List<string>(names.Length);
  492. var lowerNames = new List<string>(names.Length);
  493. foreach (var name in names)
  494. {
  495. if (string.IsNullOrEmpty(name)) continue;
  496. if (dedupNames.Contains(name)) continue;
  497. else dedupNames.Add(name);
  498. var lower = TextUtility.Lower(name);
  499. if (lowerNames.Contains(lower)) continue;
  500. else lowerNames.Add(lower);
  501. }
  502. var matched = false;
  503. // POST 优先。
  504. var data = request.Data;
  505. if (data != null && data.IsObject)
  506. {
  507. var properties = data.GetProperties();
  508. if (properties != null)
  509. {
  510. // Json 区分大小写,先全字匹配。
  511. foreach (var property in properties)
  512. {
  513. if (!property.IsProperty) continue;
  514. var name = property.Name;
  515. if (!dedupNames.Contains(name)) continue;
  516. var value = property.Value;
  517. if (value == null) continue;
  518. matched = true;
  519. var text = value.ToString();
  520. if (trim) text = TextUtility.Trim(text);
  521. if (!string.IsNullOrEmpty(text)) return text;
  522. }
  523. // 以小写模糊匹配。
  524. foreach (var property in properties)
  525. {
  526. if (!property.IsProperty) continue;
  527. var name = TextUtility.Lower(property.Name);
  528. if (!lowerNames.Contains(name)) continue;
  529. var value = property.Value;
  530. if (value == null) continue;
  531. matched = true;
  532. var text = value.ToString();
  533. if (trim) text = TextUtility.Trim(text);
  534. if (!string.IsNullOrEmpty(text)) return text;
  535. }
  536. }
  537. }
  538. // 从已解析的 Get 参数中搜索。
  539. if (request.Parameters != null)
  540. {
  541. var value = Parameter(request.Parameters, trim, names);
  542. if (!string.IsNullOrEmpty(value)) return value;
  543. if (value != null) matched = true;
  544. }
  545. return matched ? "" : null;
  546. }
  547. #endregion
  548. #region ApiResponse
  549. internal static ApiModel Model(ApiResponse response, ApiModel model)
  550. {
  551. if (response == null && model == null) return null;
  552. response.Model = model;
  553. return model;
  554. }
  555. /// <summary>设置响应。</summary>
  556. public static string Respond(ApiResponse response, Json data, bool lower = true)
  557. {
  558. if (response == null) return "Response 对象无效。";
  559. if (data != null)
  560. {
  561. if (lower) data = Json.Lower(data);
  562. response.Data = data;
  563. }
  564. return null;
  565. }
  566. /// <summary>设置响应,当发生错误时设置响应。返回错误信息。</summary>
  567. public static string Respond(ApiResponse response, IList list, bool lower = true, int depth = -1, bool force = false)
  568. {
  569. if (response == null) return "Response 对象无效。";
  570. if (list == null)
  571. {
  572. var error = "列表对象无效。";
  573. response.Error(error);
  574. return error;
  575. }
  576. var json = Json.From(list, lower, depth, force);
  577. if (json == null || !json.Available)
  578. {
  579. var error = "列表无法序列化。";
  580. response.Error(error);
  581. return error;
  582. }
  583. if (response.Data == null) response.Data = Json.NewObject();
  584. response.Data.SetProperty("count", list.Count);
  585. response.Data.SetProperty("list", Json.From(list, lower, depth, force));
  586. return null;
  587. }
  588. /// <summary>设置响应,当发生错误时设置响应。返回错误信息。</summary>
  589. public static string Respond(ApiResponse response, IRecord record, bool lower = true)
  590. {
  591. if (response == null) return "Response 对象无效。";
  592. if (record == null)
  593. {
  594. var error = "记录无效。";
  595. response.Error(error);
  596. return error;
  597. }
  598. var json = Json.From(record, lower);
  599. if (json == null || !json.Available)
  600. {
  601. var error = "记录无法序列化。";
  602. response.Error(error);
  603. return error;
  604. }
  605. if (response.Data == null) response.Data = Json.NewObject();
  606. response.Data.Reset(json);
  607. return null;
  608. }
  609. /// <summary>设置 status 为 error,并设置 message 的内容。</summary>
  610. public static void Error(ApiResponse response, string message = "未知错误。")
  611. {
  612. if (response == null) return;
  613. response.Model = null;
  614. response.Status = "error";
  615. response.Message = message ?? TextUtility.Empty;
  616. }
  617. /// <summary>设置 status 为 exception,并设置 message 的内容。</summary>
  618. public static void Exception(ApiResponse response, Exception exception, bool setData = true)
  619. {
  620. if (response == null) return;
  621. response.Model = null;
  622. response.Status = "exception";
  623. if (exception != null)
  624. {
  625. try
  626. {
  627. if (setData)
  628. {
  629. var json = ToJson(exception);
  630. response.Message = json["message"];
  631. response.Data = json;
  632. }
  633. else
  634. {
  635. response.Message = exception.Message();
  636. }
  637. }
  638. catch { }
  639. }
  640. }
  641. private static Json ToJson(Exception exception, bool withInner = true)
  642. {
  643. if (exception == null) return ToJson(new NullReferenceException());
  644. var json = Json.NewObject();
  645. json.SetProperty("type", exception.GetType().FullName);
  646. json.SetProperty("message", exception.Message);
  647. var st = exception.StackTrace;
  648. if (!string.IsNullOrEmpty(st))
  649. {
  650. var stack = Json.NewArray();
  651. var split = st.Split('\r', '\n');
  652. foreach (var line in split)
  653. {
  654. var trim = TextUtility.Trim(line);
  655. if (string.IsNullOrEmpty(trim)) continue;
  656. stack.AddItem(trim);
  657. }
  658. json.SetProperty("stack", stack);
  659. }
  660. if (withInner)
  661. {
  662. var iex = exception.InnerException;
  663. if (iex != null) json.SetProperty("inner", ToJson(exception, false));
  664. }
  665. return json;
  666. }
  667. /// <summary>停止 Invoker 对返回值的处理。</summary>
  668. public static void StopReturn(ApiResponse response)
  669. {
  670. if (response == null) return;
  671. response.StopReturn = true;
  672. }
  673. /// <summary>生成 Response 的 Json 实例。</summary>
  674. public static string ToJson(this ApiResponse response, ApiOptions options = null)
  675. {
  676. if (response == null) return "{}";
  677. if (string.IsNullOrEmpty(response.Status)) response.Status = "ok";
  678. var json = Json.NewObject();
  679. if (options == null) options = new ApiOptions();
  680. // 执行时间。
  681. if (options.WithClock)
  682. {
  683. json.SetProperty("clock", ClockUtility.Lucid(DateTime.Now));
  684. }
  685. // 持续时间。
  686. if (options.WithDuration)
  687. {
  688. if (response.Duration.NotEmpty()) json.SetProperty("duration", response.Duration);
  689. }
  690. // 随机值。
  691. var random = response.Random;
  692. if (!string.IsNullOrEmpty(random)) json.SetProperty("random", random);
  693. // 调用。
  694. if (options.WithTarget)
  695. {
  696. json.SetProperty("application", response.Application);
  697. json.SetProperty("function", response.Function);
  698. }
  699. // 状态。
  700. json.SetProperty("status", (TextUtility.IsBlank(response.Status) ? TextUtility.Empty : response.Status.ToLower()));
  701. if (!string.IsNullOrEmpty(response.Message)) json.SetProperty("message", response.Message);
  702. // 用户数据。
  703. if (response.Message == "exception" && !options.WithException) json.SetProperty("data");
  704. else json.SetProperty("data", response.Data);
  705. var indented = response.Indented || options.JsonIndent;
  706. var text = json.ToString(indented);
  707. return text;
  708. }
  709. #endregion
  710. #region ApiResult
  711. /// <summary>对 HTTP 结果设置文件名。</summary>
  712. /// <param name="result">结果。</param>
  713. /// <param name="name">文件名(未编码)。</param>
  714. public static void SetAttachemnt(this HeadResult result, string name)
  715. {
  716. if (result == null) throw new ArgumentNullException(nameof(result));
  717. if (name.IsEmpty()) throw new ArgumentNullException(nameof(name));
  718. var encoded = TextUtility.EncodeUrl(name);
  719. result.Headers.Add("Content-Disposition", $"attachment; filename={encoded}");
  720. }
  721. #endregion
  722. #region ApiFunction Parameters
  723. internal static object[] ReadParameters(ApiRequest request, ApiFunction function)
  724. {
  725. if (request == null || function == null) return null;
  726. return ReadParameters(request, function.Parameters);
  727. }
  728. /// <summary>为带有形参的 Function 准备实参。</summary>
  729. /// <param name="request">API 请求模型。</param>
  730. /// <param name="parameters">Function 的参数信息。</param>
  731. /// <returns>实参。</returns>
  732. public static object[] ReadParameters(ApiRequest request, ParameterInfo[] parameters)
  733. {
  734. if (request == null || parameters == null || parameters.Length < 1) return null;
  735. var apiParameters = parameters.Map(x => ApiParameter.Parse(x) ?? throw new Exception($"参数【{x.Name}】无效。"));
  736. return ReadParameters(request, apiParameters);
  737. }
  738. /// <summary>为带有形参的 Function 准备实参。</summary>
  739. /// <param name="request">API 请求模型。</param>
  740. /// <param name="parameters">Function 的参数信息。</param>
  741. /// <returns>实参。</returns>
  742. public static object[] ReadParameters(ApiRequest request, ApiParameter[] parameters)
  743. {
  744. if (request == null || parameters == null || parameters.Length < 1) return null;
  745. if (parameters == null) return null;
  746. var count = parameters.Length;
  747. if (count < 1) return null;
  748. // 当 Function 仅有一个参数时,尝试生成模型。
  749. if (count == 1 && parameters[0] != null)
  750. {
  751. var parameterName = parameters[0].Name;
  752. var parameterType = parameters[0].Type;
  753. // POST
  754. if (request.Method == HttpMethod.POST)
  755. {
  756. // string
  757. if (parameterType.Equals(typeof(string))) return new object[] { request.Parameters.GetValue(parameterName, true) };
  758. // json
  759. if (parameterType.Equals(typeof(Json))) return new object[] { request.PostJson };
  760. #if !NET20
  761. // dynamic
  762. if (parameterType.Equals(typeof(object)))
  763. {
  764. try
  765. {
  766. // var expando = new System.Dynamic.ExpandoObject();
  767. // var dict = expando as IDictionary<string, object>;
  768. var expando = new ObjectSet(false, true);
  769. var dict = expando.Origin();
  770. if (request.Form != null)
  771. {
  772. foreach (var kvp in request.Form)
  773. {
  774. if (dict.ContainsKey(kvp.Key)) continue;
  775. dict.Add(kvp.Key, kvp.Value);
  776. }
  777. }
  778. else if (request.PostJson)
  779. {
  780. var jprops = request.PostJson.GetProperties();
  781. foreach (var jprop in jprops)
  782. {
  783. var name = jprop.Name;
  784. if (dict.ContainsKey(name)) continue;
  785. var value = jprop.Value;
  786. if (value != null)
  787. {
  788. }
  789. dict.Add(name, value);
  790. }
  791. }
  792. return new object[] { expando };
  793. }
  794. catch { }
  795. }
  796. #endif
  797. // class
  798. if (parameterType.IsClass)
  799. {
  800. try
  801. {
  802. var entity = ReadParameter(request.Data, parameterType);
  803. if (entity == null) entity = ReadParameter(request.PostJson, parameterType);
  804. if (entity != null) return new object[] { entity.Value };
  805. }
  806. catch { }
  807. }
  808. if (request.PostJson) return new object[] { request.PostJson };
  809. if (request.Form != null) return new object[] { request.Form };
  810. if (request.PostText.NotEmpty()) return new object[] { request.PostText };
  811. return new object[] { request.PostData };
  812. }
  813. else
  814. {
  815. // string
  816. if (parameterType.Equals(typeof(string))) return new object[] { request.Parameters.GetValue(parameterName, true) };
  817. // json
  818. if (parameterType.Equals(typeof(Json))) return new object[] { Json.From(request.Parameters.GetValue(parameterName, true)) };
  819. }
  820. }
  821. var values = new object[count];
  822. for (var i = 0; i < count; i++)
  823. {
  824. if (parameters[i] != null)
  825. {
  826. var name = parameters[i].Name;
  827. var type = parameters[i].Type;
  828. var text = Parameter(request, name);
  829. values[i] = ReadParameter(text, type);
  830. }
  831. }
  832. return values;
  833. }
  834. static Class<object> ReadParameter(Json json, Type type)
  835. {
  836. if (json)
  837. {
  838. if (json.IsObject)
  839. {
  840. var properties = json.GetProperties();
  841. if (properties.Length > 0)
  842. {
  843. var entity = Json.Object(type, json, true, null, true);
  844. return new Class<object>(entity);
  845. }
  846. }
  847. if (json.IsArray)
  848. {
  849. var items = json.GetItems();
  850. if (items.Length > 0)
  851. {
  852. var entity = Json.Object(type, json, true, null, true);
  853. return new Class<object>(entity);
  854. }
  855. }
  856. }
  857. return null;
  858. }
  859. static object ReadParameter(string text, Type type)
  860. {
  861. if (type.Equals(typeof(object)) || type.Equals(typeof(string))) return text;
  862. if (type.Equals(typeof(bool))) return NumberUtility.Boolean(text);
  863. if (type.Equals(typeof(float))) return NumberUtility.Single(text);
  864. if (type.Equals(typeof(double))) return NumberUtility.Double(text);
  865. if (type.Equals(typeof(decimal))) return NumberUtility.Decimal(text);
  866. if (type.Equals(typeof(byte))) return NumberUtility.Byte(text);
  867. if (type.Equals(typeof(sbyte))) return NumberUtility.SByte(text);
  868. if (type.Equals(typeof(short))) return NumberUtility.Int16(text);
  869. if (type.Equals(typeof(ushort))) return NumberUtility.UInt16(text);
  870. if (type.Equals(typeof(int))) return NumberUtility.Int32(text);
  871. if (type.Equals(typeof(uint))) return NumberUtility.UInt32(text);
  872. if (type.Equals(typeof(long))) return NumberUtility.Int64(text);
  873. if (type.Equals(typeof(ulong))) return NumberUtility.UInt64(text);
  874. if (type.Equals(typeof(byte[]))) return TextUtility.FromBase64(text);
  875. if (type.Equals(typeof(Json))) return Json.From(text);
  876. return type.IsValueType ? Activator.CreateInstance(type) : null;
  877. }
  878. #endregion
  879. #region Enumerate Entries
  880. internal static Json Enumerate(IEnumerable<ApiApplication> applications, ApiOptions options)
  881. {
  882. var list = Json.NewArray();
  883. var count = 0;
  884. if (applications != null)
  885. {
  886. foreach (var app in applications)
  887. {
  888. if (app == null) continue;
  889. if (app.Hidden) continue;
  890. list.AddItem(app.ToJson(options));
  891. count = count + 1;
  892. }
  893. }
  894. var json = Json.NewObject();
  895. json.SetProperty("count", count);
  896. json.SetProperty("applications", list);
  897. return json;
  898. }
  899. internal static Json Enumerate(IEnumerable<ApiFunction> functions, ApiOptions options)
  900. {
  901. var count = 0;
  902. var list = Json.NewArray();
  903. if (functions != null)
  904. {
  905. foreach (var func in functions)
  906. {
  907. if (func == null) continue;
  908. if (func.Hidden) continue;
  909. list.AddItem(func.ToJson(options));
  910. count = count + 1;
  911. }
  912. }
  913. var json = Json.NewObject();
  914. json.SetProperty("count", count);
  915. json.SetProperty("functions", list);
  916. return json;
  917. }
  918. #endregion
  919. }
  920. }