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.

856 lines
31 KiB

using Apewer;
using Apewer.Models;
using Apewer.Network;
using Apewer.Source;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Reflection;
using System.Text;
#if NETFX
using System.Web;
#endif
#if NETCORE
using Microsoft.AspNetCore.Http;
#endif
namespace Apewer.Web
{
internal abstract class ApiProcessor
{
// 请求 Body 大于 100MB 时,不尝试解析 API。
const long MaxRequestBody = 100L * 1024L * 1024L;
#region fields & properties
internal ApiInvoker Invoker = null;
private DateTime Beginning = DateTime.Now;
private DateTime Ending;
private ApiRequest ApiRequest;
private ApiResponse ApiResponse;
public Dictionary<string, ApiApplication> Entries;
public bool HaveContext { get; protected set; }
// 抽象方法的返回值。
private Uri Url;
private HttpMethod Method;
protected abstract object GetContext();
protected abstract string GetClientIP();
protected abstract HttpMethod GetMethod();
protected abstract Uri GetURL();
protected abstract Uri GetReferrer();
protected abstract StringPairs GetHeaders();
protected abstract StringPairs GetCookies();
protected abstract long GetRequestLength();
protected abstract byte[] ReadRequest();
protected abstract void SetCacheControl(int seconds);
protected abstract void SetContentType(string value);
protected abstract void SetContentLength(long value);
protected abstract void Redirect(string url);
protected abstract string AddHeader(string name, string value);
protected abstract void WriteResponse(byte[] bytes);
protected abstract void WriteResponse(Stream stream);
#endregion
/// <summary>执行处理程序,返回错误信息。</summary>
public string Run()
{
try
{
// 检查依赖的属性,若不通过,则无法执行。
var preCheckError = PreCheck();
if (!string.IsNullOrEmpty(preCheckError)) return preCheckError;
// 准备变量。
ApiRequest = GetRequest();
ApiResponse = new ApiResponse();
// 准备向 Controller 传递的属性。
var an = ApiRequest.Application;
var a = null as ApiApplication;
var fn = ApiRequest.Function;
var f = null as ApiFunction;
var r = ApiRequest.Random;
var t = ApiRequest.Ticket;
// 调用。
if (Entries.ContainsKey(an))
{
a = Entries[an];
if (a.Functions.ContainsKey(fn))
{
f = a.Functions[fn];
Invoke(a, f, ApiRequest, ApiResponse);
}
else
{
Invoke(a, null, ApiRequest, ApiResponse);
}
}
else
{
var @default = ApiOptions.Default;
if (@default != null)
{
Invoke(ApiInvoker.GetApplication(@default, false), null, ApiRequest, ApiResponse);
}
else
{
ApiResponse.Error("指定的 Application 无效。");
if (an.IsEmpty() && ApiOptions.AllowEnumerate) Enumerate(Entries);
}
}
// 记录结束时间。
Ending = DateTime.Now;
// 调整响应。
ApiResponse.Beginning = Beginning;
ApiResponse.Ending = Ending;
ApiResponse.Application = an;
ApiResponse.Function = fn;
ApiResponse.Random = r;
// 向客户端输出。
return Output(ApiResponse);
}
catch (Exception ex)
{
Logger.Web.Exception(this, ex);
return ex.Message;
}
}
// 检查执行的前提条件。
string PreCheck()
{
// Context
if (!HaveContext) return "Context 无效。";
// Entries
if (Entries == null) return "Entries 无效。";
// Method
Method = GetMethod();
if (Method == HttpMethod.NULL) return "Methods 无效。";
// AccessControl
// 在此之后可以输出
if (ApiOptions.WithAccessControl)
{
AddHeader("Access-Control-Allow-Headers", "Content-Type");
AddHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
AddHeader("Access-Control-Allow-Origin", "*");
var maxage = ApiOptions.AccessControlMaxAge;
if (maxage > 0) AddHeader("Access-Control-Max-Age", maxage.ToString());
}
// URL
Url = GetURL();
var lowerPath = TextUtility.ToLower(Url.AbsolutePath);
// favicon.ico
if (!ApiOptions.AllowFavIcon)
{
if (lowerPath.StartsWith("/favicon.ico"))
{
SetCacheControl(0);
Output(null as byte[]);
return "已取消对 favicon.ico 的请求。";
}
}
// robots.txt
if (!ApiOptions.AllowRobots)
{
if (lowerPath.StartsWith("/robots.txt"))
{
const string text = "User-agent: *\nDisallow: / \n";
SetCacheControl(0);
Output(text);
return "已取消对 robots.txt 的请求。";
}
}
return null;
}
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.WithModuleName) item["module"] = i.Value.Module;
if (ApiOptions.WithTypeName) item["type"] = 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);
}
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);
}
internal static ApiController CreateController(Type type)
{
try
{
return (ApiController)Activator.CreateInstance(type);
}
catch { }
return null;
}
void Invoke(ApiApplication application, ApiFunction function, ApiRequest request, ApiResponse response)
{
if (request == null || response == null) return;
// 创建控制器实例。
if (application == null)
{
response.Error("指定的 Application 无效,无法创建实例。");
return;
}
var controller = CreateController(application.Type);
if (controller == null)
{
response.Error("创建控制器实例失败。");
return;
}
controller.Application = application;
controller.Function = function;
controller.Request = request;
controller.Response = response;
Invoke(controller);
}
void Invoke(ApiController controller)
{
if (controller == null) return;
var application = controller.Application;
var function = controller.Function;
var request = controller.Request;
var response = controller.Response;
if (application == null) return;
if (request == null || response == null) return;
// 调用功能。
try
{
if (controller.AfterInitialized != null) controller.AfterInitialized.Invoke();
// 控制器要求单独处理,此时框架不再处理 Function。
var allowFunction = controller.AllowFunction;
if (application.Independent) allowFunction = false;
// 由框架调用 Function。
if (allowFunction)
{
if (function == null)
{
var @default = controller.DefaultFunction;
if (@default != null)
{
@default.Invoke();
}
else
{
controller.Response.Error("指定的 Function 无效。");
if (ApiOptions.AllowEnumerate) Enumerate(application.Functions);
}
}
else
{
var result = function.Method.Invoke(controller, null);
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);
}
// 释放控制器。
RuntimeUtility.Dispose(controller);
}
void Output(string text, string type = "text/plain; charset=utf-8")
{
var bytes = TextUtility.ToBinary(text);
Output(bytes, type);
}
void Output(byte[] bytes, string type = "application/octet-stream")
{
var length = bytes == null ? 0 : bytes.LongLength;
SetContentType(type);
SetContentLength(length);
if (bytes != null && bytes.LongLength > 0L)
{
WriteResponse(bytes);
}
}
void Output(Stream stream, string type = "application/octet-stream")
{
SetContentType(type);
if (stream != null)
{
var length = stream.Length - stream.Position;
SetContentLength(length);
if (length > 0 && stream.CanRead) WriteResponse(stream);
RuntimeUtility.Dispose(stream);
}
}
// 输出 ApiResponse,返回错误信息。
string Output(ApiResponse apiResponse)
{
// Ticket。
if (ApiRequest.Method == HttpMethod.GET)
{
if (apiResponse.Ticket != null && !apiResponse.Cookies.HasKey("ticket", true))
{
apiResponse.Cookies.Add("ticket", apiResponse.Ticket);
}
}
// 设置自定义头。
AddHeaders(apiResponse.Headers);
var setCookies = SetCookies(apiResponse.Cookies);
if (setCookies.NotEmpty())
{
apiResponse.Error("系统错误。");
apiResponse.Data.Reset(Json.NewObject());
apiResponse.Data["cookies"] = setCookies;
}
//
if (apiResponse.Type == ApiFormat.Redirect)
{
SetCacheControl(0);
Redirect(apiResponse.RedirectUrl);
return null;
}
//
if (apiResponse.Type == ApiFormat.Json)
{
SetCacheControl(apiResponse.Expires);
Output(ExportJson(apiResponse));
return null;
}
//
if (apiResponse.Type == ApiFormat.Text)
{
SetCacheControl(apiResponse.Expires);
Output(apiResponse.TextString, apiResponse.TextType);
return null;
}
//
if (apiResponse.Type == ApiFormat.Binary)
{
var type = apiResponse.BinaryType ?? "application/octet-stream";
if (apiResponse.BinaryBytes != null)
{
SetCacheControl(apiResponse.Expires);
Output(apiResponse.BinaryBytes, type);
}
else if (apiResponse.BinaryStream != null)
{
SetCacheControl(apiResponse.Expires);
Output(apiResponse.BinaryStream, type);
}
return null;
}
//
if (apiResponse.Type == ApiFormat.File)
{
if (apiResponse.FileStream != null)
{
SetCacheControl(apiResponse.Expires);
AddHeader("Content-Disposition", $"attachment; filename={TextUtility.EncodeUrl(apiResponse.FileName)}");
Output(apiResponse.FileStream, apiResponse.FileType ?? "application/octet-stream");
}
return null;
}
// Context.Response.Flush();
// Context.Response.End();
return null;
}
void AddHeaders(StringPairs headers)
{
foreach (var header in headers) AddHeader(header.Key, header.Value);
}
string SetCookies(IEnumerable<KeyValuePair<string, string>> cookies)
{
if (cookies == null) return null;
var ps = new List<string>();
foreach (var kvp in cookies)
{
var k = kvp.Key ?? "";
var v = kvp.Value ?? "";
if (k.IsEmpty()) continue;
ps.Add(TextUtility.Merge(k, "=", v));
}
if (ps.Count < 1) return null;
var value = TextUtility.Join("; ", ps);
return AddHeader("Set-Cookie", value);
}
// 从 Context 获取 ApiRequest 模型。
ApiRequest GetRequest()
{
// 检查 URL 的 Path。
// if (string.IsNullOrEmpty(request.Path)) return null;
// 创建数据对象。
var apiRequest = new ApiRequest();
// Http Method。
apiRequest.Method = Method;
// 基本信息。
apiRequest.Headers = GetHeaders();
apiRequest.IP = GetClientIP();
apiRequest.Url = Url;
apiRequest.Referrer = GetReferrer();
apiRequest.Parameters = WebUtility.ParseParameters(Url.Query);
// Headers。
apiRequest.UserAgent = WebUtility.GetUserAgent(apiRequest.Headers);
apiRequest.Cookies = GetCookies();
// 匹配 API。
var application = null as string;
var function = null as string;
var random = null as string;
var ticket = null as string;
var session = null as string;
var page = null as string;
// 解析 POST 请求。
if (apiRequest.Method == Apewer.Network.HttpMethod.POST)
{
var post = null as byte[];
var length = GetRequestLength();
if (length < 0 || (length > 0 && length <= MaxRequestBody))
{
post = ReadRequest();
}
// 解析 POST。
if (post != null && post.Length > 1L)
{
// 检查 JSON 的格式,第一位必须是“{”或“[”。
if (post[0] == 123 || post[0] == 91)
{
var text = TextUtility.FromBinary(post);
var json = Json.Parse(text);
if (json != null && json.IsObject)
{
application = json["application"];
function = json["function"];
random = json["random"];
ticket = json["ticket"];
session = json["session"];
page = json["page"];
var data = json.GetProperty("data");
apiRequest.PostData = post;
apiRequest.PostText = text;
apiRequest.PostJson = json;
apiRequest.Data = data ?? Json.NewObject();
}
}
}
}
// 解析 URL 参数。
// URL 参数的优先级应高于 URL 路径,以避免反向代理产生的路径问题。
if (string.IsNullOrEmpty(application)) application = WebUtility.GetParameter(apiRequest.Parameters, "application");
if (string.IsNullOrEmpty(function)) function = WebUtility.GetParameter(apiRequest.Parameters, "function");
if (string.IsNullOrEmpty(random)) random = WebUtility.GetParameter(apiRequest.Parameters, "random");
if (string.IsNullOrEmpty(ticket)) ticket = WebUtility.GetParameter(apiRequest.Parameters, "ticket");
if (string.IsNullOrEmpty(session)) session = WebUtility.GetParameter(apiRequest.Parameters, "session");
if (string.IsNullOrEmpty(page)) page = WebUtility.GetParameter(apiRequest.Parameters, "page");
// 从 Cookie 中获取 Ticket。
if (string.IsNullOrEmpty(ticket)) ticket = apiRequest.Cookies.GetValue("ticket");
// 最后检查 URL 路径。
var paths = (apiRequest.Url.AbsolutePath ?? "").Split('/');
if (string.IsNullOrEmpty(application) && paths.Length >= 2) application = TextUtility.DecodeUrl(paths[1]);
if (string.IsNullOrEmpty(function) && paths.Length >= 3) function = TextUtility.DecodeUrl(paths[2]);
// 修正内容。
application = application.SafeLower().SafeTrim();
function = function.SafeLower().SafeTrim();
random = random.SafeLower().SafeTrim();
ticket = ticket.SafeLower().SafeTrim();
session = session.SafeLower().SafeTrim();
page = page.SafeTrim();
// 设置请求:回传。
apiRequest.Application = application;
apiRequest.Function = function;
apiRequest.Random = random;
// 设置请求:不回传。
apiRequest.Ticket = ticket;
apiRequest.Session = session;
apiRequest.Page = page;
// 返回结果。
return apiRequest;
}
private static string ExportJson(ApiResponse response)
{
if (response == null) return "{}";
var json = Json.NewObject();
// 执行时间。
if (ApiOptions.WithClock)
{
json.SetProperty("clock", response.Ending.ToLucid());
}
// 持续时间。
if (ApiOptions.WithDuration)
{
var seconds = Math.Floor((response.Ending - response.Beginning).TotalMilliseconds) / 1000D;
json.SetProperty("duration", seconds);
}
// 随机值。
if (response.Random.NotEmpty()) json.SetProperty("random", response.Random);
// 调用。
if (ApiOptions.WithTarget)
{
json.SetProperty("application", response.Application);
json.SetProperty("function", response.Function);
}
// 状态。
json.SetProperty("status", (TextUtility.IsBlank(response.Status) ? TextUtility.EmptyString : response.Status.ToLower()));
json.SetProperty("message", response.Message);
// Ticket。
if (response.Ticket != null) json.SetProperty("ticket", response.Ticket);
// 异常。
if (ApiOptions.AllowException && response.Exception != null)
{
try
{
var exMessage = null as string;
var exStackTrace = null as string;
var exSource = null as string;
var exHelpLink = null as string;
exMessage = response.Exception.Message;
exStackTrace = response.Exception.StackTrace;
exSource = response.Exception.Source;
exHelpLink = response.Exception.HelpLink;
// Exception 对象的主要属性。
var exJson = Json.NewObject();
exJson.SetProperty("type", response.Exception.GetType().FullName);
exJson.SetProperty("message", exMessage);
exJson.SetProperty("stack", Json.Parse((exStackTrace ?? "").Replace("\r", "").Split('\n'), false));
exJson.SetProperty("source", exSource);
exJson.SetProperty("helplink", exHelpLink);
// WebException 附加数据。
var webex = response.Exception as System.Net.WebException;
if (webex != null) exJson.SetProperty("data", Json.Parse(webex.Data));
json.SetProperty("exception", exJson);
}
catch (Exception ex)
{
var exJson = Json.NewObject();
exJson.SetProperty("message", TextUtility.Merge("设置 Exception 时再次发生异常:", ex.Message));
json.SetProperty("exception", exJson);
}
}
// 用户数据。
json.SetProperty("data", response.Data);
var indented = ApiOptions.JsonIndent || response.Indented;
var text = json.ToString(indented);
return text;
}
}
internal sealed class ApiProcessorListener : ApiProcessor
{
private HttpListenerContext Context;
private HttpListenerRequest Request;
private HttpListenerResponse Response;
/// <exception cref="ArgumentNullException"></exception>
public ApiProcessorListener(HttpListenerContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
Context = context;
Request = context.Request;
Response = context.Response;
HaveContext = true;
}
protected override object GetContext() => Context;
protected override string GetClientIP() => WebUtility.GetClientIP(Request);
protected override HttpMethod GetMethod() => WebUtility.GetMethod(Request);
protected override Uri GetURL() => WebUtility.GetUrl(Request);
protected override Uri GetReferrer() => Request.UrlReferrer;
protected override StringPairs GetHeaders() => WebUtility.GetHeaders(Request);
protected override StringPairs GetCookies() => WebUtility.GetCookies(Request);
protected override long GetRequestLength() => Request.ContentLength64;
protected override byte[] ReadRequest() => BinaryUtility.Read(Request.InputStream);
protected override void SetCacheControl(int seconds) => WebUtility.SetCacheControl(Response, seconds);
protected override void SetContentType(string value) => Response.ContentType = value ?? "";
protected override void SetContentLength(long value) => AddHeader("Content-Length", value.ToString());
protected override void Redirect(string url) => WebUtility.Redirect(Response, url);
protected override string AddHeader(string name, string value) => WebUtility.AddHeader(Response, name, value);
protected override void WriteResponse(byte[] bytes) => BinaryUtility.Write(Response.OutputStream, bytes);
protected override void WriteResponse(Stream stream) => BinaryUtility.Read(stream, Response.OutputStream);
}
#if NETFX
internal sealed class ApiProcessorIIS : ApiProcessor
{
private System.Web.HttpContext Context;
private System.Web.HttpRequest Request;
private System.Web.HttpResponse Response;
/// <exception cref="ArgumentNullException"></exception>
public ApiProcessorIIS(HttpContext context)
{
if (context == null) context = HttpContext.Current;
if (context == null) throw new ArgumentNullException(nameof(context));
Context = context;
Request = context.Request;
Response = context.Response;
HaveContext = true;
}
protected override object GetContext() => Context;
protected override string GetClientIP() => WebUtility.GetClientIP(Request);
protected override HttpMethod GetMethod() => WebUtility.GetMethod(Request);
protected override Uri GetURL() => WebUtility.GetUrl(Request);
protected override Uri GetReferrer() => Request.UrlReferrer;
protected override StringPairs GetHeaders() => WebUtility.GetHeaders(Request);
protected override StringPairs GetCookies() => WebUtility.GetCookies(Request);
protected override long GetRequestLength() => Request.ContentLength;
protected override byte[] ReadRequest() => BinaryUtility.Read(Request.InputStream);
protected override void SetCacheControl(int seconds) => WebUtility.SetCacheControl(Response, seconds);
protected override void SetContentType(string value) => Response.ContentType = value ?? "";
protected override void SetContentLength(long value) => AddHeader("Content-Length", value.ToString());
protected override void Redirect(string url) => WebUtility.Redirect(Response, url);
protected override string AddHeader(string name, string value) => WebUtility.AddHeader(Response, name, value);
protected override void WriteResponse(byte[] bytes)
{
BinaryUtility.Write(Context.Response.OutputStream, bytes);
}
protected override void WriteResponse(Stream stream)
{
BinaryUtility.Read(stream, Context.Response.OutputStream);
}
}
#endif
#if NETCORE
internal sealed class ApiProcessorCore : ApiProcessor
{
private bool AllowSynchronousIO = true;
private Microsoft.AspNetCore.Http.HttpContext Context;
private Microsoft.AspNetCore.Http.HttpRequest Request;
private Microsoft.AspNetCore.Http.HttpResponse Response;
/// <exception cref="ArgumentNullException"></exception>
public ApiProcessorCore(HttpContext context)
{
if (context == null) throw new ArgumentNullException(nameof(context));
Context = context;
Request = context.Request;
Response = context.Response;
HaveContext = true;
}
protected override object GetContext() => Context;
protected override string GetClientIP() => WebUtility.GetClientIP(Request);
protected override HttpMethod GetMethod() => WebUtility.GetMethod(Request);
protected override Uri GetURL() => WebUtility.GetUrl(Request);
protected override Uri GetReferrer() => null;
protected override StringPairs GetHeaders() => WebUtility.GetHeaders(Request);
protected override StringPairs GetCookies() => WebUtility.GetCookies(Request);
protected override long GetRequestLength() => Request.ContentLength ?? -1;
protected override byte[] ReadRequest() => BinaryUtility.Read(Request.Body);
protected override void SetCacheControl(int seconds) => WebUtility.SetCacheControl(Response, seconds);
protected override void SetContentType(string value) => Response.ContentType = value ?? "";
protected override void SetContentLength(long value) => AddHeader("Content-Length", value.ToString());
protected override void Redirect(string url) => WebUtility.Redirect(Response, url);
protected override string AddHeader(string name, string value) => WebUtility.AddHeader(Response, name, value);
protected override void WriteResponse(byte[] bytes)
{
var body = Context.Response.Body;
// sync
BinaryUtility.Write(body, bytes);
// async
// body.BeginWrite(bytes, 0, bytes.Length, (ar) => body.EndWrite(ar), null);
}
protected override void WriteResponse(Stream stream)
{
var body = Context.Response.Body;
// sync
BinaryUtility.Read(stream, body);
// async
}
private static void ReadAsync(Stream source, Stream destination, Func<long, bool> progress = null, int buffer = 4096, Action after = null)
{
if (source == null || !source.CanRead || destination == null || !destination.CanWrite)
{
after?.Invoke();
return;
}
ReadAsync(source, destination, progress, buffer, after, 0L);
}
private static void ReadAsync(Stream source, Stream destination, Func<long, bool> progress, int buffer, Action after, long total)
{
// 缓冲区。
var limit = buffer < 1 ? 1 : buffer;
var temp = new byte[limit];
// 读取一次。
try
{
source.BeginRead(temp, 0, limit, (ar1) =>
{
var count = source.EndRead(ar1);
if (count > 0)
{
destination.BeginWrite(temp, 0, count, (ar2) =>
{
destination.EndWrite(ar2);
total += count;
if (progress != null)
{
var @continue = progress.Invoke(total);
if (!@continue)
{
after?.Invoke();
return;
}
}
ReadAsync(source, destination, progress, buffer, after, total);
}, null);
}
else after?.Invoke();
}, null);
}
catch { }
}
}
#endif
}