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.
455 lines
13 KiB
455 lines
13 KiB
|
3 months ago
|
//------------------------------------------------------------------------------
|
||
|
|
// 此代码版权(除特别声明或在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://www.yuque.com/rrqm/touchsocket/index
|
||
|
|
// 交流QQ群:234762506
|
||
|
|
// 感谢您的下载和使用
|
||
|
|
//------------------------------------------------------------------------------
|
||
|
|
//------------------------------------------------------------------------------
|
||
|
|
using System;
|
||
|
|
using System.Collections.Specialized;
|
||
|
|
using System.IO;
|
||
|
|
using System.Linq;
|
||
|
|
using System.Text;
|
||
|
|
using System.Text.RegularExpressions;
|
||
|
|
using TouchSocket.Core;
|
||
|
|
using TouchSocket.Sockets;
|
||
|
|
|
||
|
|
namespace TouchSocket.Http
|
||
|
|
{
|
||
|
|
/// <summary>
|
||
|
|
/// HTTP请求定义
|
||
|
|
/// </summary>
|
||
|
|
public class HttpRequest : HttpBase
|
||
|
|
{
|
||
|
|
private bool m_canRead;
|
||
|
|
private ITcpClientBase m_client;
|
||
|
|
private byte[] m_content;
|
||
|
|
private NameValueCollection m_forms;
|
||
|
|
private NameValueCollection m_params;
|
||
|
|
private NameValueCollection m_query;
|
||
|
|
private string m_relativeURL;
|
||
|
|
private bool m_sentHeader;
|
||
|
|
private int m_sentLength;
|
||
|
|
private string m_uRL;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 构造函数
|
||
|
|
/// </summary>
|
||
|
|
/// <param name="client"></param>
|
||
|
|
/// <param name="isServer"></param>
|
||
|
|
public HttpRequest(ITcpClientBase client, bool isServer = false)
|
||
|
|
{
|
||
|
|
m_client = client;
|
||
|
|
if (isServer)
|
||
|
|
{
|
||
|
|
m_canRead = true;
|
||
|
|
CanWrite = false;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
m_canRead = false;
|
||
|
|
CanWrite = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 构造函数
|
||
|
|
/// </summary>
|
||
|
|
public HttpRequest()
|
||
|
|
{
|
||
|
|
m_canRead = false;
|
||
|
|
CanWrite = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// <inheritdoc/>
|
||
|
|
/// </summary>
|
||
|
|
public override bool CanRead => m_canRead;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// <inheritdoc/>
|
||
|
|
/// </summary>
|
||
|
|
public override bool CanWrite { get; }
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// <inheritdoc/>
|
||
|
|
/// </summary>
|
||
|
|
public override ITcpClientBase Client => m_client;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 表单数据
|
||
|
|
/// </summary>
|
||
|
|
public NameValueCollection Forms
|
||
|
|
{
|
||
|
|
get
|
||
|
|
{
|
||
|
|
if (ContentType == @"application/x-www-form-urlencoded")
|
||
|
|
{
|
||
|
|
m_forms ??= GetParameters(this.GetBody());
|
||
|
|
return m_forms;
|
||
|
|
}
|
||
|
|
|
||
|
|
return m_forms ??= new NameValueCollection();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 获取时候保持连接
|
||
|
|
/// </summary>
|
||
|
|
public bool KeepAlive
|
||
|
|
{
|
||
|
|
get
|
||
|
|
{
|
||
|
|
if (ProtocolVersion == "1.0")
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
if (GetHeader(HttpHeaders.Connection) == "keep-alive")
|
||
|
|
{
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// HTTP请求方式。
|
||
|
|
/// </summary>
|
||
|
|
public string Method { get; set; }
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Body参数
|
||
|
|
/// </summary>
|
||
|
|
public NameValueCollection Params
|
||
|
|
{
|
||
|
|
get
|
||
|
|
{
|
||
|
|
m_params ??= new NameValueCollection();
|
||
|
|
return m_params;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// url参数
|
||
|
|
/// </summary>
|
||
|
|
public NameValueCollection Query
|
||
|
|
{
|
||
|
|
get
|
||
|
|
{
|
||
|
|
m_query ??= new NameValueCollection();
|
||
|
|
return m_query;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 相对路径(不含参数)
|
||
|
|
/// </summary>
|
||
|
|
public string RelativeURL => m_relativeURL;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// Url全地址,包含参数
|
||
|
|
/// </summary>
|
||
|
|
public string URL => m_uRL;
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 构建响应数据。
|
||
|
|
/// <para>当数据较大时,不建议这样操作,可直接<see cref="WriteContent(byte[], int, int)"/></para>
|
||
|
|
/// </summary>
|
||
|
|
/// <param name="byteBlock"></param>
|
||
|
|
public void Build(ByteBlock byteBlock)
|
||
|
|
{
|
||
|
|
BuildHeader(byteBlock);
|
||
|
|
BuildContent(byteBlock);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 构建数据为字节数组。
|
||
|
|
/// </summary>
|
||
|
|
/// <returns></returns>
|
||
|
|
public byte[] BuildAsBytes()
|
||
|
|
{
|
||
|
|
using ByteBlock byteBlock = new ByteBlock();
|
||
|
|
Build(byteBlock);
|
||
|
|
return byteBlock.ToArray();
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 设置内容
|
||
|
|
/// </summary>
|
||
|
|
/// <param name="content"></param>
|
||
|
|
public override void SetContent(byte[] content)
|
||
|
|
{
|
||
|
|
m_content = content;
|
||
|
|
ContentLength = content.Length;
|
||
|
|
ContentComplated = true;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 设置Url,必须以“/”开头,可带参数
|
||
|
|
/// </summary>
|
||
|
|
/// <param name="url"></param>
|
||
|
|
/// <param name="justValue"></param>
|
||
|
|
/// <returns></returns>
|
||
|
|
public HttpRequest SetUrl(string url, bool justValue = false)
|
||
|
|
{
|
||
|
|
if (justValue || url.StartsWith("/"))
|
||
|
|
{
|
||
|
|
m_uRL = url;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
m_uRL = "/" + url;
|
||
|
|
}
|
||
|
|
ParseUrl();
|
||
|
|
return this;
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 输出
|
||
|
|
/// </summary>
|
||
|
|
public override string ToString()
|
||
|
|
{
|
||
|
|
using (ByteBlock byteBlock = new ByteBlock())
|
||
|
|
{
|
||
|
|
Build(byteBlock);
|
||
|
|
return byteBlock.ToString();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// <inheritdoc/>
|
||
|
|
/// </summary>
|
||
|
|
/// <returns></returns>
|
||
|
|
public override bool TryGetContent(out byte[] content)
|
||
|
|
{
|
||
|
|
if (!ContentComplated.HasValue)
|
||
|
|
{
|
||
|
|
if (m_contentLength == 0)
|
||
|
|
{
|
||
|
|
m_content = new byte[0];
|
||
|
|
content = m_content;
|
||
|
|
ContentComplated = true;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
try
|
||
|
|
{
|
||
|
|
using MemoryStream block1 = new MemoryStream();
|
||
|
|
using ByteBlock block2 = new ByteBlock();
|
||
|
|
byte[] buffer = block2.Buffer;
|
||
|
|
while (true)
|
||
|
|
{
|
||
|
|
int r = Read(buffer, 0, buffer.Length);
|
||
|
|
if (r == 0)
|
||
|
|
{
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
block1.Write(buffer, 0, r);
|
||
|
|
}
|
||
|
|
ContentComplated = true;
|
||
|
|
m_content = block1.ToArray();
|
||
|
|
content = m_content;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
catch
|
||
|
|
{
|
||
|
|
ContentComplated = false;
|
||
|
|
content = null;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
finally
|
||
|
|
{
|
||
|
|
m_canRead = false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else if (ContentComplated == true)
|
||
|
|
{
|
||
|
|
content = m_content;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
content = null;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// <inheritdoc/>
|
||
|
|
/// </summary>
|
||
|
|
/// <param name="buffer"></param>
|
||
|
|
/// <param name="offset"></param>
|
||
|
|
/// <param name="count"></param>
|
||
|
|
public override void WriteContent(byte[] buffer, int offset, int count)
|
||
|
|
{
|
||
|
|
if (!CanWrite)
|
||
|
|
{
|
||
|
|
throw new NotSupportedException("该对象不支持持续写入内容。");
|
||
|
|
}
|
||
|
|
if (!m_sentHeader)
|
||
|
|
{
|
||
|
|
using (ByteBlock byteBlock = new ByteBlock())
|
||
|
|
{
|
||
|
|
BuildHeader(byteBlock);
|
||
|
|
m_client.DefaultSend(byteBlock);
|
||
|
|
}
|
||
|
|
m_sentHeader = true;
|
||
|
|
}
|
||
|
|
if (m_sentLength + count <= m_contentLength)
|
||
|
|
{
|
||
|
|
m_client.DefaultSend(buffer, offset, count);
|
||
|
|
m_sentLength += count;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// <inheritdoc/>
|
||
|
|
/// </summary>
|
||
|
|
/// <param name="disposing"></param>
|
||
|
|
protected override void Dispose(bool disposing)
|
||
|
|
{
|
||
|
|
m_client = null;
|
||
|
|
base.Dispose(disposing);
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 从内存中读取
|
||
|
|
/// </summary>
|
||
|
|
protected override void LoadHeaderProterties()
|
||
|
|
{
|
||
|
|
var first = Regex.Split(RequestLine, @"(\s+)").Where(e => e.Trim() != string.Empty).ToArray();
|
||
|
|
if (first.Length > 0) Method = first[0].Trim().ToUpper();
|
||
|
|
if (first.Length > 1)
|
||
|
|
{
|
||
|
|
SetUrl(Uri.UnescapeDataString(first[1]));
|
||
|
|
}
|
||
|
|
if (first.Length > 2)
|
||
|
|
{
|
||
|
|
string[] ps = first[2].Split('/');
|
||
|
|
if (ps.Length == 2)
|
||
|
|
{
|
||
|
|
Protocols = ps[0];
|
||
|
|
ProtocolVersion = ps[1];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private void BuildContent(ByteBlock byteBlock)
|
||
|
|
{
|
||
|
|
if (ContentLength > 0)
|
||
|
|
{
|
||
|
|
byteBlock.Write(m_content);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/// <summary>
|
||
|
|
/// 构建响应头部
|
||
|
|
/// </summary>
|
||
|
|
/// <returns></returns>
|
||
|
|
private void BuildHeader(ByteBlock byteBlock)
|
||
|
|
{
|
||
|
|
StringBuilder stringBuilder = new StringBuilder();
|
||
|
|
|
||
|
|
string url = null;
|
||
|
|
if (!string.IsNullOrEmpty(m_relativeURL))
|
||
|
|
{
|
||
|
|
if (m_query == null)
|
||
|
|
{
|
||
|
|
url = m_relativeURL;
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
StringBuilder urlBuilder = new StringBuilder();
|
||
|
|
urlBuilder.Append(m_relativeURL);
|
||
|
|
urlBuilder.Append("?");
|
||
|
|
int i = 0;
|
||
|
|
foreach (var item in m_query.AllKeys)
|
||
|
|
{
|
||
|
|
urlBuilder.Append($"{item}={m_query[item]}");
|
||
|
|
if (++i < m_query.Count)
|
||
|
|
{
|
||
|
|
urlBuilder.Append("&");
|
||
|
|
}
|
||
|
|
}
|
||
|
|
url = urlBuilder.ToString();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (string.IsNullOrEmpty(url))
|
||
|
|
{
|
||
|
|
stringBuilder.Append($"{Method} / HTTP/{ProtocolVersion}\r\n");
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
stringBuilder.Append($"{Method} {url} HTTP/{ProtocolVersion}\r\n");
|
||
|
|
}
|
||
|
|
if (ContentLength > 0)
|
||
|
|
{
|
||
|
|
this.SetHeader(HttpHeaders.ContentLength, ContentLength.ToString());
|
||
|
|
}
|
||
|
|
foreach (var headerkey in Headers.AllKeys)
|
||
|
|
{
|
||
|
|
stringBuilder.Append($"{headerkey}: ");
|
||
|
|
stringBuilder.Append(Headers[headerkey] + "\r\n");
|
||
|
|
}
|
||
|
|
|
||
|
|
stringBuilder.Append("\r\n");
|
||
|
|
byteBlock.Write(Encoding.UTF8.GetBytes(stringBuilder.ToString()));
|
||
|
|
}
|
||
|
|
|
||
|
|
private NameValueCollection GetParameters(string row)
|
||
|
|
{
|
||
|
|
if (string.IsNullOrEmpty(row))
|
||
|
|
{
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
string[] kvs = row.Split('&');
|
||
|
|
if (kvs == null || kvs.Count() == 0)
|
||
|
|
{
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
NameValueCollection pairs = new NameValueCollection();
|
||
|
|
foreach (var item in kvs)
|
||
|
|
{
|
||
|
|
string[] kv = item.SplitFirst('=');
|
||
|
|
if (kv.Length == 2)
|
||
|
|
{
|
||
|
|
pairs.Add(kv[0], kv[1]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return pairs;
|
||
|
|
}
|
||
|
|
|
||
|
|
private void ParseUrl()
|
||
|
|
{
|
||
|
|
if (m_uRL.Contains("?"))
|
||
|
|
{
|
||
|
|
string[] urls = m_uRL.Split('?');
|
||
|
|
if (urls.Length > 0)
|
||
|
|
{
|
||
|
|
m_relativeURL = urls[0];
|
||
|
|
}
|
||
|
|
if (urls.Length > 1)
|
||
|
|
{
|
||
|
|
m_query = GetParameters(urls[1]);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
else
|
||
|
|
{
|
||
|
|
m_relativeURL = m_uRL;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|