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.

588 lines
21 KiB

  1. #if NETFX || NETCORE
  2. using Apewer;
  3. using Apewer.Network;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Collections.Specialized;
  7. using System.IO;
  8. using System.Reflection;
  9. using System.Web;
  10. using Apewer.Models;
  11. #if NETCORE
  12. using Microsoft.AspNetCore.Http;
  13. #endif
  14. namespace Apewer.Web
  15. {
  16. internal static class ApiInternals
  17. {
  18. #region Fields
  19. private static Dictionary<string, ApiApplication> _entries = null;
  20. private static List<Assembly> _assemblies = null;
  21. #endregion
  22. #region Relection
  23. public static Dictionary<string, ApiApplication> Entries { get { return _entries; } set { _entries = value; } }
  24. public static List<Assembly> Assemblies { get { return _assemblies; } set { _assemblies = value; } }
  25. internal static Dictionary<string, ApiApplication> GetEntries(IEnumerable<Assembly> assemblies, bool sort = true)
  26. {
  27. var entries = new Dictionary<string, ApiApplication>();
  28. var deduplicates = new List<Assembly>();
  29. foreach (var assembly in assemblies)
  30. {
  31. if (assembly == null) continue;
  32. if (deduplicates.Contains(assembly)) continue;
  33. deduplicates.Add(assembly);
  34. }
  35. var aes = new ObjectSet<ApiApplication>();
  36. foreach (var assembly in deduplicates)
  37. {
  38. var types = ClassUtility.GetTypes(assembly);
  39. foreach (var type in types)
  40. {
  41. var application = GetApplication(type);
  42. if (application == null) continue;
  43. aes[application.Name] = application;
  44. var methods = type.GetMethods();
  45. var fes = new ObjectSet<ApiFunction>(true);
  46. foreach (var method in methods)
  47. {
  48. var function = GetFunction(application, method);
  49. if (function == null) continue;
  50. fes[function.Name] = function;
  51. }
  52. var fns = new List<string>();
  53. fns.AddRange(ClassUtility.GetOrigin(fes).Keys);
  54. if (sort) fns.Sort();
  55. foreach (var i in fns) application.Functions.Add(i, fes[i]);
  56. }
  57. }
  58. var ans = new List<string>();
  59. ans.AddRange(ClassUtility.GetOrigin(aes).Keys);
  60. if (sort) ans.Sort();
  61. foreach (var i in ans) entries.Add(i, aes[i]);
  62. return entries;
  63. }
  64. internal static ApiApplication GetApplication(Type type)
  65. {
  66. if (type == null) return null;
  67. // Check Type Properties
  68. if (!type.IsClass) return null;
  69. if (type.IsAbstract) return null;
  70. if (type.IsGenericType) return null;
  71. // Check Type ApiAttributes
  72. var attributes = type.GetCustomAttributes(typeof(ApiAttribute), false);
  73. if (attributes.Length < 1) return null;
  74. // Check Base
  75. if (!ClassUtility.IsInherits(type, typeof(ApiController))) return null;
  76. // Entry
  77. var entry = new ApiApplication();
  78. entry.Type = type;
  79. entry.Assembly = type.Assembly;
  80. // Name
  81. var api = (ApiAttribute)attributes[0];
  82. var name = api.Name;
  83. if (string.IsNullOrEmpty(name)) name = type.Name;
  84. name = name.ToLower();
  85. entry.Name = name;
  86. // Caption
  87. entry.Caption = api.Caption;
  88. if (entry.Caption.Length < 1)
  89. {
  90. var captions = type.GetCustomAttributes(typeof(CaptionAttribute), true);
  91. if (captions.Length > 0)
  92. {
  93. var caption = (CaptionAttribute)captions[0];
  94. entry.Caption = caption.Title;
  95. entry.Description = caption.Description;
  96. }
  97. }
  98. // Visible
  99. entry.Visible = api.Visible;
  100. if (entry.Visible)
  101. {
  102. if (type.ContainsAttribute<HiddenAttribute>(false))
  103. {
  104. entry.Visible = false;
  105. }
  106. }
  107. // Independent
  108. if (type.ContainsAttribute<IndependentAttribute>(false))
  109. {
  110. entry.Independent = true;
  111. }
  112. // Module
  113. entry.Module = TextUtility.Join("-", entry.Assembly.GetName().Name, entry.Assembly.GetName().Version.ToString());
  114. return entry;
  115. }
  116. internal static ApiFunction GetFunction(ApiApplication application, MethodInfo method)
  117. {
  118. if (method == null) return null;
  119. // 滤除构造函数。
  120. if (method.IsConstructor) return null;
  121. // 滤除继承的方法。
  122. if (method.DeclaringType.Equals(typeof(object))) return null;
  123. if (method.DeclaringType.Equals(typeof(ApiController))) return null;
  124. // 滤除带参数的方法。
  125. var parameters = method.GetParameters();
  126. if (parameters.Length > 0) return null;
  127. // Entry
  128. var entry = new ApiFunction();
  129. entry.Type = application.Type;
  130. entry.Assembly = application.Type.Assembly;
  131. entry.Method = method;
  132. entry.Name = method.Name.ToLower();
  133. // Caption
  134. var captions = method.GetCustomAttributes(typeof(CaptionAttribute), true);
  135. if (captions.Length > 0)
  136. {
  137. var caption = (CaptionAttribute)captions[0];
  138. entry.Caption = caption.Title;
  139. entry.Description = caption.Description;
  140. }
  141. // Visible
  142. entry.Visible = true;
  143. if (application.Visible)
  144. {
  145. var hidden = method.GetCustomAttributes(typeof(HiddenAttribute), false);
  146. if (hidden.Length > 0) entry.Visible = false;
  147. }
  148. else
  149. {
  150. entry.Visible = false;
  151. }
  152. // Returnable
  153. if (method.ReturnType.Equals(typeof(string)))
  154. {
  155. entry.Returnable = true;
  156. }
  157. return entry;
  158. }
  159. #endregion
  160. #region ApiRequest
  161. internal static StringPairs ParseUrlParameters
  162. #if NETFX
  163. (System.Web.HttpRequest request) => WebUtility.ParseParameters(request.Url.Query);
  164. #else
  165. (Microsoft.AspNetCore.Http.HttpRequest request)
  166. {
  167. var list = new StringPairs();
  168. foreach (var key in request.Query.Keys)
  169. {
  170. if (request != null)
  171. {
  172. list.Add(new KeyValuePair<string, string>(key, request.Query[key]));
  173. }
  174. }
  175. return list;
  176. }
  177. #endif
  178. #if NETFX
  179. internal static ApiRequest GetRequest(System.Web.HttpRequest request)
  180. #else
  181. internal static ApiRequest GetRequest(Microsoft.AspNetCore.Http.HttpRequest request)
  182. #endif
  183. {
  184. if (request == null) return null;
  185. if (string.IsNullOrEmpty(request.Path)) return null;
  186. var apiRequest = new ApiRequest();
  187. apiRequest.IP = WebUtility.GetClientIP(request, true);
  188. apiRequest.Url = WebUtility.GetUrl(request);
  189. apiRequest.Parameters = ParseUrlParameters(request);
  190. apiRequest.UserAgent = WebUtility.GetUserAgent(request);
  191. #if NETFX
  192. apiRequest.Referrer = request.UrlReferrer;
  193. #endif
  194. // 获取 Http Method。
  195. apiRequest.Method = WebUtility.GetMethod(request);
  196. // 准备变量。
  197. var application = null as string;
  198. var function = null as string;
  199. var random = null as string;
  200. var ticket = null as string;
  201. var session = null as string;
  202. var page = null as string;
  203. // 头。
  204. foreach (var kvp in WebUtility.GetHeaders(request))
  205. {
  206. apiRequest.Headers.Add(new KeyValuePair<string, string>(kvp.Key, kvp.Value));
  207. switch (TextUtility.ToLower(kvp.Key))
  208. {
  209. case "user-agent":
  210. apiRequest.UserAgent = kvp.Value;
  211. break;
  212. }
  213. }
  214. // 解析 POST 请求。
  215. if (apiRequest.Method == HttpMethod.POST)
  216. {
  217. #if NETFX
  218. var post = BinaryUtility.Read(request.InputStream);
  219. #else
  220. var post = BinaryUtility.Read(request.Body);
  221. #endif
  222. var text = TextUtility.FromBinary(post);
  223. var json = Json.Parse(text) ?? Json.NewObject();
  224. application = json["application"];
  225. function = json["function"];
  226. random = json["random"];
  227. ticket = json["ticket"];
  228. session = json["session"];
  229. page = json["page"];
  230. var data = json.GetProperty("data");
  231. apiRequest.PostData = post;
  232. apiRequest.PostText = text;
  233. apiRequest.PostJson = json;
  234. apiRequest.Data = data ?? Json.NewObject();
  235. }
  236. // 解析 URL 参数。
  237. // URL 参数的优先级应高于 URL 路径,以避免反向代理产生的路径问题。
  238. if (string.IsNullOrEmpty(application)) application = WebUtility.GetParameter(apiRequest.Parameters, "application");
  239. if (string.IsNullOrEmpty(function)) function = WebUtility.GetParameter(apiRequest.Parameters, "function");
  240. if (string.IsNullOrEmpty(random)) random = WebUtility.GetParameter(apiRequest.Parameters, "random");
  241. if (string.IsNullOrEmpty(ticket)) ticket = WebUtility.GetParameter(apiRequest.Parameters, "ticket");
  242. if (string.IsNullOrEmpty(session)) session = WebUtility.GetParameter(apiRequest.Parameters, "session");
  243. if (string.IsNullOrEmpty(page)) page = WebUtility.GetParameter(apiRequest.Parameters, "page");
  244. // 最后检查 URL 路径。
  245. var paths = request.Path.ToString().Split('/');
  246. if (string.IsNullOrEmpty(application) && paths.Length >= 2) application = TextUtility.DecodeUrl(paths[1]);
  247. if (string.IsNullOrEmpty(function) && paths.Length >= 3) function = TextUtility.DecodeUrl(paths[2]);
  248. // 修正内容。
  249. application = application.SafeLower().SafeTrim();
  250. function = function.SafeLower().SafeTrim();
  251. random = random.SafeLower().SafeTrim();
  252. ticket = ticket.SafeLower().SafeTrim();
  253. session = session.SafeLower().SafeTrim();
  254. page = page.SafeTrim();
  255. // 设置请求:回传。
  256. apiRequest.Application = application;
  257. apiRequest.Function = function;
  258. apiRequest.Random = random;
  259. // 设置请求:不回传。
  260. apiRequest.Ticket = ticket;
  261. apiRequest.Session = session;
  262. apiRequest.Page = page;
  263. // 返回结果。
  264. return apiRequest;
  265. }
  266. #endregion
  267. #region ApiResponse
  268. /// <summary>设置 status 为 error,并设置 message 的内容。</summary>
  269. internal static void RespondError(ApiResponse response, string message = "未知错误。")
  270. {
  271. if (response == null) return;
  272. response.Type = ApiFormat.Json;
  273. response.Status = "error";
  274. response.Message = message ?? TextUtility.EmptyString;
  275. }
  276. /// <summary>设置 status 为 error,并设置 message 的内容。</summary>
  277. internal static void RespondError(ApiResponse response, Exception exception)
  278. {
  279. if (response == null) return;
  280. response.Exception = exception;
  281. response.Type = ApiFormat.Json;
  282. response.Status = "error";
  283. try
  284. {
  285. response.Message = exception == null ? "无效异常。" : exception.Message;
  286. response.Data.Reset(Json.NewObject());
  287. response.Data["message"] = exception.Message;
  288. response.Data["helplink"] = exception.HelpLink;
  289. response.Data["source"] = exception.Source;
  290. response.Data["stacktrace"] = Json.Parse(exception.StackTrace.Split('\n'), true);
  291. }
  292. catch { }
  293. }
  294. /// <summary>输出 UTF-8 文本。</summary>
  295. internal static void RespondText(ApiResponse response, string content, string type = "text/plain")
  296. {
  297. if (response == null) return;
  298. response.Type = ApiFormat.Text;
  299. response.TextString = content;
  300. response.TextType = type ?? "text/plain";
  301. }
  302. /// <summary>输出字节数组。</summary>
  303. internal static void RespondBinary(ApiResponse response, byte[] content, string type = "application/octet-stream")
  304. {
  305. if (response == null) return;
  306. response.Type = ApiFormat.Binary;
  307. response.BinaryStream = null;
  308. response.BinaryBytes = content;
  309. response.BinaryType = type ?? "application/octet-stream";
  310. }
  311. /// <summary>输出二进制。</summary>
  312. internal static void RespondBinary(ApiResponse response, Stream content, string type = "application/octet-stream")
  313. {
  314. if (response == null) return;
  315. response.Type = ApiFormat.Binary;
  316. response.BinaryStream = content;
  317. response.BinaryBytes = null;
  318. response.BinaryType = type ?? "application/octet-stream";
  319. }
  320. /// <summary>输出文件。</summary>
  321. internal static void RespondFile(ApiResponse response, Stream stream, string name, string type = "application/octet-stream")
  322. {
  323. if (response == null) return;
  324. response.Type = ApiFormat.File;
  325. response.FileStream = stream;
  326. response.FileName = name;
  327. response.FileType = type ?? "application/octet-stream";
  328. }
  329. /// <summary>重定向。</summary>
  330. public static void RespondRedirect(ApiResponse response, string url)
  331. {
  332. if (response == null) return;
  333. response.Type = ApiFormat.Redirect;
  334. response.RedirectUrl = url;
  335. }
  336. internal static string ExportJson(ApiResponse response, bool indented = true, bool exception = false)
  337. {
  338. if (response == null) return "{}";
  339. var json = Json.NewObject();
  340. json.SetProperty("beginning", response.Beginning ?? TextUtility.EmptyString);
  341. json.SetProperty("ending", response.Ending ?? TextUtility.EmptyString);
  342. json.SetProperty("random", response.Random);
  343. json.SetProperty("application", response.Application);
  344. json.SetProperty("function", response.Function);
  345. json.SetProperty("status", (TextUtility.IsBlank(response.Status) ? TextUtility.EmptyString : response.Status.ToLower()));
  346. json.SetProperty("message", response.Message);
  347. if (exception)
  348. {
  349. if (response.Exception == null) json.SetProperty("exception");
  350. else
  351. {
  352. var exmessage = null as string;
  353. var exstacktrace = null as string;
  354. var exsource = null as string;
  355. var exhelplink = null as string;
  356. try
  357. {
  358. exmessage = response.Exception.Message;
  359. exstacktrace = response.Exception.StackTrace;
  360. exsource = response.Exception.Source;
  361. exhelplink = response.Exception.HelpLink;
  362. }
  363. catch { }
  364. var exjson = Json.NewObject();
  365. exjson.SetProperty("type", response.Exception.GetType().FullName);
  366. exjson.SetProperty("message", exmessage);
  367. exjson.SetProperty("stack", Json.Parse((exstacktrace ?? "").Replace("\r", "").Split('\n'), false));
  368. exjson.SetProperty("source", exsource);
  369. exjson.SetProperty("helplink", exhelplink);
  370. if (response.Exception is System.Net.WebException)
  371. {
  372. var webex = response.Exception as System.Net.WebException;
  373. {
  374. var array = Json.NewArray();
  375. foreach (var k in webex.Data.Keys)
  376. {
  377. var item = Json.NewObject();
  378. var v = webex.Data[k];
  379. item.SetProperty(k.ToString(), Json.Parse(v.ToString()));
  380. }
  381. exjson.SetProperty("data", array);
  382. }
  383. exjson.SetProperty("", Json.Parse(webex.Response, true));
  384. }
  385. json.SetProperty("exception", exjson);
  386. }
  387. }
  388. json.SetProperty("data", response.Data);
  389. var text = json.ToString(indented);
  390. return text;
  391. }
  392. #endregion
  393. #region HttpResponse
  394. public static void AddHeader
  395. #if NETFX
  396. (System.Web.HttpResponse response, string name, string value)
  397. {
  398. if (response == null || string.IsNullOrEmpty(name) || string.IsNullOrEmpty(value)) return;
  399. #if NET20
  400. try { response.AddHeader(name, value); } catch { }
  401. #else
  402. try { response.Headers.Add(name, value); } catch { }
  403. #endif
  404. }
  405. #else
  406. (Microsoft.AspNetCore.Http.HttpResponse response, string name, string value)
  407. {
  408. if (response == null || string.IsNullOrEmpty(name) || string.IsNullOrEmpty(value)) return;
  409. try { response.Headers.Add(name, value); } catch { }
  410. }
  411. #endif
  412. public static void AddHeaders
  413. #if NETFX
  414. (System.Web.HttpResponse response, NameValueCollection collection)
  415. {
  416. if (response == null || collection == null) return;
  417. foreach (var key in collection.AllKeys) AddHeader(response, key, collection[key]);
  418. }
  419. #else
  420. (Microsoft.AspNetCore.Http.HttpResponse response, NameValueCollection collection)
  421. {
  422. if (response == null) return;
  423. if (collection == null) return;
  424. foreach (var key in collection.AllKeys) AddHeader(response, key, collection[key]);
  425. }
  426. #endif
  427. public static void AddHeaders
  428. #if NETFX
  429. (System.Web.HttpResponse response, StringPairs headers)
  430. {
  431. if (response == null || headers == null) return;
  432. foreach (var key in headers.GetAllKeys())
  433. {
  434. var values = headers.GetValues(key);
  435. foreach (var value in values) AddHeader(response, key, value);
  436. }
  437. }
  438. #else
  439. (Microsoft.AspNetCore.Http.HttpResponse response, StringPairs headers)
  440. {
  441. if (response == null || headers == null) return;
  442. foreach (var key in headers.GetAllKeys())
  443. {
  444. var values = headers.GetValues(key);
  445. foreach (var value in values) AddHeader(response, key, value);
  446. }
  447. }
  448. #endif
  449. public static void SetTextPlain
  450. #if NETFX
  451. (System.Web.HttpResponse response, string value = null)
  452. {
  453. if (response == null) return;
  454. if (string.IsNullOrEmpty(value))
  455. {
  456. response.ContentType = "text/plain";
  457. response.Charset = "utf-8";
  458. }
  459. else
  460. {
  461. response.ContentType = value;
  462. }
  463. }
  464. #else
  465. (Microsoft.AspNetCore.Http.HttpResponse response, string value = null)
  466. {
  467. if (response == null) return;
  468. response.ContentType = string.IsNullOrEmpty(value) ? "text/plain; charset=utf-8" : value;
  469. }
  470. #endif
  471. public static void SetContentLength
  472. #if NETFX
  473. (System.Web.HttpResponse response, long value)
  474. {
  475. if (response == null) return;
  476. if (value < 0L) return;
  477. response.AddHeader("Content-Length", value.ToString());
  478. }
  479. #else
  480. (Microsoft.AspNetCore.Http.HttpResponse response, long value)
  481. {
  482. if (response == null) return;
  483. if (value < 0L) return;
  484. response.ContentLength = value;
  485. }
  486. #endif
  487. public static Stream GetStream
  488. #if NETFX
  489. (System.Web.HttpResponse response)
  490. {
  491. if (response == null) return null;
  492. return response.OutputStream;
  493. }
  494. #else
  495. (Microsoft.AspNetCore.Http.HttpResponse response)
  496. {
  497. if (response == null) return null;
  498. return response.Body;
  499. }
  500. #endif
  501. #endregion
  502. }
  503. }
  504. #endif