#if NETFX || NETCORE using Apewer; using Apewer.Network; using System; using System.Collections.Generic; using System.Reflection; #if NETFX using System.Web; #endif #if NETCORE using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; using System.IO; using System.Threading.Tasks; #endif namespace Apewer.Web { /// API 调用器。 public sealed class ApiInvoker { private DateTime Beginning { get; set; } private DateTime Ending { get; set; } private ApiRequest ApiRequest { get; set; } private ApiResponse ApiResponse { get; set; } private List Assemblies { get; set; } private HttpContext Context { get; set; } private Dictionary Entries { get; set; } private bool CheckDependents() { if (Context == null) { #if NETFX Context = HttpContext.Current; #else return false; #endif } // Fix Entries & Assemblies if (Entries == null) { if (ApiInternals.Entries == null) { if (Assemblies == null) Assemblies = new List(); ApiInternals.Entries = ApiInternals.GetEntries(Assemblies, true); } Entries = ApiInternals.Entries; } return true; } private bool CheckMethod() { var method = WebUtility.GetMethod(Context.Request); switch (method) { case HttpMethod.GET: case HttpMethod.POST: return true; case HttpMethod.OPTIONS: default: return false; // 返回空内容; } } private bool CheckFavIcon() { if (ApiOptions.AllowFavIcon) return true; const string path = "/favicon.ico"; #if NETFX if (Context.Request.Url.AbsolutePath.ToLower().StartsWith(path)) return false; #else if (TextUtility.ToLower(Context.Request.Path).StartsWith(path)) return false; #endif return true; } private bool CheckRobot() { if (ApiOptions.AllowRobot) return true; const string path = "/robot.txt"; const string text = "User-agent: *\nDisallow: / \n"; var bytes = TextUtility.ToBinary(text); #if NETFX if (Context.Request.Url.AbsolutePath.ToLower().StartsWith(path)) { Context.Response.ContentType = "text/plain"; BinaryUtility.Write(Context.Response.OutputStream, bytes); return false; } #else if (TextUtility.ToLower(Context.Request.Path).StartsWith(path)) { Context.Response.ContentType = "text/plain"; Context.Response.ContentLength = bytes.Length; BinaryUtility.Write(Context.Response.Body, bytes); return false; } #endif return true; } private string Run() { // 检查依赖的属性,若不通过,则无法执行。 if (!CheckDependents()) return "缺少必要属性。"; // 过滤请求。 if (!CheckMethod()) return "已阻止方法。"; if (!CheckFavIcon()) return "已阻止请求 favicon.ico 路径。"; if (!CheckRobot()) return "已阻止请求 robot.txt 路径。"; // 准备变量。 ApiRequest = ApiInternals.GetRequest(Context.Request); ApiRequest.Context = Context; ApiResponse = new ApiResponse(); ApiResponse.Context = Context; // 准备向 Controller 传递的属性。 var an = ApiRequest.Application; var ae = null as ApiApplication; var fn = ApiRequest.Function; var fe = null as ApiFunction; var r = ApiRequest.Random; var t = ApiRequest.Ticket; // 调用。 if (Entries.ContainsKey(an)) { ae = Entries[an]; if (ae.Functions.ContainsKey(fn)) { fe = ae.Functions[fn]; Invoke(ae, fe, ApiRequest, ApiResponse); } else { if (ae.Independent) { Invoke(ae, null, ApiRequest, ApiResponse); } else { ApiResponse.Error("未指定有效的 Function 名称。"); if (fn.IsEmpty() && ApiOptions.AllowNumerate) Enumerate(ae.Functions); } } } else { ApiResponse.Error("未指定有效的 Application 名称。"); if (an.IsEmpty() && ApiOptions.AllowNumerate) Enumerate(Entries); } // 记录结束时间。 Ending = DateTime.Now; // 调整响应。 ApiResponse.Beginning = Beginning.ToLucid(); ApiResponse.Ending = Ending.ToLucid(); ApiResponse.Application = an; ApiResponse.Function = fn; ApiResponse.Random = r; // 向客户端输出。 return Output(); } private void Enumerate(Dictionary applications) { var count = 0; var list = Json.NewArray(); if (applications != null) { foreach (var i in applications) { if (!i.Value.Visible) continue; var item = Json.NewObject(); item["name"] = i.Value.Name; item["caption"] = i.Value.Caption; if (ApiOptions.ShowModule) item["module"] = i.Value.Module; if (ApiOptions.ShowClass) item["class"] = i.Value.Type.FullName; list.AddItem(item); count = count + 1; } } ApiResponse.Data.Reset(Json.NewObject()); ApiResponse.Data.SetProperty("count", count); ApiResponse.Data.SetProperty("list", list); } private void Enumerate(Dictionary functions) { var count = 0; var list = Json.NewArray(); if (functions != null) { foreach (var i in functions) { if (!i.Value.Visible) continue; var item = Json.NewObject(); item["name"] = i.Value.Name; item["caption"] = i.Value.Caption; item["description"] = i.Value.Description; list.AddItem(item); count = count + 1; } } ApiResponse.Data.Reset(Json.NewObject()); ApiResponse.Data.SetProperty("count", count); ApiResponse.Data.SetProperty("list", list); } private void Invoke(ApiApplication application, ApiFunction function, ApiRequest request, ApiResponse response) { if (request == null || response == null) return; // 创建应用程序实例。 var target = null as ApiController; try { if (application == null) { response.Error("指定的 Application 无效,无法创建实例。"); return; } else { //var instance = assembly.CreateInstance(argApplication.FullName, false); //var handler = Activator.CreateInstance(null, argApplication.FullName); //target = (Application)handler.Unwrap(); target = (ApiController)Activator.CreateInstance(application.Type); } } catch (Exception exception) { response.Error(exception); return; } // 调用功能。 try { target.Request = request; target.Response = response; target.Context = Context; if (target.AfterInitialized != null) target.AfterInitialized.Invoke(); var allowed = target.AllowFunction; if (application.Independent) allowed = false; if (allowed) { if (function == null) { response.Error("指定的 Application 不包含有效 Function。"); } else { var result = function.Method.Invoke(target, null); target.Dispose(); if (response.Status.IsBlank()) response.Status = "ok"; if (function.Returnable && result != null) { if (result is string) { var error = (string)result; if (!string.IsNullOrEmpty(error)) response.Error(error); } } } } } catch (Exception exception) { response.Error(exception.InnerException); } try { target.Dispose(); } catch { } } private string Output() { if (Context == null) return "Invoker 的 Context 无效。"; if (ApiResponse == null) return "Invoker 的 ApiResponse 无效。"; try { var response = Context.Response; var body = ApiInternals.GetStream(response); ApiInternals.AddHeaders(response, ApiResponse.Headers); switch (ApiResponse.Type) { case ApiFormat.Json: { var text = ApiInternals.ExportJson(ApiResponse, ApiOptions.JsonIndent, ApiOptions.AllowException); var data = TextUtility.ToBinary(text); ApiInternals.SetTextPlain(response); ApiInternals.SetContentLength(response, data.LongLength); body.Write(data); } break; case ApiFormat.Text: { var data = TextUtility.ToBinary(ApiResponse.TextString); var type = ApiResponse.TextType; ApiInternals.SetTextPlain(response, type); ApiInternals.SetContentLength(response, data.LongLength); body.Write(data); } break; case ApiFormat.Binary: { Context.Response.ContentType = ApiResponse.BinaryType ?? "application/octet-stream"; if (ApiResponse.BinaryBytes != null) { var data = ApiResponse.BinaryBytes; var length = data.LongLength; ApiInternals.SetContentLength(response, length); if (length > 0L) body.Write(data); } else if (ApiResponse.BinaryStream != null && ApiResponse.BinaryStream.CanRead) { var source = ApiResponse.BinaryStream; try { var length = source.Length - source.Position; ApiInternals.SetContentLength(response, length); if (length > 0) body.Write(source); } catch { } KernelUtility.Dispose(source); } } break; case ApiFormat.File: { var type = ApiResponse.FileType ?? "application/octet-stream"; var name = TextUtility.EncodeUrl(ApiResponse.FileName); var disposition = TextUtility.Merge("attachment; filename=", name); response.ContentType = type; ApiInternals.AddHeader(response, "Content-Disposition", disposition); var source = ApiResponse.FileStream; try { if (source != null && source.CanRead) { var length = source.Length - source.Position; ApiInternals.SetContentLength(response, length); if (length > 0) body.Write(source); } } catch { } KernelUtility.Dispose(source); } break; case ApiFormat.Redirect: { WebUtility.RedirectResponse(response, ApiResponse.RedirectUrl); } break; } // Context.Response.Flush(); // Context.Response.End(); } catch (Exception ex) { return ex.Message; } return null; } private ApiInvoker() { Beginning = DateTime.Now; } /// 获取 ApiServer 派生类中定义的 WebAPI 入口。 public static Dictionary GetEntries(IEnumerable assemblies, bool sort = false) { return ApiInternals.GetEntries(assemblies, sort); } /// 解析请求,并根据执行。 public static void Execute(Dictionary entries) { var invoker = new ApiInvoker(); invoker.Entries = entries; invoker.Run(); } #if NETFX /// 解析请求,获取指定程序集中定义的入口,并执行。指定程序集为 NULL 值时,将自动从应用程序域获取所有 WebAPI 入口。 public static void Execute(Assembly assembly = null) { if (assembly == null) { if (ApiInternals.Assemblies == null || ApiInternals.Assemblies.Count < 1) { var assemblies = new List(); assemblies.AddRange(AppDomain.CurrentDomain.GetAssemblies()); var calling = Assembly.GetCallingAssembly(); var contains = false; foreach (var a in assemblies) { if (a.FullName == calling.FullName) { contains = true; break; } } if (!contains) assemblies.Add(calling); ApiInternals.Assemblies = assemblies; } if (ApiInternals.Entries == null || ApiInternals.Entries.Count < 1) { var entries = ApiInternals.GetEntries(ApiInternals.Assemblies, true); ApiInternals.Entries = entries; } Execute(ApiInternals.Entries); } else { var assemblies = new List(); assemblies.Add(assembly); var invoker = new ApiInvoker(); invoker.Entries = ApiInternals.GetEntries(assemblies, true); invoker.Run(); } } #endif #if NETCORE private static Dictionary KestrelEntries = null; /// 设置 Kestrel 的 API 入口。 /// public static void SetKestrelEntries(IEnumerable assemblies, bool sort = false) { if (assemblies == null) throw new ArgumentNullException(nameof(assemblies)); KestrelEntries = ApiInternals.GetEntries(assemblies, sort); } // internal static async Task Execute(HttpContext context) // { // Invoke(context, ApiInternals.Entries); // await context.Response.StartAsync(); // } internal static string Execute(HttpContext context) { var invoker = new ApiInvoker(); invoker.Context = context; invoker.Entries = KestrelEntries; return invoker.Run(); } /// 运行 Kestrel 服务器,可指定端口和最大请求 Body 长度。默认同步运行,阻塞当前线程。 /// 注意:启动前必须设置 Kestrel Entries。 /// public static IHost RunKestrel(int port = 80, int request = 1073741824, bool async = false) { if (KestrelEntries == null) throw new MissingMemberException(); var builder1 = Host.CreateDefaultBuilder(); var builder2 = builder1.ConfigureWebHostDefaults((builder3) => { var builder4 = builder3.ConfigureKestrel((options) => { options.ListenAnyIP(port); options.AllowSynchronousIO = true; if (request > 0) options.Limits.MaxRequestBodySize = request; }); var builder5 = builder4.UseStartup(); }); var built = builder2.Build(); if (async) built.RunAsync(); else built.Run(); return built; } #endif } } #endif