|
|
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
}
|