配置解析过程中

static ngx_int_t
ngx_http_optimize_servers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf,ngx_array_t *ports)
{for (p = 0; p < ports->nelts; p++) {for (a = 0; a < port[p].addrs.nelts; a++) {if (addr[a].servers.nelts > 1
#if (NGX_PCRE)|| addr[a].default_server->captures
#endif){if (ngx_http_server_names(cf, cmcf, &addr[a]) != NGX_OK) {return NGX_ERROR;}}}if (ngx_http_init_listening(cf, &port[p]) != NGX_OK) {return NGX_ERROR;}}
}

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。

Nginx是怎么接入HTTP请求的?相关推荐

  1. Nginx 实现AJAX跨域请求

    AJAX从一个域请求另一个域会有跨域的问题.那么如何在nginx上实现ajax跨域请求呢?要在nginx上启用跨域请求,需要添加add_header Access-Control*指令.如下所示: 1 ...

  2. 解决Nginx+Tomcat下客户端https请求跳转成http的问题

    Nginx上开启https,  后端使用Tomcat,  两者间走http协议, 但发现如果tomcat应用存在跳转时, 则客户端浏览器会出现400 Bad Request的错误, 通过抓包发现原因是 ...

  3. nginx php 跨域访问权限,nginx + php 实现跨域请求填坑笔记

    最近自己构建了一个轻量级的 MVC 框架,现在将该MVC用于生产环境的项目中,目前因为项目中有跨域的请求,所以需要做一些CORS授权,但在实际使用出现了一些问题,目前已解决,故做下记录. 服务器:ce ...

  4. nginx与php处理用户请求,配置 NGINX 处理 PHP 的请求《 LEMP 网站应用运行环境 》

    NGINX 本身并不能处理 PHP 的请求,也就是它根据不认识 PHP 是什么,我们需要配置一下 NGINX,当有用户请求执行 PHP 的时候,把这些请求交给别人去处理一下,这个人知道怎么样处理 PH ...

  5. 在Nginx中让所有HTTP请求转发到HTTPS

    title: 在Nginx中让所有HTTP请求转发到HTTPS date: 2017-10-23 20:50:24 tags: nginx http https categories: 运维 背景 在 ...

  6. nginx 实现ajax跨域,Nginx 实现AJAX跨域请求

    在工作中遇到跨域请求的问题: AJAX从一个域请求另一个域会有跨域的问题.那么如何在nginx上实现ajax跨域请求呢?要在nginx上启用跨域请求,需要添加add_header Access-Con ...

  7. 关于nginx集合fastdfs时http请求无法访问,nginx的权限问题。

    关于nginx集合fastdfs时http请求无法访问.nginx的权限问题 具体安装步骤请参考 https://www.cnblogs.com/yufeng218/p/8111961.html 非常 ...

  8. Nginx+ lua实现http转发请求

    最近要使用nginx+lua实现 一个需求: 在nginx的location部分,请求时,判断用户是否为会员,如果是会员,则跳转到a页面,否则跳转到b页面. 用户服务是一个单独的服务,具体lua脚本实 ...

  9. nginx日志分析查询异常请求IP之狙击网络黑客

    1 分析 使用nginx作为web端口分发时,只要请求服务器,nginx便会在access.log与error.log文件中留下记录,包括请求时间.请求方式.浏览器..以及访问的静态文件等信息. 网络 ...

最新文章

  1. 加油!打工人!打工人分析简报
  2. c#_winform打开关闭时淡入淡出
  3. Microsoft月度中文速递
  4. Selenium爬携程酒店评论+jieba数据分析实战
  5. 高手与菜鸟,思想与技术
  6. mysql 有外键 怎么插入数据_外键约束的表怎么插入数据
  7. 汇编学习--7.16--外中断
  8. java interruptedexception_如何正确的处理InterruptedException
  9. StanfordDB class自学笔记 (2) 关系模型
  10. 小米3手机无法打开WLAN ,WIFI 的解决方法,不需刷机
  11. scrapy之spiders
  12. BFS解决一般性的泊松分酒问题
  13. 第十一课 for循环(3)---循环变量的变化
  14. 前端开发者应该知道的 Centos/Docker/Nginx/Node/Jenkins 操作
  15. vscode 插件 markdown-preview-enhanced 设置深色预览主题
  16. 算法面试必备-----数据分析常见面试题
  17. OKhttp+Gson实现从网络上获取最新新闻
  18. python字符串赋值_【python】字符串变量赋值时字符串可用单或双引号
  19. 存储过程与函数 - 存储函数的使用
  20. 2019华师在线计算机,华师计算机基础客观作业2019.pdf

热门文章

  1. #inclde<>和#include“ “的区别
  2. 中国家用Wifi常见密码TOP10排行榜,你上榜了吗?
  3. Serial RapidIO Gen2 IP 说明(四)
  4. java反向映射_推荐一款Java对象映射神器,别再傻傻手动转换了!
  5. 批量的将excel转换成pdf格式的方法
  6. Delphi用IE浏览器打开网址链接的三种
  7. 对颈椎减肥肠胃有利的瑜伽姿势总结
  8. 书院主持人 Java 方法:标记法
  9. matplotlib: 双Y轴、同一坐标轴中不同类型图、设置坐标轴刻度格式
  10. 固态硬盘和机械硬盘的区别分析