|
|
#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 {
/// <summary>API 调用器。</summary>
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<Assembly> Assemblies { get; set; }
private HttpContext Context { get; set; }
private Dictionary<string, ApiApplication> 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<Assembly>(); 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<string, ApiApplication> 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<string, ApiFunction> 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; }
/// <summary>获取 ApiServer 派生类中定义的 WebAPI 入口。</summary>
public static Dictionary<string, ApiApplication> GetEntries(IEnumerable<Assembly> assemblies, bool sort = false) { return ApiInternals.GetEntries(assemblies, sort); }
/// <summary>解析请求,并根据执行。</summary>
public static void Execute(Dictionary<string, ApiApplication> entries) { var invoker = new ApiInvoker(); invoker.Entries = entries; invoker.Run(); }
#if NETFX
/// <summary>解析请求,获取指定程序集中定义的入口,并执行。指定程序集为 NULL 值时,将自动从应用程序域获取所有 WebAPI 入口。</summary>
public static void Execute(Assembly assembly = null) { if (assembly == null) { if (ApiInternals.Assemblies == null || ApiInternals.Assemblies.Count < 1) { var assemblies = new List<Assembly>(); 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<Assembly>(); assemblies.Add(assembly);
var invoker = new ApiInvoker(); invoker.Entries = ApiInternals.GetEntries(assemblies, true); invoker.Run(); } }
#endif
#if NETCORE
private static Dictionary<string, ApiApplication> KestrelEntries = null;
/// <summary>设置 Kestrel 的 API 入口。</summary>
/// <exception cref="ArgumentNullException"></exception>
public static void SetKestrelEntries(IEnumerable<Assembly> 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(); }
/// <summary>运行 Kestrel 服务器,可指定端口和最大请求 Body 长度。默认同步运行,阻塞当前线程。</summary>
/// <remarks>注意:启动前必须设置 Kestrel Entries。</remarks>
/// <exception cref="MissingMemberException"></exception>
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<ApiStartup>(); }); var built = builder2.Build();
if (async) built.RunAsync(); else built.Run(); return built; }
#endif
}
}
#endif
|