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.

613 lines
23 KiB

3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
  1. using Apewer.Network;
  2. using Apewer.Source;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Diagnostics;
  6. using System.IO;
  7. using System.Net;
  8. using System.Reflection;
  9. using static Apewer.Web.ApiUtility;
  10. namespace Apewer.Web
  11. {
  12. internal class ApiProcessor
  13. {
  14. internal ApiInvoker Invoker = null;
  15. internal ApiEntries Entries = null;
  16. internal ApiProvider Provider = null;
  17. internal Action<ApiCatch> Catcher = null;
  18. ApiOptions Options = null;
  19. Stopwatch Stopwatch = null;
  20. Uri Url = null;
  21. HttpMethod Method = HttpMethod.NULL;
  22. ApiRequest ApiRequest = null;
  23. ApiResponse ApiResponse = null;
  24. internal ApiProcessor()
  25. {
  26. }
  27. /// <summary>执行处理程序,返回错误信息。</summary>
  28. public string Run()
  29. {
  30. if (Options == null) Options = new ApiOptions();
  31. if (Options.WithDuration)
  32. {
  33. Stopwatch = new Stopwatch();
  34. Stopwatch.Start();
  35. }
  36. var error = Flow();
  37. if (Stopwatch != null)
  38. {
  39. Stopwatch.Stop();
  40. Stopwatch = null;
  41. }
  42. return error;
  43. }
  44. string Flow()
  45. {
  46. try
  47. {
  48. // 传入字段。
  49. if (Provider == null) return "服务程序无效。";
  50. if (Invoker == null) return "调用器无效。";
  51. if (Entries == null) return "入口无效。";
  52. Options = Invoker.Options ?? new ApiOptions();
  53. Provider.Options = Options;
  54. // 检查执行的前提条件,获取 Method 和 URL。
  55. var check = Check();
  56. if (!string.IsNullOrEmpty(check)) return check;
  57. // 准备请求和响应模型。
  58. ApiRequest = GetRequest(Provider, Options, Method, Url);
  59. ApiResponse = new ApiResponse();
  60. ApiResponse.Random = ApiRequest.Random;
  61. ApiResponse.Application = ApiRequest.Application;
  62. ApiResponse.Function = ApiRequest.Function;
  63. // 调用 API。
  64. var invoke = Invoke();
  65. if (!string.IsNullOrEmpty(invoke)) return invoke;
  66. // 输出。
  67. if (Stopwatch != null)
  68. {
  69. Stopwatch.Stop();
  70. ApiResponse.Duration = Stopwatch.ElapsedMilliseconds;
  71. Stopwatch = null;
  72. }
  73. Output(Provider, Options, ApiResponse, ApiRequest, Method);
  74. return null;
  75. }
  76. catch (Exception ex)
  77. {
  78. if (Stopwatch != null)
  79. {
  80. Stopwatch.Stop();
  81. Stopwatch = null;
  82. }
  83. var message = ex.Message();
  84. Logger.Internals.Error(typeof(ApiInvoker), message);
  85. return message;
  86. }
  87. }
  88. string Check()
  89. {
  90. // 服务程序检查。
  91. var check = Provider.PreInvoke();
  92. if (!string.IsNullOrEmpty(check)) return check;
  93. // URL
  94. Url = Provider.GetUrl();
  95. if (Url == null) return "URL 无效。";
  96. Method = Provider.GetMethod();
  97. if (Method == HttpMethod.NULL) return "HTTP 方法无效。";
  98. if (Method == HttpMethod.OPTIONS) return null;
  99. // favicon.ico
  100. var lowerPath = TextUtility.AssureStarts(TextUtility.Lower(Url.AbsolutePath), "/");
  101. if (!Options.AllowFavIcon)
  102. {
  103. if (lowerPath.StartsWith("/favicon.ico"))
  104. {
  105. Output(Provider, Options, null, null, null);
  106. return "已取消对 favicon.ico 的请求。";
  107. }
  108. }
  109. // robots.txt
  110. if (!Options.AllowRobots)
  111. {
  112. if (lowerPath.StartsWith("/robots.txt"))
  113. {
  114. const string text = "User-agent: *\nDisallow: / \n";
  115. Output(Provider, Options, null, "text/plain", TextUtility.Bytes(text));
  116. return "已取消对 robots.txt 的请求。";
  117. }
  118. }
  119. return null;
  120. }
  121. // 寻找入口。
  122. string Invoke()
  123. {
  124. var appName = ApiRequest.Application;
  125. var funcName = ApiRequest.Function;
  126. var random = ApiRequest.Random;
  127. Invoke(Entries.Get(appName));
  128. if (Stopwatch != null) ApiResponse.Duration = Stopwatch.ElapsedMilliseconds;
  129. ApiResponse.Application = appName;
  130. ApiResponse.Function = funcName;
  131. ApiResponse.Random = random;
  132. return null;
  133. }
  134. // 创建控制器。
  135. void Invoke(ApiApplication application)
  136. {
  137. var request = ApiRequest;
  138. var response = ApiResponse;
  139. var function = null as ApiFunction;
  140. var controller = null as ApiController;
  141. // Application 无效,尝试默认控制器和枚举。
  142. if (application == null)
  143. {
  144. var @default = Options.Default;
  145. if (@default == null)
  146. {
  147. // 没有指定默认控制器,尝试枚举。
  148. response.Error("Invalid Application");
  149. if (Options.AllowEnumerate) response.Data = Enumerate(Entries.Enumerate(), Options);
  150. return;
  151. }
  152. else
  153. {
  154. // 创建默认控制器。
  155. try { controller = CreateController(@default, request, response, Options); }
  156. catch (Exception ex) { ApiUtility.Exception(response, ex.InnerException); }
  157. }
  158. }
  159. else
  160. {
  161. // 创建控制器时候会填充 Controller.Request 属性,可能导致 Request.Function 被篡改,所以在创建之前获取 Function。
  162. function = application.Get(request.Function);
  163. try { controller = CreateController(application.Type, request, response, Options); }
  164. catch (Exception ex) { ApiUtility.Exception(response, ex.InnerException); }
  165. }
  166. if (controller == null) response.Error("创建控制器实例失败。");
  167. else Invoke(controller, application, function, Options, request, response);
  168. RuntimeUtility.Dispose(controller);
  169. }
  170. // 调用 Function。
  171. void Invoke(ApiController controller, ApiApplication application, ApiFunction function, ApiOptions options, ApiRequest request, ApiResponse response)
  172. {
  173. // 没有 ApiApplication,使用了 Options 中指定的默认控制器。
  174. if (application == null)
  175. {
  176. application = new ApiApplication();
  177. application.Independent = true;
  178. application.Hidden = true;
  179. }
  180. try
  181. {
  182. // 控制器初始化。
  183. var initializer = ApiUtility.GetInitialier(controller);
  184. var match = initializer == null ? true : initializer.Invoke(controller);
  185. if (!match) return;
  186. if (application.Independent) return;
  187. if (function != null)
  188. {
  189. // 调用 API,获取返回值。
  190. var result = function.Method.Invoke(controller, ReadParameters(request, function));
  191. if (response.StopReturn) return;
  192. // 检查返回值。
  193. if (result == null || function.Returnable == null) return;
  194. var returnable = function.Returnable;
  195. // 已明确字符串类型,视为提示错误。
  196. if (returnable.Equals(typeof(string)))
  197. {
  198. var error = result as string;
  199. if (!string.IsNullOrEmpty(error)) response.Error(error);
  200. return;
  201. }
  202. // 已明确 Exception 类型,视为提示错误。
  203. if (result is Exception)
  204. {
  205. ApiUtility.Exception(response, result as Exception);
  206. return;
  207. }
  208. // 已明确 Json 类型。
  209. if (result is Json)
  210. {
  211. response.Data = result as Json;
  212. return;
  213. }
  214. // 已明确 Model 类型。
  215. if (result is ApiModel)
  216. {
  217. response.Model = result as ApiModel;
  218. return;
  219. }
  220. // 类型未知,尝试 ToJson 方法。
  221. var tojson = result as IToJson;
  222. if (tojson != null)
  223. {
  224. response.Data = tojson.ToJson();
  225. return;
  226. }
  227. // 类型未知,尝试 Record 模型。
  228. var record = result as IRecord;
  229. if (record != null)
  230. {
  231. response.Data = Json.From(record);
  232. return;
  233. }
  234. // 未知类型,尝试 Json 类型。
  235. var json = result as Json;
  236. if (json != null)
  237. {
  238. response.Data = json;
  239. return;
  240. }
  241. // 未知返回类型,无法明确输出格式,忽略。
  242. }
  243. else
  244. {
  245. // 未匹配到 Function,尝试 Default。
  246. var @default = ApiUtility.GetDefault(controller);
  247. if (@default != null)
  248. {
  249. @default.Invoke(controller);
  250. return;
  251. }
  252. // 没有执行任何 Function,尝试枚举。
  253. if (application.Hidden)
  254. {
  255. response.Error("Invalid Application");
  256. }
  257. else
  258. {
  259. response.Error("Invalid Function");
  260. if (options.AllowEnumerate) response.Data = Enumerate(application.Items, options);
  261. }
  262. }
  263. }
  264. catch (Exception exception)
  265. {
  266. var ex = exception.InnerException;
  267. if (Catcher != null)
  268. {
  269. ApiUtility.Exception(response, ex, false);
  270. try
  271. {
  272. var apiCatch = new ApiCatch(controller, options, ex);
  273. Catcher.Invoke(apiCatch);
  274. }
  275. catch { }
  276. return;
  277. }
  278. ApiUtility.Exception(response, ex);
  279. }
  280. }
  281. static ApiController CreateController(Type type, ApiRequest request, ApiResponse response, ApiOptions options)
  282. {
  283. var controller = (ApiController)Activator.CreateInstance(type);
  284. ApiUtility.SetProperties(controller, request, response, options);
  285. return controller;
  286. }
  287. #region static
  288. internal static ApiRequest GetRequest(ApiProvider provider, ApiOptions options, HttpMethod method, Uri url)
  289. {
  290. // 创建数据对象。
  291. var request = new ApiRequest();
  292. // Http Method。
  293. request.Method = method;
  294. // 基本信息。
  295. var ip = provider.GetClientIP();
  296. var headers = provider.GetHeaders() ?? new StringPairs();
  297. request.Headers = headers;
  298. request.IP = ip;
  299. request.Url = url;
  300. request.Referrer = provider.GetReferrer();
  301. request.Parameters = ApiUtility.Parameters(url.Query);
  302. // Headers。
  303. request.UserAgent = ApiUtility.UserAgent(headers);
  304. request.Cookies = ParseCookies(headers) ?? new CookieCollection();
  305. // 匹配 API。
  306. var application = null as string;
  307. var function = null as string;
  308. var random = null as string;
  309. var ticket = null as string;
  310. var session = null as string;
  311. var page = null as string;
  312. // 解析 POST 请求。
  313. if (request.Method == HttpMethod.POST)
  314. {
  315. var preRead = provider.PreRead();
  316. if (string.IsNullOrEmpty(preRead))
  317. {
  318. var post = null as byte[];
  319. var length = 0L;
  320. var max = options.MaxRequestBody;
  321. if (max == 0) post = new byte[0];
  322. else if (max < 0) post = provider.RequestBody().Read();
  323. else
  324. {
  325. length = provider.GetContentLength();
  326. if (length <= max) post = provider.RequestBody().Read();
  327. }
  328. length = post == null ? 0 : post.Length;
  329. if (length > 1)
  330. {
  331. request.PostData = post;
  332. if (length < 104857600)
  333. {
  334. var text = TextUtility.FromBytes(post);
  335. request.PostText = text;
  336. // 尝试解析 Json,首尾必须是“{}”或“[]”。
  337. var first = post[0];
  338. var last = post[length - 1];
  339. if ((first == 123 && last == 125) || (first == 91 && last == 93))
  340. {
  341. var json = Json.From(text);
  342. if (json != null && json.IsObject)
  343. {
  344. application = json["application"];
  345. function = json["function"];
  346. random = json["random"];
  347. ticket = json["ticket"];
  348. session = json["session"];
  349. page = json["page"];
  350. var data = json.GetProperty("data");
  351. request.PostJson = json;
  352. request.Data = data ?? Json.NewObject();
  353. }
  354. }
  355. // 尝试解析 Form,需要 application/x-www-form-urlencoded
  356. var contentType = headers.GetValue("content-type", true) ?? "";
  357. if (contentType.Contains("urlencoded")) request.Form = ApiUtility.Parameters(text);
  358. }
  359. }
  360. }
  361. }
  362. // 解析 URL 参数。
  363. // URL 参数的优先级应高于 URL 路径,以避免反向代理产生的路径问题。
  364. var urlParameters = ApiUtility.Parameters(request.Url.Query);
  365. if (string.IsNullOrEmpty(application)) application = urlParameters.GetValue("application");
  366. if (string.IsNullOrEmpty(function)) function = urlParameters.GetValue("function");
  367. if (string.IsNullOrEmpty(random)) random = urlParameters.GetValue("random");
  368. if (string.IsNullOrEmpty(ticket)) ticket = urlParameters.GetValue("ticket");
  369. if (string.IsNullOrEmpty(session)) session = urlParameters.GetValue("session");
  370. if (string.IsNullOrEmpty(page)) page = urlParameters.GetValue("page");
  371. // 从 Cookie 中获取 Ticket。
  372. var cookies = request.Cookies;
  373. if (string.IsNullOrEmpty(ticket)) ticket = cookies.GetValue("ticket");
  374. // 最后检查 URL 路径。
  375. var paths = (request.Url.AbsolutePath ?? "").Split('/');
  376. if (string.IsNullOrEmpty(application) && paths.Length >= 2) application = TextUtility.DecodeUrl(paths[1]);
  377. if (string.IsNullOrEmpty(function) && paths.Length >= 3) function = TextUtility.DecodeUrl(paths[2]);
  378. // 修正内容。
  379. application = TextUtility.Trim(application);
  380. function = TextUtility.Trim(function);
  381. random = TextUtility.Trim(random);
  382. ticket = TextUtility.Trim(ticket);
  383. session = TextUtility.Trim(session);
  384. page = TextUtility.Trim(page);
  385. // 设置请求:回传。
  386. request.Application = application;
  387. request.Function = function;
  388. request.Random = random;
  389. // 设置请求:不回传。
  390. request.Ticket = ticket;
  391. request.Session = session;
  392. request.Page = page;
  393. return request;
  394. }
  395. static StringPairs PrepareHeaders(ApiOptions options, ApiResponse response, ApiRequest request = null)
  396. {
  397. var merged = new StringPairs();
  398. if (options != null)
  399. {
  400. // 跨域访问。
  401. if (options.WithAccessControl)
  402. {
  403. merged.Add("Access-Control-Allow-Headers", "Content-Type");
  404. merged.Add("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
  405. merged.Add("Access-Control-Allow-Origin", "*");
  406. var maxage = options.AccessControlMaxAge;
  407. if (maxage > 0) merged.Add("Access-Control-Max-Age", maxage.ToString());
  408. if (request != null && request.Headers != null)
  409. {
  410. var @private = request.Headers.GetValue("Access-Control-Request-Private-Network");
  411. if (NumberUtility.Boolean(@private)) merged.Add("Access-Control-Allow-Private-Network", "true");
  412. }
  413. }
  414. // Content-Type 检查。
  415. if (options.WithContentTypeOptions || options.Default != null)
  416. {
  417. merged.Add("X-Content-Type-Options", "nosniff");
  418. }
  419. // 用于客户端,当前页面使用 HTTPS 时,将资源升级为 HTTPS。
  420. if (options.UpgradeHttps)
  421. {
  422. merged.Add("Content-Security-Policy", "upgrade-insecure-requests");
  423. }
  424. // 包含 API 的处理时间。
  425. if (options.WithDuration && response != null)
  426. {
  427. merged.Add("Duration", response.Duration.ToString() + "ms");
  428. }
  429. }
  430. if (response != null)
  431. {
  432. // Cookies。
  433. var setCookies = SetCookie(response.Cookies);
  434. if (setCookies != null)
  435. {
  436. foreach (var value in setCookies) merged.Add("Set-Cookie", value);
  437. }
  438. // 自定义头。
  439. var headers = response.Headers;
  440. if (headers != null)
  441. {
  442. foreach (var header in headers)
  443. {
  444. var key = TextUtility.Trim(header.Key);
  445. if (string.IsNullOrEmpty(key)) continue;
  446. var value = header.Value;
  447. if (string.IsNullOrEmpty(value)) continue;
  448. merged.Add(key, value);
  449. }
  450. }
  451. }
  452. return merged;
  453. }
  454. internal static string ExportJson(ApiResponse response, ApiOptions options)
  455. {
  456. if (response == null) return "{}";
  457. if (string.IsNullOrEmpty(response.Status)) response.Status = "ok";
  458. var json = Json.NewObject();
  459. // 执行时间。
  460. if (options.WithClock)
  461. {
  462. json.SetProperty("clock", ClockUtility.Lucid(DateTime.Now));
  463. }
  464. // 持续时间。
  465. if (options.WithDuration)
  466. {
  467. json.SetProperty("duration", response.Duration);
  468. }
  469. // 随机值。
  470. var random = response.Random;
  471. if (!string.IsNullOrEmpty(random)) json.SetProperty("random", random);
  472. // 调用。
  473. if (options.WithTarget)
  474. {
  475. json.SetProperty("application", response.Application);
  476. json.SetProperty("function", response.Function);
  477. }
  478. // 状态。
  479. json.SetProperty("status", (TextUtility.IsBlank(response.Status) ? TextUtility.Empty : response.Status.ToLower()));
  480. if (!string.IsNullOrEmpty(response.Message)) json.SetProperty("message", response.Message);
  481. // 用户数据。
  482. if (response.Message == "exception" && !options.WithException) json.SetProperty("data");
  483. else json.SetProperty("data", response.Data);
  484. var indented = response.Indented || options.JsonIndent;
  485. var text = json.ToString(indented);
  486. return text;
  487. }
  488. internal static void Output(ApiProvider provider, ApiOptions options, ApiResponse response, string type, byte[] bytes)
  489. {
  490. var preWrite = provider.PreWrite();
  491. if (!string.IsNullOrEmpty(preWrite)) return;
  492. var headers = PrepareHeaders(options, response);
  493. foreach (var header in headers) provider.SetHeader(header.Key, header.Value);
  494. provider.SetCache(0);
  495. provider.SetContentType(string.IsNullOrEmpty(type) ? "application/octet-stream" : type);
  496. var length = bytes == null ? 0 : bytes.Length;
  497. provider.SetContentLength(length);
  498. if (length > 0) provider.ResponseBody().Write(bytes, 0, bytes.Length);
  499. provider.Sent();
  500. }
  501. internal static void Output(ApiProvider provider, ApiOptions options, ApiResponse response, ApiRequest request, HttpMethod method)
  502. {
  503. var preWrite = provider.PreWrite();
  504. if (!string.IsNullOrEmpty(preWrite)) return;
  505. // 设置头。
  506. var headers = PrepareHeaders(options, response, request);
  507. foreach (var header in headers) provider.SetHeader(header.Key, header.Value);
  508. var model = response.Model;
  509. if (model != null)
  510. {
  511. ApiUtility.Initialize(model, request, response, options, provider);
  512. try { model.Output(); }
  513. catch (Exception ex) { Logger.Internals.Exception(model, ex); }
  514. RuntimeUtility.Dispose(model);
  515. return;
  516. }
  517. var text = ExportJson(response, options);
  518. var bytes = TextUtility.Bytes(text);
  519. provider.SetCache(0);
  520. provider.SetContentType("text/json; charset=utf-8");
  521. provider.SetContentLength(bytes.Length);
  522. var stream = provider.ResponseBody();
  523. if (stream != null && stream.CanWrite) stream.Write(bytes, 0, bytes.Length);
  524. provider.Sent();
  525. }
  526. #endregion
  527. }
  528. }