创建HttpService
定义
定义
TouchSocket.HttpTouchSocket.Http.WebSocketsdotnet add package TouchSocket.Http一、说明
HttpService是能够提供Http相关服务的基础类型。
二、产品特点
- 支持HTTPS。
 - 多种数据接收模式
 - 多地址监听(可以一次性监听多个IP及端口)
 
三、产品应用场景
- HTTP基础使用场景:可跨平台、跨语言使用。
 
四、服务器架构
服务器在收到新客户端连接时,会创建一个HttpSessionClient的派生类实例,与远程HttpClient对应,后续的数据通信均由此实例负责。
五、支持插件接口
声明自定义实例类,然后实现IHttpPlugin接口,即可实现下列事务的触发。或者继承自PluginBase类,重写相应方法即可。
| 插件方法 | 功能 | 
|---|---|
| IHttpPlugin | 当收到所有Http请求时。 | 
六、创建HttpService
HttpService的创建,基本和TcpService一致,也可以通过继承实现,下列仅演示最简单实现。
HttpService的相关事务,会通过插件触发。
var service = new HttpService();
await service.SetupAsync(new TouchSocketConfig()//加载配置
     .SetListenIPHosts(7789)
     .ConfigureContainer(a =>
     {
         a.AddConsoleLogger();
     })
     .ConfigurePlugins(a =>
     {
         //此处添加插件逻辑,插件可以使用PluginBase类实现IHttpPlugin接口完成。
         //这里使用委托直接完成
         a.Add(typeof(IHttpPlugin), async (HttpContextEventArgs e) =>
         {
             var request = e.Context.Request;//http请求体
             var response = e.Context.Response;//http响应体
             //判断url
             if (request.UrlEquals("/say"))
             {
                 await response
                 .SetStatus(200, "success")
                 .SetContent("hello")
                 .AnswerAsync();
                 return;
             }
             //如果上述url没有处理,则转到下一插件处理
             await e.InvokeNext();
         });
         //default插件应该最后添加,其作用是
         //1、为找不到的路由返回404
         //2、处理header为Option的探视跨域请求。
         a.UseDefaultHttpServicePlugin();
     }));
await service.StartAsync();
DefaultHttpServicePlugin插件最好添加在插件中,如果没有添加的话,最好自己做好缺省路由配置。
七、获取请求
每个HttpClient连接建立时,系统除了会创建一个HttpSessionClient与之对应之外,还会创建一个HttpContext实例与之对应。
所以,对于一个连接而言,后续的所有Http交互,都会反复投递同一个HttpContext实例。
所以我们可以通过其Request与Response属性获取到本次Http的请求和即将响应的响应体。
var request = e.Context.Request;//http请求体
var response = e.Context.Response;//http响应体
7.1 获取Query参数
string value = e.Context.Request.Query["key"];
7.2 获取Header参数
string value = e.Context.Request.Headers["key"];
亦或者
string value = e.Context.Request.Headers[HttpHeaders.Cookie];
7.3 获取Form参数
var multifileCollection =await e.Context.Request.GetFormCollectionAsync();
foreach (var item in multifileCollection)
{
    Console.WriteLine($"key={item.Key},value={item.Value}");
}
7.4 获取字符串Body内容
string bodyString = await e.Context.Request.GetBodyAsync();
7.5 获取小体量字节Body内容
ReadOnlyMemory<byte> content = await e.Context.Request.GetContentAsync();
7.6 持续读取Body内容
当数据太大时,可持续读取
while (true)
{
    var buffer = new byte[1024 * 64];
    using (var blockResult = await e.Context.Request.ReadAsync())
    {
        //这里可以一直处理读到的数据。
        blockResult.Memory.CopyTo(buffer);
        if (blockResult.IsCompleted)
        {
            //结束
            break;
        }
    }
}
7.7 获取Body持续写入Stream中
当数据太大时,可持续读取数据直接到流容器中。
using (var stream = new MemoryStream())
{
    //
    await e.Context.Request.ReadCopyToAsync(stream);
}
7.8 获取Body小文件
当Body内容为小文件集合时,可以使用该功能。
if (e.Context.Request.ContentLength > 1024 * 1024 * 100)//全部数据体超过100Mb则直接拒绝接收。
{
    await e.Context.Response
         .SetStatus(403, "数据过大")
         .AnswerAsync();
    return;
}
//此操作会先接收全部数据,然后再分割数据。
//所以上传文件不宜过大,不然会内存溢出。
var multifileCollection =await e.Context.Request.GetFormCollectionAsync();
foreach (var file in multifileCollection.Files)
{
    var stringBuilder = new StringBuilder();
    stringBuilder.Append($"文件名={file.FileName}\t");
    stringBuilder.Append($"数据长度={file.Length}");
    client.Logger.Info(stringBuilder.ToString());
}
await e.Context.Response
         .SetStatus()
         .FromText("Ok")
         .AnswerAsync();
八、响应请求
当收到Http请求,处理完成业务后,即可使用e.Context.Response直接参与本次响应。
8.1 设置响应状态
e.Context.Response.SetStatus(200,"success");
8.2 设置响应Header
e.Context.Response.AddHeader("key","value");
或者
e.Context.Response.AddHeader(HttpHeaders.Origin, "*");
8.3 设置响应内容
e.Context.Response.SetContent("hello");
或者直接返回Json、Xml、Text等内容。使用此快捷方式,会同时添加对应的ContentTypeHeader。
e.Context.Response.FromJson("{}");
8.4 开始响应内容
当通过上述步骤,完成了响应体的构建后,即可使用AnswerAsync直接进行响应。
例如:响应一个hello文本内容,代码大致如下
await e.Context.Response
      .SetStatus(200, "success")
      .AddHeader("key", "value")//如需要
      .FromText("hello")
      .AnswerAsync();
8.5 插件响应Get请求
public class MyHttpPlug1 : PluginBase, IHttpPlugin
{
    public async Task OnHttpRequest(IHttpSessionClient client, HttpContextEventArgs e)
    {
        var request = e.Context.Request;//http请求体
        var response = e.Context.Response;//http响应体
        if (request.IsGet()&&request.UrlEquals("/success"))
        {
            //直接响应文字
           await response
                .SetStatus(200, "success")
                .FromText("Success")
                .AnswerAsync();//直接回应
            Console.WriteLine("处理/success");
            return;
        }
        //无法处理,调用下一个插件
        await e.InvokeNext();
    }
}
8.6 响应文件请求
public class MyHttpPlug2 : PluginBase, IHttpPlugin
{
    public async Task OnHttpRequest(IHttpSessionClient client, HttpContextEventArgs e)
    {
        var request = e.Context.Request;//http请求体
        var response = e.Context.Response;//http响应体
        if (request.IsGet() && request.UrlEquals("/file"))
        {
            try
            {
                //直接回应文件。
                var fileInfo = new FileInfo(@"D:\System\Windows.iso");
                var fileName = fileInfo.Name;//可以重新制定文件名称,届时,header中会添加Content-Disposition内容
                var maxSpeed = 1024 * 1024;//最大传输速度
                var bufferLength = 1024 * 64;//一般该值越大,效率越高,但同时内存占用也更大
                var autoGzip = true;//自动判断是否应用gzip压缩。
                await response
                    .SetStatus()//必须要有状态
                    .FromFileAsync(fileInfo, e.Context.Request, fileName, maxSpeed, bufferLength, autoGzip);
                //或者直接使用HttpContext
                //await e.Context.FromFileAsync(fileInfo, fileName, maxSpeed, bufferLength, autoGzip);
            }
            catch (Exception ex)
            {
                await response.SetStatus(403, "error")
                      .FromText(ex.Message)
                      .AnswerAsync();
            }
            return;
        }
        await e.InvokeNext();
    }
}
当响应的文件,希望浏览器直接显示时(例如:html,js,css),不应该指定文件名,不然浏览器会调用下载保存操作,而非直接显示。
在响应文件时,传入请求的request,主要是当请求包含断点续传时,能成功续传。所以,应当应可能的满足该功能。
该操作支持大型文件,也支持断点续传、支持迅雷加速等。
8.7 响应页面请求
public class MyHttpPlug3 : PluginBase, IHttpPlugin
{
    public async Task OnHttpRequest(IHttpSessionClient client, HttpContextEventArgs e)
    {
        var request = e.Context.Request;//http请求体
        var response = e.Context.Response;//http响应体
        if (request.IsGet() && request.UrlEquals("/html"))
        {
            //构建html
            var sb = new StringBuilder();
            sb.Append("<!DOCTYPE html>");
            sb.Append("<html lang=\"zh\">");
            sb.Append("<head>");
            sb.Append("    <meta charset=\"UTF-8\">");
            sb.Append("    <title>TouchSocket绚丽展示</title>");
            sb.Append("    <style>");
            sb.Append("        body {");
            sb.Append("            font-family: Arial, sans-serif;");
            sb.Append("            background-color: #f0f0f0;");
            sb.Append("            display: flex;");
            sb.Append("            justify-content: center;");
            sb.Append("            align-items: center;");
            sb.Append("            height: 100vh;");
            sb.Append("            margin: 0;");
            sb.Append("            padding: 0;");
            sb.Append("            color: transparent;");
            sb.Append("        }");
            sb.Append("        h1 {");
            sb.Append("            font-size: 48px;");
            sb.Append("            letter-spacing: 2px;");
            sb.Append("            background-image: linear-gradient(to right, #ff8a00, #ffcd38);");
            sb.Append("            -webkit-background-clip: text;");
            sb.Append("            background-clip: text;");
            sb.Append("            font-weight: bold;");
            sb.Append("        }");
            sb.Append("    </style>");
            sb.Append("</head>");
            sb.Append("<body>");
            sb.Append("    <h1>TouchSocket</h1>");
            sb.Append("</body>");
            sb.Append("</html>");
            //回应html
            await response
                 .SetStatus()//必须要有状态
                 .SetContentTypeByExtension(".html")
                 .SetContent(sb.ToString())
                 .AnswerAsync();
            return;
        }
        await e.InvokeNext();
    }
}
九、进阶响应操作
9.1 响应有长度大数据
当响应的数据,在响应时,已知数据长度的话,可以使用此方法。
var request = e.Context.Request;//http请求体
var response = e.Context.Response;//http响应体
//先设置需要响应的地方
response.SetStatus(200, "success");
//然后设置数据总长度
response.ContentLength = 1024 * 1024;
for (int i = 0; i < 1024; i++)
{
    //将数据持续写入
    await response.WriteAsync(new byte[1024]);
}
9.2 响应不知长度数据(Chunk模式)
当响应的数据,在响应时,不知数据长度的话,可以使用此方法。
var request = e.Context.Request;//http请求体
var response = e.Context.Response;//http响应体
//先设置需要响应的地方
response.SetStatus(200, "success");
//设置使用Chunk模式
response.IsChunk = true;
for (int i = 0; i < 1024; i++)
{
    //将数据持续写入
    await response.WriteAsync(new byte[1024]);
}
//在正式数据传输完成后,调用此方法,客户端才知道数据结束了
await response.CompleteChunkAsync();
九、创建加密Ssl的HttpsService
Https服务器,和http服务器几乎一样,只不过增加了一个Ssl的配置。
.SetServiceSslOption(new ServiceSslOption() 
{ 
    Certificate = new X509Certificate2("Socket.pfx", "Socket"), 
    SslProtocols = SslProtocols.Tls12 
})