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.

606 lines
23 KiB

using Apewer.Network;
using Apewer.Source;
using System;
using System.Net;
using static Apewer.Web.ApiUtility;
namespace Apewer.Web
{
internal class ApiProcessor
{
internal ApiInvoker Invoker = null;
internal ApiEntries Entries = null;
internal ApiProvider Provider = null;
internal Action<ApiCatch> Catcher = null;
ApiOptions Options = null;
DateTime Begin = DateTime.UtcNow;
Uri Url = null;
HttpMethod Method = HttpMethod.NULL;
ApiRequest ApiRequest = null;
ApiResponse ApiResponse = null;
internal ApiProcessor()
{
}
/// <summary>执行处理程序,返回错误信息。</summary>
public string Run()
{
if (Options == null) Options = new ApiOptions();
var error = Flow();
return error;
}
string Flow()
{
// 传入字段。
if (Provider == null) return "服务程序无效。";
if (Invoker == null) return "调用器无效。";
if (Entries == null) return "入口无效。";
Options = Invoker.Options ?? new ApiOptions();
Provider.Options = Options;
try
{
// 检查执行的前提条件,获取 Method 和 URL。
var check = Check();
if (!string.IsNullOrEmpty(check)) return check;
// 准备请求和响应模型。
ApiRequest = GetRequest(Provider, Options, Method, Url);
ApiResponse = new ApiResponse();
ApiResponse.Random = ApiRequest.Random;
ApiResponse.Application = ApiRequest.Application;
ApiResponse.Function = ApiRequest.Function;
// 调用 API。
var invoke = Invoke();
if (!string.IsNullOrEmpty(invoke)) return invoke;
// 输出。
ApiResponse.Duration = Duration();
Output(Provider, Options, ApiResponse, ApiRequest, Method);
return null;
}
catch (Exception ex)
{
var message = ex.Message();
Logger.Internals.Error(typeof(ApiInvoker), message);
return message;
}
}
string Duration()
{
var span = DateTime.UtcNow - Begin;
var ms = span.TotalMilliseconds;
if (ms > 0D)
{
var s = span.TotalMilliseconds / 1000D;
if (s > 10D) return Math.Round(s, 1).ToString() + "s";
if (s > 1D) return Math.Round(s, 2).ToString() + "s";
if (ms > 10D) return Math.Round(ms, 0).ToString() + "ms";
return Math.Round(ms, 1).ToString() + "ms";
}
else
{
return null;
}
}
string Check()
{
// 服务程序检查。
var check = Provider.PreInvoke();
if (!string.IsNullOrEmpty(check)) return check;
// URL
Url = Provider.GetUrl();
if (Url == null) return "URL 无效。";
Method = 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 (!Options.AllowFavIcon)
{
if (lowerPath.StartsWith("/favicon.ico"))
{
Output(Provider, Options, null, null, null);
return "已取消对 favicon.ico 的请求。";
}
}
// robots.txt
if (!Options.AllowRobots)
{
if (lowerPath.StartsWith("/robots.txt"))
{
const string text = "User-agent: *\nDisallow: / \n";
Output(Provider, Options, null, "text/plain", TextUtility.Bytes(text));
return "已取消对 robots.txt 的请求。";
}
}
return null;
}
// 寻找入口。
string Invoke()
{
var appName = ApiRequest.Application;
var funcName = ApiRequest.Function;
var random = ApiRequest.Random;
Invoke(Entries.Get(appName));
ApiResponse.Duration = Duration();
ApiResponse.Application = appName;
ApiResponse.Function = funcName;
ApiResponse.Random = random;
return null;
}
// 创建控制器。
void Invoke(ApiApplication application)
{
var request = ApiRequest;
var response = ApiResponse;
var function = null as ApiFunction;
var controller = null as ApiController;
// Application 无效,尝试默认控制器和枚举。
if (application == null)
{
var @default = Options.Default;
if (@default == null)
{
// 没有指定默认控制器,尝试枚举。
response.Error("Invalid Application");
if (Options.AllowEnumerate) response.Data = Enumerate(Entries.Enumerate(), Options);
return;
}
else
{
// 创建默认控制器。
try { controller = CreateController(@default, request, response, Options); }
catch (Exception ex) { ApiUtility.Exception(response, ex.InnerException); }
}
}
else
{
// 创建控制器时候会填充 Controller.Request 属性,可能导致 Request.Function 被篡改,所以在创建之前获取 Function。
function = application.Get(request.Function);
try { controller = CreateController(application.Type, request, response, Options); }
catch (Exception ex) { ApiUtility.Exception(response, ex.InnerException); }
}
if (controller == null) response.Error("创建控制器实例失败。");
else Invoke(controller, application, function, Options, request, response);
RuntimeUtility.Dispose(controller);
}
// 调用 Function。
void Invoke(ApiController controller, ApiApplication application, ApiFunction function, ApiOptions options, ApiRequest request, ApiResponse response)
{
// 没有 ApiApplication,使用了 Options 中指定的默认控制器。
if (application == null)
{
application = new ApiApplication();
application.Independent = true;
application.Hidden = true;
}
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,获取返回值。
var result = function.Method.Invoke(controller, ReadParameters(request, function));
if (response.StopReturn) return;
// 检查返回值。
if (result == null || function.Returnable == null) return;
var returnable = function.Returnable;
// 已明确字符串类型,视为提示错误。
if (returnable.Equals(typeof(string)))
{
var error = result as string;
if (!string.IsNullOrEmpty(error)) response.Error(error);
return;
}
// 已明确 Exception 类型,视为提示错误。
if (result is Exception)
{
ApiUtility.Exception(response, result as Exception);
return;
}
// 已明确 Json 类型。
if (result is Json)
{
response.Data = result as Json;
return;
}
// 已明确 Model 类型。
if (result is ApiModel)
{
response.Model = result as ApiModel;
return;
}
// 类型未知,尝试 ToJson 方法。
var tojson = result as IToJson;
if (tojson != null)
{
response.Data = tojson.ToJson();
return;
}
// 类型未知,尝试 Record 模型。
var record = result as IRecord;
if (record != null)
{
response.Data = Json.From(record);
return;
}
// 未知类型,尝试 Json 类型。
var json = result as Json;
if (json != null)
{
response.Data = json;
return;
}
// 未知返回类型,无法明确输出格式,忽略。
}
else
{
// 未匹配到 Function,尝试 Default。
var @default = ApiUtility.GetDefault(controller);
if (@default != null)
{
@default.Invoke(controller);
return;
}
// 没有执行任何 Function,尝试枚举。
if (application.Hidden)
{
response.Error("Invalid Application");
}
else
{
response.Error("Invalid Function");
if (options.AllowEnumerate) response.Data = Enumerate(application.Items, options);
}
}
}
catch (Exception exception)
{
var ex = exception.InnerException;
if (Catcher != null)
{
ApiUtility.Exception(response, ex, false);
try
{
var apiCatch = new ApiCatch(controller, options, ex);
Catcher.Invoke(apiCatch);
}
catch { }
return;
}
ApiUtility.Exception(response, ex);
}
}
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;
}
#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 StringPairs();
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 请求。
if (request.Method == HttpMethod.POST)
{
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", true) ?? "";
if (contentType.Contains("urlencoded")) request.Form = ApiUtility.Parameters(text);
}
}
}
}
// 解析 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.Key);
if (string.IsNullOrEmpty(key)) continue;
var value = header.Value;
if (string.IsNullOrEmpty(value)) continue;
merged.Add(key, value);
}
}
}
return merged;
}
internal static string ExportJson(ApiResponse response, ApiOptions options)
{
if (response == null) return "{}";
if (string.IsNullOrEmpty(response.Status)) response.Status = "ok";
var json = Json.NewObject();
// 执行时间。
if (options.WithClock)
{
json.SetProperty("clock", ClockUtility.Lucid(DateTime.Now));
}
// 持续时间。
if (options.WithDuration)
{
if (response.Duration.NotEmpty()) json.SetProperty("duration", response.Duration);
}
// 随机值。
var random = response.Random;
if (!string.IsNullOrEmpty(random)) json.SetProperty("random", random);
// 调用。
if (options.WithTarget)
{
json.SetProperty("application", response.Application);
json.SetProperty("function", response.Function);
}
// 状态。
json.SetProperty("status", (TextUtility.IsBlank(response.Status) ? TextUtility.Empty : response.Status.ToLower()));
if (!string.IsNullOrEmpty(response.Message)) json.SetProperty("message", response.Message);
// 用户数据。
if (response.Message == "exception" && !options.WithException) json.SetProperty("data");
else json.SetProperty("data", response.Data);
var indented = response.Indented || options.JsonIndent;
var text = json.ToString(indented);
return text;
}
internal static void Output(ApiProvider provider, ApiOptions options, ApiResponse response, string type, byte[] bytes)
{
var preWrite = provider.PreWrite();
if (!string.IsNullOrEmpty(preWrite)) 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 static void Output(ApiProvider provider, ApiOptions options, ApiResponse response, ApiRequest request, HttpMethod method)
{
var preWrite = provider.PreWrite();
if (!string.IsNullOrEmpty(preWrite)) return;
// 设置头。
var headers = PrepareHeaders(options, response, request);
foreach (var header in headers) provider.SetHeader(header.Key, header.Value);
var model = response.Model;
if (model != null)
{
ApiUtility.Initialize(model, request, response, options, provider);
try { model.Output(); }
catch (Exception ex) { Logger.Internals.Exception(model, ex); }
RuntimeUtility.Dispose(model);
return;
}
var text = ExportJson(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
}
}