完整项目托管地址:https://github.com/hooow-does-it-work/http
前面文章实现了通过Content-Length
和Transfer-Encoding
分别向客户端发送数据。
C#实现HTTP服务器:(3)封装用于响应请求的应答器
C#实现HTTP服务器:(4)使用Transfer-Encoding: Chunked标头向客户端发送响应
现在,在传输数据的基础上,我们再对数据进行压缩,C#有对这两种压缩算法的实现。
即,位于System.IO.Compression
命名空间下的GZipStream
和DeflateStream
。
这里只讲怎样使用Gzip压缩数据,分别用Content-Length
和Transfer-Encoding
两种传输方式实现。
0、使用Content-Length实现Gzip压缩数据的传输。
这种方式简单说,就是压缩数据后,取得所有数据的内容,获取到内容长度,然后一股脑的发送给客户端。
直接上代码了,使用Compress
方法压缩数据,返回结果,发送到客户端,没什么理解的难度。
这里关键的是,发送Content-Encoding
标头。
HttpResponser
实现了this
索引器,所以可以直接使用[]
来设置标头。
public class HttpServer : TcpIocpServer
{
protected override void NewClient(Socket client)
{
Stream stream = new NetworkStream(client, true);
//捕获一个HttpRequest
HttpRequest request = HttpRequest.Capture(stream);
//准备发送到客户端的数据
string responseText = $"<p>hello world!</p><pre>{request.GetAllRequestHeaders()}</pre>";
byte[] responseBuffer = Encoding.ASCII.GetBytes(responseText);
//压缩数据
responseBuffer = Compress(responseBuffer);
HttpResponser responser = new HttpResponser();
//使用Content-Encoding标头,告诉客户端发送的是经过Gzip压缩的数据。
responser["Content-Encoding"] = "gzip";
responser.ContentLength = responseBuffer.Length;
responser.ContentType = "text/html";
responser.KeepAlive = false;
responser.Write(stream, responseBuffer);
stream.Close();
}
/// <summary>
/// Gzip压缩
/// </summary>
/// <param name="source">原内容</param>
/// <returns>压缩后内容</returns>
private byte[] Compress(byte[] source)
{
//使用内存流保存压缩后的数据
using (MemoryStream output = new MemoryStream())
{
using (GZipStream input = new GZipStream(output, CompressionMode.Compress))
{
input.Write(source, 0, source.Length);
}
return output.ToArray();
}
}
}
启动服务,浏览器访问:http://127.0.0.1:4189
可以看到浏览器正确处理了Gzip压缩后的数据。
2、使用Transfer-Encoding标头传输Gzip压缩的数据。
话不多说,直接上源码,代码里面使用了ChunkedResponser
应答器
这里可以查看ChunkedResponser实现
public class HttpServer : TcpIocpServer
{
protected override void NewClient(Socket client)
{
Stream stream = new NetworkStream(client, true);
//捕获一个HttpRequest
HttpRequest request = HttpRequest.Capture(stream);
//准备发送到客户端的数据
string responseText = $"<p>hello world!</p><pre>{request.GetAllRequestHeaders()}</pre>";
byte[] responseBuffer = Encoding.ASCII.GetBytes(responseText);
//压缩数据
responseBuffer = Compress(responseBuffer);
//实例化ChunkedResponser类
HttpResponser responser = new ChunkedResponser();
//使用Content-Encoding标头,告诉客户端发送的是经过Gzip压缩的数据。
responser["Content-Encoding"] = "gzip";
responser.ContentType = "text/html";
responser.KeepAlive = false;
responser.Write(stream, responseBuffer);
responser.End(stream);
stream.Close();
}
/// <summary>
/// Gzip压缩
/// </summary>
/// <param name="source">原内容</param>
/// <returns>压缩后内容</returns>
private byte[] Compress(byte[] source)
{
//使用内存流保存压缩后的数据
using (MemoryStream output = new MemoryStream())
{
using (GZipStream input = new GZipStream(output, CompressionMode.Compress))
{
input.Write(source, 0, source.Length);
}
return output.ToArray();
}
}
}
启动服务,浏览器访问:http://17.0.0.1:4189/
可以看到浏览器正确处理了使用Chunked方式传输的Gzip压缩数据。
2、后话
其实这里的逻辑相比之前的代码,仅仅是加了一个Compress
方法去压缩数据,单次压缩,单次写入。
整个逻辑中没有用到流的思想。
如果数据比较大,例如压缩大量的JS、CSS文件的时候,会产生不必要的大块内存消耗。
后面我们会实现一个ChunkedWriteStream
,将一个JS文件压缩后,以Chunked的方式发送到客户端。