详解Nginx的核心配置模块中对于请求体的接受流程

2019-10-17 19:57:32王旭

本篇文章主要会介绍nginx中请求的接收流程,包括请求头的解析和请求体的读取流程。


首先介绍一下rfc2616中定义的http请求基本格式:

Request = Request-Line  
   *(( general-header      
      | request-header      
      | entity-header ) CRLF)  
     CRLF 
     [ message-body ]  </span> 

 

第一行是请求行(request line),用来说明请求方法,要访问的资源以及所使用的HTTP版本:
Request-Line   = Method SP Request-URI SP HTTP-Version CRLF</span> 

请求方法(Method)的定义如下,其中最常用的是GET,POST方法:

Method = "OPTIONS"  
| "GET"  
| "HEAD"  
| "POST"  
| "PUT"  
| "DELETE"  
| "TRACE"  
| "CONNECT"  
| extension-method  
extension-method = token

要访问的资源由统一资源地位符URI(Uniform Resource Identifier)确定,它的一个比较通用的组成格式(rfc2396)如下:

<scheme>://<authority><path>?<query> 

一般来说根据请求方法(Method)的不同,请求URI的格式会有所不同,通常只需写出path和query部分。

http版本(version)定义如下,现在用的一般为1.0和1.1版本:

HTTP/<major>.<minor> 


请求行的下一行则是请求头,rfc2616中定义了3种不同类型的请求头,分别为general-header,request-header和entity-header,每种类型rfc中都定义了一些通用的头,其中entity-header类型可以包含自定义的头。


现在开始介绍nginx中请求头的解析,nginx的请求处理流程中,会涉及到2个非常重要的数据结构,ngx_connection_t和ngx_http_request_t,分别用来表示连接和请求,这2个数据结构在本书的前篇中已经做了比较详细的介绍,没有印象的读者可以翻回去复习一下,整个请求处理流程从头到尾,对应着这2个数据结构的分配,初始化,使用,重用和销毁。


nginx在初始化阶段,具体是在init process阶段的ngx_event_process_init函数中会为每一个监听套接字分配一个连接结构(ngx_connection_t),并将该连接结构的读事件成员(read)的事件处理函数设置为ngx_event_accept,并且如果没有使用accept互斥锁的话,在这个函数中会将该读事件挂载到nginx的事件处理模型上(poll或者epoll等),反之则会等到init process阶段结束,在工作进程的事件处理循环中,某个进程抢到了accept锁才能挂载该读事件。

static ngx_int_t 
ngx_event_process_init(ngx_cycle_t *cycle) 
{ 
  ... 
 
 
  /* 初始化用来管理所有定时器的红黑树 */ 
  if (ngx_event_timer_init(cycle->log) == NGX_ERROR) { 
    return NGX_ERROR; 
  } 
  /* 初始化事件模型 */ 
  for (m = 0; ngx_modules[m]; m++) { 
    if (ngx_modules[m]->type != NGX_EVENT_MODULE) { 
      continue; 
    } 
 
 
    if (ngx_modules[m]->ctx_index != ecf->use) { 
      continue; 
    } 
 
 
    module = ngx_modules[m]->ctx; 
 
 
    if (module->actions.init(cycle, ngx_timer_resolution) != NGX_OK) { 
      /* fatal */ 
      exit(2); 
    } 
 
 
    break; 
  } 
 
 
  ... 
 
 
  /* for each listening socket */ 
  /* 为每个监听套接字分配一个连接结构 */ 
  ls = cycle->listening.elts; 
  for (i = 0; i < cycle->listening.nelts; i++) { 
 
 
    c = ngx_get_connection(ls[i].fd, cycle->log); 
 
 
    if (c == NULL) { 
      return NGX_ERROR; 
    } 
 
 
    c->log = &ls[i].log; 
 
 
    c->listening = &ls[i]; 
    ls[i].connection = c; 
 
 
    rev = c->read; 
 
 
    rev->log = c->log; 
    /* 标识此读事件为新请求连接事件 */ 
    rev->accept = 1; 
 
 
    ... 
 
 
#if (NGX_WIN32) 
 
 
    /* windows环境下不做分析,但原理类似 */ 
 
 
#else 
    /* 将读事件结构的处理函数设置为ngx_event_accept */ 
    rev->handler = ngx_event_accept; 
    /* 如果使用accept锁的话,要在后面抢到锁才能将监听句柄挂载上事件处理模型上 */ 
    if (ngx_use_accept_mutex) { 
      continue; 
    } 
    /* 否则,将该监听句柄直接挂载上事件处理模型 */ 
    if (ngx_event_flags & NGX_USE_RTSIG_EVENT) { 
      if (ngx_add_conn(c) == NGX_ERROR) { 
        return NGX_ERROR; 
      } 
 
 
    } else { 
      if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) { 
        return NGX_ERROR; 
      } 
    } 
 
 
#endif 
 
 
  } 
 
 
  return NGX_OK; 
}