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 { /// public static class WebUtility { /// 按 & 拆分多个参数。 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; } /// 获取 URL 查询段,不存在的段为 NULL 值。可要求解码。 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; } /// 获取参数并解码,可要求修剪参数值。 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(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; } /// 获取参数并解码,默认不修剪参数值。 public static string GetParameter(string encoded, params string[] names) => GetParameter(encoded, false, names); /// 指定可能的参数名,从 URL 中获取参数值并解码,默认不修剪参数值。 public static string GetParameter(Uri url, params string[] names) => url == null ? null : GetParameter(url.Query, false, names); /// 获取参数,默认不修剪参数值。 public static string GetParameter(List> parameters, params string[] names) => GetParameter(parameters, false, names); /// 指定可能的参数名,从 URL 中获取参数值并解码,可要求修剪参数值。 public static string GetParameter(Uri url, bool trim, params string[] names) => url == null ? null : GetParameter(url.Query, trim, names); /// 获取参数,可要求修剪参数值。 public static string GetParameter(List> 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.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; } /// 修剪 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 : ""; } 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(); 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; } /// 请求来自于可信客户端。 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; } /// 获取 HTTP 方法。 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; } /// 获取 X-Forwarded-For,不存在时返回 NULL 值。 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(); 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 ""; } /// 获取 User Agent。 public static string GetUserAgent(StringPairs headers) => headers == null ? null : headers.GetValue("user-agent"); /// 获取查询字符串。 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); } /// 获取表单。 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 /// 以 POST 转移请求到其它 URL。 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; } /// 创建新控制器实例并运行。 /// 新控制器的类型。 /// 当前控制器。 public static T Run(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; } /// 创建新控制器实例并运行。 /// 当前控制器。 /// 新控制器的类型。 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 /// 获取 URL 查询段,不存在的段为 NULL 值。可要求解码。 public static string GetSegmentalUrl(ApiRequest request, int index = 3, bool decode = false) { return request == null ? null : GetSegmental(request.Url, index, decode); } /// 获取参数,指定可能的参数名,从 URL 中获取参数时将解码,默认不修剪参数值。 public static string GetParameter(ApiRequest request, params string[] names) => GetParameter(request, false, names); /// 获取参数,指定可能的参数名,从 URL 中获取参数时将解码,可要求修剪参数值。 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(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.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 /// 设置响应。 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; } /// 设置响应,当发生错误时设置响应。返回错误信息。 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; } /// 设置响应,当发生错误时设置响应。返回错误信息。 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; } /// 设置 status 为 error,并设置 message 的内容。 public static void SetError(ApiResponse response, string message = "未知错误。") { if (response == null) return; response.Type = ApiFormat.Json; response.Status = "error"; response.Message = message ?? TextUtility.EmptyString; } /// 设置 status 为 error,并设置 message 的内容。 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 { } } /// 输出 UTF-8 文本。 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"; } /// 输出字节数组。 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"; } /// 输出二进制。 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"; } /// 输出文件。 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"; } /// 重定向。 public static void SetRedirect(ApiResponse response, string url) { if (response == null) return; response.Type = ApiFormat.Redirect; response.RedirectUrl = url; } #endregion #region Application & Path /// 获取程序目录的路径。 public static string AppDirectory { get { // AppDomain.CurrentDomain.BaseDirectory // AppDomain.CurrentDomain.SetupInformation.ApplicationBase return AppDomain.CurrentDomain.BaseDirectory; } } /// 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()) : list[0]; } #endregion #region Request - GetMethod /// 获取 HTTP 方法。 public static Network.HttpMethod GetMethod(HttpListenerRequest request) { if (request == null) return Network.HttpMethod.NULL; return GetMethod(request.HttpMethod); } #if NETFX || NETCORE /// 获取 HTTP 方法。 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 /// 获取直连端的 IP。 public static string GetDirectIP(HttpListenerRequest request) { if (request == null) return null; var ip = request.UserHostAddress; return TrimIP(ip); } /// 获取客户端 IP。 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 /// 获取直连端的 IP。 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); } /// 获取客户端 IP。 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 /// 获取请求的 URL,失败时返回 NULL 值。 public static Uri GetUrl(HttpListenerRequest request) { if (request == null) return null; return request.Url; } #if NETFX || NETCORE /// 获取请求的 URL,失败时返回 NULL 值。 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 /// 解析 URL 的查询字符串,获取所有已解码的参数。 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 /// 解析 URL 的查询字符串,获取所有已解码的参数。 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(key, request.Query[key])); } list.Capacity = list.Count; return list; #endif } #endif #endregion #region Request - Headers /// 获取 HTTP 头。 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; } /// 获取 User Agent。 public static string GetUserAgent(HttpListenerRequest request) => GetUserAgent(GetHeaders(request)); #if NETFX || NETCORE /// 获取 HTTP 头。 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; } /// 获取 X-Forwarded-For。 public static string GetForwardedIP(HttpRequest request) => GetForwardedIP(GetHeaders(request)); /// 获取 User Agent。 public static string GetUserAgent(HttpRequest request) => GetUserAgent(GetHeaders(request)); #endif #endregion #region Response - Redirect /// 将客户端重新定向到新 URL。 /// 响应 302 状态。 public static void Redirect(HttpListenerResponse response, string url) { if (response == null) return; try { response.Redirect(url); } catch { } } #if NETFX /// 将客户端重新定向到新 URL。 /// 响应 302 状态。 public static void Redirect(HttpResponse response, string url) { if (response == null) return; try { response.Redirect(url, true); } catch { } } #endif #if NETCORE /// 将客户端重新定向到新 URL。 /// 默认响应 302 状态。可指定 permanent = true 以响应 301 状态。 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 /// 停止并关闭响应流。可指定向发送缓冲区的数据。 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 /// 停止并关闭响应流。可指定向发送缓冲区的数据。 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 /// 从 Response 头中移除 Server 和 X-Powered-By 属性。 public static void RemoveServer(HttpListenerResponse response) { if (response == null) return; var keys = new List(response.Headers.AllKeys); if (keys.Contains("Server")) response.Headers.Remove("Server"); if (keys.Contains("X-Powered-By")) response.Headers.Remove("X-Powered-By"); } /// 添加 Header 属性,返回错误信息。 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; } } /// 添加多个 Headers 属性。 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 /// 从 Response 头中移除 Server 和 X-Powered-By 属性。 public static void RemoveServer(HttpResponse response) { if (response == null) return; #if NETFX var keys = new List(response.Headers.AllKeys); #else var keys = new List(response.Headers.Keys); #endif if (keys.Contains("Server")) response.Headers.Remove("Server"); if (keys.Contains("X-Powered-By")) response.Headers.Remove("X-Powered-By"); } /// 添加 Header 属性,返回错误信息。 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; } } /// 添加多个 Headers 属性。 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 /// 设置缓存时间,单位为秒,最大为 2592000 秒(30 天)。 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 /// 设置缓存时间,单位为秒,最大为 2592000 秒(30 天)。 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 // 可以是除了控制字符 (CTLs)、空格 (spaces) 或制表符 (tab)之外的任何 US-ASCII 字符。 // 同时不能包含以下分隔字符: ( ) < > @ , ; : \ " / [ ] ? = { }. // 是可选的,如果存在的话,那么需要包含在双引号里面。 // 支持除了控制字符(CTLs)、空格(whitespace)、双引号(double quotes)、逗号(comma)、分号(semicolon)以及反斜线(backslash)之外的任意 US-ASCII 字符。 // 关于编码:许多应用会对 cookie 值按照URL编码(URL encoding)规则进行编码,但是按照 RFC 规范,这不是必须的。 // 不过满足规范中对于 所允许使用的字符的要求是有用的。 /// 获取 Cookies。 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(key, value)); } catch { } } } return sp; } /// 设置响应的 Cookies。 public static string SetCookies(HttpListenerResponse response, IEnumerable> 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; } /// 设置响应的 Cookie。 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 /// 获取 Cookies。 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(key, value)); } catch { } } #else foreach (var key in request.Cookies.Keys) { try { var value = request.Cookies[key] ?? ""; sp.Add(new KeyValuePair(key, value)); } catch { } } #endif } return sp; } /// 设置响应的 Cookies。 public static string SetCookies(HttpResponse response, IEnumerable> 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; } /// 设置响应的 Cookie。 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 } }