Files
ThingsGateway/framework/Foundation/ThingsGateway.Foundation/TouchSocket/Http/Extensions/HttpExtensions.cs
2023-10-16 20:36:51 +08:00

659 lines
27 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#region copyright
//------------------------------------------------------------------------------
// 此代码版权声明为全文件覆盖,如有原作者特别声明,会在下方手动补充
// 此代码版权除特别声明外的代码归作者本人Diego所有
// 源代码使用协议遵循本仓库的开源协议及附加协议
// Gitee源代码仓库https://gitee.com/diego2098/ThingsGateway
// Github源代码仓库https://github.com/kimdiego2098/ThingsGateway
// 使用文档https://diego2098.gitee.io/thingsgateway-docs/
// QQ群605534569
//------------------------------------------------------------------------------
#endregion
//------------------------------------------------------------------------------
// 此代码版权除特别声明或在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首页http://rrqm_home.gitee.io/touchsocket/
// 交流QQ群234762506
// 感谢您的下载和使用
//------------------------------------------------------------------------------
//------------------------------------------------------------------------------
using System.Text;
namespace ThingsGateway.Foundation.Http
{
/// <summary>
/// Http扩展辅助
/// </summary>
public static class HttpExtensions
{
/// <summary>
/// 根据字符串获取枚举
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="str"></param>
/// <param name="result"></param>
/// <returns></returns>
public static bool GetEnum<T>(string str, out T result) where T : struct
{
return Enum.TryParse<T>(str, out result);
}
#region HttpBase
#region
/// <summary>
/// 从Json
/// </summary>
/// <param name="request"></param>
/// <param name="value"></param>
/// <returns></returns>
public static T FromJson<T>(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;
}
/// <summary>
/// 从文本
/// </summary>
/// <param name="request"></param>
/// <param name="value"></param>
/// <returns></returns>
public static T FromText<T>(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;
}
/// <summary>
/// 从Xml格式
/// </summary>
/// <param name="request"></param>
/// <param name="value"></param>
/// <returns></returns>
public static T FromXML<T>(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
/// <summary>
/// 获取Body的字符串
/// </summary>
/// <param name="httpBase"></param>
/// <returns></returns>
public static string GetBody(this HttpBase httpBase)
{
return httpBase.TryGetContent(out var data) ? Encoding.UTF8.GetString(data) : throw new Exception("获取数据体错误。");
}
/// <summary>
/// 当数据类型为multipart/form-data时获取boundary
/// </summary>
/// <param name="httpBase"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
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].Trim();
}
}
return string.Empty;
}
/// <summary>
/// 设置内容
/// </summary>
/// <param name="httpBase"></param>
/// <param name="content"></param>
/// <param name="encoding"></param>
/// <returns></returns>
public static T SetContent<T>(this T httpBase, string content, Encoding encoding = null) where T : HttpBase
{
httpBase.SetContent(Encoding.UTF8.GetBytes(content));
return httpBase;
}
/// <summary>
/// 设置数据体长度
/// </summary>
/// <param name="httpBase"></param>
/// <param name="value"></param>
public static T SetContentLength<T>(this T httpBase, long value) where T : HttpBase
{
httpBase.ContentLength = value;
return httpBase;
}
/// <summary>
/// 从扩展名设置内容类型,必须以“.”开头
/// </summary>
/// <param name="httpBase"></param>
/// <param name="extension"></param>
/// <returns></returns>
public static T SetContentTypeByExtension<T>(this T httpBase, string extension) where T : HttpBase
{
var type = HttpTools.GetContentTypeFromExtension(extension);
httpBase.ContentType = type;
return httpBase;
}
/// <summary>
/// 写入
/// </summary>
/// <param name="httpBase"></param>
/// <param name="buffer"></param>
public static T WriteContent<T>(this T httpBase, byte[] buffer) where T : HttpBase
{
httpBase.WriteContent(buffer, 0, buffer.Length);
return httpBase;
}
#endregion HttpBase
#region HttpRequest
/// <summary>
/// 获取多文件集合。如果不存在则返回null。
/// </summary>
/// <typeparam name="TRequest"></typeparam>
/// <param name="request"></param>
/// <returns></returns>
public static MultifileCollection GetMultifileCollection<TRequest>(this TRequest request) where TRequest : HttpRequest
{
return request.GetBoundary().IsNullOrEmpty() ? null : new MultifileCollection(request);
}
/// <summary>
/// 初始化常规的请求头。
/// <para>包含:</para>
/// <list type="number">
/// <item>Connection:keep-alive</item>
/// <item>Pragma:no-cache</item>
/// <item>UserAgent:ThingsGateway.Foundation.Http</item>
/// <item>Accept:*/*</item>
/// <item>AcceptEncoding:deflate, br</item>
/// </list>
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public static TRequest InitHeaders<TRequest>(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, "ThingsGateway.Foundation.Http");
request.Headers.Add(HttpHeaders.Accept, "*/*");
request.Headers.Add(HttpHeaders.AcceptEncoding, "deflate, br");
return request;
}
/// <summary>
/// 添加Host请求头
/// </summary>
/// <param name="request"></param>
/// <param name="host"></param>
/// <returns></returns>
public static TRequest SetHost<TRequest>(this TRequest request, string host) where TRequest : HttpRequest
{
request.Headers.Add(HttpHeaders.Host, host);
return request;
}
/// <summary>
/// 对比不包含参数的Url。其中有任意一方为null则均返回False。
/// </summary>
/// <param name="request"></param>
/// <param name="url"></param>
/// <returns></returns>
public static bool UrlEquals<TRequest>(this TRequest request, string url) where TRequest : HttpRequest
{
return string.IsNullOrEmpty(request.RelativeURL) || string.IsNullOrEmpty(url)
? false
: request.RelativeURL.Equals(url, StringComparison.CurrentCultureIgnoreCase);
}
#region
/// <summary>
/// 作为Delete访问
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public static TRequest AsDelete<TRequest>(this TRequest request) where TRequest : HttpRequest
{
request.Method = HttpMethod.Delete;
return request;
}
/// <summary>
/// 作为Get访问
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public static TRequest AsGet<TRequest>(this TRequest request) where TRequest : HttpRequest
{
request.Method = HttpMethod.Get;
return request;
}
/// <summary>
/// 作为指定函数
/// </summary>
/// <param name="request"></param>
/// <param name="method"></param>
/// <returns></returns>
public static TRequest AsMethod<TRequest>(this TRequest request, string method) where TRequest : HttpRequest
{
request.Method = new HttpMethod(method);
return request;
}
/// <summary>
/// 作为Post访问
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public static TRequest AsPost<TRequest>(this TRequest request) where TRequest : HttpRequest
{
request.Method = HttpMethod.Post;
return request;
}
/// <summary>
/// 作为Put访问
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public static TRequest AsPut<TRequest>(this TRequest request) where TRequest : HttpRequest
{
request.Method = HttpMethod.Put;
return request;
}
#endregion
#region
/// <summary>
/// 是否作为Delete访问
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public static bool IsDelete<TRequest>(this TRequest request) where TRequest : HttpRequest
{
return request.Method == HttpMethod.Delete;
}
/// <summary>
/// 是否作为Get访问
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public static bool IsGet<TRequest>(this TRequest request) where TRequest : HttpRequest
{
return request.Method == HttpMethod.Get;
}
/// <summary>
/// 是否作为指定函数
/// </summary>
/// <param name="request"></param>
/// <param name="method"></param>
/// <returns></returns>
public static bool IsMethod<TRequest>(this TRequest request, string method) where TRequest : HttpRequest
{
return request.Method == new HttpMethod(method);
}
/// <summary>
/// 是否作为Post访问
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public static bool IsPost<TRequest>(this TRequest request) where TRequest : HttpRequest
{
return request.Method == HttpMethod.Post;
}
/// <summary>
/// 是否作为Put访问
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public static bool IsPut<TRequest>(this TRequest request) where TRequest : HttpRequest
{
return request.Method == HttpMethod.Put;
}
#endregion
#region
/// <summary>
/// 是否在headers中包含升级连接
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
public static bool IsUpgrade<TRequest>(this TRequest request) where TRequest : HttpRequest
{
return string.Equals(request.Headers.Get(HttpHeaders.Connection), HttpHeaders.Upgrade.GetDescription(), StringComparison.OrdinalIgnoreCase);
}
#endregion
#endregion HttpRequest
#region HttpResponse
/// <summary>
/// 设置文件类型。
/// </summary>
/// <param name="response"></param>
/// <param name="fileName"></param>
/// <returns></returns>
public static TResponse SetContentTypeFromFileName<TResponse>(this TResponse response, string fileName) where TResponse : HttpResponse
{
var contentDisposition = $"attachment;filename={System.Web.HttpUtility.UrlEncode(fileName)}";
response.Headers.Add(HttpHeaders.ContentDisposition, contentDisposition);
return response;
}
/// <summary>
/// 设置状态,并且附带时间戳。
/// </summary>
/// <param name="response"></param>
/// <param name="status"></param>
/// <param name="msg"></param>
/// <returns></returns>
public static TResponse SetStatus<TResponse>(this TResponse response, int status = 200, string msg = "Success") where TResponse : HttpResponse
{
response.StatusCode = status;
response.StatusMessage = msg;
response.Headers.Add(HttpHeaders.Server, $"ThingsGateway.Foundation.Http {HttpBase.ServerVersion}");
response.Headers.Add(HttpHeaders.Date, DateTime.Now.ToGMTString("r"));
return response;
}
/// <summary>
/// 路径文件没找到
/// </summary>
/// <param name="response"></param>
/// <returns></returns>
public static TResponse UrlNotFind<TResponse>(this TResponse response) where TResponse : HttpResponse
{
response.SetContent("<html><body><h1>404 -RRQM Not Found</h1></body></html>");
response.StatusCode = 404;
response.ContentType = "text/html;charset=utf-8";
return response;
}
#region FromFile
/// <summary>
/// 从文件响应。
/// <para>当response支持持续写入时会直接回复响应。并阻塞执行直到完成。所以在执行该方法之前请确保已设置完成所有状态字</para>
/// <para>当response不支持持续写入时会填充Content且不会响应需要自己执行Build并发送。</para>
/// </summary>
/// <param name="response">响应</param>
/// <param name="request">请求头用于尝试续传为null时则不续传。</param>
/// <param name="filePath">文件路径</param>
/// <param name="fileName">文件名,不设置时会获取路径文件名</param>
/// <param name="maxSpeed">最大速度(仅企业版有效)。</param>
/// <param name="bufferLen">读取长度。</param>
/// <exception cref="Exception"></exception>
/// <exception cref="Exception"></exception>
/// <returns></returns>
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={System.Web.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;
}
/// <summary>
/// 从文件响应。
/// <para>当response支持持续写入时会直接回复响应。并阻塞执行直到完成。所以在执行该方法之前请确保已设置完成所有状态字</para>
/// <para>当response不支持持续写入时会填充Content且不会响应需要自己执行Build并发送。</para>
/// </summary>
/// <param name="context">上下文</param>
/// <param name="filePath">文件路径</param>
/// <param name="fileName">文件名,不设置时会获取路径文件名</param>
/// <param name="maxSpeed">最大速度(仅企业版有效)。</param>
/// <param name="bufferLen">读取长度。</param>
/// <exception cref="Exception"></exception>
/// <exception cref="Exception"></exception>
/// <returns></returns>
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={System.Web.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
/// <summary>
/// 从文件响应。
/// <para>当response支持持续写入时会直接回复响应。并阻塞执行直到完成。所以在执行该方法之前请确保已设置完成所有状态字</para>
/// <para>当response不支持持续写入时会填充Content且不会响应需要自己执行Build并发送。</para>
/// </summary>
/// <param name="response">响应</param>
/// <param name="request">请求头用于尝试续传为null时则不续传。</param>
/// <param name="filePath">文件路径</param>
/// <param name="fileName">文件名,不设置时会获取路径文件名</param>
/// <param name="maxSpeed">最大速度(仅企业版有效)。</param>
/// <param name="bufferLen">读取长度。</param>
/// <exception cref="Exception"></exception>
/// <exception cref="Exception"></exception>
/// <returns></returns>
public static Task<HttpResponse> 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);
});
}
/// <summary>
/// 从文件响应。
/// <para>当response支持持续写入时会直接回复响应。并阻塞执行直到完成。所以在执行该方法之前请确保已设置完成所有状态字</para>
/// <para>当response不支持持续写入时会填充Content且不会响应需要自己执行Build并发送。</para>
/// </summary>
/// <param name="context">上下文</param>
/// <param name="filePath">文件路径</param>
/// <param name="fileName">文件名,不设置时会获取路径文件名</param>
/// <param name="maxSpeed">最大速度(仅企业版有效)。</param>
/// <param name="bufferLen">读取长度。</param>
/// <exception cref="Exception"></exception>
/// <exception cref="Exception"></exception>
/// <returns></returns>
public static Task<HttpResponse> 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
}
}