//------------------------------------------------------------------------------
// 此代码版权(除特别声明或在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
}
}