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.

512 lines
18 KiB

  1. #if NETFX || NETCORE
  2. using Apewer;
  3. using Apewer.Network;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Reflection;
  7. #if NETFX
  8. using System.Web;
  9. #endif
  10. #if NETCORE
  11. using Microsoft.AspNetCore.Http;
  12. using Microsoft.AspNetCore.Hosting;
  13. using Microsoft.Extensions.Hosting;
  14. using System.IO;
  15. using System.Threading.Tasks;
  16. #endif
  17. namespace Apewer.Web
  18. {
  19. /// <summary>API 调用器。</summary>
  20. public sealed class ApiInvoker
  21. {
  22. private DateTime Beginning { get; set; }
  23. private DateTime Ending { get; set; }
  24. private ApiRequest ApiRequest { get; set; }
  25. private ApiResponse ApiResponse { get; set; }
  26. private List<Assembly> Assemblies { get; set; }
  27. private HttpContext Context { get; set; }
  28. private Dictionary<string, ApiApplication> Entries { get; set; }
  29. private bool CheckDependents()
  30. {
  31. if (Context == null)
  32. {
  33. #if NETFX
  34. Context = HttpContext.Current;
  35. #else
  36. return false;
  37. #endif
  38. }
  39. // Fix Entries & Assemblies
  40. if (Entries == null)
  41. {
  42. if (ApiInternals.Entries == null)
  43. {
  44. if (Assemblies == null) Assemblies = new List<Assembly>();
  45. ApiInternals.Entries = ApiInternals.GetEntries(Assemblies, true);
  46. }
  47. Entries = ApiInternals.Entries;
  48. }
  49. return true;
  50. }
  51. private bool CheckMethod()
  52. {
  53. var method = WebUtility.GetMethod(Context.Request);
  54. switch (method)
  55. {
  56. case HttpMethod.GET:
  57. case HttpMethod.POST:
  58. return true;
  59. case HttpMethod.OPTIONS:
  60. default:
  61. return false; // 返回空内容;
  62. }
  63. }
  64. private bool CheckFavIcon()
  65. {
  66. if (ApiOptions.AllowFavIcon) return true;
  67. const string path = "/favicon.ico";
  68. #if NETFX
  69. if (Context.Request.Url.AbsolutePath.ToLower().StartsWith(path)) return false;
  70. #else
  71. if (TextUtility.ToLower(Context.Request.Path).StartsWith(path)) return false;
  72. #endif
  73. return true;
  74. }
  75. private bool CheckRobot()
  76. {
  77. if (ApiOptions.AllowRobot) return true;
  78. const string path = "/robot.txt";
  79. const string text = "User-agent: *\nDisallow: / \n";
  80. var bytes = TextUtility.ToBinary(text);
  81. #if NETFX
  82. if (Context.Request.Url.AbsolutePath.ToLower().StartsWith(path))
  83. {
  84. Context.Response.ContentType = "text/plain";
  85. BinaryUtility.Write(Context.Response.OutputStream, bytes);
  86. return false;
  87. }
  88. #else
  89. if (TextUtility.ToLower(Context.Request.Path).StartsWith(path))
  90. {
  91. Context.Response.ContentType = "text/plain";
  92. Context.Response.ContentLength = bytes.Length;
  93. BinaryUtility.Write(Context.Response.Body, bytes);
  94. return false;
  95. }
  96. #endif
  97. return true;
  98. }
  99. private string Run()
  100. {
  101. // 检查依赖的属性,若不通过,则无法执行。
  102. if (!CheckDependents()) return "缺少必要属性。";
  103. // 过滤请求。
  104. if (!CheckMethod()) return "已阻止方法。";
  105. if (!CheckFavIcon()) return "已阻止请求 favicon.ico 路径。";
  106. if (!CheckRobot()) return "已阻止请求 robot.txt 路径。";
  107. // 准备变量。
  108. ApiRequest = ApiInternals.GetRequest(Context.Request);
  109. ApiRequest.Context = Context;
  110. ApiResponse = new ApiResponse();
  111. ApiResponse.Context = Context;
  112. // 准备向 Controller 传递的属性。
  113. var an = ApiRequest.Application;
  114. var ae = null as ApiApplication;
  115. var fn = ApiRequest.Function;
  116. var fe = null as ApiFunction;
  117. var r = ApiRequest.Random;
  118. var t = ApiRequest.Ticket;
  119. // 调用。
  120. if (Entries.ContainsKey(an))
  121. {
  122. ae = Entries[an];
  123. if (ae.Functions.ContainsKey(fn))
  124. {
  125. fe = ae.Functions[fn];
  126. Invoke(ae, fe, ApiRequest, ApiResponse);
  127. }
  128. else
  129. {
  130. if (ae.Independent)
  131. {
  132. Invoke(ae, null, ApiRequest, ApiResponse);
  133. }
  134. else
  135. {
  136. ApiResponse.Error("未指定有效的 Function 名称。");
  137. if (fn.IsEmpty() && ApiOptions.AllowNumerate) Enumerate(ae.Functions);
  138. }
  139. }
  140. }
  141. else
  142. {
  143. ApiResponse.Error("未指定有效的 Application 名称。");
  144. if (an.IsEmpty() && ApiOptions.AllowNumerate) Enumerate(Entries);
  145. }
  146. // 记录结束时间。
  147. Ending = DateTime.Now;
  148. // 调整响应。
  149. ApiResponse.Beginning = Beginning.ToLucid();
  150. ApiResponse.Ending = Ending.ToLucid();
  151. ApiResponse.Application = an;
  152. ApiResponse.Function = fn;
  153. ApiResponse.Random = r;
  154. // 向客户端输出。
  155. return Output();
  156. }
  157. private void Enumerate(Dictionary<string, ApiApplication> applications)
  158. {
  159. var count = 0;
  160. var list = Json.NewArray();
  161. if (applications != null)
  162. {
  163. foreach (var i in applications)
  164. {
  165. if (!i.Value.Visible) continue;
  166. var item = Json.NewObject();
  167. item["name"] = i.Value.Name;
  168. item["caption"] = i.Value.Caption;
  169. if (ApiOptions.ShowModule) item["module"] = i.Value.Module;
  170. if (ApiOptions.ShowClass) item["class"] = i.Value.Type.FullName;
  171. list.AddItem(item);
  172. count = count + 1;
  173. }
  174. }
  175. ApiResponse.Data.Reset(Json.NewObject());
  176. ApiResponse.Data.SetProperty("count", count);
  177. ApiResponse.Data.SetProperty("list", list);
  178. }
  179. private void Enumerate(Dictionary<string, ApiFunction> functions)
  180. {
  181. var count = 0;
  182. var list = Json.NewArray();
  183. if (functions != null)
  184. {
  185. foreach (var i in functions)
  186. {
  187. if (!i.Value.Visible) continue;
  188. var item = Json.NewObject();
  189. item["name"] = i.Value.Name;
  190. item["caption"] = i.Value.Caption;
  191. item["description"] = i.Value.Description;
  192. list.AddItem(item);
  193. count = count + 1;
  194. }
  195. }
  196. ApiResponse.Data.Reset(Json.NewObject());
  197. ApiResponse.Data.SetProperty("count", count);
  198. ApiResponse.Data.SetProperty("list", list);
  199. }
  200. private void Invoke(ApiApplication application, ApiFunction function, ApiRequest request, ApiResponse response)
  201. {
  202. if (request == null || response == null) return;
  203. // 创建应用程序实例。
  204. var target = null as ApiController;
  205. try
  206. {
  207. if (application == null)
  208. {
  209. response.Error("指定的 Application 无效,无法创建实例。");
  210. return;
  211. }
  212. else
  213. {
  214. //var instance = assembly.CreateInstance(argApplication.FullName, false);
  215. //var handler = Activator.CreateInstance(null, argApplication.FullName);
  216. //target = (Application)handler.Unwrap();
  217. target = (ApiController)Activator.CreateInstance(application.Type);
  218. }
  219. }
  220. catch (Exception exception)
  221. {
  222. response.Error(exception);
  223. return;
  224. }
  225. // 调用功能。
  226. try
  227. {
  228. target.Request = request;
  229. target.Response = response;
  230. target.Context = Context;
  231. if (target.AfterInitialized != null) target.AfterInitialized.Invoke();
  232. var allowed = target.AllowFunction;
  233. if (application.Independent) allowed = false;
  234. if (allowed)
  235. {
  236. if (function == null)
  237. {
  238. response.Error("指定的 Application 不包含有效 Function。");
  239. }
  240. else
  241. {
  242. var result = function.Method.Invoke(target, null);
  243. target.Dispose();
  244. if (response.Status.IsBlank()) response.Status = "ok";
  245. if (function.Returnable && result != null)
  246. {
  247. if (result is string)
  248. {
  249. var error = (string)result;
  250. if (!string.IsNullOrEmpty(error)) response.Error(error);
  251. }
  252. }
  253. }
  254. }
  255. }
  256. catch (Exception exception)
  257. {
  258. response.Error(exception.InnerException);
  259. }
  260. try { target.Dispose(); } catch { }
  261. }
  262. private string Output()
  263. {
  264. if (Context == null) return "Invoker 的 Context 无效。";
  265. if (ApiResponse == null) return "Invoker 的 ApiResponse 无效。";
  266. try
  267. {
  268. var response = Context.Response;
  269. var body = ApiInternals.GetStream(response);
  270. ApiInternals.AddHeaders(response, ApiResponse.Headers);
  271. switch (ApiResponse.Type)
  272. {
  273. case ApiFormat.Json:
  274. {
  275. var text = ApiInternals.ExportJson(ApiResponse, ApiOptions.JsonIndent, ApiOptions.AllowException);
  276. var data = TextUtility.ToBinary(text);
  277. ApiInternals.SetTextPlain(response);
  278. ApiInternals.SetContentLength(response, data.LongLength);
  279. body.Write(data);
  280. }
  281. break;
  282. case ApiFormat.Text:
  283. {
  284. var data = TextUtility.ToBinary(ApiResponse.TextString);
  285. var type = ApiResponse.TextType;
  286. ApiInternals.SetTextPlain(response, type);
  287. ApiInternals.SetContentLength(response, data.LongLength);
  288. body.Write(data);
  289. }
  290. break;
  291. case ApiFormat.Binary:
  292. {
  293. Context.Response.ContentType = ApiResponse.BinaryType ?? "application/octet-stream";
  294. if (ApiResponse.BinaryBytes != null)
  295. {
  296. var data = ApiResponse.BinaryBytes;
  297. var length = data.LongLength;
  298. ApiInternals.SetContentLength(response, length);
  299. if (length > 0L) body.Write(data);
  300. }
  301. else if (ApiResponse.BinaryStream != null && ApiResponse.BinaryStream.CanRead)
  302. {
  303. var source = ApiResponse.BinaryStream;
  304. try
  305. {
  306. var length = source.Length - source.Position;
  307. ApiInternals.SetContentLength(response, length);
  308. if (length > 0) body.Write(source);
  309. }
  310. catch { }
  311. KernelUtility.Dispose(source);
  312. }
  313. }
  314. break;
  315. case ApiFormat.File:
  316. {
  317. var type = ApiResponse.FileType ?? "application/octet-stream";
  318. var name = TextUtility.EncodeUrl(ApiResponse.FileName);
  319. var disposition = TextUtility.Merge("attachment; filename=", name);
  320. response.ContentType = type;
  321. ApiInternals.AddHeader(response, "Content-Disposition", disposition);
  322. var source = ApiResponse.FileStream;
  323. try
  324. {
  325. if (source != null && source.CanRead)
  326. {
  327. var length = source.Length - source.Position;
  328. ApiInternals.SetContentLength(response, length);
  329. if (length > 0) body.Write(source);
  330. }
  331. }
  332. catch { }
  333. KernelUtility.Dispose(source);
  334. }
  335. break;
  336. case ApiFormat.Redirect:
  337. {
  338. WebUtility.RedirectResponse(response, ApiResponse.RedirectUrl);
  339. }
  340. break;
  341. }
  342. // Context.Response.Flush();
  343. // Context.Response.End();
  344. }
  345. catch (Exception ex) { return ex.Message; }
  346. return null;
  347. }
  348. private ApiInvoker()
  349. {
  350. Beginning = DateTime.Now;
  351. }
  352. /// <summary>获取 ApiServer 派生类中定义的 WebAPI 入口。</summary>
  353. public static Dictionary<string, ApiApplication> GetEntries(IEnumerable<Assembly> assemblies, bool sort = false)
  354. {
  355. return ApiInternals.GetEntries(assemblies, sort);
  356. }
  357. /// <summary>解析请求,并根据执行。</summary>
  358. public static void Execute(Dictionary<string, ApiApplication> entries)
  359. {
  360. var invoker = new ApiInvoker();
  361. invoker.Entries = entries;
  362. invoker.Run();
  363. }
  364. #if NETFX
  365. /// <summary>解析请求,获取指定程序集中定义的入口,并执行。指定程序集为 NULL 值时,将自动从应用程序域获取所有 WebAPI 入口。</summary>
  366. public static void Execute(Assembly assembly = null)
  367. {
  368. if (assembly == null)
  369. {
  370. if (ApiInternals.Assemblies == null || ApiInternals.Assemblies.Count < 1)
  371. {
  372. var assemblies = new List<Assembly>();
  373. assemblies.AddRange(AppDomain.CurrentDomain.GetAssemblies());
  374. var calling = Assembly.GetCallingAssembly();
  375. var contains = false;
  376. foreach (var a in assemblies)
  377. {
  378. if (a.FullName == calling.FullName)
  379. {
  380. contains = true;
  381. break;
  382. }
  383. }
  384. if (!contains) assemblies.Add(calling);
  385. ApiInternals.Assemblies = assemblies;
  386. }
  387. if (ApiInternals.Entries == null || ApiInternals.Entries.Count < 1)
  388. {
  389. var entries = ApiInternals.GetEntries(ApiInternals.Assemblies, true);
  390. ApiInternals.Entries = entries;
  391. }
  392. Execute(ApiInternals.Entries);
  393. }
  394. else
  395. {
  396. var assemblies = new List<Assembly>();
  397. assemblies.Add(assembly);
  398. var invoker = new ApiInvoker();
  399. invoker.Entries = ApiInternals.GetEntries(assemblies, true);
  400. invoker.Run();
  401. }
  402. }
  403. #endif
  404. #if NETCORE
  405. private static Dictionary<string, ApiApplication> KestrelEntries = null;
  406. /// <summary>设置 Kestrel 的 API 入口。</summary>
  407. /// <exception cref="ArgumentNullException"></exception>
  408. public static void SetKestrelEntries(IEnumerable<Assembly> assemblies, bool sort = false)
  409. {
  410. if (assemblies == null) throw new ArgumentNullException(nameof(assemblies));
  411. KestrelEntries = ApiInternals.GetEntries(assemblies, sort);
  412. }
  413. // internal static async Task Execute(HttpContext context)
  414. // {
  415. // Invoke(context, ApiInternals.Entries);
  416. // await context.Response.StartAsync();
  417. // }
  418. internal static string Execute(HttpContext context)
  419. {
  420. var invoker = new ApiInvoker();
  421. invoker.Context = context;
  422. invoker.Entries = KestrelEntries;
  423. return invoker.Run();
  424. }
  425. /// <summary>运行 Kestrel 服务器,可指定端口和最大请求 Body 长度。默认同步运行,阻塞当前线程。</summary>
  426. /// <remarks>注意:启动前必须设置 Kestrel Entries。</remarks>
  427. /// <exception cref="MissingMemberException"></exception>
  428. public static IHost RunKestrel(int port = 80, int request = 1073741824, bool async = false)
  429. {
  430. if (KestrelEntries == null) throw new MissingMemberException();
  431. var builder1 = Host.CreateDefaultBuilder();
  432. var builder2 = builder1.ConfigureWebHostDefaults((builder3) =>
  433. {
  434. var builder4 = builder3.ConfigureKestrel((options) =>
  435. {
  436. options.ListenAnyIP(port);
  437. options.AllowSynchronousIO = true;
  438. if (request > 0) options.Limits.MaxRequestBodySize = request;
  439. });
  440. var builder5 = builder4.UseStartup<ApiStartup>();
  441. });
  442. var built = builder2.Build();
  443. if (async) built.RunAsync();
  444. else built.Run();
  445. return built;
  446. }
  447. #endif
  448. }
  449. }
  450. #endif