HttpRequest
Netty中的httprequest类结构如下图所示
DefaultFullHttpRequest
先来看DefaultFullHttpRequest,主要参数包括HttpVersion,HttpMethod,String,即Http版本,使用的Http方法,以及url
private final ByteBuf content;
private final HttpHeaders trailingHeader;
private final boolean validateHeaders;
public DefaultFullHttpRequest(HttpVersion httpVersion, HttpMethod method, String uri) {
this(httpVersion, method, uri, Unpooled.buffer(0));
}
public DefaultFullHttpRequest(HttpVersion httpVersion, HttpMethod method, String uri, ByteBuf content) {
this(httpVersion, method, uri, content, true);
}
public DefaultFullHttpRequest(HttpVersion httpVersion, HttpMethod method, String uri,
ByteBuf content, boolean validateHeaders) {
super(httpVersion, method, uri, validateHeaders);
if (content == null) {
throw new NullPointerException("content");
}
this.content = content;
trailingHeader = new DefaultHttpHeaders(validateHeaders);
this.validateHeaders = validateHeaders;
}
再到DefaultHttpMessage,这里封装了http的headers
private HttpVersion version;
private final HttpHeaders headers;
然后来看看DefaultHttpHeaders里面包含了一个headerEntry数组,headerEntry是一个键值对
private final HeaderEntry[] entries = new HeaderEntry[BUCKET_SIZE];
private final class HeaderEntry implements Map.Entry<String, String>
HttpResponse
HttpResponse的类结构和Request基本一样,但其没有HttpMethod类型,只有一个HttpResponseStatus,所以我们一般抓到的包中协议以http开头的都是Response,有get等开头的是Request
HttpResponse类结构
private final ByteBuf content;
private final HttpHeaders trailingHeaders;
private final boolean validateHeaders;
public DefaultFullHttpResponse(HttpVersion version, HttpResponseStatus status) {
this(version, status, Unpooled.buffer(0));
}
public DefaultFullHttpResponse(HttpVersion version, HttpResponseStatus status, ByteBuf content) {
this(version, status, content, true);
}
public DefaultFullHttpResponse(HttpVersion version, HttpResponseStatus status,
ByteBuf content, boolean validateHeaders) {
super(version, status, validateHeaders);
if (content == null) {
throw new NullPointerException("content");
}
this.content = content;
trailingHeaders = new DefaultHttpHeaders(validateHeaders);
this.validateHeaders = validateHeaders;
}
HttpResponse包
HttpResponseEncoder和HttpRequestEncoder
这两个handler均继承与HttpObjectEncoder,编码方式也类似,代码如下
@Override
protected void encode(ChannelHandlerContext ctx, Object msg, List<Object> out) throws Exception {
ByteBuf buf = null;
if (msg instanceof HttpMessage) {//如果是HttpMessage执行编译
if (state != ST_INIT) {
throw new IllegalStateException("unexpected message type: " + StringUtil.simpleClassName(msg));
}
@SuppressWarnings({ "unchecked", "CastConflictsWithInstanceof" })
H m = (H) msg;
buf = ctx.alloc().buffer();
// Encode the message.
encodeInitialLine(buf, m);//编码命令
encodeHeaders(m.headers(), buf);//编码头部
buf.writeBytes(CRLF);
state = HttpHeaders.isTransferEncodingChunked(m) ? ST_CONTENT_CHUNK : ST_CONTENT_NON_CHUNK;
}
// Bypass the encoder in case of an empty buffer, so that the following idiom works:
//
// ch.write(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
//
// See https://github.com/netty/netty/issues/2983 for more information.
if (msg instanceof ByteBuf && !((ByteBuf) msg).isReadable()) {
out.add(EMPTY_BUFFER);
return;
}
if (msg instanceof HttpContent || msg instanceof ByteBuf || msg instanceof FileRegion) {//如果存在内容编码内容
if (state == ST_INIT) {
throw new IllegalStateException("unexpected message type: " + StringUtil.simpleClassName(msg));
}
final long contentLength = contentLength(msg);
if (state == ST_CONTENT_NON_CHUNK) {
if (contentLength > 0) {
if (buf != null && buf.writableBytes() >= contentLength && msg instanceof HttpContent) {
// merge into other buffer for performance reasons
buf.writeBytes(((HttpContent) msg).content());
out.add(buf);
} else {
if (buf != null) {
out.add(buf);
}
out.add(encodeAndRetain(msg));
}
} else {
if (buf != null) {
out.add(buf);
} else {
// Need to produce some output otherwise an
// IllegalStateException will be thrown
out.add(EMPTY_BUFFER);
}
}
if (msg instanceof LastHttpContent) {
state = ST_INIT;
}
} else if (state == ST_CONTENT_CHUNK) {
if (buf != null) {
out.add(buf);
}
encodeChunkedContent(ctx, msg, contentLength, out);
} else {
throw new Error();
}
} else {
if (buf != null) {
out.add(buf);
}
}
}
头部编码
@Override
protected void encodeInitialLine(ByteBuf buf, HttpRequest request) throws Exception {
request.getMethod().encode(buf);//编码方法
buf.writeByte(SP);//添加空格
// Add / as absolute path if no is present.
// See http://tools.ietf.org/html/rfc2616#section-5.1.2
String uri = request.getUri();
if (uri.length() == 0) {
uri += SLASH;
} else {//编码URL
int start = uri.indexOf("://");
if (start != -1 && uri.charAt(0) != SLASH) {
int startIndex = start + 3;
// Correctly handle query params.
// See https://github.com/netty/netty/issues/2732
int index = uri.indexOf(QUESTION_MARK, startIndex);
if (index == -1) {
if (uri.lastIndexOf(SLASH) <= startIndex) {
uri += SLASH;
}
} else {
if (uri.lastIndexOf(SLASH, index) <= startIndex) {
int len = uri.length();
StringBuilder sb = new StringBuilder(len + 1);
sb.append(uri, 0, index)
.append(SLASH)
.append(uri, index, len);
uri = sb.toString();
}
}
}
}
buf.writeBytes(uri.getBytes(CharsetUtil.UTF_8));
buf.writeByte(SP);
request.getProtocolVersion().encode(buf);//编码版本
buf.writeBytes(CRLF);
}
编码Headers
static void encode(HttpHeaders headers, ByteBuf buf) {
if (headers instanceof DefaultHttpHeaders) {
((DefaultHttpHeaders) headers).encode(buf);
} else {
for (Entry<String, String> header: headers) {
encode(header.getKey(), header.getValue(), buf);
}
}
}
@SuppressWarnings("deprecation")
static void encode(CharSequence key, CharSequence value, ByteBuf buf) {
if (!encodeAscii(key, buf)) {
buf.writeBytes(HEADER_SEPERATOR);
}
if (!encodeAscii(value, buf)) {
buf.writeBytes(CRLF);
}
}
HttpObjectDecoder
编码是从HttpObject转成bytebuf,而解码就是从bytebuf中读取HttpObject
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception {
if (resetRequested) {
resetNow();
}
switch (currentState) {
case SKIP_CONTROL_CHARS: {
if (!skipControlCharacters(buffer)) {
return;
}
currentState = State.READ_INITIAL;
}
case READ_INITIAL: try {
AppendableCharSequence line = lineParser.parse(buffer);
if (line == null) {
return;
}
String[] initialLine = splitInitialLine(line);//分割协议头
if (initialLine.length < 3) {
// Invalid initial line - ignore.
currentState = State.SKIP_CONTROL_CHARS;
return;
}
message = createMessage(initialLine);
currentState = State.READ_HEADER;
// fall-through
} catch (Exception e) {
out.add(invalidMessage(buffer, e));
return;
}
case READ_HEADER: try {
State nextState = readHeaders(buffer);//读取头部
if (nextState == null) {
return;
}
currentState = nextState;
switch (nextState) {
case SKIP_CONTROL_CHARS:
// fast-path
// No content is expected.
out.add(message);
out.add(LastHttpContent.EMPTY_LAST_CONTENT);//内容为空
resetNow();
return;
case READ_CHUNK_SIZE://分片大小
if (!chunkedSupported) {
throw new IllegalArgumentException("Chunked messages not supported");
}
// Chunked encoding - generate HttpMessage first. HttpChunks will follow.
out.add(message);
return;
default:
/**
* <a href="https://tools.ietf.org/html/rfc7230#section-3.3.3">RFC 7230, 3.3.3</a> states that
* if a request does not have either a transfer-encoding or a content-length header then the
* message body length is 0. However for a response the body length is the number of octets
* received prior to the server closing the connection. So we treat this as variable length
* chunked encoding.
*/
long contentLength = contentLength();
if (contentLength == 0 || contentLength == -1 && isDecodingRequest()) {
out.add(message);
out.add(LastHttpContent.EMPTY_LAST_CONTENT);
resetNow();
return;
}
assert nextState == State.READ_FIXED_LENGTH_CONTENT ||
nextState == State.READ_VARIABLE_LENGTH_CONTENT;
out.add(message);
if (nextState == State.READ_FIXED_LENGTH_CONTENT) {
// chunkSize will be decreased as the READ_FIXED_LENGTH_CONTENT state reads data chunk by
// chunk.
chunkSize = contentLength;
}
// We return here, this forces decode to be called again where we will decode the content
return;
}
} catch (Exception e) {
out.add(invalidMessage(buffer, e));
return;
}
case READ_VARIABLE_LENGTH_CONTENT: {//读取Content
// Keep reading data as a chunk until the end of connection is reached.
int toRead = Math.min(buffer.readableBytes(), maxChunkSize);
if (toRead > 0) {
ByteBuf content = buffer.readSlice(toRead).retain();
out.add(new DefaultHttpContent(content));
}
return;
}
case READ_FIXED_LENGTH_CONTENT: {
int readLimit = buffer.readableBytes();
// Check if the buffer is readable first as we use the readable byte count
// to create the HttpChunk. This is needed as otherwise we may end up with
// create a HttpChunk instance that contains an empty buffer and so is
// handled like it is the last HttpChunk.
//
// See https://github.com/netty/netty/issues/433
if (readLimit == 0) {
return;
}
int toRead = Math.min(readLimit, maxChunkSize);
if (toRead > chunkSize) {
toRead = (int) chunkSize;
}
ByteBuf content = buffer.readSlice(toRead).retain();
chunkSize -= toRead;
if (chunkSize == 0) {
// Read all content.
out.add(new DefaultLastHttpContent(content, validateHeaders));
resetNow();
} else {
out.add(new DefaultHttpContent(content));
}
return;
}
/**
* everything else after this point takes care of reading chunked content. basically, read chunk size,
* read chunk, read and ignore the CRLF and repeat until 0
*/
case READ_CHUNK_SIZE: try {
AppendableCharSequence line = lineParser.parse(buffer);
if (line == null) {
return;
}
int chunkSize = getChunkSize(line.toString());
this.chunkSize = chunkSize;
if (chunkSize == 0) {
currentState = State.READ_CHUNK_FOOTER;
return;
}
currentState = State.READ_CHUNKED_CONTENT;
// fall-through
} catch (Exception e) {
out.add(invalidChunk(buffer, e));
return;
}
case READ_CHUNKED_CONTENT: {
assert chunkSize <= Integer.MAX_VALUE;
int toRead = Math.min((int) chunkSize, maxChunkSize);
toRead = Math.min(toRead, buffer.readableBytes());
if (toRead == 0) {
return;
}
HttpContent chunk = new DefaultHttpContent(buffer.readSlice(toRead).retain());
chunkSize -= toRead;
out.add(chunk);
if (chunkSize != 0) {
return;
}
currentState = State.READ_CHUNK_DELIMITER;
// fall-through
}
case READ_CHUNK_DELIMITER: {
final int wIdx = buffer.writerIndex();
int rIdx = buffer.readerIndex();
while (wIdx > rIdx) {
byte next = buffer.getByte(rIdx++);
if (next == HttpConstants.LF) {
currentState = State.READ_CHUNK_SIZE;
break;
}
}
buffer.readerIndex(rIdx);
return;
}
case READ_CHUNK_FOOTER: try {//读取最后一段Content
LastHttpContent trailer = readTrailingHeaders(buffer);
if (trailer == null) {
return;
}
out.add(trailer);
resetNow();
return;
} catch (Exception e) {
out.add(invalidChunk(buffer, e));
return;
}
case BAD_MESSAGE: {
// Keep discarding until disconnection.
buffer.skipBytes(buffer.readableBytes());
break;
}
case UPGRADED: {
int readableBytes = buffer.readableBytes();
if (readableBytes > 0) {
// Keep on consuming as otherwise we may trigger an DecoderException,
// other handler will replace this codec with the upgraded protocol codec to
// take the traffic over at some point then.
// See https://github.com/netty/netty/issues/2173
out.add(buffer.readBytes(readableBytes));
}
break;
}
}
}
HttpContentCompressor
这个handler其实就是对Content进行了压缩,包含了压缩方式和压缩等级,这里有gzip和zlib两种压缩方式
@Override
protected Result beginEncode(HttpResponse headers, String acceptEncoding) throws Exception {
String contentEncoding = headers.headers().get(HttpHeaders.Names.CONTENT_ENCODING);
if (contentEncoding != null &&
!HttpHeaders.Values.IDENTITY.equalsIgnoreCase(contentEncoding)) {
return null;
}
ZlibWrapper wrapper = determineWrapper(acceptEncoding);
if (wrapper == null) {
return null;
}
String targetContentEncoding;
switch (wrapper) {
case GZIP:
targetContentEncoding = "gzip";
break;
case ZLIB:
targetContentEncoding = "deflate";
break;
default:
throw new Error();
}
return new Result(
targetContentEncoding,
new EmbeddedChannel(ZlibCodecFactory.newZlibEncoder(
wrapper, compressionLevel, windowBits, memLevel)));
}