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.
1206 lines
43 KiB
1206 lines
43 KiB
using Apewer.Models;
|
|
using Apewer.Source;
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Collections.Specialized;
|
|
using System.IO;
|
|
using System.Net;
|
|
using System.Text;
|
|
|
|
#if NETFX
|
|
using System.Web;
|
|
#endif
|
|
|
|
#if NETCORE
|
|
using Microsoft.AspNetCore.Http;
|
|
#endif
|
|
|
|
namespace Apewer.Web
|
|
{
|
|
|
|
/// <summary></summary>
|
|
public static class WebUtility
|
|
{
|
|
|
|
/// <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, bool trim, params string[] names)
|
|
{
|
|
if (string.IsNullOrEmpty(encoded)) return null;
|
|
if (names == null || names.Length < 1) return null;
|
|
|
|
// Names 转为小写,加强适配。
|
|
var lowerNames = new List<string>(names.Length);
|
|
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;
|
|
if (trim) right = TextUtility.ToLower(right);
|
|
var lowerLeft = TextUtility.ToLower(left);
|
|
if (lowerNames.Contains(right))
|
|
{
|
|
matched = true;
|
|
if (!string.IsNullOrEmpty(right)) return right;
|
|
}
|
|
}
|
|
|
|
return matched ? "" : null;
|
|
}
|
|
|
|
/// <summary>获取参数并解码,默认不修剪参数值。</summary>
|
|
public static string GetParameter(string encoded, params string[] names) => GetParameter(encoded, false, names);
|
|
|
|
/// <summary>指定可能的参数名,从 URL 中获取参数值并解码,默认不修剪参数值。</summary>
|
|
public static string GetParameter(Uri url, params string[] names) => url == null ? null : GetParameter(url.Query, false, names);
|
|
|
|
/// <summary>获取参数,默认不修剪参数值。</summary>
|
|
public static string GetParameter(List<KeyValuePair<string, string>> parameters, params string[] names) => GetParameter(parameters, false, names);
|
|
|
|
/// <summary>指定可能的参数名,从 URL 中获取参数值并解码,可要求修剪参数值。</summary>
|
|
public static string GetParameter(Uri url, bool trim, params string[] names) => url == null ? null : GetParameter(url.Query, trim, names);
|
|
|
|
/// <summary>获取参数,可要求修剪参数值。</summary>
|
|
public static string GetParameter(List<KeyValuePair<string, string>> 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<string>(names.Length);
|
|
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 (trim) value = TextUtility.ToLower(value);
|
|
if (!string.IsNullOrEmpty(value)) return value;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>修剪 IP 地址,去除无效的部分。</summary>
|
|
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 : "";
|
|
}
|
|
|
|
private static string GetClientIP(string directIP, StringPairs headers)
|
|
{
|
|
// 获取直连 IP。
|
|
var ip = directIP;
|
|
|
|
if (NetworkUtility.FromLAN(ip)) // 直连 IP 来自内网。
|
|
{
|
|
// 请求可能由内网服务器反向代理,检测 Forwarded IP,取最后一个 IP。
|
|
foreach (var header in headers)
|
|
{
|
|
if (header.Key != "x-forwarded-for") continue;
|
|
|
|
var fips = new List<string>();
|
|
var split = header.Value.Split(',', ' ');
|
|
foreach (var para in split)
|
|
{
|
|
var fip = TrimIP(para);
|
|
if (!string.IsNullOrEmpty(fip)) fips.Add(fip);
|
|
}
|
|
if (fips.Count > 0) return fips[fips.Count - 1];
|
|
break;
|
|
}
|
|
}
|
|
else // 直连 IP 来自公网。
|
|
{
|
|
// 请求可能由阿里云 CDN 反向代理,检测 Ali-CDN-Real-IP。
|
|
foreach (var header in headers)
|
|
{
|
|
if (header.Key != "ali-cdn-real-ip") continue;
|
|
|
|
var alicdn_ip = TrimIP(header.Value);
|
|
if (!string.IsNullOrEmpty(alicdn_ip)) return alicdn_ip;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return ip;
|
|
}
|
|
|
|
/// <summary>请求来自于可信客户端。</summary>
|
|
public static bool FromTrusted(string clientIP, params string[] addresses)
|
|
{
|
|
var cip = TrimIP(clientIP);
|
|
if (!NetworkUtility.IsIP(cip)) return false;
|
|
|
|
// 解析可信地址。
|
|
if (addresses == null) return false;
|
|
foreach (var address in addresses)
|
|
{
|
|
if (NetworkUtility.IsIP(address))
|
|
{
|
|
if (address == cip) return true;
|
|
continue;
|
|
}
|
|
|
|
var tip = NetworkUtility.Resolve(address);
|
|
if (NetworkUtility.IsIP(tip))
|
|
{
|
|
if (tip == cip) return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <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>获取 X-Forwarded-For,不存在时返回 NULL 值。</summary>
|
|
public static string GetForwardedIP(StringPairs headers)
|
|
{
|
|
if (headers == null) return "";
|
|
var value = headers.GetValue("x-forwarded-for", true);
|
|
if (string.IsNullOrEmpty(value)) return "";
|
|
|
|
var split = value.Split(',');
|
|
var ips = new List<string>();
|
|
foreach (var para in split)
|
|
{
|
|
var ip = TrimIP(para);
|
|
if (string.IsNullOrEmpty(ip)) continue;
|
|
ips.Add(ip);
|
|
}
|
|
if (ips.Count > 0) return string.Join(", ", ips.ToArray());
|
|
|
|
return "";
|
|
}
|
|
|
|
/// <summary>获取 User Agent。</summary>
|
|
public static string GetUserAgent(StringPairs headers) => headers == null ? null : headers.GetValue("user-agent");
|
|
|
|
/// <summary>获取查询字符串。</summary>
|
|
public static string QueryString(ApiRequest request, string name)
|
|
{
|
|
if (request == null) return null;
|
|
if (request.Parameters == null) return null;
|
|
if (string.IsNullOrEmpty(name)) return null;
|
|
|
|
return request.Parameters.GetValue(name, true);
|
|
}
|
|
|
|
/// <summary>获取表单。</summary>
|
|
public static string Form(ApiRequest request, string name)
|
|
{
|
|
if (request == null) return null;
|
|
if (string.IsNullOrEmpty(request.PostText)) return null;
|
|
if (string.IsNullOrEmpty(name)) return null;
|
|
|
|
var ps = ParseParameters(request.PostText, true);
|
|
return ps.GetValue(name, true);
|
|
}
|
|
|
|
#region ApiController
|
|
|
|
/// <summary>以 POST 转移请求到其它 URL。</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 "ApiController 无效。";
|
|
|
|
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 = new Network.HttpClient();
|
|
c.Request.Url = url;
|
|
c.Request.Method = Network.HttpMethod.POST;
|
|
// TODO 设置 Request 的 Cookies。
|
|
c.Request.Data = s.ToString().GetBytes();
|
|
|
|
var e = c.Send();
|
|
if (e != null) return e.Message;
|
|
|
|
// TODO 解析 Response 的 Cookies。
|
|
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;
|
|
}
|
|
|
|
/// <summary>创建新控制器实例并运行。</summary>
|
|
/// <typeparam name="T">新控制器的类型。</typeparam>
|
|
/// <param name="current">当前控制器。</param>
|
|
public static T Run<T>(ApiController current) where T : ApiController, new()
|
|
{
|
|
if (current == null) return null;
|
|
var target = new T();
|
|
target.Request = current.Request;
|
|
target.Response = current.Response;
|
|
target.AfterInitialized?.Invoke();
|
|
return target;
|
|
}
|
|
|
|
/// <summary>创建新控制器实例并运行。</summary>
|
|
/// <param name="current">当前控制器。</param>
|
|
/// <param name="controller">新控制器的类型。</param>
|
|
public static ApiController Run(ApiController current, Type controller)
|
|
{
|
|
if (current == null || controller == null) return null;
|
|
if (!RuntimeUtility.IsInherits(controller, typeof(ApiController))) return null;
|
|
var target = ApiProcessor.CreateController(controller);
|
|
if (target == null) return null;
|
|
target.Request = current.Request;
|
|
target.Response = current.Response;
|
|
target.AfterInitialized?.Invoke();
|
|
return target;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ApiRequest
|
|
|
|
/// <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);
|
|
}
|
|
|
|
/// <summary>获取参数,指定可能的参数名,从 URL 中获取参数时将解码,默认不修剪参数值。</summary>
|
|
public static string GetParameter(ApiRequest request, params string[] names) => GetParameter(request, false, names);
|
|
|
|
/// <summary>获取参数,指定可能的参数名,从 URL 中获取参数时将解码,可要求修剪参数值。</summary>
|
|
public static string GetParameter(ApiRequest request, bool trim, params string[] names)
|
|
{
|
|
if (request == null) return null;
|
|
if (names == null || names.Length < 1) return null;
|
|
|
|
var dedupNames = new List<string>(names.Length);
|
|
var lowerNames = new List<string>(names.Length);
|
|
foreach (var name in names)
|
|
{
|
|
if (string.IsNullOrEmpty(name)) continue;
|
|
if (dedupNames.Contains(name)) continue;
|
|
else dedupNames.Add(name);
|
|
|
|
var lower = TextUtility.ToLower(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.ToLower(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 = GetParameter(request.Parameters, trim, names);
|
|
if (!string.IsNullOrEmpty(value)) return value;
|
|
if (value != null) matched = true;
|
|
}
|
|
|
|
return matched ? "" : null;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ApiResponse
|
|
|
|
/// <summary>设置响应。</summary>
|
|
public static string Respond(ApiResponse response, Json data, bool lower = true)
|
|
{
|
|
if (response == null) return "Response 对象无效。";
|
|
|
|
if (data != null)
|
|
{
|
|
if (lower) data = Json.ToLower(data);
|
|
response.Data.Reset(data);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>设置响应,当发生错误时设置响应。返回错误信息。</summary>
|
|
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.Parse(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.Parse(list, lower, depth, force));
|
|
return null;
|
|
}
|
|
|
|
/// <summary>设置响应,当发生错误时设置响应。返回错误信息。</summary>
|
|
public static string Respond(ApiResponse response, Record record, bool lower = true)
|
|
{
|
|
if (response == null) return "Response 对象无效。";
|
|
|
|
if (record == null)
|
|
{
|
|
var error = "记录无效。";
|
|
response.Error(error);
|
|
return error;
|
|
}
|
|
|
|
var json = Json.Parse(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;
|
|
}
|
|
|
|
/// <summary>设置 status 为 error,并设置 message 的内容。</summary>
|
|
public static void SetError(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>
|
|
public static void SetError(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>
|
|
public static void SetText(ApiResponse response, string content, string type = "text/plain; charset=utf-8")
|
|
{
|
|
if (response == null) return;
|
|
response.Type = ApiFormat.Text;
|
|
response.TextString = content;
|
|
response.TextType = type ?? "text/plain; charset=utf-8";
|
|
}
|
|
|
|
/// <summary>输出字节数组。</summary>
|
|
public static void SetBinary(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>
|
|
public static void SetBinary(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>
|
|
public static void SetFile(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 SetRedirect(ApiResponse response, string url)
|
|
{
|
|
if (response == null) return;
|
|
response.Type = ApiFormat.Redirect;
|
|
response.RedirectUrl = url;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Application & Path
|
|
|
|
/// <summary>获取程序目录的路径。</summary>
|
|
public static string AppDirectory
|
|
{
|
|
get
|
|
{
|
|
// AppDomain.CurrentDomain.BaseDirectory
|
|
// AppDomain.CurrentDomain.SetupInformation.ApplicationBase
|
|
return AppDomain.CurrentDomain.BaseDirectory;
|
|
}
|
|
}
|
|
|
|
/// <summary>MapPath。</summary>
|
|
public static string MapPath(params string[] names)
|
|
{
|
|
var list = new List<string>();
|
|
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()) : list[0];
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Request - GetMethod
|
|
|
|
/// <summary>获取 HTTP 方法。</summary>
|
|
public static Network.HttpMethod GetMethod(HttpListenerRequest request)
|
|
{
|
|
if (request == null) return Network.HttpMethod.NULL;
|
|
return GetMethod(request.HttpMethod);
|
|
}
|
|
|
|
#if NETFX || NETCORE
|
|
|
|
/// <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
|
|
}
|
|
|
|
#endif
|
|
|
|
#endregion
|
|
|
|
#region Request - Client IP
|
|
|
|
/// <summary>获取直连端的 IP。</summary>
|
|
public static string GetDirectIP(HttpListenerRequest request)
|
|
{
|
|
if (request == null) return null;
|
|
var ip = request.UserHostAddress;
|
|
return TrimIP(ip);
|
|
}
|
|
|
|
/// <summary>获取客户端 IP。</summary>
|
|
public static string GetClientIP(HttpListenerRequest request)
|
|
{
|
|
if (request == null) return null;
|
|
var headers = GetHeaders(request, true);
|
|
var directIP = GetDirectIP(request);
|
|
return GetClientIP(directIP, headers);
|
|
}
|
|
|
|
#if NETFX || NETCORE
|
|
|
|
/// <summary>获取直连端的 IP。</summary>
|
|
public static string GetDirectIP(HttpRequest request)
|
|
{
|
|
if (request == null) return null;
|
|
#if NETFX
|
|
var ip = request.UserHostAddress;
|
|
#else
|
|
if (request.HttpContext == null || request.HttpContext.Connection == null) return null;
|
|
var ip = request.HttpContext.Connection.RemoteIpAddress.ToString();
|
|
#endif
|
|
return TrimIP(ip);
|
|
}
|
|
|
|
/// <summary>获取客户端 IP。</summary>
|
|
public static string GetClientIP(HttpRequest request)
|
|
{
|
|
if (request == null) return null;
|
|
var headers = GetHeaders(request, true);
|
|
var directIP = GetDirectIP(request);
|
|
return GetClientIP(directIP, headers);
|
|
}
|
|
|
|
#endif
|
|
|
|
#endregion
|
|
|
|
#region Request - URL
|
|
|
|
/// <summary>获取请求的 URL,失败时返回 NULL 值。</summary>
|
|
public static Uri GetUrl(HttpListenerRequest request)
|
|
{
|
|
if (request == null) return null;
|
|
return request.Url;
|
|
}
|
|
|
|
#if NETFX || NETCORE
|
|
|
|
/// <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
|
|
}
|
|
|
|
#endif
|
|
|
|
#endregion
|
|
|
|
#region Request - Parameters
|
|
|
|
/// <summary>解析 URL 的查询字符串,获取所有已解码的参数。</summary>
|
|
public static StringPairs ParseUrlParameters(HttpListenerRequest request)
|
|
{
|
|
if (request == null) return new StringPairs();
|
|
if (request.Url == null) return new StringPairs();
|
|
return ParseParameters(request.Url.Query);
|
|
}
|
|
|
|
#if NETFX || NETCORE
|
|
|
|
/// <summary>解析 URL 的查询字符串,获取所有已解码的参数。</summary>
|
|
public static StringPairs ParseUrlParameters(HttpRequest request)
|
|
{
|
|
if (request == null) return new StringPairs();
|
|
#if NETFX
|
|
if (request.Url == null) return new StringPairs();
|
|
return ParseParameters(request.Url.Query);
|
|
#else
|
|
var list = new StringPairs();
|
|
if (request.Query == null) return list;
|
|
|
|
var keys = request.Query.Keys;
|
|
list.Capacity = keys.Count;
|
|
foreach (var key in keys)
|
|
{
|
|
list.Add(new KeyValuePair<string, string>(key, request.Query[key]));
|
|
}
|
|
list.Capacity = list.Count;
|
|
return list;
|
|
#endif
|
|
}
|
|
|
|
#endif
|
|
|
|
#endregion
|
|
|
|
#region Request - Headers
|
|
|
|
/// <summary>获取 HTTP 头。</summary>
|
|
public static StringPairs GetHeaders(HttpListenerRequest request, bool lowerKey = false)
|
|
{
|
|
var sp = new StringPairs();
|
|
if (request != null && request.Headers != null)
|
|
{
|
|
foreach (var key in request.Headers.AllKeys)
|
|
{
|
|
try
|
|
{
|
|
var v = request.Headers[key];
|
|
if (string.IsNullOrEmpty(v)) continue;
|
|
var k = lowerKey ? key.ToLower() : key;
|
|
sp.Add(k, v);
|
|
}
|
|
catch { }
|
|
}
|
|
}
|
|
return sp;
|
|
}
|
|
|
|
/// <summary>获取 User Agent。</summary>
|
|
public static string GetUserAgent(HttpListenerRequest request) => GetUserAgent(GetHeaders(request));
|
|
|
|
#if NETFX || NETCORE
|
|
|
|
/// <summary>获取 HTTP 头。</summary>
|
|
public static StringPairs GetHeaders(HttpRequest request, bool lowerKey = false)
|
|
{
|
|
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
|
|
{
|
|
try
|
|
{
|
|
var v = request.Headers[key];
|
|
if (string.IsNullOrEmpty(v)) continue;
|
|
var k = lowerKey ? key.ToLower() : key;
|
|
sp.Add(k, v);
|
|
}
|
|
catch { }
|
|
}
|
|
}
|
|
return sp;
|
|
}
|
|
|
|
/// <summary>获取 X-Forwarded-For。</summary>
|
|
public static string GetForwardedIP(HttpRequest request) => GetForwardedIP(GetHeaders(request));
|
|
|
|
/// <summary>获取 User Agent。</summary>
|
|
public static string GetUserAgent(HttpRequest request) => GetUserAgent(GetHeaders(request));
|
|
|
|
#endif
|
|
|
|
#endregion
|
|
|
|
#region Response - Redirect
|
|
|
|
/// <summary>将客户端重新定向到新 URL。</summary>
|
|
/// <remarks>响应 302 状态。</remarks>
|
|
public static void Redirect(HttpListenerResponse response, string url)
|
|
{
|
|
if (response == null) return;
|
|
try { response.Redirect(url); } catch { }
|
|
}
|
|
|
|
#if NETFX
|
|
|
|
/// <summary>将客户端重新定向到新 URL。</summary>
|
|
/// <remarks>响应 302 状态。</remarks>
|
|
public static void Redirect(HttpResponse response, string url)
|
|
{
|
|
if (response == null) return;
|
|
try { response.Redirect(url, true); } catch { }
|
|
}
|
|
|
|
#endif
|
|
|
|
#if NETCORE
|
|
|
|
/// <summary>将客户端重新定向到新 URL。</summary>
|
|
/// <remarks>默认响应 302 状态。可指定 permanent = true 以响应 301 状态。</remarks>
|
|
public static void Redirect(HttpResponse response, string url, bool permanent = false)
|
|
{
|
|
if (response == null) return;
|
|
try { response.Redirect(url, permanent); } catch { }
|
|
}
|
|
|
|
#endif
|
|
|
|
#endregion
|
|
|
|
#region Response - Stop
|
|
|
|
/// <summary>停止并关闭响应流。可指定向发送缓冲区的数据。</summary>
|
|
public static void Stop(HttpListenerResponse response, bool flush = true)
|
|
{
|
|
if (response == null) return;
|
|
try { if (flush) response.OutputStream.Flush(); } catch { }
|
|
try { response.Close(); } catch { }
|
|
}
|
|
|
|
#if NETFX || NETCORE
|
|
|
|
/// <summary>停止并关闭响应流。可指定向发送缓冲区的数据。</summary>
|
|
public static void Stop(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
|
|
}
|
|
|
|
#endif
|
|
|
|
#endregion
|
|
|
|
#region Response - Headers
|
|
|
|
/// <summary>从 Response 头中移除 Server 和 X-Powered-By 属性。</summary>
|
|
public static void RemoveServer(HttpListenerResponse response)
|
|
{
|
|
if (response == null) return;
|
|
var keys = new List<string>(response.Headers.AllKeys);
|
|
if (keys.Contains("Server")) response.Headers.Remove("Server");
|
|
if (keys.Contains("X-Powered-By")) response.Headers.Remove("X-Powered-By");
|
|
}
|
|
|
|
/// <summary>添加 Header 属性,返回错误信息。</summary>
|
|
public static string AddHeader(HttpListenerResponse response, string name, string value)
|
|
{
|
|
if (response == null || string.IsNullOrEmpty(name) || string.IsNullOrEmpty(value)) return "参数无效。";
|
|
try
|
|
{
|
|
response.AddHeader(name, value);
|
|
return null;
|
|
}
|
|
catch (Exception ex) { return ex.Message; }
|
|
}
|
|
|
|
/// <summary>添加多个 Headers 属性。</summary>
|
|
public static void AddHeaders(HttpListenerResponse 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);
|
|
}
|
|
}
|
|
|
|
#if NETFX || NETCORE
|
|
|
|
/// <summary>从 Response 头中移除 Server 和 X-Powered-By 属性。</summary>
|
|
public static void RemoveServer(HttpResponse response)
|
|
{
|
|
if (response == null) return;
|
|
#if NETFX
|
|
var keys = new List<string>(response.Headers.AllKeys);
|
|
#else
|
|
var keys = new List<string>(response.Headers.Keys);
|
|
#endif
|
|
if (keys.Contains("Server")) response.Headers.Remove("Server");
|
|
if (keys.Contains("X-Powered-By")) response.Headers.Remove("X-Powered-By");
|
|
}
|
|
|
|
/// <summary>添加 Header 属性,返回错误信息。</summary>
|
|
public static string AddHeader(HttpResponse response, string name, string value)
|
|
{
|
|
if (response == null || string.IsNullOrEmpty(name) || string.IsNullOrEmpty(value)) return "参数无效。";
|
|
try
|
|
{
|
|
#if NET20
|
|
response.AddHeader(name, value);
|
|
#else
|
|
response.Headers.Add(name, value);
|
|
#endif
|
|
return null;
|
|
}
|
|
catch (Exception ex) { return ex.Message; }
|
|
}
|
|
|
|
/// <summary>添加多个 Headers 属性。</summary>
|
|
public static void AddHeaders(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
|
|
|
|
#endregion
|
|
|
|
#region Response - Cache Control
|
|
|
|
/// <summary>设置缓存时间,单位为秒,最大为 2592000 秒(30 天)。</summary>
|
|
public static void SetCacheControl(HttpListenerResponse response, int seconds = 0)
|
|
{
|
|
if (response == null) return;
|
|
var s = seconds;
|
|
if (s < 0) s = 0;
|
|
if (s > 2592000) s = 2592000;
|
|
if (s > 0)
|
|
{
|
|
AddHeader(response, "Cache-Control", $"public, max-age={s}, s-maxage={s}");
|
|
}
|
|
else
|
|
{
|
|
AddHeader(response, "Cache-Control", "no-cache, no-store, must-revalidate");
|
|
AddHeader(response, "Pragma", "no-cache");
|
|
}
|
|
}
|
|
|
|
#if NETFX || NETCORE
|
|
|
|
/// <summary>设置缓存时间,单位为秒,最大为 2592000 秒(30 天)。</summary>
|
|
public static void SetCacheControl(HttpResponse response, int seconds = 0)
|
|
{
|
|
if (response == null) return;
|
|
var s = seconds;
|
|
if (s < 0) s = 0;
|
|
if (s > 2592000) s = 2592000;
|
|
#if NETFX
|
|
if (s > 0)
|
|
{
|
|
response.CacheControl = "public";
|
|
response.Cache.SetCacheability(HttpCacheability.Public);
|
|
response.Cache.SetMaxAge(TimeSpan.FromSeconds(seconds));
|
|
response.Cache.SetProxyMaxAge(TimeSpan.FromSeconds(seconds));
|
|
response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
|
|
}
|
|
else
|
|
{
|
|
var minutes = s < 60 ? 0 : (s / 60);
|
|
response.CacheControl = "no-cache";
|
|
response.Cache.SetCacheability(HttpCacheability.NoCache);
|
|
response.Cache.SetNoStore();
|
|
AddHeader(response, "Pragma", "no-cache");
|
|
}
|
|
#else
|
|
if (s > 0)
|
|
{
|
|
AddHeader(response, "Cache-Control", $"public, max-age={s}, s-maxage={s}");
|
|
}
|
|
else
|
|
{
|
|
AddHeader(response, "Cache-Control", "no-cache, no-store, must-revalidate");
|
|
AddHeader(response, "Pragma", "no-cache");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
#endif
|
|
|
|
#endregion
|
|
|
|
#region Cookies
|
|
|
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
|
|
// https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Set-Cookie
|
|
|
|
// <cookie-name> 可以是除了控制字符 (CTLs)、空格 (spaces) 或制表符 (tab)之外的任何 US-ASCII 字符。
|
|
// 同时不能包含以下分隔字符: ( ) < > @ , ; : \ " / [ ] ? = { }.
|
|
|
|
// <cookie-value> 是可选的,如果存在的话,那么需要包含在双引号里面。
|
|
// 支持除了控制字符(CTLs)、空格(whitespace)、双引号(double quotes)、逗号(comma)、分号(semicolon)以及反斜线(backslash)之外的任意 US-ASCII 字符。
|
|
|
|
// 关于编码:许多应用会对 cookie 值按照URL编码(URL encoding)规则进行编码,但是按照 RFC 规范,这不是必须的。
|
|
// 不过满足规范中对于 <cookie-value> 所允许使用的字符的要求是有用的。
|
|
|
|
/// <summary>获取 Cookies。</summary>
|
|
public static StringPairs GetCookies(HttpListenerRequest request)
|
|
{
|
|
var sp = new StringPairs();
|
|
if (request != null && request.Cookies != null)
|
|
{
|
|
for (var i = 0; i < request.Cookies.Count; i++)
|
|
{
|
|
try
|
|
{
|
|
var cookie = request.Cookies[i];
|
|
var key = cookie.Name;
|
|
var value = cookie.Value ?? "";
|
|
sp.Add(new KeyValuePair<string, string>(key, value));
|
|
}
|
|
catch { }
|
|
}
|
|
}
|
|
return sp;
|
|
}
|
|
|
|
/// <summary>设置响应的 Cookies。</summary>
|
|
public static string SetCookies(HttpListenerResponse response, IEnumerable<KeyValuePair<string, string>> cookies)
|
|
{
|
|
// experimentation_subject_id
|
|
if (cookies == null) return "参数 Response 无效。";
|
|
foreach (var kvp in cookies)
|
|
{
|
|
var error = SetCookie(response, kvp.Key, kvp.Value);
|
|
if (error.NotEmpty()) return error;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>设置响应的 Cookie。</summary>
|
|
public static string SetCookie(HttpListenerResponse response, string key, string value)
|
|
{
|
|
if (response == null) return null;
|
|
var k = key.SafeTrim();
|
|
var v = value.SafeTrim();
|
|
if (k.IsEmpty()) return "参数 Key 无效。";
|
|
|
|
try
|
|
{
|
|
var cookie = new Cookie(k, v);
|
|
var now = DateTime.Now;
|
|
cookie.Expires = v.IsEmpty() ? now.AddDays(-1) : now.AddYears(1);
|
|
response.SetCookie(cookie);
|
|
return null;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return ex.Message;
|
|
}
|
|
}
|
|
|
|
#if NETFX || NETCORE
|
|
|
|
/// <summary>获取 Cookies。</summary>
|
|
public static StringPairs GetCookies(HttpRequest request)
|
|
{
|
|
var sp = new StringPairs();
|
|
if (request != null && request.Cookies != null)
|
|
{
|
|
#if NETFX
|
|
foreach (var key in request.Cookies.AllKeys)
|
|
{
|
|
try
|
|
{
|
|
var cookie = request.Cookies[key];
|
|
var value = cookie.Value ?? "";
|
|
sp.Add(new KeyValuePair<string, string>(key, value));
|
|
}
|
|
catch { }
|
|
}
|
|
#else
|
|
foreach (var key in request.Cookies.Keys)
|
|
{
|
|
try
|
|
{
|
|
var value = request.Cookies[key] ?? "";
|
|
sp.Add(new KeyValuePair<string, string>(key, value));
|
|
}
|
|
catch { }
|
|
}
|
|
#endif
|
|
}
|
|
return sp;
|
|
}
|
|
|
|
/// <summary>设置响应的 Cookies。</summary>
|
|
public static string SetCookies(HttpResponse response, IEnumerable<KeyValuePair<string, string>> cookies)
|
|
{
|
|
// experimentation_subject_id
|
|
if (cookies == null) return "参数 Response 无效。";
|
|
foreach (var kvp in cookies)
|
|
{
|
|
var error = SetCookie(response, kvp.Key, kvp.Value);
|
|
if (error.NotEmpty()) return error;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// <summary>设置响应的 Cookie。</summary>
|
|
public static string SetCookie(HttpResponse response, string key, string value)
|
|
{
|
|
if (response == null) return null;
|
|
var k = key.SafeTrim();
|
|
var v = value.SafeTrim();
|
|
if (k.IsEmpty()) return "参数 Key 无效。";
|
|
|
|
try
|
|
{
|
|
#if NETFX
|
|
var cookie = new HttpCookie(k, v);
|
|
var now = DateTime.Now;
|
|
cookie.Expires = v.IsEmpty() ? now.AddDays(-1) : now.AddYears(1);
|
|
response.SetCookie(cookie);
|
|
|
|
#else
|
|
// var options = new CookieOptions();
|
|
// response.Cookies.Append(key, value, options);
|
|
response.Cookies.Append(key, value);
|
|
#endif
|
|
return null;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return ex.Message;
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
#endregion
|
|
|
|
}
|
|
|
|
}
|
|
|