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.

447 lines
17 KiB

4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
  1. using Apewer.Network;
  2. using Apewer.Web;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Text;
  6. namespace Apewer.Internals
  7. {
  8. internal static class ApiHelper
  9. {
  10. #region Cookies
  11. // https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Set-Cookie
  12. // <cookie-name> 可以是除了控制字符 (CTLs)、空格 (spaces) 或制表符 (tab)之外的任何 US-ASCII 字符。
  13. // 同时不能包含以下分隔字符: ( ) < > @ , ; : \ " / [ ] ? = { }.
  14. // <cookie-value> 是可选的,如果存在的话,那么需要包含在双引号里面。
  15. // 支持除了控制字符(CTLs)、空格(whitespace)、双引号(double quotes)、逗号(comma)、分号(semicolon)以及反斜线(backslash)之外的任意 US-ASCII 字符。
  16. // 关于编码:许多应用会对 cookie 值按照URL编码(URL encoding)规则进行编码,但是按照 RFC 规范,这不是必须的。
  17. // 不过满足规范中对于 <cookie-value> 所允许使用的字符的要求是有用的。
  18. internal static StringPairs ParseCookies(StringPairs headers)
  19. {
  20. var cookies = new StringPairs();
  21. if (headers == null) return cookies;
  22. var hvs = headers.GetValues("cookie", true, false);
  23. foreach (var hv in hvs)
  24. {
  25. if (string.IsNullOrEmpty(hv)) continue;
  26. var lines = hv.Split(';');
  27. foreach (var line in lines)
  28. {
  29. var e = line.IndexOf("=");
  30. if (e < 0) continue;
  31. var name = TextUtility.Left(line, e, true);
  32. var value = TextUtility.Right(line, line.Length - e - 1, true);
  33. if (string.IsNullOrEmpty(name)) continue;
  34. if (string.IsNullOrEmpty(value)) continue;
  35. cookies.Add(name, value);
  36. }
  37. }
  38. return cookies;
  39. }
  40. // 生成 Response 头中 Set-Cookie 的值。
  41. internal static string SetCookie(IEnumerable<KeyValuePair<string, string>> cookies)
  42. {
  43. if (cookies == null) return null;
  44. var ps = new List<string>();
  45. foreach (var kvp in cookies)
  46. {
  47. var k = kvp.Key ?? "";
  48. var v = kvp.Value ?? "";
  49. if (k.IsEmpty()) continue;
  50. ps.Add(TextUtility.Merge(k, "=", v));
  51. }
  52. if (ps.Count < 1) return null;
  53. var value = TextUtility.Join("; ", ps);
  54. return value;
  55. }
  56. #endregion
  57. #region ApiFunction Parameterse
  58. // 为带有形参的 Function 准备实参。
  59. internal static object[] ReadParameters(ApiRequest request, ApiFunction function)
  60. {
  61. if (request == null || function == null) return null;
  62. var pis = function.Parameters;
  63. if (pis == null) return null;
  64. var count = pis.Length;
  65. if (count < 1) return null;
  66. // 当 Function 仅有一个参数时,尝试生成模型。
  67. if (count == 1)
  68. {
  69. if (function.ParamIsRecord)
  70. {
  71. try
  72. {
  73. var record = Activator.CreateInstance(pis[0].ParameterType);
  74. Json.Object(record, request.Data, true, null, true);
  75. return new object[] { record };
  76. }
  77. catch { }
  78. return new object[] { null };
  79. }
  80. // 未知类型的参数,继续使用通用方法获取。
  81. }
  82. var ps = new object[count];
  83. for (var i = 0; i < count; i++)
  84. {
  85. var name = pis[i].Name;
  86. var type = pis[i].ParameterType;
  87. var text = ApiUtility.Parameter(request, name);
  88. ps[i] = ReadParameter(text, type);
  89. }
  90. return ps;
  91. }
  92. static object ReadParameter(string text, Type type)
  93. {
  94. if (type.Equals(typeof(object)) || type.Equals(typeof(string))) return text;
  95. if (type.Equals(typeof(byte[]))) return TextUtility.FromBase64(text);
  96. if (type.Equals(typeof(float))) return NumberUtility.Single(text);
  97. if (type.Equals(typeof(double))) return NumberUtility.Double(text);
  98. if (type.Equals(typeof(decimal))) return NumberUtility.Decimal(text);
  99. if (type.Equals(typeof(byte))) return NumberUtility.Byte(text);
  100. if (type.Equals(typeof(sbyte))) return NumberUtility.SByte(text);
  101. if (type.Equals(typeof(short))) return NumberUtility.Int16(text);
  102. if (type.Equals(typeof(ushort))) return NumberUtility.UInt16(text);
  103. if (type.Equals(typeof(int))) return NumberUtility.Int32(text);
  104. if (type.Equals(typeof(uint))) return NumberUtility.UInt32(text);
  105. if (type.Equals(typeof(long))) return NumberUtility.Int64(text);
  106. if (type.Equals(typeof(ulong))) return NumberUtility.UInt64(text);
  107. return type.IsValueType ? Activator.CreateInstance(type) : null;
  108. }
  109. #endregion
  110. #region Enumerate Entries
  111. internal static Json Enumerate(IEnumerable<ApiApplication> applications, ApiOptions options)
  112. {
  113. var list = Json.NewArray();
  114. var count = 0;
  115. if (applications != null)
  116. {
  117. foreach (var app in applications)
  118. {
  119. if (app == null) continue;
  120. if (app.Hidden) continue;
  121. list.AddItem(app.ToJson(options));
  122. count = count + 1;
  123. }
  124. }
  125. var json = Json.NewObject();
  126. json.SetProperty("count", count);
  127. json.SetProperty("list", list);
  128. return json;
  129. }
  130. internal static Json Enumerate(IEnumerable<ApiFunction> functions, ApiOptions options)
  131. {
  132. var count = 0;
  133. var list = Json.NewArray();
  134. if (functions != null)
  135. {
  136. foreach (var func in functions)
  137. {
  138. if (func == null) continue;
  139. if (func.Hidden) continue;
  140. list.AddItem(func.ToJson(options));
  141. count = count + 1;
  142. }
  143. }
  144. var json = Json.NewObject();
  145. json.SetProperty("count", count);
  146. json.SetProperty("list", list);
  147. return json;
  148. }
  149. #endregion
  150. #region Request
  151. internal static ApiRequest GetRequest(ApiProvider provider, ApiOptions options, HttpMethod method, Uri url)
  152. {
  153. // 创建数据对象。
  154. var request = new ApiRequest();
  155. // Http Method。
  156. request.Method = method;
  157. // 基本信息。
  158. var ip = provider.GetClientIP() ?? null;
  159. var headers = provider.GetHeaders() ?? new StringPairs();
  160. request.Headers = headers;
  161. request.IP = ip;
  162. request.Url = url;
  163. request.Referrer = provider.GetReferrer();
  164. request.Parameters = ApiUtility.Parameters(url.Query);
  165. // Headers。
  166. request.UserAgent = ApiUtility.UserAgent(headers);
  167. request.Cookies = ParseCookies(headers) ?? new StringPairs();
  168. // 匹配 API。
  169. var application = null as string;
  170. var function = null as string;
  171. var random = null as string;
  172. var ticket = null as string;
  173. var session = null as string;
  174. var page = null as string;
  175. // 解析 POST 请求。
  176. if (request.Method == HttpMethod.POST)
  177. {
  178. var preRead = provider.PreRead();
  179. if (string.IsNullOrEmpty(preRead))
  180. {
  181. var post = null as byte[];
  182. var length = 0L;
  183. var max = options.MaxRequestBody;
  184. if (max == 0) post = new byte[0];
  185. else if (max < 0) post = provider.RequestBody().Read();
  186. else
  187. {
  188. length = provider.GetContentLength();
  189. if (length <= max) post = provider.RequestBody().Read();
  190. }
  191. length = post == null ? 0 : post.Length;
  192. if (length > 1)
  193. {
  194. request.PostData = post;
  195. if (length < 104857600)
  196. {
  197. var text = TextUtility.FromBytes(post);
  198. request.PostText = text;
  199. // 尝试解析 Json,首尾必须是“{}”或“[]”。
  200. var first = post[0];
  201. var last = post[length - 1];
  202. if ((first == 123 && last == 125) || (first == 91 && last == 93))
  203. {
  204. var json = Json.From(text);
  205. if (json != null && json.IsObject)
  206. {
  207. application = json["application"];
  208. function = json["function"];
  209. random = json["random"];
  210. ticket = json["ticket"];
  211. session = json["session"];
  212. page = json["page"];
  213. var data = json.GetProperty("data");
  214. request.PostJson = json;
  215. request.Data = data ?? Json.NewObject();
  216. }
  217. }
  218. }
  219. }
  220. }
  221. }
  222. // 解析 URL 参数。
  223. // URL 参数的优先级应高于 URL 路径,以避免反向代理产生的路径问题。
  224. var urlParameters = ApiUtility.Parameters(request.Url.Query);
  225. if (string.IsNullOrEmpty(application)) application = urlParameters.GetValue("application");
  226. if (string.IsNullOrEmpty(function)) function = urlParameters.GetValue("function");
  227. if (string.IsNullOrEmpty(random)) random = urlParameters.GetValue("random");
  228. if (string.IsNullOrEmpty(ticket)) ticket = urlParameters.GetValue("ticket");
  229. if (string.IsNullOrEmpty(session)) session = urlParameters.GetValue("session");
  230. if (string.IsNullOrEmpty(page)) page = urlParameters.GetValue("page");
  231. // 从 Cookie 中获取 Ticket。
  232. var cookies = request.Cookies;
  233. if (string.IsNullOrEmpty(ticket)) ticket = cookies.GetValue("ticket");
  234. // 最后检查 URL 路径。
  235. var paths = (request.Url.AbsolutePath ?? "").Split('/');
  236. if (string.IsNullOrEmpty(application) && paths.Length >= 2) application = TextUtility.DecodeUrl(paths[1]);
  237. if (string.IsNullOrEmpty(function) && paths.Length >= 3) function = TextUtility.DecodeUrl(paths[2]);
  238. // 修正内容。
  239. application = TextUtility.Trim(application);
  240. function = TextUtility.Trim(function);
  241. random = TextUtility.Trim(random);
  242. ticket = TextUtility.Trim(ticket);
  243. session = TextUtility.Trim(session);
  244. page = TextUtility.Trim(page);
  245. // 设置请求:回传。
  246. request.Application = application;
  247. request.Function = function;
  248. request.Random = random;
  249. // 设置请求:不回传。
  250. request.Ticket = ticket;
  251. request.Session = session;
  252. request.Page = page;
  253. return request;
  254. }
  255. #endregion
  256. #region Response
  257. static StringPairs PrepareHeaders(ApiOptions options, ApiResponse response)
  258. {
  259. var merged = new StringPairs();
  260. if (options != null)
  261. {
  262. // 跨域访问。
  263. if (options.WithAccessControl)
  264. {
  265. merged.Add("Access-Control-Allow-Headers", "Content-Type");
  266. merged.Add("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
  267. merged.Add("Access-Control-Allow-Origin", "*");
  268. var maxage = options.AccessControlMaxAge;
  269. if (maxage > 0) merged.Add("Access-Control-Max-Age", maxage.ToString());
  270. }
  271. // Content-Type 检查。
  272. if (options.WithContentTypeOptions || options.Default != null)
  273. {
  274. merged.Add("X-Content-Type-Options", "nosniff");
  275. }
  276. // 用于客户端,当前页面使用 HTTPS 时,将资源升级为 HTTPS。
  277. if (options.UpgradeHttps)
  278. {
  279. merged.Add("Content-Security-Policy", "upgrade-insecure-requests");
  280. }
  281. }
  282. if (response != null)
  283. {
  284. // Cookies。
  285. var setCookie = SetCookie(response.Cookies);
  286. if (!string.IsNullOrEmpty(setCookie))
  287. {
  288. merged.Add("Set-Cookie", setCookie);
  289. }
  290. // 自定义头。
  291. var headers = response.Headers;
  292. if (headers != null)
  293. {
  294. foreach (var header in headers)
  295. {
  296. var key = TextUtility.Trim(header.Key);
  297. if (string.IsNullOrEmpty(key)) continue;
  298. var value = header.Value;
  299. if (string.IsNullOrEmpty(value)) continue;
  300. merged.Add(key, value);
  301. }
  302. }
  303. }
  304. return merged;
  305. }
  306. internal static string ExportJson(ApiResponse response, ApiOptions options)
  307. {
  308. if (response == null) return "{}";
  309. if (string.IsNullOrEmpty(response.Status)) response.Status = "ok";
  310. var json = Json.NewObject();
  311. // 执行时间。
  312. if (options.WithClock)
  313. {
  314. json.SetProperty("clock", ClockUtility.Lucid(DateTime.Now));
  315. }
  316. // 持续时间。
  317. if (options.WithDuration)
  318. {
  319. json.SetProperty("duration", response.Duration);
  320. }
  321. // 随机值。
  322. var random = response.Random;
  323. if (!string.IsNullOrEmpty(random)) json.SetProperty("random", random);
  324. // 调用。
  325. if (options.WithTarget)
  326. {
  327. json.SetProperty("application", response.Application);
  328. json.SetProperty("function", response.Function);
  329. }
  330. // 状态。
  331. json.SetProperty("status", (TextUtility.IsBlank(response.Status) ? TextUtility.Empty : response.Status.ToLower()));
  332. if (!string.IsNullOrEmpty(response.Message)) json.SetProperty("message", response.Message);
  333. // 用户数据。
  334. if (response.Message == "exception" && !options.WithException) json.SetProperty("data");
  335. else json.SetProperty("data", response.Data);
  336. var indented = response.Indented || options.JsonIndent;
  337. var text = json.ToString(indented);
  338. return text;
  339. }
  340. internal static void Output(ApiProvider provider, ApiOptions options, string type, byte[] bytes)
  341. {
  342. var preWrite = provider.PreWrite();
  343. if (!string.IsNullOrEmpty(preWrite)) return;
  344. var headers = PrepareHeaders(options, null);
  345. foreach (var header in headers) provider.SetHeader(header.Key, header.Value);
  346. provider.SetCache(0);
  347. provider.SetContentType(string.IsNullOrEmpty(type) ? "application/octet-stream" : type);
  348. var length = bytes == null ? 0 : bytes.Length;
  349. provider.SetContentLength(length);
  350. if (length > 0) provider.ResponseBody().Write(bytes);
  351. }
  352. internal static void Output(ApiProvider provider, ApiOptions options, ApiResponse response, ApiRequest request, HttpMethod method)
  353. {
  354. var preWrite = provider.PreWrite();
  355. if (!string.IsNullOrEmpty(preWrite)) return;
  356. // 设置头。
  357. var headers = PrepareHeaders(options, null);
  358. foreach (var header in headers) provider.SetHeader(header.Key, header.Value);
  359. var model = response.Model;
  360. if (model != null)
  361. {
  362. ApiUtility.Initialize(model, request, response, options, provider);
  363. try { model.Output(); } catch { }
  364. RuntimeUtility.Dispose(model);
  365. return;
  366. }
  367. var json = TextUtility.Bytes(ExportJson(response, options));
  368. provider.SetCache(0);
  369. provider.SetContentType("text/json; charset=utf-8");
  370. provider.SetContentLength(json.Length);
  371. var stream = provider.ResponseBody();
  372. if (stream != null && stream.CanWrite) stream.Write(json);
  373. }
  374. #endregion
  375. }
  376. }