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.

614 lines
23 KiB

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