using Apewer.Network; using Apewer.Source; using System; using System.Net; using System.Reflection; using static Apewer.Web.ApiUtility; namespace Apewer.Web { internal class ApiProcessor { private ApiContext _context = null; internal ApiProcessor(ApiContext context) => _context = context ?? throw new ArgumentNullException(nameof(context)); #region prepare /// 执行处理程序,返回错误信息。 public void Run() { var url = null as Uri; var method = HttpMethod.NULL; var response = null as ApiResponse; try { // 检查执行的前提条件,获取 Method 和 URL。 var check = Check(ref method, ref url); if (!string.IsNullOrEmpty(check)) { Logger.Internals.Error(typeof(ApiInvoker), check); return; } // 准备请求模型。 var request = GetRequest(_context.Provider, _context.Options, method, url); _context.Request = request; // 准备响应模型。 response = new ApiResponse(); response.Random = request.Random; response.Application = request.Application; response.Function = request.Function; _context.Response = response; // 调用 API。 Invoke(); } catch (Exception ex) { var message = ex.Message(); Logger.Internals.Error(typeof(ApiInvoker), message); } finally { // 输出。 if (response != null) { try { response.Duration = Duration(_context.Beginning); Output(_context.Provider, _context.Options, response, null, method); } catch { } finally { RuntimeUtility.Dispose(response.Model); } } } } static string Duration(DateTime beginning) { var span = DateTime.Now - beginning; var ms = span.TotalMilliseconds; if (ms < 1000) return Math.Round(ms, 0).ToString() + "ms"; if (ms < 10000) return Math.Round(ms / 1000, 2).ToString() + "s"; if (ms < 60000) return Math.Round(ms / 1000, 1).ToString() + "s"; return Math.Round(ms / 1000, 0).ToString() + "s"; } string Check(ref HttpMethod method, ref Uri url) { // 服务程序检查。 var check = _context.Provider.PreInvoke(); if (!string.IsNullOrEmpty(check)) return check; // URL url = _context.Provider.GetUrl(); if (url == null) return "URL 无效。"; method = _context.Provider.GetMethod(); if (method == HttpMethod.NULL) return "HTTP 方法无效。"; if (method == HttpMethod.OPTIONS) return null; // favicon.ico var lowerPath = TextUtility.AssureStarts(TextUtility.Lower(url.AbsolutePath), "/"); if (!_context.Options.AllowFavIcon) { if (lowerPath.StartsWith("/favicon.ico")) { Output(_context.Provider, _context.Options, null, null, null); return "已取消对 favicon.ico 的请求。"; } } // robots.txt if (!_context.Options.AllowRobots) { if (lowerPath.StartsWith("/robots.txt")) { const string text = "User-agent: *\nDisallow: / \n"; Output(_context.Provider, _context.Options, null, "text/plain", TextUtility.Bytes(text)); return "已取消对 robots.txt 的请求。"; } } return null; } // 寻找入口。 void Invoke() { // 路由 if (_context.Options.UseRoute) { var path = _context?.Request?.Url?.AbsolutePath; path = path.TrimEnd('/'); var action = _context.Entries.GetAction(path); if (action != null) { _context.ApiAction = action; Invoke(action); _context.Response.Duration = Duration(_context.Beginning); return; } } // 反射 if (_context.Options.UseReflection) { var appName = _context.Request.Application; var application = _context.Entries.GetApplication(appName); Invoke(application); _context.Response.Duration = Duration(_context.Beginning); return; } // 未匹配到 _context.Response.Duration = Duration(_context.Beginning); _context.Response.Model = new ApiStatusModel(404); } #endregion #region common // 创建控制器实例 static ApiController CreateController(Type type, ApiRequest request, ApiResponse response, ApiOptions options) { var controller = (ApiController)Activator.CreateInstance(type); ApiUtility.SetProperties(controller, request, response, options); return controller; } static void Invoke(ApiContext context, MethodInfo method, ApiParameter[] parameters) { context.MethodInfo = method; // 调用。 var parametersValue = ReadParameters(context.Request, parameters); var controller = context.Controller; var returnValue = method.Invoke(controller, parametersValue); // 程序要求停止输出。 var response = context.Response; if (response.StopReturn) return; // 已经有了返回模型。 if (response.Model != null) return; // 没有返回类型。 var returnType = method.ReturnType; if (returnType == null) return; // 已明确字符串类型。 if (returnType.Equals(typeof(string))) { var textValue = returnValue as string; var textRenderer = context.Options.TextRenderer; if (textRenderer != null) { textRenderer.Invoke(context, textValue); return; } // 默认视为提示错误 if (!string.IsNullOrEmpty(textValue)) response.Error(textValue); return; } // 已明确 Exception 类型,视为提示错误。 if (returnValue is Exception) { ApiUtility.Exception(response, returnValue as Exception); return; } // 已明确 Json 类型。 if (returnValue is Json json) { var renderer = context.Options.JsonRenderer; if (renderer != null) { renderer.Invoke(context, json); return; } // 默认设置到 data 属性。 response.Data = json; return; } // 已明确 Model 类型。 if (returnValue is IApiModel model) { response.Model = model; return; } // 已明确 Result 类型。 if (returnValue is IActionResult result) { response.Model = result; return; } // 类型未知,尝试 ToJson 方法。 if (returnValue is IToJson toJson) { var tojson = toJson.ToJson(); var renderer = context.Options.JsonRenderer; if (renderer != null) { renderer.Invoke(context, tojson); return; } response.Data = tojson; return; } // 未知返回类型,尝试使用默认渲染器。 var defaultRenderer = context.Options.DefaultRenderer; if (defaultRenderer != null) defaultRenderer.Invoke(context, returnValue); } #endregion #region route // 执行 Action。 void Invoke(ApiAction action) { var controller = null as ApiController; try { // 准备控制器。 controller = CreateController(action.Type, _context.Request, _context.Response, _context.Options); // 准备参数。 var parameters = action.Parameters; var values = ReadParameters(_context.Request, parameters); // 调用。 _context.Controller = controller; Invoke(_context, action.MethodInfo, action.Parameters); } catch (Exception ex) { if (ex.InnerException != null) ex = ex.InnerException; ApiUtility.Exception(_context.Response, ex, _context.Options.WithException); var catcher = _context.Invoker.Catcher; if (catcher != null) { try { var apiCatch = new ApiCatch(_context, ex); catcher.Invoke(apiCatch); } catch { } } } finally { RuntimeUtility.Dispose(controller); } } #endregion #region reflection // 创建控制器。 void Invoke(ApiApplication application) { var options = _context.Options; var entries = _context.Entries; var request = _context.Request; var response = _context.Response; // Application 无效,尝试默认控制器和枚举。 if (application == null) { var @default = options.Default; if (@default == null) { // 没有指定默认控制器,尝试枚举。 response.Status = "notfound"; response.Message = "Not Found"; if (options.AllowEnumerate) response.Data = Enumerate(entries.Applications, options); return; } else { // 创建默认控制器。 var controller = null as ApiController; try { controller = CreateController(@default, request, response, options); Invoke(controller, application, null, options, request, response); } catch (Exception ex) { ApiUtility.Exception(response, ex.InnerException ?? ex); } finally { RuntimeUtility.Dispose(controller); } } } else { // 创建控制器时候会填充 Controller.Request 属性,可能导致 Request.Function 被篡改,所以在创建之前获取 Function。 var function = application.GetFunction(request.Function); var controller = null as ApiController; try { controller = CreateController(application.Type, request, response, options); Invoke(controller, application, function, options, request, response); } catch (Exception ex) { ApiUtility.Exception(response, ex.InnerException ?? ex); } finally { RuntimeUtility.Dispose(controller); } } } // 调用 Function。 void Invoke(ApiController controller, ApiApplication application, ApiFunction function, ApiOptions options, ApiRequest request, ApiResponse response) { try { // 控制器初始化。 var initializer = ApiUtility.GetInitialier(controller); var match = initializer == null ? true : initializer.Invoke(controller); if (!match) return; if (application.Independent) return; if (function != null) { // 调用 API,获取返回值。 _context.Controller = controller; Invoke(_context, function.Method, function.Parameters); } else { // 未匹配到 Function,尝试 Default。 var @default = ApiUtility.GetDefault(controller); if (@default != null) { @default.Invoke(controller); return; } // 没有执行任何 Function,尝试枚举。 response.Status = "notfound"; if (application.Hidden) { response.Message = "Not Found"; } else { response.Message = "Not Found"; if (options.AllowEnumerate) response.Data = Enumerate(application.Functions, options); } } } catch (Exception ex) { if (ex.InnerException != null) ex = ex.InnerException; ApiUtility.Exception(_context.Response, ex, _context.Options.WithException); var catcher = _context.Invoker.Catcher; if (catcher != null) { try { var apiCatch = new ApiCatch(_context, ex); catcher.Invoke(apiCatch); } catch { } } } } #endregion #region static internal static ApiRequest GetRequest(ApiProvider provider, ApiOptions options, HttpMethod method, Uri url) { // 创建数据对象。 var request = new ApiRequest(); // Http Method。 request.Method = method; // 基本信息。 var ip = provider.GetClientIP(); var headers = provider.GetHeaders() ?? new HttpHeaders(); request.Headers = headers; request.IP = ip; request.Url = url; request.Referrer = provider.GetReferrer(); request.Parameters = ApiUtility.Parameters(url.Query); // Headers。 request.UserAgent = ApiUtility.UserAgent(headers); request.Cookies = ParseCookies(headers) ?? new CookieCollection(); // 匹配 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 请求。 switch (request.Method) { case HttpMethod.PATCH: case HttpMethod.POST: case HttpMethod.PUT: var preRead = provider.PreRead(); if (string.IsNullOrEmpty(preRead)) { var post = null as byte[]; var length = 0L; var max = options.MaxRequestBody; if (max == 0) post = new byte[0]; else if (max < 0) post = provider.RequestBody().Read(); else { length = provider.GetContentLength(); if (length <= max) post = provider.RequestBody().Read(); } length = post == null ? 0 : post.Length; if (length > 1) { request.PostData = post; if (length < 104857600) { var text = TextUtility.FromBytes(post); request.PostText = text; // 尝试解析 Json,首尾必须是“{}”或“[]”。 var first = post[0]; var last = post[length - 1]; if ((first == 123 && last == 125) || (first == 91 && last == 93)) { var json = Json.From(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"); request.PostJson = json; request.Data = data ?? Json.NewObject(); } } // 尝试解析 Form,需要 application/x-www-form-urlencoded var contentType = headers.GetValue("Content-Type") ?? ""; if (contentType.Contains("urlencoded")) request.Form = ApiUtility.Parameters(text); } } } break; } // 解析 URL 参数。 // URL 参数的优先级应高于 URL 路径,以避免反向代理产生的路径问题。 var urlParameters = ApiUtility.Parameters(request.Url.Query); if (string.IsNullOrEmpty(application)) application = urlParameters.GetValue("application"); if (string.IsNullOrEmpty(function)) function = urlParameters.GetValue("function"); if (string.IsNullOrEmpty(random)) random = urlParameters.GetValue("random"); if (string.IsNullOrEmpty(ticket)) ticket = urlParameters.GetValue("ticket"); if (string.IsNullOrEmpty(session)) session = urlParameters.GetValue("session"); if (string.IsNullOrEmpty(page)) page = urlParameters.GetValue("page"); // 从 Cookie 中获取 Ticket。 var cookies = request.Cookies; if (string.IsNullOrEmpty(ticket)) ticket = cookies.GetValue("ticket"); // 最后检查 URL 路径。 var paths = (request.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 = TextUtility.Trim(application); function = TextUtility.Trim(function); random = TextUtility.Trim(random); ticket = TextUtility.Trim(ticket); session = TextUtility.Trim(session); page = TextUtility.Trim(page); // 设置请求:回传。 request.Application = application; request.Function = function; request.Random = random; // 设置请求:不回传。 request.Ticket = ticket; request.Session = session; request.Page = page; return request; } static StringPairs PrepareHeaders(ApiOptions options, ApiResponse response, ApiRequest request = null) { var merged = new StringPairs(); if (options != null) { // 跨域访问。 if (options.WithAccessControl) { merged.Add("Access-Control-Allow-Headers", "Content-Type"); merged.Add("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); merged.Add("Access-Control-Allow-Origin", "*"); var maxage = options.AccessControlMaxAge; if (maxage > 0) merged.Add("Access-Control-Max-Age", maxage.ToString()); if (request != null && request.Headers != null) { var @private = request.Headers.GetValue("Access-Control-Request-Private-Network"); if (NumberUtility.Boolean(@private)) merged.Add("Access-Control-Allow-Private-Network", "true"); } } // Content-Type 检查。 if (options.WithContentTypeOptions || options.Default != null) { merged.Add("X-Content-Type-Options", "nosniff"); } // 用于客户端,当前页面使用 HTTPS 时,将资源升级为 HTTPS。 if (options.UpgradeHttps) { merged.Add("Content-Security-Policy", "upgrade-insecure-requests"); } // 包含 API 的处理时间。 if (options.WithDuration && response != null) { if (response.Duration.NotEmpty()) merged.Add("Duration", response.Duration); } } if (response != null) { // Cookies。 var setCookies = SetCookie(response.Cookies); if (setCookies != null) { foreach (var value in setCookies) merged.Add("Set-Cookie", value); } // 自定义头。 var headers = response.Headers; if (headers != null) { foreach (var header in headers) { var key = TextUtility.Trim(header.Name); if (string.IsNullOrEmpty(key)) continue; var value = header.Value; if (string.IsNullOrEmpty(value)) continue; merged.Add(key, value); } } } return merged; } internal void Output(ApiProvider provider, ApiOptions options, ApiResponse response, string type, byte[] bytes) { var preWrite = provider.PreWrite(); if (!string.IsNullOrEmpty(preWrite)) return; if (response != null) { var responsePreOutput = response.PreOutput; if (responsePreOutput != null) { var @continue = responsePreOutput.Invoke(_context); if (!@continue) return; } } var invokerPreOutput = _context.Invoker.PreOutput; if (invokerPreOutput != null) { var @continue = invokerPreOutput.Invoke(_context); if (!@continue) return; } var optionsPreOutput = _context.Options.PreOutput; if (optionsPreOutput != null) { var @continue = optionsPreOutput.Invoke(_context); if (!@continue) return; } var headers = PrepareHeaders(options, response); foreach (var header in headers) provider.SetHeader(header.Key, header.Value); provider.SetCache(0); provider.SetContentType(string.IsNullOrEmpty(type) ? "application/octet-stream" : type); var length = bytes == null ? 0 : bytes.Length; provider.SetContentLength(length); if (length > 0) provider.ResponseBody().Write(bytes, 0, bytes.Length); provider.Sent(); } internal void Output(ApiProvider provider, ApiOptions options, ApiResponse response, ApiRequest request, HttpMethod method) { var preWrite = provider.PreWrite(); if (!string.IsNullOrEmpty(preWrite)) return; if (response != null) { var responsePreOutput = response.PreOutput; if (responsePreOutput != null) { var @continue = responsePreOutput.Invoke(_context); if (!@continue) return; } } var invokerPreOutput = _context.Invoker.PreOutput; if (invokerPreOutput != null) { var @continue = invokerPreOutput.Invoke(_context); if (!@continue) return; } var optionsPreOutput = _context.Options.PreOutput; if (optionsPreOutput != null) { var @continue = optionsPreOutput.Invoke(_context); if (!@continue) return; } // 设置头。 var headers = PrepareHeaders(options, response, request); foreach (var header in headers) provider.SetHeader(header.Key, header.Value); // 自定义模型 var model = response.Model as IApiModel; var result = response.Model as IActionResult; if (model != null) { try { model.Output(_context); } catch (Exception ex) { Logger.Internals.Exception(ex, model); } RuntimeUtility.Dispose(model); return; } else if (result != null) { try { result.ExecuteResult(_context); } catch (Exception ex) { Logger.Internals.Exception(ex, result); } RuntimeUtility.Dispose(result); return; } var text = ApiUtility.ToJson(response, options); var bytes = TextUtility.Bytes(text); provider.SetCache(0); provider.SetContentType("text/json; charset=utf-8"); provider.SetContentLength(bytes.Length); var stream = provider.ResponseBody(); if (stream != null && stream.CanWrite) stream.Write(bytes, 0, bytes.Length); provider.Sent(); } #endregion } }