ginx源码分析—架构设计思想
在Nginx的源码中,可以将其分为三个部分:ngx_init_cycle之前、ngx_init_cycle函数以及ngx_init_cycle之后。在ngx_init_cycle之前,代码主要是为了重新启动Nginx而准备的,例如在这个阶段可以接受外部信号,保存传递的参数等。同时,后续的函数也考虑了是否正在重启Nginx。
ngx_init_cycle函数是一个庞大的函数,它调用了各个模块的钩子函数。在这个过程中,可以看到Nginx结构体的使用,例如所有的模块都是ngx_module_t这个结构体。然而,这个结构体中的ctx变量和commands type这三个变量使得各个模块的功能有所不同。将每一个模块都有所归类。
对于一个标准的HTTP请求,Nginx框架试图结束完整的HTTP头部,并在接收到完整的HTTP头部后将请求分发到具体的HTTP模块中处理。最常见的情况是根据请求的URI(URI在HTTP请求的头部信息中)和nginx.conf里的location配置项的匹配度来决定如何分发。
HTTP模块间数据流的传递,各个模块之间的写通过工作,配置文件中的Location块决定了匹配某种URI的请求将会由相应的HTTP模块处理。因此,运行时HTTP框架会在接收完毕HTTP请求的头部后,将请求的URI与配置文件中的所有location进行匹配,匹配后再根据Location{}内的配置项选择HTTP模块来调用。
在ngx_module_t结构体中,init_module和init_process函数指针分别在初始化所有模块时被调用和在worker进程已经产生时被调用。这两个函数指针在ngx_core_event_module模块中实现了与Nginx框架相关的调用。对于一个HTTP模块(如NGX_HTTP_MODULE),ngx_module_t中的ctx指向ngx_http_module_t类型的结构体。这个结构体中有8个函数指针,Nginx会在不同的时刻调用这些回调函数来读取重载配置文件。
一个HTTP请求的处理过程受到许多配置项的影响,这是因为一个HTTP请求可能会被多个HTTP模块同时处理,从而导致先后顺序的问题。由于同一个配置项可以适用于多个server location配置块,因此这个配置项将针对不同的请求产生作用。用户可以根据自己的需求定义模块如何参与HTTP请求的处理。
可以将配置项理解为对某一类HTTP请求感兴趣,当这类请求到达时,系统会根据配置项来判断哪些模块对该请求感兴趣,并调用相应的HTTP模块进行处理。对于具有参数的配置项,NGINX提供了14种常用类型来标识配置项中的参数类型,并提供相应的参数来处理这些参数。
在NGINX源码中,http{}块下有一个ngx_http_conf_ctx_t结构,而每个serve{}块下也有一个ngx_http_conf_ctx_t结构。此外,NGINX允许普通的HTTP处理模块介入其7个阶段来处理请求。然而,大多数HTTP模块(官方模块或第三方模块)仅在NGX_HTTP_CONTENT_PHASE阶段处理请求。
HTTP框架有两种方法让HTTP模块介入处理请求:第一种是,任意一个HTTP模块会对所有的用户请求产生作用;第二种方法是,只对请求的URI匹配了nginx.conf中某些location表达式下的HTTP模块起作用。大部分模块都采用第二种方法处理请求,这种方法的特点是,一个请求仅由一个HTTP模块(在NGX_HTTP_CONTENT_PHASE阶段)处理。如果希望多个HTTP模块共同处理一个请求,通常是由subrequest功能来完成,即将原始请求分为多个子请求,每个子请求再由一个HTTP模块在NGX_HTTP_CONTENT_PHASE阶段处理。
HTTP过滤模块与普通HTTP模块的区别在于,一个请求可以被任意个HTTP过滤模块处理。因此,普通的HTTP模块更倾向于完成请求的核心功能,如静态文件复制等;而HTTP过滤模块则负责处理一些附加功能,如gzip压缩、图片缩略图制作等。这些过滤模块的效果可以根据需要叠加,例如先由Not_modify过滤模块处理浏览器缓存信息,再交给range过滤模块处理HTTP range协议(支持断点续传),然后交由gzip过滤模块进行压缩。一个请求经过各HTTP过滤模块流水线般地依次进行处理。
在普通HTTP模块处理请求完毕,并调用ngx_http_send_header发送HTTP头部,或者调用ngx_http_output_filter发送HTTP包体时,才会由这两个方法依次调用所有的HTTP过滤模块处理这个请求。因此,HTTP过滤模块仅处理服务器发往客户端的HTTP响应,而不处理客户端发往服务器的HTTP请求。
NGINX在进行回应时,分为头部回应和包体回应。过滤链表也分为两个部分。对于某一个过滤模块,插入的时候都是插入这个链表的首部。因此,HTTP过滤模块越靠后,在实际执行请求时就越优先执行。在初始化HTTP过滤模块时,每一个HTTP过滤模块都是将自己插入到整个单链表的首部。