之前在本机研究dropwizard时,本着默认的情况下应该开启gzip压缩,因此在相应的yml配置中开启gzip标记(它本身也是开启的).但是在相应的请求返回中并没有出现相应的gzip标识,在chrome浏览器下的标识信息为如下的响应所示:
从上面的标识来看,在响应头上并没有gzip标识.
然后开始痛苦的处理过程,从浏览器,客户端,服务端,然后最后从网络层逐步处理,最终找到问题的原因及出处.
欢迎来到Flym的程序之家
之前在本机研究dropwizard时,本着默认的情况下应该开启gzip压缩,因此在相应的yml配置中开启gzip标记(它本身也是开启的).但是在相应的请求返回中并没有出现相应的gzip标识,在chrome浏览器下的标识信息为如下的响应所示:
从上面的标识来看,在响应头上并没有gzip标识.
然后开始痛苦的处理过程,从浏览器,客户端,服务端,然后最后从网络层逐步处理,最终找到问题的原因及出处.
我们准备实现一个简单的http服务器。它具备有接收客户端请求,并通过读取相应的数据进行协议分析,最后返回相应的数据的能力。
从根本上来说,http服务器也就是一个网络服务器,nio框架netty也有一个参考的http协议实现。本参考实现不使用第三方网络包,仅通过javaSE来完成一个http协议实现,以用于在读取http协议时有一个更清晰的认识。
本实现的细节在一定程度上参考了netty,tomcat等现有的实现,但这里详细地把相应的理论细节解释清楚。
本篇为第1节,即通过构建一个nio服务端来作基本的网络处理。
在nio中,服务端建立起相应的serverSocket,并向selector注册相应的事件。当事件发生时去做相应的事情即可,在这个过程中数据的传输和代码实现是异步的,即在有数据的情况下才能做相应的事情,但数据和操作之间并不同步,即不能一次性地把所有的数据都读取完毕,或者不能一次性地把所有的数据都返回给客户端。所以,在整个实现中,需要在事件处理中不断地监听相应的事件,并通过在处理数据和事件切换中不断地变换具体的业务处理,直到一个完整地业务被处理掉。
在整个实现中,涉及到2个问题。一是事件的划分和处理,二是具体事件的隔离和处理。
1 事件的划分和处理
在服务端,需要处理3类事件信息,接收数据请求,读取请求数据,写入响应数据。读取数据和写入数据都是在connection已经建立好的情况下进行,而接收数据请求则表示服务端已经准备好接收由客户端发起的一个请求。这里将数据的处理和请求的接收分开,即分成2个大的处理部分。一个简单的原因即在于当连接已经建立起之后,服务端是一定要处理这个数据请求的;而连接还没有建立时,是可以拒绝处理的。即在当请求端有大量的请求在准备接收时,服务端不会因为请求的处理太多影响到后端的数据处理。
这里涉及到线程网络处理的问题,如果大量的处理线程都处理请求接收时,就意味着后台有大量的连接需要处理,而之前已经接收到的请求处理就会受到相应的影响,进而影响到整个系统的稳定性。将接收和处理分离,有利于更好地实现请求限制和分发。
在进行Http传输中,可以通过设置Transfer-Encoding以进行传输编码。其中chunked编码表示分块进行数据传输,其通过一个0长度的chunk块表示数据传输完毕。对于在传输过程中的chunked块,则通过以下的一个数据格式进行描述:
chunk-size [ chunk-extension ] CRLF chunk-data CRLF
不考虑chunked-extension属性,则可以理解为先描述块长度,然后接回车换行,接下来再描述相应长度的数据信息,最后接回车换行符,即完成一个chunk编码信息。
这里的chunk-size,与其它的传输不同,这里需要设置为 16 进制数字字符串。即不是传输认为的设置一个长度值即可。对于一个长度值为255的chunk块,其16进制为ff(小写表示),直接编码即为 ff。但这里要求为数字字符串,即以字符串的方式来描述编码值,编码为'f''f',针对f字符的编码值为 66(16进制),那么255长度的实际编码值即为 66 66。
一个255长度的编码片断,类似如下所示:
如上所示的标记处,前面为头结束的2个CRLF值。
在Http编码中,只有这里的chunked编码有点特别。其它的长度信息如content-length值等,都是使用正常的长度值(16进制),确实有点奇怪。在编码时,如果对这里的编码头没写正确,那么输出到客户端的数据永远都是错的…
前段时间在项目中看到如下的代码:
HttpServletResponse response = (HttpServletResponse)servletResponse; response.setHeader("Transfer-Encoding", "utf8"); filterChain.doFilter(servletRequest, servletResponse);
原意是想对输出的内容进行编码,却用错了响应头,结果这个错误的响应头对后面的客户端程序带来了许多麻烦。这里有必要对这个这块的内容进行详细地了解。
传输数据编码:Transfer-Encoding
数据编码,即表示数据在网络传输当中,使用怎么样的保证方式来保证数据是安全成功地传输处理。可以是分段传输,也可以是不分段,直接使用原数据进行传输。
有效的值为:Trunked和Identity.
传输内容编码:Content-Encoding
内容编码,即整个数据信息是在数据器端经过怎样的编码处理,然后客户端会以怎么的编码来反向处理,以得到原始的内容。这里的内容编码主要是指压缩编码,即服务器端压缩,客户端解压缩。
可以参考的值为:gzip,compress,deflate和identity。
传输内容格式:Content-Type
内容格式,即接收的数据最终是以何种的形式显示在浏览器中。可以是一个图片,还是一段文本,或者是一段html。内容格式额外支持可选参数,charset,即实际内容的字符集。通过字符集,客户端可以对数据进行解编码,以最终显示可以看得懂的文字(而不是一段byte[]或者是乱码)。
3种描述信息,可以由下图来表示(来源于《Http权威指南》):
从上文中,可以看出,实际上原filter中的内容可能是想表达以下的意思:
response.setContentType("text/html;charset=UTF8"); //或者 response.setContentType("application/json;charset=UTF8");