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.

589 lines
21 KiB

#if NETFX || NETCORE
using Apewer;
using Apewer.Network;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Reflection;
using System.Web;
using Apewer.Models;
#if NETCORE
using Microsoft.AspNetCore.Http;
#endif
namespace Apewer.Web
{
internal static class ApiInternals
{
#region Fields
private static Dictionary<string, ApiApplication> _entries = null;
private static List<Assembly> _assemblies = null;
#endregion
#region Relection
public static Dictionary<string, ApiApplication> Entries { get { return _entries; } set { _entries = value; } }
public static List<Assembly> Assemblies { get { return _assemblies; } set { _assemblies = value; } }
internal static Dictionary<string, ApiApplication> GetEntries(IEnumerable<Assembly> assemblies, bool sort = true)
{
var entries = new Dictionary<string, ApiApplication>();
var deduplicates = new List<Assembly>();
foreach (var assembly in assemblies)
{
if (assembly == null) continue;
if (deduplicates.Contains(assembly)) continue;
deduplicates.Add(assembly);
}
var aes = new ObjectSet<ApiApplication>();
foreach (var assembly in deduplicates)
{
var types = ClassUtility.GetTypes(assembly);
foreach (var type in types)
{
var application = GetApplication(type);
if (application == null) continue;
aes[application.Name] = application;
var methods = type.GetMethods();
var fes = new ObjectSet<ApiFunction>(true);
foreach (var method in methods)
{
var function = GetFunction(application, method);
if (function == null) continue;
fes[function.Name] = function;
}
var fns = new List<string>();
fns.AddRange(ClassUtility.GetOrigin(fes).Keys);
if (sort) fns.Sort();
foreach (var i in fns) application.Functions.Add(i, fes[i]);
}
}
var ans = new List<string>();
ans.AddRange(ClassUtility.GetOrigin(aes).Keys);
if (sort) ans.Sort();
foreach (var i in ans) entries.Add(i, aes[i]);
return entries;
}
internal static ApiApplication GetApplication(Type type)
{
if (type == null) return null;
// Check Type Properties
if (!type.IsClass) return null;
if (type.IsAbstract) return null;
if (type.IsGenericType) return null;
// Check Type ApiAttributes
var attributes = type.GetCustomAttributes(typeof(ApiAttribute), false);
if (attributes.Length < 1) return null;
// Check Base
if (!ClassUtility.IsInherits(type, typeof(ApiController))) return null;
// Entry
var entry = new ApiApplication();
entry.Type = type;
entry.Assembly = type.Assembly;
// Name
var api = (ApiAttribute)attributes[0];
var name = api.Name;
if (string.IsNullOrEmpty(name)) name = type.Name;
name = name.ToLower();
entry.Name = name;
// Caption
entry.Caption = api.Caption;
if (entry.Caption.Length < 1)
{
var captions = type.GetCustomAttributes(typeof(CaptionAttribute), true);
if (captions.Length > 0)
{
var caption = (CaptionAttribute)captions[0];
entry.Caption = caption.Title;
entry.Description = caption.Description;
}
}
// Visible
entry.Visible = api.Visible;
if (entry.Visible)
{
if (type.ContainsAttribute<HiddenAttribute>(false))
{
entry.Visible = false;
}
}
// Independent
if (type.ContainsAttribute<IndependentAttribute>(false))
{
entry.Independent = true;
}
// Module
entry.Module = TextUtility.Join("-", entry.Assembly.GetName().Name, entry.Assembly.GetName().Version.ToString());
return entry;
}
internal static ApiFunction GetFunction(ApiApplication application, MethodInfo method)
{
if (method == null) return null;
// 滤除构造函数。
if (method.IsConstructor) return null;
// 滤除继承的方法。
if (method.DeclaringType.Equals(typeof(object))) return null;
if (method.DeclaringType.Equals(typeof(ApiController))) return null;
// 滤除带参数的方法。
var parameters = method.GetParameters();
if (parameters.Length > 0) return null;
// Entry
var entry = new ApiFunction();
entry.Type = application.Type;
entry.Assembly = application.Type.Assembly;
entry.Method = method;
entry.Name = method.Name.ToLower();
// Caption
var captions = method.GetCustomAttributes(typeof(CaptionAttribute), true);
if (captions.Length > 0)
{
var caption = (CaptionAttribute)captions[0];
entry.Caption = caption.Title;
entry.Description = caption.Description;
}
// Visible
entry.Visible = true;
if (application.Visible)
{
var hidden = method.GetCustomAttributes(typeof(HiddenAttribute), false);
if (hidden.Length > 0) entry.Visible = false;
}
else
{
entry.Visible = false;
}
// Returnable
if (method.ReturnType.Equals(typeof(string)))
{
entry.Returnable = true;
}
return entry;
}
#endregion
#region ApiRequest
internal static StringPairs ParseUrlParameters
#if NETFX
(System.Web.HttpRequest request) => WebUtility.ParseParameters(request.Url.Query);
#else
(Microsoft.AspNetCore.Http.HttpRequest request)
{
var list = new StringPairs();
foreach (var key in request.Query.Keys)
{
if (request != null)
{
list.Add(new KeyValuePair<string, string>(key, request.Query[key]));
}
}
return list;
}
#endif
#if NETFX
internal static ApiRequest GetRequest(System.Web.HttpRequest request)
#else
internal static ApiRequest GetRequest(Microsoft.AspNetCore.Http.HttpRequest request)
#endif
{
if (request == null) return null;
if (string.IsNullOrEmpty(request.Path)) return null;
var apiRequest = new ApiRequest();
apiRequest.IP = WebUtility.GetClientIP(request, true);
apiRequest.Url = WebUtility.GetUrl(request);
apiRequest.Parameters = ParseUrlParameters(request);
apiRequest.UserAgent = WebUtility.GetUserAgent(request);
#if NETFX
apiRequest.Referrer = request.UrlReferrer;
#endif
// 获取 Http Method。
apiRequest.Method = WebUtility.GetMethod(request);
// 准备变量。
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;
// 头。
foreach (var kvp in WebUtility.GetHeaders(request))
{
apiRequest.Headers.Add(new KeyValuePair<string, string>(kvp.Key, kvp.Value));
switch (TextUtility.ToLower(kvp.Key))
{
case "user-agent":
apiRequest.UserAgent = kvp.Value;
break;
}
}
// 解析 POST 请求。
if (apiRequest.Method == HttpMethod.POST)
{
#if NETFX
var post = BinaryUtility.Read(request.InputStream);
#else
var post = BinaryUtility.Read(request.Body);
#endif
var text = TextUtility.FromBinary(post);
var json = Json.Parse(text) ?? Json.NewObject();
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");
// 最后检查 URL 路径。
var paths = request.Path.ToString().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;
}
#endregion
#region ApiResponse
/// <summary>设置 status 为 error,并设置 message 的内容。</summary>
internal static void RespondError(ApiResponse response, string message = "未知错误。")
{
if (response == null) return;
response.Type = ApiFormat.Json;
response.Status = "error";
response.Message = message ?? TextUtility.EmptyString;
}
/// <summary>设置 status 为 error,并设置 message 的内容。</summary>
internal static void RespondError(ApiResponse response, Exception exception)
{
if (response == null) return;
response.Exception = exception;
response.Type = ApiFormat.Json;
response.Status = "error";
try
{
response.Message = exception == null ? "无效异常。" : exception.Message;
response.Data.Reset(Json.NewObject());
response.Data["message"] = exception.Message;
response.Data["helplink"] = exception.HelpLink;
response.Data["source"] = exception.Source;
response.Data["stacktrace"] = Json.Parse(exception.StackTrace.Split('\n'), true);
}
catch { }
}
/// <summary>输出 UTF-8 文本。</summary>
internal static void RespondText(ApiResponse response, string content, string type = "text/plain")
{
if (response == null) return;
response.Type = ApiFormat.Text;
response.TextString = content;
response.TextType = type ?? "text/plain";
}
/// <summary>输出字节数组。</summary>
internal static void RespondBinary(ApiResponse response, byte[] content, string type = "application/octet-stream")
{
if (response == null) return;
response.Type = ApiFormat.Binary;
response.BinaryStream = null;
response.BinaryBytes = content;
response.BinaryType = type ?? "application/octet-stream";
}
/// <summary>输出二进制。</summary>
internal static void RespondBinary(ApiResponse response, Stream content, string type = "application/octet-stream")
{
if (response == null) return;
response.Type = ApiFormat.Binary;
response.BinaryStream = content;
response.BinaryBytes = null;
response.BinaryType = type ?? "application/octet-stream";
}
/// <summary>输出文件。</summary>
internal static void RespondFile(ApiResponse response, Stream stream, string name, string type = "application/octet-stream")
{
if (response == null) return;
response.Type = ApiFormat.File;
response.FileStream = stream;
response.FileName = name;
response.FileType = type ?? "application/octet-stream";
}
/// <summary>重定向。</summary>
public static void RespondRedirect(ApiResponse response, string url)
{
if (response == null) return;
response.Type = ApiFormat.Redirect;
response.RedirectUrl = url;
}
internal static string ExportJson(ApiResponse response, bool indented = true, bool exception = false)
{
if (response == null) return "{}";
var json = Json.NewObject();
json.SetProperty("beginning", response.Beginning ?? TextUtility.EmptyString);
json.SetProperty("ending", response.Ending ?? TextUtility.EmptyString);
json.SetProperty("random", response.Random);
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);
if (exception)
{
if (response.Exception == null) json.SetProperty("exception");
else
{
var exmessage = null as string;
var exstacktrace = null as string;
var exsource = null as string;
var exhelplink = null as string;
try
{
exmessage = response.Exception.Message;
exstacktrace = response.Exception.StackTrace;
exsource = response.Exception.Source;
exhelplink = response.Exception.HelpLink;
}
catch { }
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);
if (response.Exception is System.Net.WebException)
{
var webex = response.Exception as System.Net.WebException;
{
var array = Json.NewArray();
foreach (var k in webex.Data.Keys)
{
var item = Json.NewObject();
var v = webex.Data[k];
item.SetProperty(k.ToString(), Json.Parse(v.ToString()));
}
exjson.SetProperty("data", array);
}
exjson.SetProperty("", Json.Parse(webex.Response, true));
}
json.SetProperty("exception", exjson);
}
}
json.SetProperty("data", response.Data);
var text = json.ToString(indented);
return text;
}
#endregion
#region HttpResponse
public static void AddHeader
#if NETFX
(System.Web.HttpResponse response, string name, string value)
{
if (response == null || string.IsNullOrEmpty(name) || string.IsNullOrEmpty(value)) return;
#if NET20
try { response.AddHeader(name, value); } catch { }
#else
try { response.Headers.Add(name, value); } catch { }
#endif
}
#else
(Microsoft.AspNetCore.Http.HttpResponse response, string name, string value)
{
if (response == null || string.IsNullOrEmpty(name) || string.IsNullOrEmpty(value)) return;
try { response.Headers.Add(name, value); } catch { }
}
#endif
public static void AddHeaders
#if NETFX
(System.Web.HttpResponse response, NameValueCollection collection)
{
if (response == null || collection == null) return;
foreach (var key in collection.AllKeys) AddHeader(response, key, collection[key]);
}
#else
(Microsoft.AspNetCore.Http.HttpResponse response, NameValueCollection collection)
{
if (response == null) return;
if (collection == null) return;
foreach (var key in collection.AllKeys) AddHeader(response, key, collection[key]);
}
#endif
public static void AddHeaders
#if NETFX
(System.Web.HttpResponse response, StringPairs headers)
{
if (response == null || headers == null) return;
foreach (var key in headers.GetAllKeys())
{
var values = headers.GetValues(key);
foreach (var value in values) AddHeader(response, key, value);
}
}
#else
(Microsoft.AspNetCore.Http.HttpResponse response, StringPairs headers)
{
if (response == null || headers == null) return;
foreach (var key in headers.GetAllKeys())
{
var values = headers.GetValues(key);
foreach (var value in values) AddHeader(response, key, value);
}
}
#endif
public static void SetTextPlain
#if NETFX
(System.Web.HttpResponse response, string value = null)
{
if (response == null) return;
if (string.IsNullOrEmpty(value))
{
response.ContentType = "text/plain";
response.Charset = "utf-8";
}
else
{
response.ContentType = value;
}
}
#else
(Microsoft.AspNetCore.Http.HttpResponse response, string value = null)
{
if (response == null) return;
response.ContentType = string.IsNullOrEmpty(value) ? "text/plain; charset=utf-8" : value;
}
#endif
public static void SetContentLength
#if NETFX
(System.Web.HttpResponse response, long value)
{
if (response == null) return;
if (value < 0L) return;
response.AddHeader("Content-Length", value.ToString());
}
#else
(Microsoft.AspNetCore.Http.HttpResponse response, long value)
{
if (response == null) return;
if (value < 0L) return;
response.ContentLength = value;
}
#endif
public static Stream GetStream
#if NETFX
(System.Web.HttpResponse response)
{
if (response == null) return null;
return response.OutputStream;
}
#else
(Microsoft.AspNetCore.Http.HttpResponse response)
{
if (response == null) return null;
return response.Body;
}
#endif
#endregion
}
}
#endif