//------------------------------------------------------------------------------ // 此代码版权(除特别声明或在XREF结尾的命名空间的代码)归作者本人若汝棋茗所有 // 源代码使用协议遵循本仓库的开源协议及附加协议,若本仓库没有设置,则按MIT开源协议授权 // CSDN博客:https://blog.csdn.net/qq_40374647 // 哔哩哔哩视频:https://space.bilibili.com/94253567 // Gitee源代码仓库:https://gitee.com/RRQM_Home // Github源代码仓库:https://github.com/RRQM // API首页:https://touchsocket.net/ // 交流QQ群:234762506 // 感谢您的下载和使用 //------------------------------------------------------------------------------ using System; using System.IO; using System.Text; using System.Threading.Tasks; using Codice.Utils; using TouchSocket.Core; namespace TouchSocket.Http { /// /// Http扩展辅助 /// public static class HttpExtensions { /// /// 根据字符串获取枚举 /// /// /// /// /// public static bool GetEnum(string str, out T result) where T : struct { return Enum.TryParse(str, out result); } #region HttpBase /// /// 添加Header参数 /// /// /// /// /// public static TRequest AddHeader(this TRequest request, string key, string value) where TRequest : HttpBase { request.Headers.Add(key, value); return request; } /// /// 添加Header参数 /// /// /// /// /// public static TRequest AddHeader(this TRequest request, HttpHeaders key, string value) where TRequest : HttpBase { request.Headers.Add(key, value); return request; } #region 设置内容 /// /// 从Json /// /// /// /// public static T FromJson(this T request, string value) where T : HttpBase { request.SetContent(Encoding.UTF8.GetBytes(value)); request.Headers.Add(HttpHeaders.ContentType, "application/json;charset=UTF-8"); return request; } /// /// 从文本 /// /// /// /// public static T FromText(this T request, string value) where T : HttpBase { request.SetContent(Encoding.UTF8.GetBytes(value)); request.Headers.Add(HttpHeaders.ContentType, "text/plain;charset=UTF-8"); return request; } /// /// 从Xml格式 /// /// /// /// public static T FromXML(this T request, string value) where T : HttpBase { request.SetContent(Encoding.UTF8.GetBytes(value)); request.Headers.Add(HttpHeaders.ContentType, "application/xml;charset=UTF-8"); return request; } #endregion 设置内容 /// /// 获取Body的字符串 /// /// /// public static string GetBody(this HttpBase httpBase) { return httpBase.TryGetContent(out var data) ? Encoding.UTF8.GetString(data) : throw new Exception("获取数据体错误。"); } /// /// 当数据类型为multipart/form-data时,获取boundary /// /// /// /// public static string GetBoundary(this HttpBase httpBase) { if (httpBase.ContentType.IsNullOrEmpty()) { return string.Empty; } var strs = httpBase.ContentType.Split(';'); if (strs.Length == 2) { strs = strs[1].Split('='); if (strs.Length == 2) { return strs[1].Replace("\"",string.Empty).Trim(); } } return string.Empty; } /// /// 设置内容 /// /// /// /// /// public static T SetContent(this T httpBase, string content, Encoding encoding = null) where T : HttpBase { httpBase.SetContent(Encoding.UTF8.GetBytes(content)); return httpBase; } /// /// 设置数据体长度 /// /// /// public static T SetContentLength(this T httpBase, long value) where T : HttpBase { httpBase.ContentLength = value; return httpBase; } /// /// 从扩展名设置内容类型,必须以“.”开头 /// /// /// /// public static T SetContentTypeByExtension(this T httpBase, string extension) where T : HttpBase { var type = HttpTools.GetContentTypeFromExtension(extension); httpBase.ContentType = type; return httpBase; } /// /// 写入 /// /// /// public static T WriteContent(this T httpBase, byte[] buffer) where T : HttpBase { httpBase.WriteContent(buffer, 0, buffer.Length); return httpBase; } #endregion HttpBase #region HttpRequest /// /// 获取多文件集合。如果不存在,则返回null。 /// /// /// /// public static MultifileCollection GetMultifileCollection(this TRequest request) where TRequest : HttpRequest { return request.GetBoundary().IsNullOrEmpty() ? null : new MultifileCollection(request); } /// /// 初始化常规的请求头。 /// 包含: /// /// Connection:keep-alive /// Pragma:no-cache /// UserAgent:TouchSocket.Http /// /// /// /// public static TRequest InitHeaders(this TRequest request) where TRequest : HttpRequest { request.Headers.Add(HttpHeaders.Connection, "keep-alive"); request.Headers.Add(HttpHeaders.Pragma, "no-cache"); request.Headers.Add(HttpHeaders.UserAgent, "TouchSocket.Http"); return request; } /// /// 添加Host请求头 /// /// /// /// public static TRequest SetHost(this TRequest request, string host) where TRequest : HttpRequest { request.Headers.Add(HttpHeaders.Host, host); return request; } /// /// 添加Query参数 /// /// /// /// /// public static TRequest AddQuery(this TRequest request, string key, string value) where TRequest : HttpRequest { request.Query.Add(key, value); return request; } /// /// 对比不包含参数的Url。其中有任意一方为null,则均返回False。 /// /// /// /// public static bool UrlEquals(this TRequest request, string url) where TRequest : HttpRequest { if (string.IsNullOrEmpty(request.RelativeURL) || string.IsNullOrEmpty(url)) { return false; } else { return request.RelativeURL.Equals(url, StringComparison.CurrentCultureIgnoreCase); } } #region 设置函数 /// /// 作为Delete访问 /// /// /// public static TRequest AsDelete(this TRequest request) where TRequest : HttpRequest { request.Method = HttpMethod.Delete; return request; } /// /// 作为Get访问 /// /// /// public static TRequest AsGet(this TRequest request) where TRequest : HttpRequest { request.Method = HttpMethod.Get; return request; } /// /// 作为指定函数 /// /// /// /// public static TRequest AsMethod(this TRequest request, string method) where TRequest : HttpRequest { request.Method = new HttpMethod(method); return request; } /// /// 作为Post访问 /// /// /// public static TRequest AsPost(this TRequest request) where TRequest : HttpRequest { request.Method = HttpMethod.Post; return request; } /// /// 作为Put访问 /// /// /// public static TRequest AsPut(this TRequest request) where TRequest : HttpRequest { request.Method = HttpMethod.Put; return request; } #endregion 设置函数 #region 判断函数 /// /// 是否作为Delete访问 /// /// /// public static bool IsDelete(this TRequest request) where TRequest : HttpRequest { return request.Method == HttpMethod.Delete; } /// /// 是否作为Get访问 /// /// /// public static bool IsGet(this TRequest request) where TRequest : HttpRequest { return request.Method == HttpMethod.Get; } /// /// 是否作为指定函数 /// /// /// /// public static bool IsMethod(this TRequest request, string method) where TRequest : HttpRequest { return request.Method == new HttpMethod(method); } /// /// 是否作为Post访问 /// /// /// public static bool IsPost(this TRequest request) where TRequest : HttpRequest { return request.Method == HttpMethod.Post; } /// /// 是否作为Put访问 /// /// /// public static bool IsPut(this TRequest request) where TRequest : HttpRequest { return request.Method == HttpMethod.Put; } #endregion 判断函数 #region 判断属性 /// /// 是否在headers中包含升级连接 /// /// /// public static bool IsUpgrade(this TRequest request) where TRequest : HttpRequest { return string.Equals(request.Headers.Get(HttpHeaders.Connection), HttpHeaders.Upgrade.GetDescription(), StringComparison.OrdinalIgnoreCase); } #endregion 判断属性 #endregion HttpRequest #region HttpResponse /// /// 设置文件类型。 /// /// /// /// public static TResponse SetContentTypeFromFileName(this TResponse response, string fileName) where TResponse : HttpResponse { var contentDisposition = "attachment;" + "filename=" + HttpUtility.UrlEncode(fileName); response.Headers.Add(HttpHeaders.ContentDisposition, contentDisposition); return response; } /// /// 判断返回的状态码是否为成功。 /// /// /// /// 当不指定具体的状态码时,只要状态码在200-299之间则为。 /// 当指定时,状态码不仅必须要在200-299之间,还必须是指定的状态码才会返回。 /// /// public static bool IsSuccess(this TResponse response, int? status = default) where TResponse : HttpResponse { if (status.HasValue) { return response.StatusCode == status && response.StatusCode >= 200 && response.StatusCode < 300; } else { return response.StatusCode >= 200 && response.StatusCode < 300; } } /// /// 设置状态,并且附带时间戳。 /// /// /// /// /// public static TResponse SetStatus(this TResponse response, int status, string msg) where TResponse : HttpResponse { response.StatusCode = status; response.StatusMessage = msg; response.Headers.Add(HttpHeaders.Server, $"TouchSocket.Http {HttpBase.ServerVersion}"); response.Headers.Add(HttpHeaders.Date, DateTime.Now.ToGMTString()); return response; } /// /// 设置默认Success状态,并且附带时间戳。 /// /// /// /// public static TResponse SetStatus(this TResponse response) where TResponse : HttpResponse { return SetStatus(response, 200, "Success"); } /// /// 路径文件没找到 /// /// /// public static TResponse UrlNotFind(this TResponse response) where TResponse : HttpResponse { response.SetContent("

404 -Not Found

"); response.SetStatus(404, "Not Found"); response.ContentType = "text/html;charset=utf-8"; return response; } #region FromFile /// /// 从文件响应。 /// 当response支持持续写入时,会直接回复响应。并阻塞执行,直到完成。所以在执行该方法之前,请确保已设置完成所有状态字 /// 当response不支持持续写入时,会填充Content,且不会响应,需要自己执行Build,并发送。 /// /// 响应 /// 请求头,用于尝试续传,为null时则不续传。 /// 文件路径 /// 文件名,不设置时会获取路径文件名 /// 最大速度(仅企业版有效)。 /// 读取长度。 /// /// /// public static HttpResponse FromFile(this HttpResponse response, string filePath, HttpRequest request, string fileName = null, int maxSpeed = 1024 * 1024 * 10, int bufferLen = 1024 * 64) { using (var reader = FilePool.GetReader(filePath)) { response.SetContentTypeByExtension(Path.GetExtension(filePath)); var contentDisposition = "attachment;" + "filename=" + HttpUtility.UrlEncode(fileName ?? Path.GetFileName(filePath)); response.Headers.Add(HttpHeaders.ContentDisposition, contentDisposition); response.Headers.Add(HttpHeaders.AcceptRanges, "bytes"); if (response.CanWrite) { HttpRange httpRange; var range = request?.Headers.Get(HttpHeaders.Range); if (string.IsNullOrEmpty(range)) { response.SetStatus(); response.ContentLength = reader.FileStorage.FileInfo.Length; httpRange = new HttpRange() { Start = 0, Length = reader.FileStorage.FileInfo.Length }; } else { httpRange = HttpRange.GetRange(range, reader.FileStorage.FileInfo.Length); if (httpRange == null) { response.ContentLength = reader.FileStorage.FileInfo.Length; httpRange = new HttpRange() { Start = 0, Length = reader.FileStorage.FileInfo.Length }; } else { response.SetContentLength(httpRange.Length) .SetStatus(206, "Partial Content"); response.Headers.Add(HttpHeaders.ContentRange, string.Format("bytes {0}-{1}/{2}", httpRange.Start, httpRange.Length + httpRange.Start - 1, reader.FileStorage.FileInfo.Length)); } } reader.Position = httpRange.Start; var surLen = httpRange.Length; var flowGate = new FlowGate { Maximum = maxSpeed }; using (var block = new ByteBlock(bufferLen)) { while (surLen > 0) { var r = reader.Read(block.Buffer, 0, (int)Math.Min(bufferLen, surLen)); if (r == 0) { break; } flowGate.AddCheckWait(r); response.WriteContent(block.Buffer, 0, r); surLen -= r; } } } else { if (reader.FileStorage.FileInfo.Length > 1024 * 1024) { throw new OverlengthException("当该对象不支持写入时,仅支持1Mb以内的文件。"); } using (var byteBlock = new ByteBlock((int)reader.FileStorage.FileInfo.Length)) { using (var block = new ByteBlock(bufferLen)) { while (true) { var r = reader.Read(block.Buffer, 0, bufferLen); if (r == 0) { break; } byteBlock.Write(block.Buffer, 0, r); } response.SetContent(byteBlock.ToArray()); } } } } return response; } /// /// 从文件响应。 /// 当response支持持续写入时,会直接回复响应。并阻塞执行,直到完成。所以在执行该方法之前,请确保已设置完成所有状态字 /// 当response不支持持续写入时,会填充Content,且不会响应,需要自己执行Build,并发送。 /// /// 上下文 /// 文件路径 /// 文件名,不设置时会获取路径文件名 /// 最大速度(仅企业版有效)。 /// 读取长度。 /// /// /// public static HttpResponse FromFile(this HttpContext context, string filePath, string fileName = null, int maxSpeed = 1024 * 1024 * 10, int bufferLen = 1024 * 64) { using (var reader = FilePool.GetReader(filePath)) { context.Response.SetContentTypeByExtension(Path.GetExtension(filePath)); var contentDisposition = "attachment;" + "filename=" + HttpUtility.UrlEncode(fileName ?? Path.GetFileName(filePath)); context.Response.Headers.Add(HttpHeaders.ContentDisposition, contentDisposition); context.Response.Headers.Add(HttpHeaders.AcceptRanges, "bytes"); if (context.Response.CanWrite) { HttpRange httpRange; var range = context.Request?.Headers.Get(HttpHeaders.Range); if (string.IsNullOrEmpty(range)) { context.Response.SetStatus(); context.Response.ContentLength = reader.FileStorage.FileInfo.Length; httpRange = new HttpRange() { Start = 0, Length = reader.FileStorage.FileInfo.Length }; } else { httpRange = HttpRange.GetRange(range, reader.FileStorage.FileInfo.Length); if (httpRange == null) { context.Response.ContentLength = reader.FileStorage.FileInfo.Length; httpRange = new HttpRange() { Start = 0, Length = reader.FileStorage.FileInfo.Length }; } else { context.Response.SetContentLength(httpRange.Length) .SetStatus(206, "Partial Content"); context.Response.Headers.Add(HttpHeaders.ContentRange, string.Format("bytes {0}-{1}/{2}", httpRange.Start, httpRange.Length + httpRange.Start - 1, reader.FileStorage.FileInfo.Length)); } } reader.Position = httpRange.Start; var surLen = httpRange.Length; var flowGate = new FlowGate { Maximum = maxSpeed }; using (var block = new ByteBlock(bufferLen)) { while (surLen > 0) { var r = reader.Read(block.Buffer, 0, (int)Math.Min(bufferLen, surLen)); if (r == 0) { break; } flowGate.AddCheckWait(r); context.Response.WriteContent(block.Buffer, 0, r); surLen -= r; } } } else { if (reader.FileStorage.FileInfo.Length > 1024 * 1024) { throw new OverlengthException("当该对象不支持写入时,仅支持1Mb以内的文件。"); } using (var byteBlock = new ByteBlock((int)reader.FileStorage.FileInfo.Length)) { using (var block = new ByteBlock(bufferLen)) { while (true) { var r = reader.Read(block.Buffer, 0, bufferLen); if (r == 0) { break; } byteBlock.Write(block.Buffer, 0, r); } context.Response.SetContent(byteBlock.ToArray()); } } } } return context.Response; } #endregion FromFile #region FromFileAsync /// /// 从文件响应。 /// 当response支持持续写入时,会直接回复响应。并阻塞执行,直到完成。所以在执行该方法之前,请确保已设置完成所有状态字 /// 当response不支持持续写入时,会填充Content,且不会响应,需要自己执行Build,并发送。 /// /// 响应 /// 请求头,用于尝试续传,为null时则不续传。 /// 文件路径 /// 文件名,不设置时会获取路径文件名 /// 最大速度(仅企业版有效)。 /// 读取长度。 /// /// /// public static Task FromFileAsync(this HttpResponse response, string filePath, HttpRequest request, string fileName = null, int maxSpeed = 1024 * 1024 * 10, int bufferLen = 1024 * 64) { return Task.Run(() => { return FromFile(response, filePath, request, fileName, maxSpeed, bufferLen); }); } /// /// 从文件响应。 /// 当response支持持续写入时,会直接回复响应。并阻塞执行,直到完成。所以在执行该方法之前,请确保已设置完成所有状态字 /// 当response不支持持续写入时,会填充Content,且不会响应,需要自己执行Build,并发送。 /// /// 上下文 /// 文件路径 /// 文件名,不设置时会获取路径文件名 /// 最大速度(仅企业版有效)。 /// 读取长度。 /// /// /// public static Task FromFileAsync(this HttpContext context, string filePath, string fileName = null, int maxSpeed = 1024 * 1024 * 10, int bufferLen = 1024 * 64) { return Task.Run(() => { return FromFile(context, filePath, fileName, maxSpeed, bufferLen); }); } #endregion FromFileAsync #endregion HttpResponse } }