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 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 /// 执行处理程序,返回错误信息。 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 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 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> cookies) { if (cookies == null) return null; var ps = new List(); 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; /// 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; /// 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; /// 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 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 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 }