完整项目托管地址:https://github.com/sometiny/http
前面文章封装了一个响应应答器:C#实现HTTP服务器:(3)封装用于响应请求的应答器
现在我们基于应答器,尝试使用Chunked方式,向浏览器发送响应。
修改前面文章NewClient重载的内容如下。
public class HttpServer : TcpIocpServer
{
protected override void NewClient(Socket client)
{
Stream stream = new NetworkStream(client, true);
//捕获一个HttpRequest
HttpRequest request = HttpRequest.Capture(stream);
//实例化一个Chunked模式的应答器
HttpResponser responser = new ChunkedResponser();
//发送个Server标头给客户端,验证下标头是否正确
responser.Server = "IServer/1.0";
responser.ContentType = "text/html";
responser.KeepAlive = false;
//发送响应内容
//ChunkedResponser的Write方法会对数据进行Chunked封装
//可以多次调用Write向客户端发送数据
responser.Write(stream, "<p>hello world!</p>");
responser.Write(stream, $"<pre>{request.GetAllRequestHeaders()}</pre>");
//必须调用End方法,向客户端发送结束包
responser.End(stream);
stream.Close();
}
}
服务器启动成功后,浏览器访问:http://127.0.0.1:4189/
可以看到,浏览器正确解析了我们发送的响应以及Server标头。
使用Chunked方式发送数据,无需关心发送的内容长度,只要在最终发送一个结束包到客户端,客户端即可接收到完整内容。
下面的源码说明了Chunked包如何封装。
下篇文章,我们将实现发送Gzip压缩数据到客户端。
ChunkedResponser类源码
/// <summary>
/// 实现发送Chunked类型数据的应答器
/// </summary>
public class ChunkedResponser : HttpResponser
{
//回车换行
private static byte[] _crlf = new byte[] { 13, 10 };
//结束包内容
private static byte[] _endingChunk = Encoding.ASCII.GetBytes("0\r\n\r\n");
public ChunkedResponser() : this(200) { }
public ChunkedResponser(int statusCode) : base(statusCode) { }
protected override void WriteHeader(Stream stream)
{
//移除长度标头,确保Chunked传输
Response.RemoveHeader("Content-Length");
//设置传输方式为Chunked
this["Transfer-Encoding"] = "Chunked";
base.WriteHeader(stream);
}
/// <summary>
/// 对数据封包后,向基础流写入Chunk包
/// </summary>
/// <param name="stream"></param>
/// <param name="buffer"></param>
/// <param name="offset"></param>
/// <param name="size"></param>
public override void Write(Stream stream, byte[] buffer, int offset, int size)
{
///组装包头,包含长度数据
///数据长度的16进制表示形式+\r\n
///举例:
///如果数据长度是10,那么包头是:a\r\n
///如果数据长度是20,那么包头是:14\r\n
///如果数据长度是256,那么包头是:100\r\n
string length = size.ToString("x") + "\r\n";
byte[] lengthBuffer = Encoding.ASCII.GetBytes(length);
///注意调用的是父类Write
///写入长度数据
base.Write(stream, lengthBuffer, 0, lengthBuffer.Length);
///写入包数据
base.Write(stream, buffer, offset, size);
///写入包尾,固定的回车换行
base.Write(stream, _crlf, 0, 2);
}
/// <summary>
/// 向基础流写入结束包,即长度为0的Chunk包,编码后的值是0\r\n\r\n
/// 这里直接写固定值,省去封包
/// 必须向客户端发送结束包,不然客户端会一直等待数据。
/// </summary>
/// <param name="stream">基础流</param>
public override void End(Stream stream)
{
base.Write(stream, _endingChunk, 0, 5);
}
/// <summary>
/// 打开一个流,用于写入数据
/// </summary>
/// <param name="baseStream"></param>
/// <returns>ChunkedWriteStream流</returns>
public override Stream OpenWrite(Stream baseStream)
{
return new ChunkedWriteStream(base.OpenWrite(baseStream), true);
}
}