using Apewer.Network; using Apewer.Source; using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Text; namespace Apewer.Web { /// public static class ApiUtility { #region Text /// 修剪 IP 地址,去除无效的部分。 public static string TrimIP(string text) { var trimmed = TextUtility.Trim(text); if (string.IsNullOrEmpty(trimmed)) return ""; var ip = TextUtility.Trim(trimmed.Split(':')[0]); return NetworkUtility.IsIP(ip) ? ip : ""; } /// 按 & 拆分多个参数。 public static StringPairs Parameters(string query, bool decode = true) { var input = query; var list = new StringPairs(); if (!string.IsNullOrEmpty(input)) { if (input[0] == '?') input = input.Substring(1); var args = input.Split('&'); foreach (var arg in args) { var equals = arg.IndexOf("="); var left = equals < 0 ? arg : arg.Substring(0, equals); var right = equals < 0 ? "" : arg.Substring(equals + 1); if (decode) { left = TextUtility.DecodeUrl(left); right = TextUtility.DecodeUrl(right); } list.Add(left, right); } } return list; } /// 获取参数并解码,可要求修剪参数值。 public static string Parameter(string encoded, bool trim, params string[] names) { if (string.IsNullOrEmpty(encoded)) return null; if (names == null || names.Length < 1) return null; // Names 转为小写,加强适配。 var lowerNames = new List(names.Length); foreach (var name in names) { var lower = TextUtility.Lower(name); if (string.IsNullOrEmpty(lower)) continue; lowerNames.Add(lower); } if (lowerNames.Count < 1) return null; // 分参数对比。 var parameters = Parameters(encoded); var matched = false; foreach (var parameter in parameters) { var left = parameter.Key; var right = parameter.Value; if (trim) right = TextUtility.Lower(right); var lowerLeft = TextUtility.Lower(left); if (lowerNames.Contains(right)) { matched = true; if (!string.IsNullOrEmpty(right)) return right; } } return matched ? "" : null; } /// 获取参数,可要求修剪参数值。 public static string Parameter(StringPairs parameters, bool trim, params string[] names) { if (parameters == null || parameters.Count < 1) return null; if (names == null || names.Length < 1) return null; var lowerNames = new List(names.Length); foreach (var name in names) { var lower = TextUtility.Lower(name); if (string.IsNullOrEmpty(lower)) continue; lowerNames.Add(lower); } foreach (var parameter in parameters) { var lowerKey = TextUtility.Lower(parameter.Key); if (lowerNames.Contains(lowerKey)) { var value = parameter.Value; if (trim) value = TextUtility.Lower(value); if (!string.IsNullOrEmpty(value)) return value; } } return null; } /// 获取 User Agent。 public static string UserAgent(StringPairs headers) => headers == null ? null : headers.GetValue("user-agent"); // 从 Uri 对象中解析路径片段。 private static string[] Segmentals(Uri url) { if (url == null) return null; if (string.IsNullOrEmpty(url.AbsolutePath)) return null; var segmentals = url.AbsolutePath.Split('/'); return segmentals; } // 获取已经解析的路径片段。 private static string Segmental(string[] segmentals, int index = 3, bool decode = false) { if (segmentals == null || segmentals.Length < 1) return null; if (index < 1 || index >= segmentals.Length) return null; var segmental = segmentals[index]; if (decode) segmental = TextUtility.DecodeUrl(segmental); return segmental; } #endregion #region Headers /// public static HttpMethod Method(string method) { if (!string.IsNullOrEmpty(method)) { var upper = TextUtility.Upper(method); if (upper.Contains("OPTIONS")) return HttpMethod.OPTIONS; else if (upper.Contains("POST")) return HttpMethod.POST; else if (upper.Contains("GET")) return HttpMethod.GET; else if (upper.Contains("CONNECT")) return HttpMethod.CONNECT; else if (upper.Contains("DELETE")) return HttpMethod.DELETE; else if (upper.Contains("HEAD")) return HttpMethod.HEAD; else if (upper.Contains("PATCH")) return HttpMethod.PATCH; else if (upper.Contains("PUT")) return HttpMethod.PUT; else if (upper.Contains("TRACE")) return HttpMethod.TRACE; } return HttpMethod.NULL; } /// 获取 X-Forwarded-For,不存在时返回 NULL 值。 public static string[] GetForwardedIP(StringPairs headers) { if (headers != null) { var value = headers.GetValue("x-forwarded-for", true); if (!string.IsNullOrEmpty(value)) { var fips = new List(); var split = value.Split(',', ' '); foreach (var para in split) { var fip = TrimIP(para); if (!string.IsNullOrEmpty(fip)) fips.Add(fip); } return fips.ToArray(); } } return new string[0]; } #endregion #region ApiController /// 设置控制器属性。 public static void SetProperties(ApiController controller, ApiRequest request, ApiResponse response, ApiOptions options) { if (controller == null) return; controller.Request = request; controller.Response = response; controller._options = options; } /// 获取由控制器构造函数指定的初始化程序。 public static Func GetInitialier(this ApiController controller) => controller == null ? null : controller._func; /// 获取由控制器构造函数指定的默认程序。 public static Action GetDefault(this ApiController controller) => controller == null ? null : controller._default; /// 获取选项。 public static ApiOptions GetOptions(this ApiController controller) => controller == null ? null : controller._options; /// 以 POST 转移请求到其它 URL。 private static string Transfer(ApiController controller, string url, string application = null, string function = null) { if (controller == null || controller.Request == null || controller.Response == null) return "ApiControllser 无效。"; if (url.IsEmpty()) return "ApiController 无效。"; var s = Json.NewObject(); s.SetProperty("random", TextUtility.Guid()); s.SetProperty("application", application.IsEmpty() ? controller.Request.Application : application); s.SetProperty("function", function.IsEmpty() ? controller.Request.Function : function); s.SetProperty("data", controller.Request.Data); s.SetProperty("session", controller.Request.Session); s.SetProperty("ticket", controller.Request.Ticket); s.SetProperty("page", controller.Request.Page); var c = new Network.HttpClient(); c.Request.Url = url; c.Request.Method = Network.HttpMethod.POST; // TODO 设置 Request 的 Cookies。 c.Request.Data = s.ToString().Bytes(); var e = c.Send(); if (e != null) return e.Message; // TODO 解析 Response 的 Cookies。 var r = Json.From(c.Response.Data.Text()); if (r == null || !r.Available) { controller.Response.Error("请求失败。"); return "请求失败。"; } if (r["status"] != "ok") { controller.Response.Error(r["message"]); return r["message"]; } controller.Response.Data.Reset(r.GetProperty("data")); return null; } /// 创建指定类型的控制器,并引用当前控制器的 Request 和 Response。 public static T Create(this ApiController current) where T : ApiController, new() { var controller = new T(); if (current != null) { controller.Request = current.Request; controller.Response = current.Response; } return controller; } /// 使用默认控制器处理请求。 public static void UseDefault(this ApiController current) { if (current == null) return; var options = GetOptions(current); if (options == null) return; var type = options.Default; if (type == null) return; var controller = null as ApiController; try { controller = (ApiController)Activator.CreateInstance(type); SetProperties(controller, current.Request, current.Response, options); } catch { return; } controller.GetInitialier()?.Invoke(controller); } #endregion #region ApiRequest /// 获取 URL 路径段,不存在的段为 NULL 值。可要求解码。 public static string Segmental(this ApiRequest request, int index = 3, bool decode = false) { if (request == null) return null; if (request._segmentals == null) request._segmentals = Segmentals(request.Url); return Segmental(request._segmentals, index, decode); } /// 获取参数,指定可能的参数名,默认将修剪参数值。 /// 当从 URL 中获取参数时将解码。 public static string Parameter(this ApiRequest request, params string[] names) => Parameter(request, true, names); /// 获取参数,指定可能的参数名,可要求修剪参数值。 /// 当从 URL 中获取参数时将解码。 public static string Parameter(ApiRequest request, bool trim, params string[] names) { if (request == null) return null; if (names == null || names.Length < 1) return null; var dedupNames = new List(names.Length); var lowerNames = new List(names.Length); foreach (var name in names) { if (string.IsNullOrEmpty(name)) continue; if (dedupNames.Contains(name)) continue; else dedupNames.Add(name); var lower = TextUtility.Lower(name); if (lowerNames.Contains(lower)) continue; else lowerNames.Add(lower); } var matched = false; // POST 优先。 var data = request.Data; if (data != null && data.IsObject) { var properties = data.GetProperties(); if (properties != null) { // Json 区分大小写,先全字匹配。 foreach (var property in properties) { if (!property.IsProperty) continue; var name = property.Name; if (!dedupNames.Contains(name)) continue; var value = property.Value; if (value == null) continue; matched = true; var text = value.ToString(); if (trim) text = TextUtility.Trim(text); if (!string.IsNullOrEmpty(text)) return text; } // 以小写模糊匹配。 foreach (var property in properties) { if (!property.IsProperty) continue; var name = TextUtility.Lower(property.Name); if (!lowerNames.Contains(name)) continue; var value = property.Value; if (value == null) continue; matched = true; var text = value.ToString(); if (trim) text = TextUtility.Trim(text); if (!string.IsNullOrEmpty(text)) return text; } } } // 从已解析的 Get 参数中搜索。 if (request.Parameters != null) { var value = Parameter(request.Parameters, trim, names); if (!string.IsNullOrEmpty(value)) return value; if (value != null) matched = true; } return matched ? "" : null; } #endregion #region ApiResponse internal static ApiModel Model(ApiResponse response, string type, ApiModel model) { if (response == null && model == null) return null; if (!string.IsNullOrEmpty(type)) model.ContentType = type; response.Model = model; return model; } /// 设置响应。 public static string Respond(ApiResponse response, Json data, bool lower = true) { if (response == null) return "Response 对象无效。"; if (data != null) { if (lower) data = Json.Lower(data); response.Data = data; } return null; } /// 设置响应,当发生错误时设置响应。返回错误信息。 public static string Respond(ApiResponse response, IList list, bool lower = true, int depth = -1, bool force = false) { if (response == null) return "Response 对象无效。"; if (list == null) { var error = "列表对象无效。"; response.Error(error); return error; } var json = Json.From(list, lower, depth, force); if (json == null || !json.Available) { var error = "列表无法序列化。"; response.Error(error); return error; } if (response.Data == null) response.Data = Json.NewObject(); response.Data.SetProperty("count", list.Count); response.Data.SetProperty("list", Json.From(list, lower, depth, force)); return null; } /// 设置响应,当发生错误时设置响应。返回错误信息。 public static string Respond(ApiResponse response, IRecord record, bool lower = true) { if (response == null) return "Response 对象无效。"; if (record == null) { var error = "记录无效。"; response.Error(error); return error; } var json = Json.From(record, lower); if (json == null || !json.Available) { var error = "记录无法序列化。"; response.Error(error); return error; } if (response.Data == null) response.Data = Json.NewObject(); response.Data.Reset(json); return null; } /// 设置 status 为 error,并设置 message 的内容。 public static void Error(ApiResponse response, string message = "未知错误。") { if (response == null) return; response.Model = null; response.Status = "error"; response.Message = message ?? TextUtility.Empty; } /// 设置 status 为 exception,并设置 message 的内容。 public static void Exception(ApiResponse response, Exception exception) { if (response == null) return; response.Model = null; response.Status = "exception"; try { response.Message = exception == null ? "无效异常。" : exception.Message; response.Data = ToJson(exception); } catch { } } /// private static Json ToJson(Exception exception) { if (exception == null) return null; var json = Json.NewObject(); try { var ex = exception; json.SetProperty("type", exception.GetType().FullName); try { json.SetProperty("message", $"ex.Message({ex.GetType().FullName})"); json.SetProperty("helplink", ex.HelpLink); json.SetProperty("source", ex.Source); json.SetProperty("stack", Json.From((ex.StackTrace ?? "").Replace("\r", "").Split('\n'), false)); } catch { } // WebException 附加数据。 var webex = ex as System.Net.WebException; if (webex != null) { json.SetProperty("data", Json.From(webex.Data)); } } catch { } return json; } #endregion #region ApiModel /// 初始化 ApiMode 的属性。 public static void Initialize(ApiModel model, ApiRequest request, ApiResponse response, ApiOptions options, ApiProvider provider) { if (model == null) return; model._request = request; model._response = response; model._options = options; model._provider = provider; } #endregion #region Application & Path /// MapPath。 public static string MapPath(params string[] names) { var list = new List(); if (names != null) { var invalid = StorageUtility.InvalidPathChars; foreach (var name in names) { if (string.IsNullOrEmpty(name)) continue; var split = name.Split('/', '\\'); foreach (var s in split) { var sb = new StringBuilder(); foreach (var c in s) { if (Array.IndexOf(invalid, "c") < 0) sb.Append(c); } var t = sb.ToString(); if (!string.IsNullOrEmpty(t)) list.Add(t); } } } return list.Count > 1 ? StorageUtility.CombinePath(list.ToArray()) : ""; } #endregion } }