欢迎光临
我们一直在努力

源码分析 Nginx 请求行读取流程

        在前面的文章中我们讲解了当一个请求到达时,nginx是如何建立连接并且读取数据的。在读取数据完成之后,nginx会将读取事件的回调方法设置为ngx_http_process_request_line(),这个方法主要有如下几个作用:

  • 读取客户端请求的数据,如果客户端数据读取不全,则继续监听客户端读事件以读取完整数据;
  • 解析读取到的客户端数据,将各个参数存储到表征当前请求的ngx_http_request_t结构体中;
  • 将读事件的回调方法设置为ngx_http_process_request_headers(),以继续处理客户端发送来的header数据。

这里需要说明的一点是,所谓的请求行指的是http请求报文中类似于GET /index HTTP/1.1的部分,根据http协议,这一部分下面的数据才是各个header数据,而这里解析请求行数据的过程是不包含如何解析header数据的(这部分我们将在下一篇文章中进行讲解)。

1. 请求行处理主流程

请求行处理的主流程主要是在ngx_http_process_request_line()方法中,如下是该方法的源码:

static void ngx_http_process_request_line(ngx_event_t *rev) {
  ssize_t n;
  ngx_int_t rc, rv;
  ngx_str_t host;
  ngx_connection_t *c;
  ngx_http_request_t *r;

  c = rev->data;
  r = c->data;

  if (rev->timedout) {
    ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
    c->timedout = 1;
    ngx_http_close_request(r, NGX_HTTP_REQUEST_TIME_OUT);
    return;
  }

  rc = NGX_AGAIN;
  for (;;) {
    if (rc == NGX_AGAIN) {
      // 这里的ngx_http_read_request_header()方法的主要作用是返回在连接上读取到的数据
      n = ngx_http_read_request_header(r);

      // n为NGX_AGAIN时,说明已经将当前事件添加到事件队列中了,因而直接返回,而NGX_ERROR则说明
      // 当前读取失败了,也直接返回
      if (n == NGX_AGAIN || n == NGX_ERROR) {
        return;
      }
    }

    // 这里主要是解析请求行的数据,比如"GET /index HTTP/1.1"
    rc = ngx_http_parse_request_line(r, r->header_in);

    // NGX_OK表示请求行的数据是完整的,并且已经解析完成
    if (rc == NGX_OK) {
      r->request_line.len = r->request_end - r->request_start;
      r->request_line.data = r->request_start;
      r->request_length = r->header_in->pos - r->request_start;
      r->method_name.len = r->method_end - r->request_start + 1;
      r->method_name.data = r->request_line.data;

      if (r->http_protocol.data) {
        r->http_protocol.len = r->request_end - r->http_protocol.data;
      }

      // 标记与参数相关的属性
      if (ngx_http_process_request_uri(r) != NGX_OK) {
        return;
      }

      if (r->host_start && r->host_end) {
        host.len = r->host_end - r->host_start;
        host.data = r->host_start;

        // 校验host,并且将host转换为小写形式
        rc = ngx_http_validate_host(&host, r->pool, 0);

        if (rc == NGX_DECLINED) {
          ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
          return;
        }

        if (rc == NGX_ERROR) {
          ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
          return;
        }

        // 设置用于处理当前请求的server和location块
        if (ngx_http_set_virtual_server(r, &host) == NGX_ERROR) {
          return;
        }

        r->headers_in.server = host;
      }

      // NGX_HTTP_VERSION_10表示当前请求是http 1.0,这里就是判断当前请求是否为0.9版本的请求,
      // 如果是0.9版本的请求,那么是没有请求头的
      if (r->http_version < NGX_HTTP_VERSION_10) {
        if (r->headers_in.server.len == 0 
            && ngx_http_set_virtual_server(r, &r->headers_in.server) == NGX_ERROR) {
          return;
        }

        // 对于0.9版本的请求,直接处理请求
        ngx_http_process_request(r);
        return;
      }

      // 初始化headers链表
      if (ngx_list_init(&r->headers_in.headers, 
                        r->pool, 20, sizeof(ngx_table_elt_t)) != NGX_OK) {
        ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
        return;
      }

      c->log->action = "reading client request headers";

      // 将事件的处理方法设置为ngx_http_process_request_headers()方法
      rev->handler = ngx_http_process_request_headers;
      ngx_http_process_request_headers(rev);
      return;
    }

    if (rc != NGX_AGAIN) {
      ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
      return;
    }

    /* NGX_AGAIN: a request line parsing is still incomplete */
    // 走到这里,说明返回值是NGX_AGAIN,也即读取请求行还未读取完全,如果缓冲区还有可用数据,
    // 则不做任何处理,否则继续下一次循环
    if (r->header_in->pos == r->header_in->end) {
      // 为当前request申请大块内存
      rv = ngx_http_alloc_large_header_buffer(r, 1);
      if (rv == NGX_ERROR) {
        ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
        return;
      }

      if (rv == NGX_DECLINED) {
        r->request_line.len = r->header_in->end - r->request_start;
        r->request_line.data = r->request_start;
        ngx_http_finalize_request(r, NGX_HTTP_REQUEST_URI_TOO_LARGE);
        return;
      }
    }
  }
}

对于这里的主流程,我们可以看到,其首先是检查读事件是否过期,如果过期了,则直接返回。如果没有过期,则进入一个无限for循环,如下是这个循环的工作:

  • 调用ngx_http_read_request_header()方法读取数据,在这个方法中,nginx会检查当前连接句柄上是否存在可读的数据,如果不存在,继续将当前事件添加到事件框架中。由于当前事件的回调方法是上面的主流程方法,也即ngx_http_process_request_line(),因而当再次触发读事件时,还是会走到这里的ngx_http_read_request_header()方法读取数据,这也就是为什么在ngx_http_read_request_header()方法返回NGX_AGAIN时,主流程可以直接返回而不做任何处理的原因;
  • 调用ngx_http_parse_request_line()方法解析请求行的数据,这个方法比较长,但是逻辑非常简单,主要工作就是一个字符一个字符的比对请求行中的各个数据,然后将其设置到ngx_http_request_t中,我们这里不对齐进行深入讲解;
  • 根据前面两个方法的返回值处理当前请求,主要分为NGX_OK、NGX_AGAIN、NGX_DECLINED和NGX_ERROR。当返回值是NGX_DECLINED时,表示请求行太长,此时会给客户端返回414状态码;当返回值是NGX_ERROR时,会直接关闭当前连接,并且返回500状态码;而NGX_AGAIN则会继续等待触发下一次的事件循环;
  • 在响应码为NGX_OK时,会设置ngx_http_request_t中的相关字段,并且会检查请求行数据的合法性。需要注意的是,在这个分支最后检查完时会分情况,如果当前http请求版本为0.9版本,由于0.9版本没有请求头,因而直接调用ngx_http_process_request()进入http模块的11个阶段进行处理。如果当前版本是1.0以上,由于其是需要传请求头的,因而会调用ngx_http_process_request_headers()方法读取并处理请求头数据。

2. 请求行数据读取

我们这里主要看一下nginx是如何读取请求行数据的,如下是ngx_http_read_request_header()方法的源码:

static ssize_t ngx_http_read_request_header(ngx_http_request_t *r) {
  ssize_t n;
  ngx_event_t *rev;
  ngx_connection_t *c;
  ngx_http_core_srv_conf_t *cscf;

  c = r->connection;
  rev = c->read;

  // 计算当前还有多少数据未处理
  n = r->header_in->last - r->header_in->pos;

  // 如果n大于0,说明还有读取到的数据未处理,则直接返回n
  if (n > 0) {
    return n;
  }

  // 走到这里,说明当前读取到的数据都已经处理完了,因而这里会进行判断,如果当前事件的ready参数为1,
  // 则表示当前连接的句柄上存储还未读取的数据,因而调用c->recv()方法读取数据,否则继续将当前事件
  // 添加到事件队列中,并且继续监听当前连接句柄的读事件
  // 这里的c->recv()方法实际指向的是ngx_unix_recv()方法,主要作用就是读取指定连接上的数据
  if (rev->ready) {
    // 在连接文件描述符上读取数据
    n = c->recv(c, r->header_in->last, r->header_in->end - r->header_in->last);
  } else {
    n = NGX_AGAIN;
  }

  // 如果n为NGX_AGAIN,则将当前事件添加到事件监听器中,并且继续监听当前epoll句柄的读事件
  if (n == NGX_AGAIN) {
    if (!rev->timer_set) {
      cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
      ngx_add_timer(rev, cscf->client_header_timeout);
    }

    if (ngx_handle_read_event(rev, 0) != NGX_OK) {
      ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
      return NGX_ERROR;
    }

    return NGX_AGAIN;
  }

  // 如果n为0,说明客户端关闭了连接
  if (n == 0) {
    ngx_log_error(NGX_LOG_INFO, c->log, 0, "client prematurely closed connection");
  }

  // 如果客户端关闭了连接或者读取异常,则回收当前的request结构体
  if (n == 0 || n == NGX_ERROR) {
    c->error = 1;
    c->log->action = "reading client request headers";
    ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST);
    return NGX_ERROR;
  }

  // 更新当前读取到的数据指针
  r->header_in->last += n;

  return n;
}

这里读取请求行的数据流程整体也比较简单,其主要流程如下:

  • 检查当前读取缓冲区中是否存在还未处理的数据,如果存在,则直接返回,以让后面的ngx_http_parse_request_line()方法解析这些数据;
  • 检查当前的事件是否处于可执行状态,是则调用c->recv()方法从连接的句柄上读取数据,返回值n表示读取到的数据大小;
  • 如果当前事件处于不可执行状态,则将当前事件添加到事件框架中,并且在epoll句柄上为当前连接注册读事件;
  • 在第二步中,如果读取数据的返回值是NGX_ERROR或者0(0表示客户端断开了连接),则给客户端返回400响应码。

可以看到,这里对于请求行数据的处理过程,简单的说就是如果存在数据就读取,如果不存在则注册当前的读取事件。

3. 小结

本文主要讲解了nginx是如何读取请求行数据,并且对请求行数据进行解析的,并且着重说明了在读取请求行数据时,如果读取到的数据不完整时,nginx是如何通过事件模型进行处理的。

赞(0) 打赏
转载请注明来源:IT技术资讯 » 源码分析 Nginx 请求行读取流程

评论 抢沙发

评论前必须登录!

 

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏