ngx_http_parse_request_line
r: 指向HTTP请求结构的指针,用于存储解析结果b: 包含请求数据的缓冲区指针返回值: 状态码(NGX_OK表示成功,NGX_AGAIN需要更多数据,其他为错误码)
./nginx-1.24.0/src/http/ngx_http_parse.c
ngx_int_t
ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b)
{
u_char c, ch, *p, *m;
enum {
sw_start = 0,
sw_method,
sw_spaces_before_uri,
sw_schema,
sw_schema_slash,
sw_schema_slash_slash,
sw_host_start,
sw_host,
sw_host_end,
sw_host_ip_literal,
sw_port,
sw_after_slash_in_uri,
sw_check_uri,
sw_uri,
sw_http_09,
sw_http_H,
sw_http_HT,
sw_http_HTT,
sw_http_HTTP,
sw_first_major_digit,
sw_major_digit,
sw_first_minor_digit,
sw_minor_digit,
sw_spaces_after_digit,
sw_almost_done
} state;
state = r->state;
for (p = b->pos; p < b->last; p++) {
ch = *p;
switch (state) {
/* HTTP methods: GET, HEAD, POST */
case sw_start:
r->request_start = p;
if (ch == CR || ch == LF) {
break;
}
if ((ch < 'A' || ch > 'Z') && ch != '_' && ch != '-') {
return NGX_HTTP_PARSE_INVALID_METHOD;
}
state = sw_method;
break;
case sw_method:
if (ch == ' ') {
r->method_end = p - 1;
m = r->request_start;
switch (p - m) {
case 3:
if (ngx_str3_cmp(m, 'G', 'E', 'T', ' ')) {
r->method = NGX_HTTP_GET;
break;
}
if (ngx_str3_cmp(m, 'P', 'U', 'T', ' ')) {
r->method = NGX_HTTP_PUT;
break;
}
break;
case 4:
if (m[1] == 'O') {
if (ngx_str3Ocmp(m, 'P', 'O', 'S', 'T')) {
r->method = NGX_HTTP_POST;
break;
}
if (ngx_str3Ocmp(m, 'C', 'O', 'P', 'Y')) {
r->method = NGX_HTTP_COPY;
break;
}
if (ngx_str3Ocmp(m, 'M', 'O', 'V', 'E')) {
r->method = NGX_HTTP_MOVE;
break;
}
if (ngx_str3Ocmp(m, 'L', 'O', 'C', 'K')) {
r->method = NGX_HTTP_LOCK;
break;
}
} else {
if (ngx_str4cmp(m, 'H', 'E', 'A', 'D')) {
r->method = NGX_HTTP_HEAD;
break;
}
}
break;
case 5:
if (ngx_str5cmp(m, 'M', 'K', 'C', 'O', 'L')) {
r->method = NGX_HTTP_MKCOL;
break;
}
if (ngx_str5cmp(m, 'P', 'A', 'T', 'C', 'H')) {
r->method = NGX_HTTP_PATCH;
break;
}
if (ngx_str5cmp(m, 'T', 'R', 'A', 'C', 'E')) {
r->method = NGX_HTTP_TRACE;
break;
}
break;
case 6:
if (ngx_str6cmp(m, 'D', 'E', 'L', 'E', 'T', 'E')) {
r->method = NGX_HTTP_DELETE;
break;
}
if (ngx_str6cmp(m, 'U', 'N', 'L', 'O', 'C', 'K')) {
r->method = NGX_HTTP_UNLOCK;
break;
}
break;
case 7:
if (ngx_str7_cmp(m, 'O', 'P', 'T', 'I', 'O', 'N', 'S', ' '))
{
r->method = NGX_HTTP_OPTIONS;
}
if (ngx_str7_cmp(m, 'C', 'O', 'N', 'N', 'E', 'C', 'T', ' '))
{
r->method = NGX_HTTP_CONNECT;
}
break;
case 8:
if (ngx_str8cmp(m, 'P', 'R', 'O', 'P', 'F', 'I', 'N', 'D'))
{
r->method = NGX_HTTP_PROPFIND;
}
break;
case 9:
if (ngx_str9cmp(m,
'P', 'R', 'O', 'P', 'P', 'A', 'T', 'C', 'H'))
{
r->method = NGX_HTTP_PROPPATCH;
}
break;
}
state = sw_spaces_before_uri;
break;
}
if ((ch < 'A' || ch > 'Z') && ch != '_' && ch != '-') {
return NGX_HTTP_PARSE_INVALID_METHOD;
}
break;
/* space* before URI */
case sw_spaces_before_uri:
if (ch == '/') {
r->uri_start = p;
state = sw_after_slash_in_uri;
break;
}
c = (u_char) (ch | 0x20);
if (c >= 'a' && c <= 'z') {
r->schema_start = p;
state = sw_schema;
break;
}
switch (ch) {
case ' ':
break;
default:
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
break;
case sw_schema:
c = (u_char) (ch | 0x20);
if (c >= 'a' && c <= 'z') {
break;
}
if ((ch >= '0' && ch <= '9') || ch == '+' || ch == '-' || ch == '.')
{
break;
}
switch (ch) {
case ':':
r->schema_end = p;
state = sw_schema_slash;
break;
default:
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
break;
case sw_schema_slash:
switch (ch) {
case '/':
state = sw_schema_slash_slash;
break;
default:
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
break;
case sw_schema_slash_slash:
switch (ch) {
case '/':
state = sw_host_start;
break;
default:
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
break;
case sw_host_start:
r->host_start = p;
if (ch == '[') {
state = sw_host_ip_literal;
break;
}
state = sw_host;
/* fall through */
case sw_host:
c = (u_char) (ch | 0x20);
if (c >= 'a' && c <= 'z') {
break;
}
if ((ch >= '0' && ch <= '9') || ch == '.' || ch == '-') {
break;
}
/* fall through */
case sw_host_end:
r->host_end = p;
switch (ch) {
case ':':
state = sw_port;
break;
case '/':
r->uri_start = p;
state = sw_after_slash_in_uri;
break;
case '?':
r->uri_start = p;
r->args_start = p + 1;
r->empty_path_in_uri = 1;
state = sw_uri;
break;
case ' ':
/*
* use single "/" from request line to preserve pointers,
* if request line will be copied to large client buffer
*/
r->uri_start = r->schema_end + 1;
r->uri_end = r->schema_end + 2;
state = sw_http_09;
break;
default:
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
break;
case sw_host_ip_literal:
if (ch >= '0' && ch <= '9') {
break;
}
c = (u_char) (ch | 0x20);
if (c >= 'a' && c <= 'z') {
break;
}
switch (ch) {
case ':':
break;
case ']':
state = sw_host_end;
break;
case '-':
case '.':
case '_':
case '~':
/* unreserved */
break;
case '!':
case '$':
case '&':
case '\'':
case '(':
case ')':
case '*':
case '+':
case ',':
case ';':
case '=':
/* sub-delims */
break;
default:
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
break;
case sw_port:
if (ch >= '0' && ch <= '9') {
break;
}
switch (ch) {
case '/':
r->port_end = p;
r->uri_start = p;
state = sw_after_slash_in_uri;
break;
case '?':
r->port_end = p;
r->uri_start = p;
r->args_start = p + 1;
r->empty_path_in_uri = 1;
state = sw_uri;
break;
case ' ':
r->port_end = p;
/*
* use single "/" from request line to preserve pointers,
* if request line will be copied to large client buffer
*/
r->uri_start = r->schema_end + 1;
r->uri_end = r->schema_end + 2;
state = sw_http_09;
break;
default:
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
break;
/* check "/.", "//", "%", and "\" (Win32) in URI */
case sw_after_slash_in_uri:
if (usual[ch >> 5] & (1U << (ch & 0x1f))) {
state = sw_check_uri;
break;
}
switch (ch) {
case ' ':
r->uri_end = p;
state = sw_http_09;
break;
case CR:
r->uri_end = p;
r->http_minor = 9;
state = sw_almost_done;
break;
case LF:
r->uri_end = p;
r->http_minor = 9;
goto done;
case '.':
r->complex_uri = 1;
state = sw_uri;
break;
case '%':
r->quoted_uri = 1;
state = sw_uri;
break;
case '/':
r->complex_uri = 1;
state = sw_uri;
break;
#if (NGX_WIN32)
case '\\':
r->complex_uri = 1;
state = sw_uri;
break;
#endif
case '?':
r->args_start = p + 1;
state = sw_uri;
break;
case '#':
r->complex_uri = 1;
state = sw_uri;
break;
case '+':
r->plus_in_uri = 1;
break;
default:
if (ch < 0x20 || ch == 0x7f) {
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
state = sw_check_uri;
break;
}
break;
/* check "/", "%" and "\" (Win32) in URI */
case sw_check_uri:
if (usual[ch >> 5] & (1U << (ch & 0x1f))) {
break;
}
switch (ch) {
case '/':
#if (NGX_WIN32)
if (r->uri_ext == p) {
r->complex_uri = 1;
state = sw_uri;
break;
}
#endif
r->uri_ext = NULL;
state = sw_after_slash_in_uri;
break;
case '.':
r->uri_ext = p + 1;
break;
case ' ':
r->uri_end = p;
state = sw_http_09;
break;
case CR:
r->uri_end = p;
r->http_minor = 9;
state = sw_almost_done;
break;
case LF:
r->uri_end = p;
r->http_minor = 9;
goto done;
#if (NGX_WIN32)
case '\\':
r->complex_uri = 1;
state = sw_after_slash_in_uri;
break;
#endif
case '%':
r->quoted_uri = 1;
state = sw_uri;
break;
case '?':
r->args_start = p + 1;
state = sw_uri;
break;
case '#':
r->complex_uri = 1;
state = sw_uri;
break;
case '+':
r->plus_in_uri = 1;
break;
default:
if (ch < 0x20 || ch == 0x7f) {
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
break;
}
break;
/* URI */
case sw_uri:
if (usual[ch >> 5] & (1U << (ch & 0x1f))) {
break;
}
switch (ch) {
case ' ':
r->uri_end = p;
state = sw_http_09;
break;
case CR:
r->uri_end = p;
r->http_minor = 9;
state = sw_almost_done;
break;
case LF:
r->uri_end = p;
r->http_minor = 9;
goto done;
case '#':
r->complex_uri = 1;
break;
default:
if (ch < 0x20 || ch == 0x7f) {
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
break;
}
break;
/* space+ after URI */
case sw_http_09:
switch (ch) {
case ' ':
break;
case CR:
r->http_minor = 9;
state = sw_almost_done;
break;
case LF:
r->http_minor = 9;
goto done;
case 'H':
r->http_protocol.data = p;
state = sw_http_H;
break;
default:
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
break;
case sw_http_H:
switch (ch) {
case 'T':
state = sw_http_HT;
break;
default:
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
break;
case sw_http_HT:
switch (ch) {
case 'T':
state = sw_http_HTT;
break;
default:
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
break;
case sw_http_HTT:
switch (ch) {
case 'P':
state = sw_http_HTTP;
break;
default:
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
break;
case sw_http_HTTP:
switch (ch) {
case '/':
state = sw_first_major_digit;
break;
default:
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
break;
/* first digit of major HTTP version */
case sw_first_major_digit:
if (ch < '1' || ch > '9') {
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
r->http_major = ch - '0';
if (r->http_major > 1) {
return NGX_HTTP_PARSE_INVALID_VERSION;
}
state = sw_major_digit;
break;
/* major HTTP version or dot */
case sw_major_digit:
if (ch == '.') {
state = sw_first_minor_digit;
break;
}
if (ch < '0' || ch > '9') {
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
r->http_major = r->http_major * 10 + (ch - '0');
if (r->http_major > 1) {
return NGX_HTTP_PARSE_INVALID_VERSION;
}
break;
/* first digit of minor HTTP version */
case sw_first_minor_digit:
if (ch < '0' || ch > '9') {
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
r->http_minor = ch - '0';
state = sw_minor_digit;
break;
/* minor HTTP version or end of request line */
case sw_minor_digit:
if (ch == CR) {
state = sw_almost_done;
break;
}
if (ch == LF) {
goto done;
}
if (ch == ' ') {
state = sw_spaces_after_digit;
break;
}
if (ch < '0' || ch > '9') {
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
if (r->http_minor > 99) {
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
r->http_minor = r->http_minor * 10 + (ch - '0');
break;
case sw_spaces_after_digit:
switch (ch) {
case ' ':
break;
case CR:
state = sw_almost_done;
break;
case LF:
goto done;
default:
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
break;
/* end of request line */
case sw_almost_done:
r->request_end = p - 1;
switch (ch) {
case LF:
goto done;
default:
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
}
}
b->pos = p;
r->state = state;
return NGX_AGAIN;
done:
b->pos = p + 1;
if (r->request_end == NULL) {
r->request_end = p;
}
r->http_version = r->http_major * 1000 + r->http_minor;
r->state = sw_start;
if (r->http_version == 9 && r->method != NGX_HTTP_GET) {
return NGX_HTTP_PARSE_INVALID_09_METHOD;
}
return NGX_OK;
}
该函数用于解析HTTP请求的第一行(请求行)
函数概述
ngx_int_t ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b)
r: 指向HTTP请求结构的指针,用于存储解析结果b: 包含请求数据的缓冲区指针- 返回值: 状态码(NGX_OK表示成功,NGX_AGAIN需要更多数据,其他为错误码)
状态枚举
enum {
sw_start = 0, // 初始状态
sw_method, // 解析HTTP方法
sw_spaces_before_uri, // 方法后的空格
sw_schema, // 协议方案(如http)
sw_schema_slash, // 方案后的第一个斜杠
sw_schema_slash_slash, // 方案后的第二个斜杠
sw_host_start, // 主机名开始
sw_host, // 解析主机名
sw_host_end, // 主机名结束
sw_host_ip_literal, // IPv6地址(方括号格式)
sw_port, // 端口号
sw_after_slash_in_uri, // URI中的斜杠后
sw_check_uri, // 检查URI
sw_uri, // 解析URI
sw_http_09, // HTTP/0.9
sw_http_H, // 解析"H"
sw_http_HT, // 解析"HT"
sw_http_HTT, // 解析"HTT"
sw_http_HTTP, // 解析"HTTP"
sw_first_major_digit, // HTTP主版本号第一个数字
sw_major_digit, // HTTP主版本号
sw_first_minor_digit, // HTTP次版本号第一个数字
sw_minor_digit, // HTTP次版本号
sw_spaces_after_digit, // 版本号后的空格
sw_almost_done // 即将完成
} state;
这段代码定义了一个枚举类型,用于表示HTTP请求行解析过程中的各个状态。
这是NGINX实现HTTP协议解析的核心状态机设计
状态机设计目的
这个状态机用于逐步解析HTTP请求的第一行(如 GET /index.html HTTP/1.1),将复杂的字符串解析过程分解为多个明确的阶段,每个阶段对应一个状态。通过状态转移,最终完成请求方法、URI、HTTP版本的解析。
状态分类与作用
1. 初始阶段
sw_start
解析的起点,等待第一个有效字符(通常是HTTP方法的第一个字母,如G表示GET)。
2. HTTP方法解析
sw_method
正在解析HTTP方法(如GET、POST)。通过字符匹配确定具体方法类型(最多支持9个字符的方法名)。
3. URI前的处理
sw_spaces_before_uri
方法名后的空格(如GET[空格]/),可能直接进入URI解析,或检测到协议方案(如http:)。
4. 协议方案解析(可选)
sw_schema
解析协议前缀(如http:或https:)。sw_schema_slash和sw_schema_slash_slash
严格匹配协议后的两个斜杠(://),确保格式正确(如http://)。
5. 主机名和端口解析
sw_host_start
主机名解析开始(如example.com或IPv6的[::1])。sw_host
解析主机名字符(允许字母、数字、点、破折号)。sw_host_ip_literal
特殊处理IPv6地址(如[2001:db8::1])。sw_host_end
主机名结束,可能遇到端口号(:)、路径(/)或查询参数(?)。sw_port
解析端口号(如:8080)。
6. URI路径解析
sw_after_slash_in_uri
路径开始的斜杠后(如/index.html),检查特殊字符(.、%、?等)。sw_check_uri
验证URI合法性(如是否包含非法字符)。sw_uri
通用URI字符处理,直到遇到空格或换行。
7. HTTP版本解析
sw_http_09
兼容HTTP/0.9(无版本号的请求,如GET /)。sw_http_H→sw_http_HT→sw_http_HTT→sw_http_HTTP
逐步匹配字符串HTTP/(严格校验协议标识)。sw_first_major_digit和sw_major_digit
解析主版本号(如HTTP/1.1中的1)。sw_first_minor_digit和sw_minor_digit
解析次版本号(如HTTP/1.1中的1)。
8. 结束处理
sw_spaces_after_digit
版本号后的空格(允许空格或直接换行)。sw_almost_done
请求行即将结束,等待最后的换行符(\n)。
状态机的关键特点
-
严格协议合规性
每个状态严格校验字符合法性(如方法必须大写,版本号必须数字等),确保符合HTTP标准(RFC 2616/7230)。 -
性能优化
- 通过状态跳转避免重复检查。
- 使用
sw_http_H等递进状态快速匹配固定字符串(HTTP/)。 - 主机名和URI的解析采用最小化分支策略。
-
错误处理
非法字符或顺序错误会立即返回错误码(如NGX_HTTP_PARSE_INVALID_METHOD)。 -
内存高效
仅记录关键位置的指针(如r->uri_start、r->host_end),不复制字符串。
状态转移示例
以解析 GET /index.html HTTP/1.1\r\n 为例:
sw_start→sw_method(遇到G)。sw_method→sw_spaces_before_uri(遇到空格)。sw_spaces_before_uri→sw_after_slash_in_uri(遇到/)。sw_after_slash_in_uri→sw_check_uri→sw_uri(解析路径)。sw_uri→sw_http_09(遇到空格)。sw_http_09→sw_http_H→ … →sw_http_HTTP(匹配HTTP/)。sw_http_HTTP→sw_first_major_digit→sw_major_digit→sw_first_minor_digit→sw_minor_digit(解析1.1)。sw_minor_digit→sw_almost_done→ 完成(遇到\n)。
state = r->state;
此时的 状态是
state=0
主循环
for (p = b->pos; p < b->last; p++) {
ch = *p; // 获取当前字符
此时
ch=G
状态机详细解析
1. sw_start - 初始状态
case sw_start:
r->request_start = p; // 记录请求行开始位置
// 跳过回车换行(请求前的空行)
if (ch == CR || ch == LF) break;
// 验证方法首字符(必须是大写字母、下划线或破折号)
if ((ch < 'A' || ch > 'Z') && ch != '_' && ch != '-') {
return NGX_HTTP_PARSE_INVALID_METHOD;
}
state = sw_method; // 转移到方法解析状态
break;
2. sw_method - 解析HTTP方法
case sw_method:
if (ch == ' ') { // 遇到空格表示方法结束
r->method_end = p - 1;
m = r->request_start;
// 根据方法长度进行分派
switch (p - m) {
case 3: // GET, PUT等3字母方法
if (ngx_str3_cmp(m, 'G', 'E', 'T', ' ')) {
r->method = NGX_HTTP_GET;
break;
}
// ... 其他3字母方法 ...
case 4: // POST, HEAD等4字母方法
// ... 方法比较 ...
// 其他长度情况...
}
state = sw_spaces_before_uri; // 转移到URI前空格状态
break;
}
// 验证方法字符
if ((ch < 'A' || ch > 'Z') && ch != '_' && ch != '-') {
return NGX_HTTP_PARSE_INVALID_METHOD;
}
break;
此时
r->method = NGX_HTTP_GET; 成立
3. sw_spaces_before_uri - 方法后的空格
case sw_spaces_before_uri:
if (ch == '/') { // 绝对路径URI
r->uri_start = p;
state = sw_after_slash_in_uri;
break;
}
// 检查是否有协议方案(如http://)
c = (u_char) (ch | 0x20); // 快速转换为小写
if (c >= 'a' && c <= 'z') {
r->schema_start = p;
state = sw_schema;
break;
}
// 只允许空格
switch (ch) {
case ' ': break;
default: return NGX_HTTP_PARSE_INVALID_REQUEST;
}
break;
case sw_spaces_before_uri:
if (ch == '/') { // 绝对路径URI
r->uri_start = p;
state = sw_after_slash_in_uri;
break;
}
- 作用:处理HTTP方法后的空格,并检查URI的起始字符。
- 逻辑:
- 如果当前字符是
/,表示这是一个绝对路径URI(如GET /index.html)。 - 记录URI的起始位置到
r->uri_start。 - 转移到
sw_after_slash_in_uri状态,准备解析URI路径。
- 如果当前字符是
- 意义:直接处理常见的不带协议前缀的URI(占大多数请求),快速进入路径解析状态。
此时
ch=/
c = (u_char) (ch | 0x20); // 快速转换为小写
if (c >= 'a' && c <= 'z') {
r->schema_start = p;
state = sw_schema;
break;
}
- 作用:检测是否包含协议方案(如
http:)。 - 逻辑:
ch | 0x20:通过位操作将字符强制转换为小写(例如H→h),避免昂贵的tolower()调用。- 如果字符是字母(
a-z),可能是协议前缀(如http)的首字母:- 记录协议起始位置到
r->schema_start。 - 转移到
sw_schema状态,开始解析协议(如http:)。
- 记录协议起始位置到
switch (ch) {
case ' ': break;
default: return NGX_HTTP_PARSE_INVALID_REQUEST;
}
- 作用:验证当前字符是否为合法空格。
- 逻辑:
- 如果是空格(
),继续留在当前状态(可能还有连续空格)。 - 如果是其他字符(如
\t、%等),返回错误码NGX_HTTP_PARSE_INVALID_REQUEST。
- 如果是空格(
- 意义:
- 协议严格性:HTTP标准要求方法后必须用空格分隔URI(不允许制表符等)。
- 错误快速失败:遇到非法字符立即终止解析,避免无效后续处理。
break;
- 作用:结束当前
case的处理,返回主循环继续读取下一个字符。 - 逻辑:
- 如果没有触发任何转移条件(如仅读到空格),保持当前状态,继续检查下一个字符。
- 意义:允许跳过多余的空格(如
GET[多个空格]/)。
4. URI相关状态
这些状态处理:
- 协议方案(如http:)
- 斜杠(//)
- 主机名/IP地址
- 端口号
- URI路径
- 查询字符串(?)
- 片段标识(#)
// 协议方案(如http:)
case sw_schema:
// 验证方案字符(a-z,0-9,+,-,.)
if (ch == ':') {
r->schema_end = p;
state = sw_schema_slash;
}
break;
// 期待第一个斜杠(http:/)
case sw_schema_slash:
if (ch == '/') state = sw_schema_slash_slash;
else return error;
break;
// 期待第二个斜杠(http://)
case sw_schema_slash_slash:
if (ch == '/') state = sw_host_start;
else return error;
break;
// 主机名解析(支持IPv6地址[])
case sw_host_start:
r->host_start = p;
if (ch == '[') state = sw_host_ip_literal;
else state = sw_host;
// 继续执行下一个状态
// 主机名字符
case sw_host:
// 有效字符: a-z,0-9,.,-
if (无效字符) state = sw_host_end;
break;
// 端口号解析(:后)
case sw_port:
if (ch >= '0' && ch <= '9') break; // 数字
// 处理端口结束
if (ch == '/') {
r->port_end = p;
r->uri_start = p;
state = sw_after_slash_in_uri;
}
// 其他情况...
break;
5. URI路径解析
/* check "/.", "//", "%", and "\" (Win32) in URI */
case sw_after_slash_in_uri:
if (usual[ch >> 5] & (1U << (ch & 0x1f))) {
state = sw_check_uri;
break;
}
usual 是一个位图,标记哪些字符是"常规"URI字符
使用位运算快速检查当前字符ch是否是常规字符
ch >> 5:
这是右移5位操作,相当于除以32
作用:确定字符ch在usual数组中的索引位置
ch & 0x1f:
这是与操作,保留低5位(因为0x1f=31=二进制11111)
作用:确定字符在32位组内的具体位位置
1U << (ch & 0x1f):
创建一个掩码,将1左移到位图中对应的位置
位与操作&:
检查位图中对应位是否被设置
如果结果非零,表示该字符是常规字符
如果是常规字符,切换到sw_check_uri状态并跳出当前处理
switch (ch) {
case ' ':
r->uri_end = p;
state = sw_http_09;
break;
在 HTTP 请求行中,空格用于分隔方法、URI 和协议版本(如 GET /index.html HTTP/1.1)
当在 URI 部分遇到空格时,它表示 URI 的结束
state = sw_http_09;:
将解析器状态切换为 sw_http_09,表示接下来要解析 HTTP 版本
case CR:
r->uri_end = p;
r->http_minor = 9;
state = sw_almost_done;
break;
case LF:
r->uri_end = p;
r->http_minor = 9;
goto done;
case '.':
r->complex_uri = 1;
state = sw_uri;
break;
case '%':
r->quoted_uri = 1;
state = sw_uri;
break;
case '/':
r->complex_uri = 1;
state = sw_uri;
break;
#if (NGX_WIN32)
case '\\':
r->complex_uri = 1;
state = sw_uri;
break;
#endif
case '?':
r->args_start = p + 1;
state = sw_uri;
break;
case '#':
r->complex_uri = 1;
state = sw_uri;
break;
case '+':
r->plus_in_uri = 1;
break;
default:
if (ch < 0x20 || ch == 0x7f) {
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
state = sw_check_uri;
break;
}
break;
6. HTTP版本解析
// 寻找"HTTP/"
/* space+ after URI */
case sw_http_09:
switch (ch) {
case ' ':
break;
case CR:
r->http_minor = 9;
state = sw_almost_done;
break;
case LF:
r->http_minor = 9;
goto done;
case 'H':
r->http_protocol.data = p;
state = sw_http_H;
break;
default:
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
break;
这个状态处理 HTTP 请求行中 URI 之后的部分,主要做三件事:
- 处理 HTTP/0.9 简单请求(没有版本号的请求)
- 检测 HTTP/1.x 协议标识的开始
- 验证请求行格式的正确性
case sw_http_09:
switch (ch) {
- 状态上下文:进入这个状态表示已经解析完 URI,现在要解析 URI 后面的部分
case ' ':
break;
- 作用:处理空格字符
- 逻辑:
- 遇到空格时保持当前状态继续读取
- 允许请求行中 URI 和 HTTP 版本之间有多个空格
case CR:
r->http_minor = 9;
state = sw_almost_done;
break;
- 作用:处理回车符(CR),标识 HTTP/0.9 请求结束
- 逻辑:
- 设置协议版本为 HTTP/0.9 (
http_minor = 9) - 转移到
sw_almost_done状态,等待最后的换行符(LF) - 对应 HTTP/0.9 的简单格式:
GET <URI>\r\n
- 设置协议版本为 HTTP/0.9 (
case LF:
r->http_minor = 9;
goto done;
- 作用:处理换行符(LF),直接完成 HTTP/0.9 请求解析
- 逻辑:
- 设置协议版本为 HTTP/0.9
- 使用
goto done跳转到解析完成处理 - 处理不标准的换行格式(缺少CR的情况)
case 'H':
r->http_protocol.data = p;
state = sw_http_H;
break;
- 作用:检测到 HTTP/1.x 协议的开头
- 逻辑:
- 记录协议字符串的起始位置 (
http_protocol.data) - 转移到
sw_http_H状态开始解析完整的 “HTTP/” 字符串 - 对应格式:
GET /index.html HTTP/1.1
- 记录协议字符串的起始位置 (
default:
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
break;
- 作用:处理非法字符
- 逻辑:
- 任何不符合上述情况的字符都会导致解析失败
- 返回
NGX_HTTP_PARSE_INVALID_REQUEST错误码 - 确保请求行严格符合 HTTP 协议规范
状态转移示意图
[sw_http_09]
│
├── ' ' → 保持状态
├── CR → 设置HTTP/0.9 → [sw_almost_done]
├── LF → 设置HTTP/0.9 → 完成解析
├── 'H' → 记录位置 → [sw_http_H]
└── 其他 → 返回错误
// 解析"HTTP/"的后续字母
case sw_http_H:
switch (ch) {
case 'T':
state = sw_http_HT;
break;
default:
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
break;
case sw_http_HT:
switch (ch) {
case 'T':
state = sw_http_HTT;
break;
default:
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
break;
case sw_http_HTT:
switch (ch) {
case 'P':
state = sw_http_HTTP;
break;
default:
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
break;
case sw_http_HTTP:
switch (ch) {
case '/':
state = sw_first_major_digit;
break;
default:
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
break;
// 版本号解析
/* first digit of major HTTP version */
case sw_first_major_digit:
if (ch < '1' || ch > '9') {
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
r->http_major = ch - '0';
if (r->http_major > 1) {
return NGX_HTTP_PARSE_INVALID_VERSION;
}
state = sw_major_digit;
break;
/* major HTTP version or dot */
case sw_major_digit:
if (ch == '.') {
state = sw_first_minor_digit;
break;
}
if (ch < '0' || ch > '9') {
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
r->http_major = r->http_major * 10 + (ch - '0');
if (r->http_major > 1) {
return NGX_HTTP_PARSE_INVALID_VERSION;
}
break;
/* first digit of minor HTTP version */
case sw_first_minor_digit:
if (ch < '0' || ch > '9') {
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
r->http_minor = ch - '0';
state = sw_minor_digit;
break;
/* minor HTTP version or end of request line */
case sw_minor_digit:
if (ch == CR) {
state = sw_almost_done;
break;
}
if (ch == LF) {
goto done;
}
if (ch == ' ') {
state = sw_spaces_after_digit;
break;
}
if (ch < '0' || ch > '9') {
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
if (r->http_minor > 99) {
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
r->http_minor = r->http_minor * 10 + (ch - '0');
break;
case sw_spaces_after_digit:
switch (ch) {
case ' ':
break;
case CR:
state = sw_almost_done;
break;
case LF:
goto done;
default:
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
break;
/* end of request line */
case sw_almost_done:
r->request_end = p - 1;
switch (ch) {
case LF:
goto done;
default:
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
}
}
b->pos = p;
r->state = state;
return NGX_AGAIN;
完成处理
done:
b->pos = p + 1; // 推进缓冲区位置
if (r->request_end == NULL) {
r->request_end = p;
}
// 组合版本号(如1.1→1001)
r->http_version = r->http_major * 1000 + r->http_minor;
r->state = sw_start; // 重置状态
// HTTP/0.9只允许GET方法
if (r->http_version == 9 && r->method != NGX_HTTP_GET) {
return NGX_HTTP_PARSE_INVALID_09_METHOD;
}
return NGX_OK;
更多推荐


所有评论(0)