文章目录
HttpObjectDecoder
固定常量定义
java
// 请求行/响应行 最大允许的长度
public static final int DEFAULT_MAX_INITIAL_LINE_LENGTH = 4096;
// 每个请求头/响应头的最大允许的长度
public static final int DEFAULT_MAX_HEADER_SIZE = 8192;
// 默认支持分块传输, 否则不支持transfer-encoding: chunked 请求头/响应头
public static final boolean DEFAULT_CHUNKED_SUPPORTED = true;
// 是否到达分块大小后, 才封装为1个消息体传给后面的入站处理器
public static final boolean DEFAULT_ALLOW_PARTIAL_CHUNKS = true;
// 最大允许读取的分块大小
public static final int DEFAULT_MAX_CHUNK_SIZE = 8192;
// 是否校验头名字的合法性
public static final boolean DEFAULT_VALIDATE_HEADERS = true;
// 初始化AppendableCharSequence的底层字符数组的长度
public static final int DEFAULT_INITIAL_BUFFER_SIZE = 128;
// 允许重复的Content-Length头对应的值,但值必须一样
public static final boolean DEFAULT_ALLOW_DUPLICATE_CONTENT_LENGTHS = false;
成员属性定义
java
// 最大允许读取的分块大小
private final int maxChunkSize;
// 是否支持分块传输, 即是否支持transfer-encoding: chunked 请求头/响应头
private final boolean chunkedSupported;
// 是否到达分块大小后, 才封装为1个消息体传给后面的入站处理器
private final boolean allowPartialChunks;
// 是否校验头名字的合法性
protected final boolean validateHeaders;
// 允许重复的Content-Length头对应的值,但值必须一样
private final boolean allowDuplicateContentLengths;
// 头解析器
private final HeaderParser headerParser;
// 行解析器
private final LineParser lineParser;
// 请求行+请求头 / 响应行 + 响应头
private HttpMessage message;
// 当前处理过程中读取到应该要接收到的块大小值
private long chunkSize;
// content-length头的值, 如果存在的话 >= 0
private long contentLength = Long.MIN_VALUE;
// 标记是否需要重置请求
private volatile boolean resetRequested;
// 当前处理过程中正在处理到的请求头
private CharSequence name;
// 当前处理过程中正在处理到的请求头对应的值
private CharSequence value;
// 最后1块http消息体
private LastHttpContent trailer;
// 初始状态为 <跳过控制字符>
private State currentState = State.SKIP_CONTROL_CHARS;
State
java
private enum State {
// 跳过控制字符
SKIP_CONTROL_CHARS,
// 开始读取
READ_INITIAL,
// 读取头
READ_HEADER,
// 读取可变长度内容
READ_VARIABLE_LENGTH_CONTENT,
// 读取固定长度内容
READ_FIXED_LENGTH_CONTENT,
// 读取块大小
READ_CHUNK_SIZE,
// 读取块内容
READ_CHUNKED_CONTENT,
// 读取块分隔符
READ_CHUNK_DELIMITER,
// 读取块尾部
READ_CHUNK_FOOTER,
// 错误消息
BAD_MESSAGE,
// 协议升级
UPGRADED
}
HeaderParser
java
private static class HeaderParser implements ByteProcessor {
// 可追加字符的字符串实现(就是由1个字符串数据,然后扩容就是加大数组容量后,拷贝原字符数组)
private final AppendableCharSequence seq;
// 最多允许处理的字符长度
private final int maxLength;
// 表示当前已经处理了多少个字节
int size;
HeaderParser(AppendableCharSequence seq, int maxLength) {
this.seq = seq;
this.maxLength = maxLength;
}
public AppendableCharSequence parse(ByteBuf buffer) {
// 保存旧的size
final int oldSize = size;
// 重置seq的position置为0
seq.reset();
// 逐个字节遍历buffer, 找到目标字符, 就停止遍历, 返回目标字符的索引
int i = buffer.forEachByte(this);
// 如果没有找到目标字符,恢复旧size,返回null
if (i == -1) {
size = oldSize;
return null;
}
// 即找到了目标字符
// buffer的readerIndex向前移动1位
buffer.readerIndex(i + 1);
// 返回处理后的seq
return seq;
}
public void reset() {
size = 0;
}
@Override
public boolean process(byte value) throws Exception {
// 0xFF 即 1111 1111
char nextByte = (char) (value & 0xFF);
// 如果遇到了换行符
if (nextByte == HttpConstants.LF) {
// seq目前的长度
int len = seq.length();
// 如果seq的最后面是个回车符
if (len >= 1 && seq.charAtUnsafe(len - 1) == HttpConstants.CR) {
// size减1
-- size;
// seq长度减1
seq.setLength(len - 1);
}
// 返回false, 表示不要再继续遍历了,已经找到了
return false;
}
// 如果未遇到换行符, size+1,但不能超过maxLength
increaseCount();
// seq加上这个字节
seq.append(nextByte);
// 表示需要继续遍历
return true;
}
protected final void increaseCount() {
if (++ size > maxLength) {
throw newException(maxLength);
}
}
protected TooLongFrameException newException(int maxLength) {
return new TooLongHttpHeaderException("HTTP header is larger than " + maxLength + " bytes.");
}
}
LineParser
java
private final class LineParser extends HeaderParser {
LineParser(AppendableCharSequence seq, int maxLength) {
super(seq, maxLength);
}
@Override
public AppendableCharSequence parse(ByteBuf buffer) {
// 解析前, 先将size置为0
reset();
// 沿用父类HeaderParser的逻辑
return super.parse(buffer);
}
@Override
public boolean process(byte value) throws Exception {
// 如果当前状态是 <跳过控制字符>
if (currentState == State.SKIP_CONTROL_CHARS) {
char c = (char) (value & 0xFF);
// [\u0000-\u001F] 和 [\u007F-\u009F] 是控制字符
// \t、\n、\u000B、\f、\r、\u001C、\u001D、\u001E、\u001F 是空白字符
if (Character.isISOControl(c) || Character.isWhitespace(c)) {
// size加1
increaseCount();
// 跳过 控制字符 和 空白字符
return true;
}
// 跳过所有的连续的控制字符 和 空白字符后,或 没有控制字符和空白字符后,
// 直接切换到下1个状态 <开始读取>
currentState = State.READ_INITIAL;
}
// 沿用父类HeaderParser的逻辑, 也是去找换行符
return super.process(value);
}
@Override
protected TooLongFrameException newException(int maxLength) {
return new TooLongHttpLineException("An HTTP line is larger than " + maxLength + " bytes.");
}
}
构造方法
java
protected HttpObjectDecoder(
int maxInitialLineLength,
int maxHeaderSize,
int maxChunkSize,
boolean chunkedSupported,
boolean validateHeaders,
int initialBufferSize,
boolean allowDuplicateContentLengths,
boolean allowPartialChunks) {
checkPositive(maxInitialLineLength, "maxInitialLineLength");
checkPositive(maxHeaderSize, "maxHeaderSize");
checkPositive(maxChunkSize, "maxChunkSize");
// 可追加字符的字符串实现(就是由1个字符串数据,然后扩容就是加大数组容量后,拷贝原字符数组)
AppendableCharSequence seq = new AppendableCharSequence(initialBufferSize);
// 行解析器(继承自头解析器,区别在于会跳过控制字符和空白字符)
lineParser = new LineParser(seq, maxInitialLineLength);
// 头解析器(找到LF换行符就获取1个头)
headerParser = new HeaderParser(seq, maxHeaderSize);
this.maxChunkSize = maxChunkSize;
this.chunkedSupported = chunkedSupported
this.validateHeaders = validateHeaders;
this.allowDuplicateContentLengths = allowDuplicateContentLengths;
this.allowPartialChunks = allowPartialChunks;
}
decode
java
@Override
protected void decode(ChannelHandlerContext ctx,
ByteBuf buffer,
List<Object> out) throws Exception {
// 如果需要重置请求
if (resetRequested) {
// 重置各个属性
resetNow();
}
switch (currentState) {
case SKIP_CONTROL_CHARS: // 初始状态就是 <跳过控制字符>, 直接进入下一个case(不判断条件)
// Fall-through
case READ_INITIAL: try {
// 遍历buffer中的每个字节,
// 如果能找到LF换行符, 那么line将不为null, 则可以解析该请求行/响应头
// 如果未找到LF换行符, 那么line为null, 说明后面的LF还未收到, 则继续接收
AppendableCharSequence line = lineParser.parse(buffer);
if (line == null) { // 未找到LF换行符的情况
// 继续接收, 等待下次处理
return;
}
// 拆分http请求行
String[] initialLine = splitInitialLine(line);
// 请求行 拆分结果若不足3个,说明有问题,继续恢复当前状态为初始状态,结束当前处理
if (initialLine.length < 3) {
currentState = State.SKIP_CONTROL_CHARS;
return;
}
// 将请求行/响应行封装为HttpMessage,
// 请求会被封装为 DefaultHttpRequest
// 响应会被封装为 DefaultHttpResponse
message = createMessage(initialLine);
// 读取完请求行/响应行 之后, 就要读取 头信息了,切换当前状态为 读取头信息
currentState = State.READ_HEADER;
// 直接进入下一个case(不判断条件)
} catch (Exception e) {
// 如果读取行信息发生异常,则封装出1个 GET /bad-request http/1.1 请求体为空的DefaultFullHttpRequest添加到out中,给后面的入站处理器处理
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:
// 直接将http消息添加到out中,
out.add(message);
// 并补上一个LastHttpContent.EMPTY_LAST_CONTENT(这个里面没有trailerHeader)
out.add(LastHttpContent.EMPTY_LAST_CONTENT);
// 重置各个属性,因为已经解码完了
resetNow();
// 结束当前方法
return;
// 说明请求方希望通过chunked的方式传输内容
case READ_CHUNK_SIZE:
// 如果当前不支持chunked的方式传输,则抛出异常
if (!chunkedSupported) {
throw new IllegalArgumentException("Chunked messages not supported");
}
// 基于chunked的方式传输内容,需要先将http消息传给后面的入站处理器,后面再解析分块内容
// 下次解码,会跑到下面的 case READ_CHUNK_SIZE: 的分支上去哦
out.add(message);
// 结束当前方法
return;
default:
// 那就只剩下 可变长度内容 和 固定长度内容 咯
// 获取content-length头的值
long contentLength = contentLength();
// 如果content-length是0 或者 content-length是-1并且是请求解码器
if (contentLength == 0 || contentLength == -1 && isDecodingRequest()) {
// 先添加http消息到out中
out.add(message);
// 并补上一个LastHttpContent.EMPTY_LAST_CONTENT(这个里面没有trailerHeader)
out.add(LastHttpContent.EMPTY_LAST_CONTENT);
// 重置各个属性,因为已经解码完了
resetNow();
// 结束当前方法
return;
}
// 因为只剩下这2种情况了,并且消息体是有内容的情况
assert nextState == State.READ_FIXED_LENGTH_CONTENT ||
nextState == State.READ_VARIABLE_LENGTH_CONTENT;
// 也是先添加http消息到out中
out.add(message);
// 如果是 读取 固定长度内容,则将chunkSize 赋值为 contentLength
if (nextState == State.READ_FIXED_LENGTH_CONTENT) {
chunkSize = contentLength;
}
// 针对剩下的2种情况,都是由chunkSize去决定读取多长的内容
// 读取内容交给 下次解码
return;
}
} catch (Exception e) {
// 发生了异常
// 则封装出1个 GET /bad-request http/1.1 请求体为空的DefaultFullHttpRequest添加到out中,给后面的入站处理器处理out.add(invalidMessage(buffer, e));
return;
}
//
case READ_VARIABLE_LENGTH_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.readRetainedSlice(toRead);
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 an 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.readRetainedSlice(toRead);
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);
if (!allowPartialChunks && buffer.readableBytes() < toRead) {
return;
}
toRead = Math.min(toRead, buffer.readableBytes());
if (toRead == 0) {
return;
}
HttpContent chunk = new DefaultHttpContent(buffer.readRetainedSlice(toRead));
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 {
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;
}
default:
break;
}
}
resetNow
java
private void resetNow() {
// 获取到当前解码的http消息(请求行/响应头 + 请求头/响应头)
HttpMessage message = this.message;
// 重置 http消息为null
this.message = null;
// 重置请求头名为null
name = null;
// 重置请求头值为null
value = null;
// 重置content-length为 Long的最小值
contentLength = Long.MIN_VALUE;
// 将size属性置为0,即处理过的字符数量为0
lineParser.reset();
// 将size属性置为0,即处理过的字符数量为0
headerParser.reset();
// 重置最后一块http消息体为null
trailer = null;
// 如果当前解码器不是请求解码器,即是响应解码器
if (!isDecodingRequest()) {
// 拿到解码到的http响应消息
HttpResponse res = (HttpResponse) message;
// 如果res的响应状态码不是101,则返回false
// 如果res的响应状态码是101,此时,如果upgrade头是null,则返回true,如果upgrade既不是http/1.0,也不是http/1.1,则返回true,其它情况返回false
if (res != null && isSwitchingToNonHttp1Protocol(res)) {
// 将当前状态置为升级协议状态
currentState = State.UPGRADED;
// 结束方法
return;
}
}
// 将重置请求标记位置为false
resetRequested = false;
// 重置当前状态为 跳过控制字符
currentState = State.SKIP_CONTROL_CHARS;
}
splitInitialLine
java
private static String[] splitInitialLine(AppendableCharSequence sb) {
int aStart;
int aEnd;
int bStart;
int bEnd;
int cStart;
int cEnd;
// 请求行 如: GET /uri http/1.1
// 响应行 如: http/1.1 200 OK
// 找第1部分
// 找到不为空格符的字符
aStart = findNonSPLenient(sb, 0);
// 找到空格符
aEnd = findSPLenient(sb, aStart);
// 找第2部分
bStart = findNonSPLenient(sb, aEnd);
bEnd = findSPLenient(sb, bStart);
// 找第3部分
cStart = findNonSPLenient(sb, bEnd);
cEnd = findEndOfString(sb);
// 返回的数组只会是3个元素
return new String[] {
sb.subStringUnsafe(aStart, aEnd),
sb.subStringUnsafe(bStart, bEnd),
cStart < cEnd? sb.subStringUnsafe(cStart, cEnd) : "" };
}
readHeaders
java
private State readHeaders(ByteBuf buffer) {
// HttpMessage是包含头信息的, 因为刚刚只读取到了请求行,现在需要补充头信息了
final HttpMessage message = this.message;
final HttpHeaders headers = message.headers();
// 先去尝试找到1个头信息, 如果遇到了LF, 则说明找到了, 如果没遇到LF, 则需要 继续等一直等到LF为止或超过headerParser允许的最大长度为止
// 如果找到LF, 则至少返回的是空字符串"", 即表示没有任何头信息
AppendableCharSequence line = headerParser.parse(buffer);
if (line == null) { // 没遇到LF, 则需要 继续等一直等到LF为止
return null;
}
// 上面至少找到了1个头信息
if (line.length() > 0) {
do {
// 获取当前解析行的第一个字符
char firstChar = line.charAtUnsafe(0);
if (name != null && (firstChar == ' ' || firstChar == '\t')) {
// 此处接着上一轮的未接收完的头接着处理,如果第一个字符是 ' ' 或 \t
// 则将上次的值 拼接上1个' ' 加上当前的这 一行
String trimmedLine = line.toString().trim();
String valueStr = String.valueOf(value);
value = valueStr + ' ' + trimmedLine;
} else {
if (name != null) {
// 此处接着上一轮的未接收完的头接着处理, 直接添加上一轮的头到headers中
headers.add(name, value);
}
// 通过冒号":"分割 头的名字 和 头的值
splitHeader(line);
}
// 又去尝试找下一行的头信息, 如果下一行的头信息 的 LF还没接收到的话, 则继续等接收到后,再继续处理
line = headerParser.parse(buffer);
// LF还没接收到的话, 则继续等接收到后,再继续处理
// 或者是请求头的下一行直接就是\r\n,表示请求头结束了, 此时的line就是空字符串,line的长度就为0了,直接跳出循环,由下面的步骤添加最后解析的头
if (line == null) {
return null;
}
} while (line.length() > 0);
}
// 添加上最后解析的头
if (name != null) {
headers.add(name, value);
}
// 头已经解析完了,将name和value重置为null
name = null;
value = null;
// 行 和 头 信息已经解析过了,创建1个HttpMessageDecoderResult,它继承了DecoderResult
// 表示解码成功 SIGNAL_SUCCESS
HttpMessageDecoderResult decoderResult = new HttpMessageDecoderResult(lineParser.size, headerParser.size);
// 将 解码成功的结果 设置给 HttpMessage
message.setDecoderResult(decoderResult);
// 获取到Content-Length头对应的值
List<String> contentLengthFields = headers.getAll(HttpHeaderNames.CONTENT_LENGTH);
// 如果Content-Length头的值存在
if (!contentLengthFields.isEmpty()) {
// http协议版本 是否为 http/1.0及之前的版本
HttpVersion version = message.protocolVersion();
boolean isHttp10OrEarlier = version.majorVersion() < 1
|| (version.majorVersion() == 1 && version.minorVersion() == 0);
// 获取content-length头对应的值
contentLength = HttpUtil.normalizeAndGetContentLength(contentLengthFields,
isHttp10OrEarlier, allowDuplicateContentLengths);
// 设置content-length头对应的值
if (contentLength != -1) {
headers.set(HttpHeaderNames.CONTENT_LENGTH, contentLength);
}
}
// 判断内容是不是一定是空的
if (isContentAlwaysEmpty(message)) {
// 如果 内容是空的,移除掉transfer-encoding中的chunked值
HttpUtil.setTransferEncodingChunked(message, false);
// 当前状态修改为 <跳过控制字符>
return State.SKIP_CONTROL_CHARS;
}
// 当前Http消息是否包含值为chunked 的transfer-encoding头
else if (HttpUtil.isTransferEncodingChunked(message)) {
// 如果同时也存在content-length头,并且协议版本是http/1.1 ,
// 那么就需要移除content-length头,并且会将contentLength属性的值改为Long.MIN_VALUE
// 这说明http/1.1 不支持同时含有 transfer-encoding: chunked 和 content-length: 128
// 并且transfer-encoding的优先级更高哦
if (!contentLengthFields.isEmpty() && message.protocolVersion() == HttpVersion.HTTP_1_1) {
handleTransferEncodingChunkedWithContentLength(message);
}
// 当前状态切换为 <读取块大小>
return State.READ_CHUNK_SIZE;
} else if (contentLength() >= 0) {
// 走到这里说明 transfer-encoding头不包含chunked 或者 不包含transfer-encoding头
// 并且此时content-length的值 大于0
// 当前状态切换为 <读取固定长度内容>
return State.READ_FIXED_LENGTH_CONTENT;
} else {
// 走到这里, 说明既没有 transfer-encoding头不包含chunked 或者 不包含transfer-encoding头,content-length也未指定
// 那就将当前状体切换为 <读取可变长度内容>
return State.READ_VARIABLE_LENGTH_CONTENT;
}
}
splitHeader
java
private void splitHeader(AppendableCharSequence sb) {
final int length = sb.length();
int nameStart;
int nameEnd;
int colonEnd;
int valueStart;
int valueEnd;
// 从偏移量为0开始,找到给定字符串中 非空白字符 的 起始索引
nameStart = findNonWhitespace(sb, 0);
// 从 起始索引 开始查找
for (nameEnd = nameStart; nameEnd < length; nameEnd ++) {
// 拿到nameEnd位置的字符
char ch = sb.charAtUnsafe(nameEnd);
// 如果字符是冒号":",或者 (不是请求解码器 并且是 ' ' 或 (char) 0x09 )
if (ch == ':' || (!isDecodingRequest() && isOWS(ch))) {
// 那就结束查找
break;
}
}
// nameEnd找到了结束都还没有找到,都还没找到冒号,就抛出异常了
if (nameEnd == length) {
throw new IllegalArgumentException("No colon found");
}
// 从nameEnd开始查找冒号
for (colonEnd = nameEnd; colonEnd < length; colonEnd ++) {
if (sb.charAtUnsafe(colonEnd) == ':') {
// 冒号所在索引 + 1
colonEnd ++;
break;
}
}
// 拿到 请求头的名字(name是成员属性)
name = sb.subStringUnsafe(nameStart, nameEnd);
// 找到 请求头 值的开始位置
valueStart = findNonWhitespace(sb, colonEnd);
// 如果全是空白字符,那就是空字符串
if (valueStart == length) {
value = EMPTY_VALUE; // "" (value是成员属性)
} else {
// 从后面开始查找值结束的位置
valueEnd = findEndOfString(sb);
// 截取开始位置和结束位置的部分作为值
value = sb.subStringUnsafe(valueStart, valueEnd); //(value是成员属性)
}
}
findNonWhitespace
java
private static int findNonWhitespace(AppendableCharSequence sb, int offset) {
for (int result = offset; result < sb.length(); ++result) {
char c = sb.charAtUnsafe(result);
/*
java认为是空白字符(不包括补充字符)的有:
It is a Unicode space character (SPACE_SEPARATOR, LINE_SEPARATOR, or PARAGRAPH_SEPARATOR) but is not also a non-breaking space ('\u00A0', '\u2007', '\u202F').
It is '\t', U+0009 HORIZONTAL TABULATION.
It is '\n', U+000A LINE FEED.
It is '\u000B', U+000B VERTICAL TABULATION.
It is '\f', U+000C FORM FEED.
It is '\r', U+000D CARRIAGE RETURN.
It is '\u001C', U+001C FILE SEPARATOR.
It is '\u001D', U+001D GROUP SEPARATOR.
It is '\u001E', U+001E RECORD SEPARATOR.
It is '\u001F', U+001F UNIT SEPARATOR.
*/
// 不是空白字符, 就返回该非空白字符的索引
if (!Character.isWhitespace(c)) {
return result;
} else if (!isOWS(c)) { // 是空白字符但只允许是: ' ' 或 (char) 0x09 , 其它均抛出异常
// Only OWS is supported for whitespace
throw new IllegalArgumentException("Invalid separator, only a single space or horizontal tab allowed," +
" but received a '" + c + "' (0x" + Integer.toHexString(c) + ")");
}
}
// 若全部都是 ' ' 或 (char) 0x09 ,则返回整个字符串的长度
return sb.length();
}
normalizeAndGetContentLength
java
public static long normalizeAndGetContentLength(
List<? extends CharSequence> contentLengthFields, boolean isHttp10OrEarlier,
boolean allowDuplicateContentLengths) {
// 如果content-length的值是空的,那就返回-1
if (contentLengthFields.isEmpty()) {
return -1;
}
// 先获取第一个值
String firstField = contentLengthFields.get(0).toString();
// 如果content-length的值不止1个 或者 第一个值包含"," 那就意味着content-length头有指定多个值
boolean multipleContentLengths =
contentLengthFields.size() > 1 || firstField.indexOf(COMMA) >= 0;
// 如果 content-length头有指定多个值 并且 http协议版本是 http/1.0之后的版本
if (multipleContentLengths && !isHttp10OrEarlier) {
// 如果允许content-length头可以有指定多个值,那这些值都必须要相同才可以,不然会抛出异常哦
if (allowDuplicateContentLengths) {
// 初始值为null
String firstValue = null;
for (CharSequence field : contentLengthFields) {
// 使用","分割
String[] tokens = field.toString().split(COMMA_STRING, -1);
// 遍历分割后的数组
for (String token : tokens) {
// 去掉前后的空白字符
String trimmed = token.trim();
if (firstValue == null) {
// 赋初始值
firstValue = trimmed;
} else if (!trimmed.equals(firstValue)) { // 如果跟之前的值不一致,则抛异常
throw new IllegalArgumentException(
"Multiple Content-Length values found:");
}
}
}
// 将值赋给firstField
firstField = firstValue;
} else {
// 如果不允许指定多个值,则直接抛出异常
throw new IllegalArgumentException(
"Multiple Content-Length values found: " + contentLengthFields);
}
}
// firstField值不能为空,并且第一个字符必须是数字,否则抛出异常
if (firstField.isEmpty() || !Character.isDigit(firstField.charAt(0))) {
// Reject the message as invalid
throw new IllegalArgumentException("Content-Length value is not a number: " + firstField);
}
try {
// 转为Long
final long value = Long.parseLong(firstField);
// content-length的值不能是负数
return checkPositiveOrZero(value, "Content-Length value");
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Content-Length value is not a number");
}
}
isContentAlwaysEmpty
java
protected boolean isContentAlwaysEmpty(HttpMessage msg) {
// 如果 HttpMessage是响应消息
if (msg instanceof HttpResponse) {
HttpResponse res = (HttpResponse) msg;
int code = res.status().code();
// 如果响应状态码是 100-200,
if (code >= 100 && code < 200) {
// 如果code不是101,则返回true,表示content是空的
// 如果code是101,如果响应头包括sec-websocket-accept头,则返回true,表示content是空的
// 如果响应头不包括sec-websocket-accept头,并且 不包含 值为websocket的Upgrade头,则返回true,表示content是空的
// 其它情况返回false
return !(code == 101 && !res.headers().contains(HttpHeaderNames.SEC_WEBSOCKET_ACCEPT)
&& res.headers().contains(HttpHeaderNames.UPGRADE, HttpHeaderValues.WEBSOCKET, true));
}
switch (code) {
// 204(No Content)、304(Redirection)响应状态码表示响应体内容一定为空
case 204: case 304:
return true;
default:
// 其它响应状态码,需要根据实际情况判断
return false;
}
}
// 对于请求,则需要根据实际情况判断
return false;
}
setTransferEncodingChunked
HttpUtil工具类中的方法:若chunked为true,表示要给transfer-encoding头,添加chunked值,并移除掉content-length头;若chunked为false,则移除掉transfer-encoding头中的chunked值,但允许保留transfer-encoding头的其它值
java
public static void setTransferEncodingChunked(HttpMessage m, boolean chunked) {
// 若chunked为true,表示要给transfer-encoding头,添加chunked值,并移除掉content-length头;
if (chunked) {
m.headers().set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED);
m.headers().remove(HttpHeaderNames.CONTENT_LENGTH);
} else {
// 若chunked为false,则移除掉transfer-encoding头中的chunked值,但允许保留transfer-encoding头的其它值
List<String> encodings = m.headers().getAll(HttpHeaderNames.TRANSFER_ENCODING);
// 如果transfer-encoding头没有值就算了
if (encodings.isEmpty()) {
return;
}
List<CharSequence> values = new ArrayList<CharSequence>(encodings);
Iterator<CharSequence> valuesIt = values.iterator();
while (valuesIt.hasNext()) {
CharSequence value = valuesIt.next();
if (HttpHeaderValues.CHUNKED.contentEqualsIgnoreCase(value)) {
valuesIt.remove();
}
}
if (values.isEmpty()) {
m.headers().remove(HttpHeaderNames.TRANSFER_ENCODING);
} else {
m.headers().set(HttpHeaderNames.TRANSFER_ENCODING, values);
}
}
}