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.
568 lines
20 KiB
568 lines
20 KiB
using Apewer;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Text;
|
|
using System.Diagnostics;
|
|
using Apewer.Models;
|
|
using System.IO;
|
|
|
|
#if NETFX
|
|
using System.Web;
|
|
#endif
|
|
|
|
#if NETCORE
|
|
using Microsoft.AspNetCore.Http;
|
|
#endif
|
|
|
|
namespace Apewer.Web
|
|
{
|
|
|
|
/// <summary></summary>
|
|
public static class WebUtility
|
|
{
|
|
|
|
#region url
|
|
|
|
/// <summary>按 & 拆分多个参数。</summary>
|
|
public static StringPairs ParseParameters(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;
|
|
}
|
|
|
|
/// <summary>获取 URL 查询段,不存在的段为 NULL 值。可要求解码。</summary>
|
|
public static string GetSegmental(Uri url, int index = 3, bool decode = false)
|
|
{
|
|
try
|
|
{
|
|
if (url == null) return null;
|
|
if (index < 0) return null;
|
|
var sus = url.AbsolutePath.Split('/');
|
|
if (sus.Length > index)
|
|
{
|
|
var result = sus[index];
|
|
if (decode) result = TextUtility.DecodeUrl(result);
|
|
return result;
|
|
}
|
|
}
|
|
catch { }
|
|
return null;
|
|
}
|
|
|
|
/// <summary>获取参数并解码。</summary>
|
|
public static string GetParameter(string encoded, params string[] names)
|
|
{
|
|
if (string.IsNullOrEmpty(encoded)) return null;
|
|
if (names == null || names.Length < 1) return null;
|
|
|
|
// Names 转为小写,加强适配。
|
|
var lowerNames = new List<string>();
|
|
foreach (var name in names)
|
|
{
|
|
var lower = TextUtility.ToLower(name);
|
|
if (string.IsNullOrEmpty(lower)) continue;
|
|
lowerNames.Add(lower);
|
|
}
|
|
if (lowerNames.Count < 1) return null;
|
|
|
|
// 分参数对比。
|
|
var parameters = ParseParameters(encoded);
|
|
var matched = false;
|
|
foreach (var parameter in parameters)
|
|
{
|
|
var left = parameter.Key;
|
|
var right = parameter.Value;
|
|
var lowerLeft = TextUtility.ToLower(left);
|
|
if (lowerNames.Contains(right))
|
|
{
|
|
matched = true;
|
|
if (!string.IsNullOrEmpty(right)) return right;
|
|
}
|
|
}
|
|
|
|
return matched ? "" : null;
|
|
}
|
|
|
|
/// <summary>指定可能的参数名,从 URL 中获取参数值并解码。</summary>
|
|
public static string GetParameter(Uri url, params string[] names)
|
|
{
|
|
return url == null ? null : GetParameter(url.Query, names);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#if NETFX || NETCORE
|
|
|
|
#region http
|
|
|
|
/// <summary>获取直连端的 IP。</summary>
|
|
public static string GetDirectIP(ApiRequest request)
|
|
{
|
|
if (request == null) return null;
|
|
if (request.Context == null) return null;
|
|
return GetDirectIP(request.Context.Request);
|
|
}
|
|
|
|
/// <summary>获取直连端的 IP。</summary>
|
|
public static string GetDirectIP(HttpRequest request)
|
|
{
|
|
if (request == null) return null;
|
|
#if NETFX
|
|
return request.UserHostAddress;
|
|
#else
|
|
if (request.HttpContext == null || request.HttpContext.Connection == null) return null;
|
|
return request.HttpContext.Connection.RemoteIpAddress.ToString();
|
|
#endif
|
|
}
|
|
|
|
/// <summary>获取客户端 IP。</summary>
|
|
public static string GetClientIP(ApiRequest request, bool withProxy = false)
|
|
{
|
|
if (request == null) return null;
|
|
if (request.Context == null) return null;
|
|
return GetClientIP(request.Context.Request, withProxy);
|
|
}
|
|
|
|
/// <summary>获取客户端 IP。</summary>
|
|
#if NETFX
|
|
public static string GetClientIP(System.Web.HttpRequest request, bool withProxy = false)
|
|
#else
|
|
public static string GetClientIP(Microsoft.AspNetCore.Http.HttpRequest request, bool withProxy = false)
|
|
#endif
|
|
{
|
|
if (request == null) return null;
|
|
var direct = GetDirectIP(request);
|
|
var x_forwarded_for = GetForwardedIP(request);
|
|
|
|
if (string.IsNullOrEmpty(direct))
|
|
{
|
|
return string.IsNullOrEmpty(x_forwarded_for) ? "NULL" : $"{x_forwarded_for},NULL";
|
|
}
|
|
else
|
|
{
|
|
return string.IsNullOrEmpty(x_forwarded_for) ? $"{direct}" : $"{x_forwarded_for},{direct}";
|
|
}
|
|
}
|
|
|
|
/// <summary>请求来自于可信客户端。</summary>
|
|
public static bool FromTrusted(HttpRequest request, params string[] addresses)
|
|
{
|
|
if (request == null) return false;
|
|
|
|
// 检测直连 IP,
|
|
var direct = GetDirectIP(request);
|
|
if (!string.IsNullOrEmpty(direct))
|
|
{
|
|
if (NetworkUtility.FromLAN(direct)) return true;
|
|
}
|
|
|
|
// 遍历传入的地址。
|
|
if (addresses != null && addresses.Length > 0)
|
|
{
|
|
// 读取 HTTP 头。
|
|
var forwarded = GetForwardedIP(request);
|
|
if (!NetworkUtility.IsIP(forwarded)) return false;
|
|
|
|
foreach (var address in addresses)
|
|
{
|
|
if (NetworkUtility.IsIP(address))
|
|
{
|
|
if (forwarded == address) return true;
|
|
}
|
|
else
|
|
{
|
|
var ip = NetworkUtility.Resolve(address);
|
|
if (NetworkUtility.IsIP(ip))
|
|
{
|
|
if (forwarded == ip) return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>请求来自于可信客户端。</summary>
|
|
public static bool FromTrusted(HttpContext context, params string[] addresses)
|
|
{
|
|
if (context == null) return false;
|
|
return FromTrusted(context.Request, addresses);
|
|
}
|
|
|
|
/// <summary>请求来自于可信客户端。</summary>
|
|
public static bool FromTrusted(ApiRequest request, params string[] addresses)
|
|
{
|
|
if (request == null) return false;
|
|
return FromTrusted(request.Context, addresses);
|
|
}
|
|
|
|
/// <summary>获取 HTTP 方法。</summary>
|
|
public static Network.HttpMethod GetMethod(string method)
|
|
{
|
|
if (!string.IsNullOrEmpty(method))
|
|
{
|
|
var upper = TextUtility.ToUpper(method);
|
|
if (upper.Contains("OPTIONS")) return Network.HttpMethod.OPTIONS;
|
|
else if (upper.Contains("POST")) return Network.HttpMethod.POST;
|
|
else if (upper.Contains("GET")) return Network.HttpMethod.GET;
|
|
else if (upper.Contains("CONNECT")) return Network.HttpMethod.CONNECT;
|
|
else if (upper.Contains("DELETE")) return Network.HttpMethod.DELETE;
|
|
else if (upper.Contains("HEAD")) return Network.HttpMethod.HEAD;
|
|
else if (upper.Contains("PATCH")) return Network.HttpMethod.PATCH;
|
|
else if (upper.Contains("PUT")) return Network.HttpMethod.PUT;
|
|
else if (upper.Contains("TRACE")) return Network.HttpMethod.TRACE;
|
|
}
|
|
return Network.HttpMethod.NULL;
|
|
}
|
|
|
|
/// <summary>获取 HTTP 方法。</summary>
|
|
public static Network.HttpMethod GetMethod(HttpRequest request)
|
|
{
|
|
if (request == null) return Network.HttpMethod.NULL;
|
|
#if NETFX
|
|
return GetMethod(request.HttpMethod);
|
|
#else
|
|
return GetMethod(request.Method);
|
|
#endif
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region request
|
|
|
|
/// <summary>获取请求的 URL,失败时返回 NULL 值。</summary>
|
|
public static Uri GetUrl(HttpRequest request)
|
|
{
|
|
if (request == null) return null;
|
|
#if NETFX
|
|
return request.Url;
|
|
#else
|
|
var context = request.HttpContext;
|
|
if (context == null) return null;
|
|
|
|
var https = request.IsHttps;
|
|
var port = context.Connection.LocalPort;
|
|
var query = request.QueryString == null ? null : request.QueryString.Value;
|
|
|
|
var sb = new StringBuilder();
|
|
sb.Append(https ? "https://" : "http://");
|
|
sb.Append(request.Host.Host ?? "");
|
|
if ((https && port != 443) || (!https && port != 80))
|
|
{
|
|
sb.Append(":");
|
|
sb.Append(port);
|
|
}
|
|
sb.Append(request.Path);
|
|
if (!string.IsNullOrEmpty(query))
|
|
{
|
|
sb.Append("?");
|
|
sb.Append(query);
|
|
}
|
|
|
|
var url = sb.ToString();
|
|
var uri = new Uri(url);
|
|
return uri;
|
|
#endif
|
|
}
|
|
|
|
/// <summary>获取 HTTP 头。</summary>
|
|
public static StringPairs GetHeaders(HttpRequest request)
|
|
{
|
|
var sp = new StringPairs();
|
|
if (request != null && request.Headers != null)
|
|
{
|
|
#if NETFX
|
|
foreach (var key in request.Headers.AllKeys)
|
|
#else
|
|
foreach (var key in request.Headers.Keys)
|
|
#endif
|
|
{
|
|
var value = request.Headers[key].ToString();
|
|
sp.Add(new KeyValuePair<string, string>(key, value));
|
|
}
|
|
}
|
|
return sp;
|
|
}
|
|
|
|
/// <summary>获取 X-Forwarded-For。</summary>
|
|
public static string GetForwardedIP(StringPairs headers)
|
|
{
|
|
if (headers == null) return null;
|
|
var value = headers.GetValue("x-forwarded-for");
|
|
if (!string.IsNullOrEmpty(value)) value = value.Split(":")[0];
|
|
return value;
|
|
}
|
|
|
|
/// <summary>获取 X-Forwarded-For。</summary>
|
|
public static string GetForwardedIP(ApiRequest request)
|
|
{
|
|
if (request == null) return null;
|
|
return GetForwardedIP(request.Headers);
|
|
}
|
|
|
|
/// <summary>获取 X-Forwarded-For。</summary>
|
|
public static string GetForwardedIP(HttpRequest request) => GetForwardedIP(GetHeaders(request));
|
|
|
|
/// <summary>获取 User Agent。</summary>
|
|
public static string GetUserAgent(StringPairs headers)
|
|
{
|
|
if (headers == null) return null;
|
|
return headers.GetValue("user-agent");
|
|
}
|
|
|
|
/// <summary>获取 User Agent。</summary>
|
|
public static string GetUserAgent(HttpRequest request) => GetUserAgent(GetHeaders(request));
|
|
|
|
#endregion
|
|
|
|
#region response
|
|
|
|
/// <summary>发送 302 状态,将客户端重新定向到新 URL。</summary>
|
|
public static void RedirectResponse(HttpResponse response, string url)
|
|
{
|
|
if (response == null) return;
|
|
try { response.Redirect(url, true); } catch { }
|
|
}
|
|
|
|
/// <summary>停止并关闭响应流。可指定向发送缓冲区的数据。</summary>
|
|
public static void StopResponse(HttpResponse response, bool flush = true)
|
|
{
|
|
if (response == null) return;
|
|
#if NETFX
|
|
try { if (flush) response.Flush(); } catch { }
|
|
try { response.Close(); } catch { }
|
|
// try { response.End(); } catch { }
|
|
#endif
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region api
|
|
|
|
#if NETFX
|
|
|
|
/// <summary>获取 URL 查询段,不存在的段为 NULL 值。可要求解码。</summary>
|
|
public static string GetSegmentalUrl(ApiRequest request, int index = 3, bool decode = false)
|
|
{
|
|
return request == null ? null : GetSegmental(request.Url, index, decode);
|
|
}
|
|
|
|
#endif
|
|
|
|
/// <summary>获取参数。</summary>
|
|
public static string GetParameter(List<KeyValuePair<string, string>> parameters, params string[] names)
|
|
{
|
|
if (parameters == null || parameters.Count < 1) return null;
|
|
if (names == null || names.Length < 1) return null;
|
|
|
|
var lowerNames = new List<string>();
|
|
foreach (var name in names)
|
|
{
|
|
var lower = TextUtility.ToLower(name);
|
|
if (string.IsNullOrEmpty(lower)) continue;
|
|
lowerNames.Add(lower);
|
|
}
|
|
|
|
foreach (var parameter in parameters)
|
|
{
|
|
var lowerKey = TextUtility.ToLower(parameter.Key);
|
|
if (lowerNames.Contains(lowerKey))
|
|
{
|
|
var value = parameter.Value;
|
|
if (!string.IsNullOrEmpty(value)) return value;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>获取参数,指定可能的参数名,从 URL 中获取参数时将解码。</summary>
|
|
public static string GetParameter(ApiRequest request, params string[] names)
|
|
{
|
|
if (request == null) return null;
|
|
if (names == null || names.Length < 1) return null;
|
|
|
|
var sameNames = new List<string>();
|
|
var lowerNames = new List<string>();
|
|
foreach (var name in names)
|
|
{
|
|
if (string.IsNullOrEmpty(name)) continue;
|
|
if (!sameNames.Contains(name)) sameNames.Add(name);
|
|
|
|
var lower = TextUtility.ToLower(name);
|
|
if (string.IsNullOrEmpty(lower)) continue;
|
|
if (!lowerNames.Contains(lower)) lowerNames.Add(lower);
|
|
}
|
|
|
|
// 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;
|
|
if (sameNames.Contains(property.Name) && property.Value != null)
|
|
{
|
|
var value = property.Value;
|
|
if (value == null) continue;
|
|
var text = value.ToString();
|
|
if (!string.IsNullOrEmpty(text)) return text;
|
|
}
|
|
}
|
|
|
|
// 以小写模糊匹配。
|
|
foreach (var property in properties)
|
|
{
|
|
if (!property.IsProperty) continue;
|
|
var lowerName = TextUtility.ToLower(property.Name);
|
|
if (lowerNames.Contains(lowerName) && property.Value != null)
|
|
{
|
|
var value = property.Value;
|
|
if (value == null) continue;
|
|
var text = value.ToString();
|
|
if (!string.IsNullOrEmpty(text)) return text;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 从已解析的 Parameters 中搜索。
|
|
if (request.Parameters != null)
|
|
{
|
|
var value = GetParameter(request.Parameters);
|
|
if (!string.IsNullOrEmpty(value)) return value;
|
|
}
|
|
|
|
// GET 参数。
|
|
if (request.Parameters != null)
|
|
{
|
|
// 全字匹配。
|
|
foreach (var kvp in request.Parameters)
|
|
{
|
|
if (string.IsNullOrEmpty(kvp.Key) || string.IsNullOrEmpty(kvp.Value)) continue;
|
|
if (sameNames.Contains(kvp.Key)) return kvp.Value;
|
|
}
|
|
|
|
// 以小写模糊匹配。
|
|
foreach (var kvp in request.Parameters)
|
|
{
|
|
var lowerKey = TextUtility.ToLower(kvp.Key);
|
|
if (string.IsNullOrEmpty(lowerKey) || string.IsNullOrEmpty(kvp.Value)) continue;
|
|
if (lowerNames.Contains(lowerKey)) return kvp.Value;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>转移 WebAPI 请求到其它服务器、应用或功能。</summary>
|
|
public 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 "ApiControllser 无效。";
|
|
|
|
var s = Json.NewObject();
|
|
s.SetProperty("random", TextUtility.NewGuid());
|
|
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 = Network.HttpClient.SimplePost(url, s.ToString().GetBytes());
|
|
var r = Json.Parse(c.Response.Data.GetString());
|
|
|
|
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;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Log
|
|
|
|
private static object LogLocker = new object();
|
|
|
|
/// <summary>获取日志文件路径发生错误时返回 NULL 值。</summary>
|
|
/// <remarks>d:\app\log\2020-02-02.log</remarks>
|
|
/// <remarks>d:\www\app_data\log\2020-02-02.log</remarks>
|
|
public static string GetLogPath()
|
|
{
|
|
// 找到 App_Data 目录。
|
|
var appDir = KernelUtility.ApplicationBasePath;
|
|
var dataDir = Path.Combine(appDir, "app_data");
|
|
if (StorageUtility.DirectoryExists(dataDir)) appDir = dataDir;
|
|
|
|
// 检查 Log 目录,不存在时创建,创建失败时返回。
|
|
var logDir = Path.Combine(appDir, "log");
|
|
if (!StorageUtility.AssureDirectory(logDir)) return null;
|
|
|
|
// 文件不存在时创建新文件,无法创建时返回。
|
|
var date = DateTime.Now.ToLucid(true, false, false, false);
|
|
var filePath = Path.Combine(logDir, date + ".log");
|
|
StorageUtility.CreateFile(filePath, 0, false);
|
|
if (!StorageUtility.FileExists(filePath)) return null;
|
|
|
|
// 返回 log 文件路径。
|
|
return filePath;
|
|
}
|
|
|
|
/// <summary>写入日志。</summary>
|
|
public static string WriteLog(params object[] content)
|
|
{
|
|
lock (LogLocker)
|
|
{
|
|
var path = GetLogPath();
|
|
if (path.IsEmpty()) return "无法获取日志文件路径。";
|
|
|
|
var text = TextUtility.Join(" | ", content);
|
|
text = TextUtility.Merge(DateTimeUtility.NowLucid, " ", text, "\r\n");
|
|
|
|
var bytes = TextUtility.ToBinary(text);
|
|
if (!StorageUtility.AppendFile(path, bytes)) return "写日志文件失败。";
|
|
|
|
return null;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
}
|
|
|