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.

607 lines
23 KiB

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