• Nginx是怎么接入HTTP请求的?


    配置解析过程中

    1. static ngx_int_t
    2. ngx_http_optimize_servers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf,
    3. ngx_array_t *ports)
    4. {
    5. for (p = 0; p < ports->nelts; p++) {
    6. for (a = 0; a < port[p].addrs.nelts; a++) {
    7. if (addr[a].servers.nelts > 1
    8. #if (NGX_PCRE)
    9. || addr[a].default_server->captures
    10. #endif
    11. )
    12. {
    13. if (ngx_http_server_names(cf, cmcf, &addr[a]) != NGX_OK) {
    14. return NGX_ERROR;
    15. }
    16. }
    17. }
    18. if (ngx_http_init_listening(cf, &port[p]) != NGX_OK) {
    19. return NGX_ERROR;
    20. }
    21. }
    22. }

    ngx_http_optimize_servers

    • 遍历cmcf->ports数组,port->addrs数组中每个addr都生成域名哈希表(ngx_http_server_names)。
    • 每个port都调用ngx_http_init_listening

    ngx_http_init_listening

    • 每次遍历都会判断是否是绑定了通配地址*:port,如果绑定了但是现在这个addr没绑定,就continue。否则就调用ngx_http_add_listening
    • 调用ngx_http_add_addrs,为hport(即ls->servers)的成员赋值。

    ngx_http_add_listening
    从cycle->listening中分配出一个元素ls,

    • 设置ls->handler为ngx_http_init_connection。
    • 根据addr的监听选项设置ls对应的监听参数,例如rcvbuf,reuseport等。

    ngx_http_add_addrs

    hport->addrs数组的每个元素就和监听端口port的addrs对应起来,包括ssl、http2、quic等连接属性,还有hash,wc_head,wc_tail等都复制了过来(这几个是域名哈希表)

    配置解析之后

    在ngx_conf_parse之后,会调用CORE_MODULE的init_conf。event模块的init_conf中,调用ngx_clone_listening,会对cycle->listening数组每个进行复制,个数等于配置的worker进程个数,每个worker进程都有自己的listening对象。

    在ngx_open_listening_sockets中,遍历cycle->listening数组,创建套接字fd,socket(),bind(),listen()。这样对于监听的IP:Port,每个worker进程都有不同的fd。

    紧接着调用ngx_configure_listening_sockets,遍历cycle->listening数组,根据ls里面的属性,设置socket选项。

    worker进程启动

    ngx_event_process_init

    worker进程启动后,会调用每个模块的init_process回调函数。在event模块的ngx_event_process_init函数中。

    • 为连接池(ngx_connection_t)分配内存,个数为worker_connections。
    • 为每个c对象的read、write事件分配内存,个数也是worker_connections。
    • 为cycle->listening数组中的每个元素分配一个连接对象c,c->listening指向对应的listening对象。把c->read的handler设置为ngx_event_accept,
    • 把c->read加入到epoll,等待客户端发送数据触发。

    ngx_event_accept

    • accept(),得到服务连接的套接字描述符s、连接对象c。
    • 对c进行初始化,c->pool、c->log、c->recv_chain等。
    • 然后把c->read加入到epoll。ngx_add_conn
    • 调用ls->handler,即ngx_http_init_connection。

    ngx_http_init_connection

    • c->listening->servers指向的不是域名数组,而且hport,保存的是端口下面所有addr和addr的哈希表信息。遍历每个addr,通过对比连接的服务端地址和hport->addrs里面的地址,就知道具体要用哪个配置了(hc->addr_conf = &addr[i].conf)。
    • 设置rev->handler,根据是否是https、http2、quic等设置相应的回调函数(开始解析http请求)。在这里设置http2的回调汗水,可能是为http2明文设置的。因为h2密文得先进行SSL握手,得把rev->handler设置为在ngx_http_ssl_handshake。等SSL握手的时候,才会根据ALPN的结果设置http2的解析函数ngx_http_v2_init。 如果是HTTP/1.1,就设置rev->handler为ngx_http_wait_request_handler。

    ngx_http_wait_request_handler

    • 分配c->buffer,大小为client_header_buffer_size。然后调用c->recv,接收数据。如果返回EAGAIN就把读时间加入到epoll,设置超时定时器。如果返回NGX_ERROR或返回0,就关闭连接。
    • 如果返回值大于0,则调用create_request,c->data = r 。设置读事件rev->handler 为ngx_http_process_request_line,调用ngx_http_process_request_line。

    ngx_http_process_request_line

    • 调用ngx_http_read_request_header
      • 如果r->header_in->last - r->header_in->pos 大于0 ,说明已经有数据了,返回长度n
      • 否则调用c->recv接收数据,如果返回NGX_AGAIN,则读事件加入定时器、epoll。
      • 如果n == 0 或-1,则说明连接错误,结束请求(400),否则r->header_in->last += n
    • 如果ngx_http_read_request_header 返回值是NGX_AGAIN或NGX_ERROR,则跳出循环
    • 否则调用ngx_http_parse_request_line,解析请求行。
    • 如果解析成功则调用ngx_http_process_request_uri 解析URI
    • 如果URI解析成功则继续验证请求行中的Host是否合法
    • 如果host合法则调用ngx_http_set_virtual_server,根据host找到对应的srv_conf。
    • 设置rev->handler 为ngx_http_process_request_headers,循环调用ngx_http_read_request_header,去解析每个收到的请求头。
    • 如果所有请求头都解析完了,则调用ngx_http_process_request_header,这个函数是校验一些固定的请求头的值是否合法,例如server,host,content_length等。如果不合法则返回对应的错误码,例如405,501。调用ngx_http_process_request

    ngx_http_process_request

    • 设置c->read->handler,c->write->handler 都为ngx_http_request_handler
    • 设置r->read_event_handler为ngx_http_block_reading,因为请求读完了。
    • 执行ngx_http_handler
      • 设置r->phase_handler = cmcf->phase_engine.server_rewrite_index
      • 设置r->write_event_handler = ngx_http_core_run_phases
      • 执行ngx_http_core_run_phases(r)

    ngx_http_core_run_phases

    这块的具体逻辑有空好好详细梳理,现在只看和proxy_pass以及之后的逻辑相关的。

    在ngx_http_core_content_phase中,判断如果r->content_handlr 不为空,则设置r->write_event_handler 为ngx_http_request_empty_handler,然后执行ngx_http_finalize_request(r, r->content_handler(r))。

    对于proxy_pass而言,r->content_handler就是ngx_http_proxy_handler,由此进入upstream处理流程。

    ngx_http_proxy_handler

    • 调用ngx_http_upstream_create,创建r->upstream结构并初始化。
    • 根据proxy_pass中是否有变量来计算upstream相关的URL
    • 设置r->upstream的回调函数,
      • u->create_request = ngx_http_proxy_create_request
      • u->reinit_request = ngx_http_proxy_reinit_request
      • u->process_header = ngx_http_proxy_process_status_line
      • u->abort_request = ngx_http_proxy_abort_request
      • u->finalize_request = ngx_http_proxy_finalize_request
    • 调用ngx_http_read_client_request_body(r,ngx_http_upstream_init)。从ngx_http_upstream_init开始,就开始执行upstream_init_request、upstream_connect、upstream_send_request、upstream_process_header、upstream_send_response等流程,
    • 如果ngx_http_read_client_request_body 返回值rc大于300则直接返回rc,否则返回NGX_DONE,即-4。因为upstream_connect的时候,非阻塞调用直接返回NGX_AGAIN,所以对于proxy_handler而言,读取请求body后的post_handler很快就结束了,proxy_handler就返回NGX_DONE了。这也是为什么在debug日志中,可以看到finalize_request被调两次,第一次就是ngx_http_proxy_handler返回NGX_DONE。

    ngx_http_read_client_request_body

    • 在这里r->main->count++。
    • 如果是子请求,或r->request_body已经存在或者r->discard_body,则设置r->request_body_no_buffering = 0,直接调用post_handler,即ngx_http_upstream_init,返回NGX_OK
    • 如果客户端带了expect头,则发生响应100-Continue,然后返回rc的默认值。
    • 如果content_length_n < 0,并且不是chunked传输,则说明没body,设置r->request_body_no_buffering = 0,调用post_handler,返回NGX_OK。
    • 如果读取到body了,则调用ngx_http_request_body_filter。
    • 如果读完了rb->rest == 0,则r->request_body_no_buffering = 0,直接调post_handler,返回NGX_OK。

    在ngx_http_read_client_request_body执行完之后,在ngx_http_finalize_request中,如果判断rc值是NGX_DONE,则直接调用ngx_http_finalize_connection,在这里判断r->main->count != 1,则调用ngx_http_close_request,在这里对r->main->count -- ,然后返回。

    此时上述所有函数调用还是在ngx_http_core_content_phase中,content_phase返回的是NGX_OK,然后回到ngx_http_core_run_phases中,对于rc是NGX_OK的情况,直接return。

    不管走到哪个流程,都把c->read、c->write加入到了epoll,并设置了r->write_event_handler、r->read_event_handler,在epoll被触发或定时器被触发后,就会调用对应的函数。

    例如upstream_connect函数中,在ngx_event_connect_peer返回NGX_AGAIN之后,就设置了u->peer.connection 的write->handler ,read->handler为ngx_http_upstream_handler。然后设置了u->write_event_handler 为ngx_http_upstream_send_request_handler,设置了u->read_event_handler为ngx_http_upstream_process_header。然后添加到timer中就返回了,等着epoll触发或定时器超时触发。

    在epoll触发或定时器触发之后,如果再次调用ngx_http_finalize_request,就是真正走请求结束流程了(不考虑子请求)。如果rc > 300,就走特殊流程ngx_http_finalize_request(r, ngx_http_special_response_handler(r, rc)); 如果配置了error_page,就special_response_handler中就执行ngx_http_send_error_page。

    TODO

    • 分析upstream处理响应的流程
      • buffering和非buffering的区别
      • free_bufs、busy_bufs、out_bufs的逻辑关系
    • 分析body_filter对 body的处理

    u->input_filter默认值是ngx_http_upstream_non_buffered_filter

    在proxy_buffering off模式下,u->input_filter实际值是ngx_http_proxy_non_buffered_copy_filter,如果上游是chunked格式的响应,则是ngx_http_proxy_non_buffered_chunked_filter。

    做的工作就是把从u->free_bufs上摘下一个元素,并串到u->out_bufs链上。然后指向u->buffer。

  • 相关阅读:
    ArcGIS JS自定义Accessor,并通过watchUtils相关方法watch属性
    你不知道的JS 之 this& this指向
    前端教程-小程序
    webpack构建vue项目 基础02 之 css、scss、postcss、autoprefixer、去掉多余的css相关配置
    Three.js 这样写就有阴影效果啦
    sqlite3 dbconfig说明
    react-router-dom 实用技巧及3种传参方式
    网络链接失败怀疑是服务器处于非正常状态?如何用本地电脑查看服务器是否正常?
    详解IIC通信协议以及FPGA实现
    Linux 操作系统中如何检查系统的启动和关机日志
  • 原文地址:https://blog.csdn.net/Leopard_89/article/details/126566566