`
bollaxu
  • 浏览: 216998 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

Nginx的HTTP请求处理

阅读更多

 

Nginx在7层负载交换、反向代理服务领域使用比较广泛。Nginx的结构也比较简单,除了底层几个核心的模块(如ngx_core_module,ngx_event_core_module,ngx_errlog_module等)之外,其它的主要是基于上述核心模块的http和mail的模块组,负责处理相关服务。而这些模块也可以在编译的时候被enable/disable,取决于对实际功能的需求。在这里,我来分析一下Nginx用的最多的功能,即处理http请求的工作流程。

在事件处理的分析中,提到过当有http请求过来时事件的触发和处理过程。我们知道,在一个子进程accept()请求之后,会调用ngx_http_init_connection()函数。这个函数会添加一个读事件,并设置其handler为ngx_http_init_request()。但是,对于http模块的载入以及初始化,却是要从http_block()开始。在父进程(master process)调用ngx_init_cycle()的时候,会调用一次ngx_conf_parse()函数(先不在这里分析),这个时候(解析到了"http {...}" block),ngx_http_module模块的set()函数即ngx_http_block()就被调用。

static char * ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
	/* the main http context */
	//它里面只有三个成员:(void**)main_conf,(void**)srv_conf和(void**)loc_conf,注意它们是双层指针
	//每个NGX_HTTP_MODULE模块都有一个main_conf[i],srv_conf[i]和loc_conf[i]指向相应的上下文,但不是每个都会用到。
	ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
	*(ngx_http_conf_ctx_t **) conf = ctx;
	/* 清点NGX_HTTP_MODULE类型模块个数并给每个模块设定索引(index) */
	ngx_http_max_module = 0;
	for (m = 0; ngx_modules[m]; m++) {
		if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
			continue;
		}
		ngx_modules[m]->ctx_index = ngx_http_max_module++;
	}
	//main_conf在http{...}里面的所有上下文中都是一致的
	//分配内存,个数为http模块的个数
	ctx->main_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);

	//用来合并server{...}里面的srv_conf
	//分配内存,个数为http模块的个数
	ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
	
	//用来合并 /loc {...} 里面的loc_conf
	//分配内存,个数为http模块的个数
	ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);

	//如有定义的话,调用每个http模块里面的create_main_conf(),create_srv_conf()和create_loc_conf()
	for (;;) {
		//如果是NGX_HTTP_MODULE模块
		module = ngx_modules[m]->ctx;//module的上下文,为ngx_http_module_t类型
		mi = ngx_modules[m]->ctx_index;//在http模块组里的索引
		//如有的话,就调用每个模块的下列函数,并存入ctx相应的索引位
		//一般在 http {...}里面而不在 server {...}里面有命令的模块有create_main_conf()
		//一般在 server {...}里面而不在 /loc {...}里面有命令的模块有create_srv_conf()
		//一般在 /loc {...}里面有命令的模块有create_loc_conf()
		ctx->main_conf[mi] = module->create_main_conf(cf);//创建模块自定的上下文,存入相应位置
		ctx->srv_conf[mi] = module->create_srv_conf(cf);//创建模块自定的srv上下文,存入相应位置
		ctx->loc_conf[mi] = module->create_loc_conf(cf);//创建模块自定的loc上下文,存入相应位置
	}
	pcf = *cf;
	cf->ctx = ctx;//把ctx放入cf
	for (;;) {
		//调用每个NGX_HTTP_MODULE模块的preconfiguration()函数
		//一般模块的preconfiguration()作用是添加一些模块要用的变量到ngx_http_core_main_conf_t的hash表variables_keys
		module->preconfiguration(cf);
	}
	/* parse inside the http{} block */
	cf->module_type = NGX_HTTP_MODULE;
	cf->cmd_type = NGX_HTTP_MAIN_CONF;
	rv = ngx_conf_parse(cf, NULL);//会调用指令的set()函数,如果有嵌套的block会继续调用ngx_conf_parse()。大部分的http{}(但在server{}外)的指令都是给ngx_http_core_loc_conf_t的成员赋值。
	//获取ngx_http_core_module(定义一些http公用的命令和变量)的main_conf
	cmcf = ctx->main_conf[ngx_http_core_module.ctx_index];
	cscfp = cmcf->servers.elts;//cmcf->servers在ngx_http_core_create_main_conf里面初始化并分配内存,并在server{} block里面读取配置并设置相关变量
	for (;;) {
		//调用每个NGX_HTTP_MODULE模块的init_main_conf(),调用ngx_http_merge_servers()
		rv = module->init_main_conf(cf, ctx->main_conf[mi]);//对main_conf上下文的一些成员变量做初始化
		rv = ngx_http_merge_servers(cf, cmcf, module, mi);//每个server{} block都有一个srv_conf(继承ngx_http_core_module的srv_conf),且其ctx变量指向一组main_conf[],srv_conf[]和loc_conf[],储存了各个模块的main_conf,srv_conf,loc_conf(但是只有在server{}中调用模块的指令,才会用到这些xxx_conf)
	}
	/* create location trees */
	//给每个server创建location tree
	for (s = 0; s < cmcf->servers.nelts; s++) {
		clcf = cscfp[s]->ctx->loc_conf[ngx_http_core_module.ctx_index];//每个srv(虚拟主机)的ngx_http_core_loc_conf_t
		//location根据字母顺序排序
		ngx_http_init_locations(cf, cscfp[s], clcf);
		//建立静态location树(三叉树)
		//在http://blog.csdn.net/benbendy1984/archive/2010/11/18/6019336.aspx有简单介绍
		ngx_http_init_static_location_trees(cf, clcf);
	}
	//初始化几个phase的handler(分配内存)
	if (ngx_http_init_phases(cf, cmcf) != NGX_OK) {
		return NGX_CONF_ERROR;
	}
	//初始化header的hash table
	if (ngx_http_init_headers_in_hash(cf, cmcf) != NGX_OK) {
		return NGX_CONF_ERROR;
	}
	for (;;) {
		//注册每个模块对应phase的处理函数
		//这儿的处理函数是通用的(如http模块的所有location)
		//特定location的处理函数在指令(directive)的set()里面设置
		module->postconfiguration(cf);
	}
	//设置pre-defined variables的get_handler等,并放入hash table里
	if (ngx_http_variables_init_vars(cf) != NGX_OK) {
		return NGX_CONF_ERROR;
	}
	//注册各http模块phase的checker,并把postconfiguration注册的handler都放到cmcf的phase_engine,phase_engine把cheker和对应的handler一同放入数组,并通过next变量链接,供之后处理请求的时候按phase顺序调用。
	//在http://simohayha.iteye.com/blog/670326有比较详细的介绍
	if (ngx_http_init_phase_handlers(cf, cmcf) != NGX_OK) {
		return NGX_CONF_ERROR;
	}
	//把server_name储存在hash table里,最后给每个listening socket注册ls->handler=ngx_http_init_connection;
	//详见http://blog.csdn.net/ccdd14/archive/2010/09/12/5878459.aspx
	if (ngx_http_optimize_servers(cf, cmcf, cmcf->ports) != NGX_OK) {
		return NGX_CONF_ERROR;
	}
	return NGX_CONF_OK;
}
 


当nginx收到请求的时候,就会调用(1)ngx_http_init_request,开始了对请求的处理过程。

static void ngx_http_init_request(ngx_event_t *rev)
{
	//获取event的connection
	c = rev->data;
	//如果事件超时
	//...

	hc = c->data;
	//给这个ngx_http_connection_t类型的指针分配内存
	//...

	r = hc->request;
	//创建(re-init)一个新的request
	//...

	c->data = r;
	r->http_connection = hc;
	c->sent = 0;
	r->signature = NGX_HTTP_MODULE;

	/* find the server configuration for the address:port */
	port = c->listening->servers;//在http_block()的时候就已经设置完成(在"listen"指令和ngx_http_optimize_servers函数里)
	r->connection = c;

	//如果有多个监听的addr
	if (port->naddrs > 1) {
		/*
		* there are several addresses on this port and one of them
		* is an "*:port" wildcard so getsockname() in ngx_http_server_addr()
		* is required to determine a server address
		*/
		if (ngx_connection_local_sockaddr(c, NULL, 0) != NGX_OK) {
			ngx_http_close_connection(c);
			return;
		}
		switch (c->local_sockaddr->sa_family) {
			default: /* AF_INET */
				sin = (struct sockaddr_in *) c->local_sockaddr;
				addr = port->addrs;
				/* the last address is "*" */
				for (i = 0; i < port->naddrs - 1; i++) {
					if (addr[i].addr == sin->sin_addr.s_addr) {
						break;
					}
				}
				addr_conf = &addr[i].conf;
				break;
		}
	}
	else {
		switch (c->local_sockaddr->sa_family) {
			default: /* AF_INET */
				addr = port->addrs;
				addr_conf = &addr[0].conf;
				break;
		}
	}
	r->virtual_names = addr_conf->virtual_names;
	/* the default server configuration for the address:port */
	cscf = addr_conf->core_srv_conf;
	r->main_conf = cscf->ctx->main_conf;
	r->srv_conf = cscf->ctx->srv_conf;
	r->loc_conf = cscf->ctx->loc_conf;
	//event的handler设为ngx_http_process_request_line
	rev->handler = ngx_http_process_request_line;
	//设置request的读事件
	r->read_event_handler = ngx_http_block_reading;
	clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
	c->log->file = clcf->error_log->file;

	r->ctx = ngx_pcalloc(r->pool, sizeof(void *) * ngx_http_max_module);
	cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
	//分配内存给variables
	r->variables = ngx_pcalloc(r->pool, cmcf->variables.nelts * 
					sizeof(ngx_http_variable_value_t));
	c->single_connection = 1;
	c->destroyed = 0;

	//对r的其它的初始化
	r->main = r;//主请求
	r->method = NGX_HTTP_UNKNOWN;		
	r->headers_in.content_length_n = -1;	
	r->headers_in.keep_alive_n = -1;
	r->headers_out.content_length_n = -1;
	r->headers_out.last_modified_time = -1;
	r->uri_changes = NGX_HTTP_MAX_URI_CHANGES + 1;
	r->subrequests = NGX_HTTP_MAX_SUBREQUESTS + 1;
	r->http_state = NGX_HTTP_READING_REQUEST_STATE;
	ctx = c->log->data;
	ctx->request = r;
	ctx->current_request = r;
	r->log_handler = ngx_http_log_error_handler;
	rev->handler(rev); //ngx_http_process_request_line
}
 

下面,分别调用了下列函数,在这里不一一详细分析:
-->(2)ngx_http_process_request_line()//读取、解析请求行,并存入r的相应成员(如uri,args相关的重要指针)。
-->(3)ngx_http_process_request_headers()//把header line逐行解析并以key-value形式存入r。
-->(4)ngx_http_process_request()
-->(5)ngx_http_handler()
-->(6)ngx_http_core_run_phases()

void ngx_http_core_run_phases(ngx_http_request_t *r)
{
	cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);
	ph = cmcf->phase_engine.handlers;//拿到phase_engine里存放所有handler的数组

	//如果不是内部定向(rewrite)的话,r->phase_handler = 0;
	//每个phase的checker在ngx_http_init_phase_handlers设置
	//每个phase的handler在ngx_http_init_phase_handlers设置
	//ph(ngx_http_phase_handler_s类型)的顺序:越早在postconfiguration()时注册handler的模块,它的handler在ph里面就越靠后被调用。
	while (ph[r->phase_handler].checker) {
		//ngx_http_init_phase_handlers里面注册每个phase的checker和handler
		rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]);
		if (rc == NGX_OK) {
			return;
		}
	}
}
 

关于ngx_http_core_main_conf_t,它里面有两个成员,phase_engine和phases。其中phases是一个存放所有模块的在postconfiguration()函数里面注册的相关phase的handler。比如,ngx_http_index_module里面,就push了一个NGX_HTTP_CONTENT_PHASE的handler:ngx_http_index_handler。这个handler就被存放在ngx_http_core_main_conf_t的phases[NGX_HTTP_CONTENT_PHASE]这个数组里面。而phase_engine里面的handlers(ngx_http_phase_handler_s类型)则是一个checker和handler的函数对,next指向下一个phase在phase_engine里面的index。

比如说,最基本的一个处理过程:NGX_HTTP_FIND_CONFIG_PHASE --> NGX_HTTP_PREACCESS_PHASE --> NGX_HTTP_ACCESS_PHASE --> NGX_HTTP_CONTENT_PHASE。被调用的函数是:

//NGX_HTTP_FIND_CONFIG_PHASE的checker
ngx_http_core_find_config_phase()
{
	//调用ngx_http_core_find_static_location(),比较r的uri和config tree的名字,找到合适的/location,并更新r->loc_conf
	ngx_http_core_find_location();
	//基于r->loc_conf,更新r的相应成员变量
	//其中如果有一些location调用了某些模块的命令设置了特定的clcf->handler,r->content_handler = clcf->handler。在ngx_http_core_content_phase()里面就会被调用。
	ngx_http_update_location_config();
	//goto next handler即next phase
	r->phase_handler++;
}
 

NGX_HTTP_PREACCESS_PHASE的checker是ngx_http_core_generic_phase(),即调用ph->handler(r)。在默认的模块配置里,NGX_HTTP_PREACCESS_PHASE类型的有ngx_http_limit_req_module和ngx_http_limit_zone_module两个模块,那么它们的ngx_http_limit_req_handler()和ngx_http_limit_zone_handler()就会被调用。在ngx_http_core_generic_phase()里面调用ph->handler(r)之后即控制下一个被调用的函数(是到下一个phase或者是本个phase的下一个handler)。

//NGX_HTTP_ACCESS_PHASE的checker
ngx_http_core_access_phase()
{
	rc = ph->handler(r);
	//根据rc来判断选择是下一个phase或者是本个phase的下一个handler
	//...
}
 

在模块的默认配置里,NGX_HTTP_ACCESS_PHASE类型的是ngx_http_access_module和ngx_http_auth_basic_module,即ph->handler(r)调用了ngx_http_access_handler()和ngx_http_auth_basic_handler()

//NGX_HTTP_CONTENT_PHASE的checker
ngx_http_core_content_phase()
{
	//如果location有特定的handler
	//e.g. ngx_http_proxy_module的"proxy_pass"命令就会设置location的handler "ngx_http_proxy_handler"
	if (r->content_handler) {
		r->write_event_handler = ngx_http_request_empty_handler;
		ngx_http_finalize_request(r, r->content_handler(r));
		return NGX_OK;
	}
	//没有location没有特定的handler
	rc = ph->handler(r);
	ph++;
	//跳到下一个NGX_HTTP_CONTENT_PHASE的handler或者下一个phase的checker
	if (ph->checker) {
		r->phase_handler++;
		return NGX_AGAIN;
	}
	//...
}
 

默认的配置里有3个NGX_HTTP_CONTENT_PHASE类型的模块有ngx_http_static_module,ngx_http_index_module和ngx_http_autoindex_module,调用它们的ngx_http_static_handler(),ngx_http_index_handler()和ngx_http_autoindex_handler()。


通过实际运行的的debug信息,可以看到每个phase的checker被调用时候的handler的r->phase_handler(即index)。根据实际的情况(如根据不同location配置),不一定每个handler都会被调用(通过设置r->phase_handler = ph->next),甚至有时候会直接跳到另外一个phase(比如在ngx_http_index_handler()里面调用ngx_http_internal_redirect(),重新进入ngx_http_handler(),即重新把phase走一遍)。这些都是根据每个phase的handler的实现以及实际的请求来决定的。

关于每个phase的具体功能:
typedef enum {
	//0读取请求phase
	NGX_HTTP_POST_READ_PHASE = 0,
	//1这个阶段主要是处理全局的(server block)的rewrite。
	NGX_HTTP_SERVER_REWRITE_PHASE,
	//2这个阶段主要是通过uri来查找对应的location,然后根据loc_conf设置r的相应变量
	//e.g. 根据location内配置的具体命令设置r->content_handler,到NGX_HTTP_CONTENT_PHASE调用
	NGX_HTTP_FIND_CONFIG_PHASE,
	//3这个主要处理location的rewrite
	NGX_HTTP_REWRITE_PHASE,
	//4post rewrite,这个主要是进行一些校验以及收尾工作,以便于交给后面的模块。
	NGX_HTTP_POST_REWRITE_PHASE,
	//5比如流控这种类型的access就放在这个phase,也就是说它主要是进行一些比较粗粒度的access。
	NGX_HTTP_PREACCESS_PHASE,
	//6这个比如存取控制,权限验证就放在这个phase,一般来说处理动作是交给下面的模块做的.这个主要是做一些细粒度的access
	NGX_HTTP_ACCESS_PHASE,
	//7一般来说当上面的access模块得到access_code之后就会由这个模块根据access_code来进行操作
	NGX_HTTP_POST_ACCESS_PHASE,
	//8try_file模块,也就是对应配置文件中的try_files指令,可接收多个路径作为参数,当前一个路径的资源无法找到,则自动查找下一个路径
	NGX_HTTP_TRY_FILES_PHASE,
	//9内容处理模块
	NGX_HTTP_CONTENT_PHASE,  
	//10log模块
	NGX_HTTP_LOG_PHASE
} ngx_http_phases;
 

Nginx的http上下文(在读取配置的时候设置):
 


  • 大小: 89.4 KB
0
0
分享到:
评论

相关推荐

    Nginx中http请求处理过程

    Nginx中http请求处理过程 有不少地方不是很明白 ,还望大家共同交流

    nginx处理http请求实例详解

    主要介绍了nginx处理http请求实例详解的相关资料,需要的朋友可以参考下

    Nginx 重定向时获取域名的方法示例

    如果你在处理 Nginx 重定向时要获取原请求的域名(比如 HTTP 到 HTTPS),请用 $host 而不是 $server_name 。 问题和解决方案 今天碰到一个问题,服务器上一个子域名的请求重定向到另一个子域名上面去了。查了一段...

    Nginx丢弃http包体处理实例详解

    http框架丢弃http请求包体和上一篇文章http框架接收包体, 都是由http框架提供的两个方法,供http各个模块调用,从而决定对包体做什么处理。是选择丢弃还是接收,都是由模块决定的。例如静态资源模块,如果接收到...

    Nginx的超时timeout配置详解

    Nginx 处理的每个请求均有相应的超时设置。如果做好这些超时时间的限定,判定超时后资源被释放,用来处理其他的请求,以此提升 Nginx 的性能。 keepalive_timeout HTTP 是一种无状态协议,客户端向服务器发送一个 ...

    nginx处理http请求实现过程解析

    nginx会根据过来的http请求头里的Host字段里的值,来判断使用哪个server{}。 如果请求头里没有Host字段,或者Host字段里的值,和Nginx配置文件里的server{}里的{server_name}都不匹配,则使用第一个server{},来处理...

    如何利用nginx通过正则拦截指定url请求详解

    nginx是非常出色web服务器,对于静态文件的处理非常高效,同时它的代理转发功能和其它后台服务器搭配起来也非常的简单高效。 location 我们知道nginx会对请求进行解析,然后回得到关于请求的url等信息,我们只需要对...

    Nginx负载均衡特点

    6、Nginx对请求的异步处理可以帮助节点服务器减轻负载; 7、Nginx能支持http和Email,这样就在适用范围上面小很多; 8、不支持Session的保持、对Big request header的支持不是很好, 另外默认的只有Round-robin...

    nginx对http请求处理的各个阶段详析

    在编写nginx的http的模块的时候,需要在各个阶段对http请求做相应的处理,以达到不同的目的,比如请求发起的时候是否有访问权限、内容生成的时候进行过滤或者其它处理等等。如果在编译nginx模块内注册的处理阶段不正确...

    实战Nginx高性能Web服务器

    3、高性能Web服务器Nginx的配置与部署研究(3)Nginx的请求处理方式 内容:该文翻译自Nginx.org官网,为读者详述Nginx对HTTP请求的处理方式。 4、高性能Web服务器Nginx的配置与部署研究(4)Nginx常用命令 内容:...

    nginx.zip工具

    2.反向代理服务器:客户端本来可以直接通过HTTP协议访问某网站应用服务器,网站管理员可以在中间加上一个Nginx,客户端请求Nginx,Nginx请求应用服务器,然后将结果返回给客户端,此时Nginx就是反向代理服务器 ...

    详解nginx请求头数据读取流程

    在上一篇文章中,我们讲解了nginx是...在介绍请求头的读取流程之前,我们首先展示一个http请求报文的示例: POST /web/book/read HTTP/1.1 Host: localhost Connection: keep-alive Content-Length: 365 Accept: ap

    详解nginx的请求限制(连接限制和请求限制)

    一,背景  我们经常会遇到这种情况,服务器流量异常,...http_limit_req_module 模块来实现,该模块可以通过定义的 键值来限制请求处理的频率。特别的,可以限制来自单个IP地址的请求处理频率。 限制的方法如同漏斗,

    windows版支持连接请求处理的前向代理的nginx

    加入 https://github.com/chobits/ngx_http_proxy_connect_module.git 支持支持连接请求处理的前向代理,启动nginx之前、之后,使用如下代码测试: curl https://www.baidu.com -x 127.0.0.1:9999 只有在nginx启动后...

    nginx-goodies-nginx-sticky-module-ng-08a395c66e42.zip

    (b)后端服务器处理完请求,将响应数据返回给nginx。 (c)此时nginx生成带route的cookie,返回给客户端。route的值与后端服务器对应,可能是明文,也可能是md5、sha1等Hash值 (d)客户端接收请求,并保存带...

    搭建nginx点播服务器

    高性能:Nginx以其出色的性能而闻名,能够同时处理大量的并发连接请求。这使得它成为点播服务器的理想选择,可以在高负载情况下提供稳定的性能。 内容分发:Nginx可以用于分发静态媒体文件,如音频和视频。它支持...

    nginx中一个请求的count计数跟踪浅析.docx

    一个http请求来了,会进入jtxy的模块处理,jtxy会创建出一个子请求发送给jtcmd,jtcmd里处理呢又会创建出一个upstream流到我们的上游非http服务A来处理,A处理完成后得到结果,会把结果返回给jtcmd的子请求,jtcmd的...

    nginx与uwsgi与https部署.doc

    详细介绍python+django+nginx+uwsgi配置过程,1. Apache: 世界使用排名第一的Web服务器 2. Nginx: 轻量级的Web...负责接收 nginx 请求转发并处理后发给 Django 应用以及接收 Django 应用返回信息转发给 nginx;

    Nginx服务器中处理AJAX跨域请求的配置方法讲解

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

Global site tag (gtag.js) - Google Analytics