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.

519 lines
19 KiB

#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 = true)
{
if (assemblies == null) throw new ArgumentNullException(nameof(assemblies));
KestrelEntries = ApiInternals.GetEntries(assemblies, sort);
}
/// <summary>设置 Kestrel 的 API 入口。</summary>
public static void SetKestrelEntries(Assembly assembly, bool sort = true)
{
var assemblies = new Assembly[] { assembly };
if (assemblies[0] == null) assemblies[0] = Assembly.GetCallingAssembly();
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>注意:启动前应使用 SetKestrelEntries 方法设置 Kestrel Entries。</remarks>
public static IHost RunKestrel(int port = 80, int request = 1073741824, bool async = false)
{
if (KestrelEntries == null) SetKestrelEntries(Assembly.GetCallingAssembly());
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