TCP没有一请求一包的边界
连接的read handler从等待请求切到ngx_http_process_request_line,读取header buffer后调用ngx_http_parse_request_line(r,b)。解析器逐字符推进enum状态:method、URI、协议版本、CRLF等。r->state在下次进入时恢复,b->pos指向已消费位置,字段指针记录method与URI等边界。NGX_AGAIN表示当前输入不足,不表示请求被拒绝。
TCP可能把请求行拆成任意多个片段,也可能把header和body一起送来。解析器不能用一次recv等于完整消息的假设。遇到无效method、协议字符、URI问题等返回具体解析错误,调用者转为协议错误处理;完整请求行还要经ngx_http_process_request_uri做规范化和安全检查,Host及headers另有解析步骤。
buffer不够时调用者尝试large header buffer;请求行过大、header field过大、总buffer不足有各自边界。client_header_timeout约束读取过程,慢速发送不可能无限占用。请求头里的Host影响server选择,但TLS场景SNI选证书更早,二者不应混作一个阶段。
11 · HTTP请求行:分片输入与状态保存 read header bytes → r.state / b.pos(输入);r.state / b.pos → parse characters(恢复);parse characters → AGAIN / retain state(不完整);AGAIN / retain state → read header bytes(等read事件);parse characters → OK / URI check(完整);parse characters → invalid / finalize(错误) 输入 恢复 不完整 等read事件 完整 错误 STATE 01 read header bytes STATE 02 r.state b.pos STATE 03 parse characters STATE 04 AGAIN retain state STATE 05 OK URI check STATE 06 invalid finalize 机制图11:TCP没有一请求一包的边界
真实源码: src/http/ngx_http_parse.c · L101—L812 。连续原文,包含所讨论函数的完整分支与边界。
复制代码
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) {
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 ;
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;
case sw_host:
c = (u_char) (ch | 0x20 );
if (c >= 'a' && c <= 'z' ) {
break ;
}
if ((ch >= '0' && ch <= '9' ) || ch == '.' || ch == '-' ) {
break ;
}
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 ' ' :
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 '~' :
break ;
case '!' :
case '$' :
case '&' :
case '\'' :
case '(' :
case ')' :
case '*' :
case '+' :
case ',' :
case ';' :
case '=' :
break ;
default :
return NGX_HTTP_PARSE_INVALID_REQUEST;
}
break ;
case sw_port:
if (ch >= '0' && ch <= '9' ) {
break ;
}
switch (ch) {
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 ' ' :
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_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 ;
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 ;
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 ;
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 ;
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 ;
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 ;
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 ;
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 ;
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;
}
完整链: ngx_http_wait_request_handler → create_request → process_request_line → read_request_header → parse_request_line → process_request_uri → process_request_headers → process_request。成功只意味着该阶段完成;body未必已读,上游也尚未连接。
实验: lab中使用Python socket分两次发送GET /ver与sion HTTP/1.1...,组成合法/version;完整split实验见experiments.md。记录返回状态;再发送小写method或故意损坏HTTP版本,比较error日志。不要把畸形输入发送到线上站点。
思考: 解析字段经常指向header buffer内部,没有复制字符串;移动/扩大buffer必须相应修正指针。这解释了为什么buffer生命周期与请求状态机必须一起读。
返回目录