`

Netty源码学习-HttpChunkAggregator-HttpRequestEncoder-HttpResponseDecoder

阅读更多
今天看Netty如何实现一个Http Server
org.jboss.netty.example.http.file.HttpStaticFileServerPipelineFactory:
		pipeline.addLast("decoder", new HttpRequestDecoder());
        pipeline.addLast("aggregator", new HttpChunkAggregator(65536));
        pipeline.addLast("encoder", new HttpResponseEncoder());

分析一下这三个ChannalHandler

HttpResponseEncoder比较简单,没什么可说的
按照Http协议规定的Response的格式
把org.jboss.netty.handler.codec.http.HttpResponse转为ChannelBuffer就可以了
注意添加必要的CR(Carriage Return)和LF(Line Feed)

HttpRequestDecoder
如何解析HttpRequest?很容易想到要用ReplayingDecoder:
所以 HttpRequestDecoder extends ReplayingDecoder
根据HttpRequest的消息结构,定义State:
	protected static enum State {
        SKIP_CONTROL_CHARS,
        READ_INITIAL,		//请求行
        READ_HEADER,	//请求头
        READ_VARIABLE_LENGTH_CONTENT,
        READ_VARIABLE_LENGTH_CONTENT_AS_CHUNKS,
        READ_FIXED_LENGTH_CONTENT,		//请求主体
        READ_FIXED_LENGTH_CONTENT_AS_CHUNKS,
        READ_CHUNK_SIZE,
        READ_CHUNKED_CONTENT,
        READ_CHUNKED_CONTENT_AS_CHUNKS,
        READ_CHUNK_DELIMITER,
        READ_CHUNK_FOOTER;
    }

后面的几个定义与“http chunk”有关,稍后再分析
简单地看一看decode方法:
protected Object decode(...) {
        switch (state) {
			//...
			case READ_INITIAL: {
				String[] initialLine = splitInitialLine(readLine(buffer, maxInitialLineLength));
				message = createMessage(initialLine);
				checkpoint(State.READ_HEADER);
			}
			case READ_HEADER: {
				State nextState = readHeaders(buffer);
				checkpoint(nextState);
			}
		}
		//...
}
private State readHeaders(ChannelBuffer buffer) throws TooLongFrameException {
        final HttpMessage message = this.message;
        String line = readHeader(buffer);
        String name = null;
        String value = null;
		//...
        message.addHeader(name, value);
}

很好理解。ReplayingDecoder使用时,“可以认为”数据已经接收完整,因此,按行读入并解析就可以了
那如何“断行”呢?别忘了,HttpResponse时会写入CRLF,因此readLine时,遇到CRLF就表示读到行尾了

真正的难题在HttpChunkAggregator
这个ChannelHandler有什么用?

首先看看“http chunk”是什么
当Server返回的HttpResponse是动态生成,无法“一开始”就确定Content-Length时,
可以采用“http chunk”

举例:
一般形式的HttpResponse:
HTTP/1.1 200 OK
Date: Mon, 22 Mar 2004 11:15:03 GMT
Content-Type: text/html
Content-Length: 129
Expires: Sat, 27 Mar 2004 21:12:00 GMT

<html><body><p>The file you requested is 3,400 bytes long and was last modified: Sat, 20 Mar 2004 21:12:00 GMT.</p></body></html>

对应的“http chunk”形式HttpResponse:
HTTP/1.1 200 OK
Date: Mon, 22 Mar 2004 11:15:03 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Trailer: Expires

29
<html><body><p>The file you requested is 
5
3,400
23
bytes long and was last modified: 
1d
Sat, 20 Mar 2004 21:12:00 GMT
13
.</p></body></html>
0
Expires: Sat, 27 Mar 2004 21:12:00 GMT

简单地说,“http chunk”就是
1.在header加入“Transfer-Encoding: chunked”,
2.把body分成多段(每段的格式是:字节流的长度+CRLF+字节流+CRLF)
长度为0的段表示结束
注意表示长度的数字是十六进制,计算长度时不包括末尾的CRLF
详见《HTTP权威指南》

HttpChunkAggregator的作用就是把“http chunk”形式转为一般形式

如何实现?
与“http chunk”对着干就可以了:
1.把“Transfer-Encoding: chunked”去掉
2.把分段的数据组合成一段
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
            throws Exception {

        Object msg = e.getMessage();
        HttpMessage currentMessage = this.currentMessage;

        if (msg instanceof HttpMessage) {
            HttpMessage m = (HttpMessage) msg;

            //处理Expect: 100-continue请求
            if (is100ContinueExpected(m)) {
                write(ctx, succeededFuture(ctx.getChannel()), CONTINUE.duplicate());
            }

            if (m.isChunked()) {
                //移除 'Transfer-Encoding' 
                List<String> encodings = m.getHeaders(HttpHeaders.Names.TRANSFER_ENCODING);
                encodings.remove(HttpHeaders.Values.CHUNKED);
                if (encodings.isEmpty()) {
                    m.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING);
                }
                m.setChunked(false);
                this.currentMessage = m;
            } else {
                // Not a chunked message - pass through.
                this.currentMessage = null;
                ctx.sendUpstream(e);
            }
        } else if (msg instanceof HttpChunk) {
            HttpChunk chunk = (HttpChunk) msg;
            ChannelBuffer content = currentMessage.getContent();
            appendToCumulation(chunk.getContent());

            if (chunk.isLast()) {
                this.currentMessage = null;
                //“http chunk”有时候会把一个“拖挂”(例如上面例子里面的Expires)放到最后
				//要读取这个值并设置header
                if (chunk instanceof HttpChunkTrailer) {
                    HttpChunkTrailer trailer = (HttpChunkTrailer) chunk;
                    for (Entry<String, String> header: trailer.getHeaders()) {
                        currentMessage.setHeader(header.getKey(), header.getValue());
                    }
                }

                //设置Content-Length,这个值就是各段大小的总和
                currentMessage.setHeader(
                        HttpHeaders.Names.CONTENT_LENGTH,
                        String.valueOf(content.readableBytes()));

                Channels.fireMessageReceived(ctx, currentMessage, e.getRemoteAddress());
            }
        } else {
            ctx.sendUpstream(e);
        }
    }

从上面r的if-else判断也可看到,HttpChunkAggregator是把“http chunk”分成两大部分来decode的:
HttpMessage和HttpChunk
这两部分的分割是谁来做?当然是HttpRequestDecoder
这就是为什么API里面会说要把HttpChunkAggregator放在HttpRequestDecoder的后面

回头细看一下HttpRequestDecoder的decode方法:
case READ_HEADER: {
	State nextState = readHeaders(buffer);
	checkpoint(nextState);
	if (nextState == State.READ_CHUNK_SIZE) {
		// Chunked encoding
		message.setChunked(true);
		// Generate HttpMessage first.  HttpChunks will follow.
		return message;
}
case READ_CHUNK_SIZE: {
	String line = readLine(buffer, maxInitialLineLength);
	int chunkSize = getChunkSize(line);
	this.chunkSize = chunkSize;
	//...
	checkpoint(State.READ_CHUNKED_CONTENT);
}
case READ_CHUNKED_CONTENT: {
	HttpChunk chunk = new DefaultHttpChunk(buffer.readBytes((int) chunkSize));
	checkpoint(State.READ_CHUNK_DELIMITER);
	return chunk;
}

可以看到,HttpRequestDecoder把接收到的每一个分段数据组装成一个HttpChunk返回给
下一个Handler(这里是HttpChunkAggregator)处理
所以HttpChunkAggregator要把多个HttpChunk读取完毕后再组装在一起
另外HttpChunkAggregator组装时如果发现content-length超过maxContentLength,
就会抛TooLongFrameException

另外,上面还提到了http的100-continue:
有时Client发送数据前会先发送Expect: 100-continue,如果Server愿意接受数据,则返回类似如下的响应:
HTTP/1.1 100 Continue

HTTP/1.1 200 OK
Date: Fri, 31 Dec 1999 23:59:59 GMT
Content-Type: text/plain
Content-Length: 42
some-footer: some-value
another-footer: another-value

abcdefghijklmnoprstuvwxyz1234567890abcdef

这是两个HttpResponse
第二个才是真实的、响应的数据,Client可以直接忽略第一个

0
0
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics