HTTP >= 1.0 的版本中,请求行后紧跟的就是请求头了,Nginx使用ngx_http_parse_header_line来对请求头进行解析。
/* 解析HTTP请求头
* param r: 待处理的HTTP请求r
* b: 存放请求头的缓冲区
* return : 解析完请求头的一行时返回NGX_OK;
* 解析完整个请求头时返回NGX_HTTP_PARSE_HEADER_DONE;
* 解析出错时返回NGX_HTTP_PARSE_INVALID_HEADER;
* 其他返回NGX_AGAIN, 表示需要读取新的请求头内容
*/
ngx_int_t ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b)
{
/* HTTP请求头格式, 头部字段名不区分大小写, 值中可以包含空格:
* [头部字段名]:(空格..空格)[值](空格..空格)[回车符][换行符]
* ......
* [头部字段名]:(空格..空格)[值](空格..空格)[回车符][换行符]
* [回车符][换行符]
*/
u_char c, ch, *p;
enum {
sw_start = 0, // 初始状态
sw_name, // 解析请求头字段名
sw_space_before_value, // 解析请求头字段值前的空格
sw_value, // 解析请求头字段值
sw_space_after_value, // 解析请求头字段值后紧跟空格后的空格
sw_almost_done, // 解析标记请求头的行尾的换行符
sw_header_almost_done, // 解析标记请求头结束的换行符
sw_ignore_line, // 忽略请求头前的无用行
sw_done, // 解析完请求头的一行
sw_header_done // 解析完整个请求头
} state; // 枚举类型变量: 请求头解析状态
// 获取HTTP请求r的当前状态state
state = r->state;
// 获取缓冲区b中未解析的有效内容的起始位置p
p = b->pos;
while (p < b->last && state < sw_done) {
// p小于b->last, 表示缓冲区内仍要未解析的有效内容;
// state小于sw_done, 表示对于请求头的某一行仍未解析完毕
// 获取当前待解析的字符ch
ch = *p++;
switch (state) {
case sw_start:
// 当前状态为初始状态
switch (ch) {
case CR:
// 如果当前字符为\r, 表明请求头为空, 且遇到了标记请求头结束的回车符
// 置r->header_end为p-1, 记录请求头在缓冲区中的结束位置
r->header_end = p - 1;
// 置state为sw_header_almost_done, 表示解析标记请求头结束的换行符
state = sw_header_almost_done;
break;
case LF:
// 如果当前字符为\n, 表明请求头为空, 且遇到了结束的换行符
// 置r->header_end为p-1, 记录请求头在缓冲区中的结束位置
r->header_end = p - 1;
// 置state为sw_header_done, 表示解析完整个请求头
state = sw_header_done;
break;
default:
// 置state为sw_name, 表示解析请求头字段名
state = sw_name;
// 置r->header_name_start为p-1, 标记当前字段名的起始位置
r->header_name_start = p - 1;
// 尝试将当前字符转换为对应的小写字母
c = (u_char) (ch | 0x20);
if (c >= 'a' && c <= 'z') {
// 如果c为小写字母, 说明是有效的, 直接跳过
break;
}
if (ch == '-' || ch == '_' || ch == '~' || ch == '.') {
// 如果当前字符为-、_、~、., 也是有效的, 直接跳过
break;
}
if (ch >= '0' && ch <= '9') {
// 如果当前字符为数字, 也是有效的, 直接跳过
break;
}
// 当前字符为其他字符, 说明是无效的, 返回NGX_HTTP_PARSE_INVALID_HEADER
return NGX_HTTP_PARSE_INVALID_HEADER;
}
break;
case sw_name:
// 当前状态为解析请求头字段名
// 尝试将当前字符转换为对应的小写字母
c = (u_char) (ch | 0x20);
if (c >= 'a' && c <= 'z') {
// 如果c为小写字母, 说明是有效的, 直接跳过
break;
}
if (ch == ':') {
// 如果当前字符为:, 说明遇到头部字段名后紧跟的:
// 置r->header_name_end为p-1, 记录当前头部字段名的结束位置
r->header_name_end = p - 1;
// 置state为sw_space_before_value, 表示解析请求头字段值前的空格
state = sw_space_before_value;
break;
}
if (ch == '-' || ch == '_' || ch == '~' || ch == '.') {
// 如果当前字符为-、_、~、., 也是有效的, 直接跳过
break;
}
if (ch >= '0' && ch <= '9') {
// 如果当前字符为数字, 也是有效的, 直接跳过
break;
}
/* IIS can send duplicate "HTTP/1.1 ..." lines */
// 看此注释, 是说IIS在请求头前可能会发送重复的"HTTP/1.1 ..."行, 对于这种行我们需要忽略
if (ch == '/'
&& r->proxy
&& p - r->header_start == 5
&& ngx_strncmp(r->header_start, "HTTP", 4) == 0)
{
// 置state为sw_ignore_line, 表示忽略请求头前的无用行
state = sw_ignore_line;
break;
}
// 当前字符为其他字符, 说明是无效的, 返回NGX_HTTP_PARSE_INVALID_HEADER
return NGX_HTTP_PARSE_INVALID_HEADER;
case sw_space_before_value:
// 当前状态为解析请求头字段值前的空格
switch (ch) {
case ' ':
// 如果当前字符正是空格, 那么直接跳过
break;
case CR:
// 如果当前字符为\r, 说明请求头为空, 而该字符为标记请求头结束的回车符
// 置r->header_start和r->header_end为p-1, 记录当前行的字段值的起始位置和结束位置
r->header_start = r->header_end = p - 1;
// 置state为sw_almost_done, 表示解析标记请求头结束的换行符
state = sw_almost_done;
break;
case LF:
// 如果当前字符为\n, 说明请求头为空, 而该字符为标记请求头结束的换行符
// 置r->header_start和r->header_end为p-1, 记录当前行的字段值的起始位置和结束位置
r->header_start = r->header_end = p - 1;
// 置state为sw_done, 表示解析完整个请求头
state = sw_done;
break;
default:
// 当前字符是其他字符, 说明是请求头字段值的一部分
// 置r->header_start为p-1, 记录当前行的字段值的起始位置
r->header_start = p - 1;
// 置state为sw_value, 表示解析请求头字段值
state = sw_value;
break;
}
break;
case sw_value:
// 当前状态为解析请求头字段值
switch (ch) {
case ' ':
// 如果当前字符为空格, 说明遇到字段值后紧跟的一个空格了
// 置r->header_end为p-1, 记录请求头的当前行的结束位置
r->header_end = p - 1;
// 置state为sw_space_after_value, 表示解析请求头字段值后紧跟空格后的空格
state = sw_space_after_value;
break;
case CR:
// 如果当前字符为\r, 说明遇到标记请求头的行尾的回车符
// 置r->header_end为p-1, 记录当前行的字段值的结束位置
r->header_end = p - 1;
// 置state为sw_almost_done, 表示解析标记请求头的行尾的换行符
state = sw_almost_done;
break;
case LF:
// 如果当前字符为\n, 说明遇到标记请求头的行尾的换行符
// 置r->header_end为p-1, 记录当前行的字段值的结束位置
r->header_end = p - 1;
// 置state为sw_done, 表示解析完请求头的一行
state = sw_done;
break;
}
break;
case sw_space_after_value:
// 当前状态为解析请求头字段值后紧跟空格后的空格
switch (ch) {
case ' ':
// 如果当前字符正是空格, 那么直接跳过
break;
case CR:
// 如果当前字符是\r, 表示遇到标记请求头的行尾的回车符
// 置state为sw_almost_done, 表示解析标记请求头的行尾的换行符
state = sw_almost_done;
break;
case LF:
// 如果当前字符是\n, 表示遇到标记请求头的行尾的换行符
// 置state为sw_done, 表示解析完请求头的一行
state = sw_done;
break;
default:
// 当前字符是其他字符, 说明也是请求头字段值的一部分, 因为值中可以包含空格
// 置state为sw_value
state = sw_value;
break;
}
break;
case sw_ignore_line:
// 当前状态为忽略请求头前的无用行
switch (ch) {
case LF:
// 如果当前字符为\n, 说明遇到无用行的结尾
// 置state为sw_start, 即初始状态
state = sw_start;
break;
default:
// 如果当前字符是其他字符, 则直接忽略
break;
}
break;
case sw_almost_done:
// 当前状态为解析标记请求头的行尾的换行符
switch (ch) {
case LF:
// 如果当前字符正是\n, 说明该行解析完毕
// 置state为sw_done
state = sw_done;
break;
default:
// 当前字符是其他字符, 说明是非法字符
// 返回NGX_HTTP_PARSE_INVALID_HEADER
return NGX_HTTP_PARSE_INVALID_HEADER;
}
break;
case sw_header_almost_done:
// 当前状态为解析标记请求头结束的换行符
switch (ch) {
case LF:
// 如果当前字符正是\n, 说明整个请求头解析完毕
// 置state为sw_header_done
state = sw_header_done;
break;
default:
// 当前字符是其他字符, 说明是非法字符
// 返回NGX_HTTP_PARSE_INVALID_HEADER
return NGX_HTTP_PARSE_INVALID_HEADER;
}
break;
case sw_done:
case sw_header_done:
break;
}
}
// p指向待解析的下一个字符, 置b->pos为p
b->pos = p;
if (state == sw_done) {
// 如果当前状态为sw_done, 说明解析完请求头的一行
// 重置r->state为sw_start初始状态以用于解析下一行
r->state = sw_start;
// 返回NGX_OK
return NGX_OK;
} else if (state == sw_header_done) {
// 如果当前状态为sw_header_done, 说明解析完整个请求头
// 重置r->state为sw_start初始状态
r->state = sw_start;
// 返回NGX_HTTP_PARSE_HEADER_DONE
return NGX_HTTP_PARSE_HEADER_DONE;
} else {
// 如果当前状态是其他状态, 说明只解析了某行的一部分, 然而缓冲区中已没有多余的有效内容
// 记录当前状态
r->state = state;
// 返回NGX_AGAIN
return NGX_AGAIN;
}
}