当前位置:

首页 > 编程开发 > nginx怎么处理http请求

nginx怎么处理http请求

一、event事件与http框架的交互在接收完http请求行、http请求头部后,会调用ngx_http_process_request这个函数开始处理http请求。因为一个http请求由11个处理阶段组成,而每一个处理阶段都允许多个http模块介入,因此在这个函数中,将调度各个阶段的http模块共同完成这个请求。//接收到http请求行与请求头后,http的处理流程,是第一个http处理请求的读事件回调//这个函数执行后,将把读写事件的回调设置为ngx_http_request_handler。这样下次

一、event事件与http框架的交互

        在接收完http请求行、http请求头部后,会调用ngx_http_process_request这个函数开始处理http请求。因为一个http请求由11个处理阶段组成,而每一个处理阶段都允许多个http模块介入,因此在这个函数中,将调度各个阶段的http模块共同完成这个请求。

//接收到http请求行与请求头后,http的处理流程,是第一个http处理请求的读事件回调 
//这个函数执行后,将把读写事件的回调设置为ngx_http_request_handler。这样下次再有事件时 
//将调用ngx_http_request_handler函数来处理,而不会再调用ngx_http_process_request了 
static void ngx_http_process_request(ngx_http_request_t *r) 
{ 
  ngx_connection_t *c; 
  c = r->connection; 
  //因为已经接收完http请求行、请求头部了,准备调用各个http模块处理请求了。 
  //因此需要接收任何来自客户端的读事件,也就不存在接收http请求头部超时问题 
  if (c->read->timer_set)  
  { 
    ngx_del_timer(c->read); 
  } 
  //重新设置当前连接的读写事件回调 
  c->read->handler = ngx_http_request_handler; 
  c->write->handler = ngx_http_request_handler; 
  //设置http请求对象的读事件回调,这个回调不做任何的事情。 
  //那http请求对象的读事件回调,与上面的连接对应的读事件回调有什么关系呢? 
  //当读事件发生后,连接对应的读事件回调ngx_http_request_handler会被调用, 
  //在这个回调内会调用http请求对象的读事件回调ngx_http_block_reading,而这个回调是 
  //不会做任何事件的,因此相当于忽略了读事件。因为已经接收完了请求行请求头,现在要做的是调用各个http模块, 
  //对接收到的请求行请求头进行处理 
  r->read_event_handler = ngx_http_block_reading; 
 
  //调用各个http模块协同处理这个请求 
  ngx_http_handler(r); 
  //处理子请求 
  ngx_http_run_posted_requests(c); 
}

      ngx_http_process_request函数只会被调用一次。如果一次调度并不能处理完11个http阶段,那会将连接对象对应的读事件、写事件回调设置为ngx_http_request_handler。而请求对象的读事件设置为ngx_http_block_reading, 请求对象的写事件回调设置为ngx_http_core_run_phases, 这个回调在ngx_http_handler内设置。这样在事件再次到来时不会调用

ngx_http_process_request函数处理了。那event事件模块的读写事件回调与http请求对象的读写事件回调有什么关系呢?

nginx怎么处理http请求

//http请求处理读与写事件的回调,在ngx_http_process_request函数中设置。 
//这个函数中将会调用http请求对象的读写事件回调。将event事件模块与http框架关联起来 
static void ngx_http_request_handler(ngx_event_t *ev) 
{ 
  //如果同时发生读写事件,则只有写事件才会触发。写事件优先级更高 
  if (ev->write)  
  { 
    r->write_event_handler(r);  //在函数ngx_http_handler设置为:ngx_http_core_run_phases 
 
  } 
  else 
  { 
    r->read_event_handler(r);  //在函数ngx_http_process_request设置为:ngx_http_block_reading 
  } 
 
  //处理子请求 
  ngx_http_run_posted_requests(c); 
}

可以看到,连接对象的读事件回调中,会调用http请求对象的读事件回调。连接对象的写事件回调会调用http请求对象的写事件回调。

nginx怎么处理http请求

        图中可看出,在event的读事件发生时,epoll返回后会调用读事件的回调ngx_http_request_handler。在这个读事件回调中,又会调用http框架,也就是http请求对象的读事件回调ngx_http_block_reading,这个http请求对象的读事件回调是不做任何事情的,相当于忽略读事件。因此http框架将会返回到事件模块。那为什么要忽略读事件呢?因为http请求行、请求头部都已经全部接收完成了, 现在要做的是调度各个http模块共同协作,完成对接收到的请求行,请求头部的处理。因此不需要接收来自客户端任何数据了。

        nginx怎么处理http请求

        对于写事件的处理就复杂多了,  在event的写事件发生时,epoll返回后会调用写事件的回调ngx_http_request_handler,在这个写事件回调中,又会调用http框架,也就是http请求对象的写事件回调ngx_http_core_run_phases。这个http框架的回调会调度介入11个请求阶段的各个http模块的hander方法,共同完成http请求。

二、调度http模块处理请求

        在上面代码中,会调度ngx_http_core_run_phases这个函数,使得各个http模块能介入到http请求中来。而这个函数是在ngx_http_handler设置的。

//调用各个http模块协同处理这个请求 
void ngx_http_handler(ngx_http_request_t *r) 
{ 
  //不需要进行内部跳转。什么是内部跳转? 例如有个location结构,里面的 
  // 
  if (!r->internal)   
  { 
    //将数组序号设为0,表示从数组第一个元素开始处理http请求 
    //这个下标很重要,决定了当前要处理的是11个阶段中的哪一个阶段, 
    //以及由这个阶段的哪个http模块处理请求 
    r->phase_handler = 0; 
  }  
  else  
  { 
    //需要做内部跳转 
    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); 
    //将序号设置为server_rewrite_index 
    r->phase_handler = cmcf->phase_engine.server_rewrite_index; 
  } 
  //设置请求对象的写事件回调,这个回调将会调度介入11个http阶段的各个http模块 
  //共同完成对请求的处理 
  r->write_event_handler = ngx_http_core_run_phases; 
  //开始调度介入11个http阶段的各个http模块 
  ngx_http_core_run_phases(r); 
}

而ngx_http_core_run_phases函数就很简单了,调度介入11个http处理阶段的所有http模块的checker方法。

//调用各个http模块协同处理这个请求, checker函数内部会修改phase_handler 
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; 
  //调用各个http模块的checker方法,使得各个http模块可以介入http请求 
  while (ph[r->phase_handler].checker) 
  { 
    rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]); 
    //从http模块返回ngx_ok,http框架则会把控制全交还给事件模块 
    if (rc == ngx_ok)     
    { 
      return; 
    } 
  }

假设阶段2有三个http模块介入了http请求, 阶段3有一个模块介入了http请求、阶段4也有一个模块介入了请求。当开始处理阶段2时,将调用阶段2中的所有http模块进行处理,此时phase_handler指向阶段2的开始位置。之后每处理完阶段2中的一个模块时,phase_handler指向阶段2的下一个模块,直到阶段2处理完成。

nginx怎么处理http请求

        当阶段2中的所有http模块都处理完成时,phase_handler将指向阶段3

nginx怎么处理http请求

        因阶段3只有一个http模块,因此当阶段3中的所有http模块都处理完成时,phase_handler将指向阶段4

nginx怎么处理http请求

        那这个handlers数组是什么时候创建的呢? 每一个http模块的checker回调又是做什么呢? 接下来将分析这两个问题

三、11个http请求阶段数组创建

        在解析nginx.conf配置文件时,解析到http块时,会调用ngx_http_block这个函数开始解析http块。在这个函数中,也会把所有需要介入到11个http请求阶段的http模块,注册到数组中。

//开始解析http块 
static char * ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 
{ 
  //http配置解析完成后的后续处理,使得各个http模块可以介入到11个http阶段 
  for (m = 0; ngx_modules[m]; m++)  
  { 
    if (ngx_modules[m]->type != ngx_http_module)  
    { 
      continue; 
    } 
 
    module = ngx_modules[m]->ctx; 
    if (module->postconfiguration)  
    { 
      //每一个http模块的在这个postconfiguration函数中,都可以把自己注册到11个http阶段 
      if (module->postconfiguration(cf) != ngx_ok)  
      { 
        return ngx_conf_error; 
      } 
    } 
  } 
}

        例如ngx_http_static_module静态模块,会将自己介入11个http阶段的ngx_http_content_phase阶段回调设置为ngx_http_static_handler

//静态模块将自己注册到11个http请求阶段中的ngx_http_content_phase阶段 
static ngx_int_t ngx_http_static_init(ngx_conf_t *cf) 
{ 
  cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); 
  h = ngx_array_push(&cmcf->phases[ngx_http_content_phase].handlers); 
 
  //静态模块在ngx_http_content_phase阶段的处理方法 
  *h = ngx_http_static_handler; 
 
  return ngx_ok; 
}

例如: ngx_http_access_module访问权限模块,会将自己介入11个http阶段的ngx_http_access_phase阶段回调设置为ngx_http_access_handler

//访问权限模块将自己注册到11个http请求阶段中的ngx_http_access_phase阶段 
static ngx_int_t ngx_http_access_init(ngx_conf_t *cf) 
{ 
  cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module); 
  h = ngx_array_push(&cmcf->phases[ngx_http_access_phase].handlers); 
   
  //访问权限模块在ngx_http_access_phase阶段的处理方法 
  *h = ngx_http_access_handler; 
   
  return ngx_ok; 
}

上面的这些操作,只是把需要介入到11个http阶段的http模块保存到了ngx_http_core_main_conf_t中的phases成员中,并没有保存到phase_engine中。那什么时候将phases的内容保存到phase_engine中呢?  还是在ngx_http_block函数中完成

//开始解析http块 
static char * ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 
{ 
    //初始化请求的各个阶段 
  if (ngx_http_init_phase_handlers(cf, cmcf) != ngx_ok)  
  { 
    return ngx_conf_error; 
  } 
}

假设阶段1有一个http模块介入请求,阶段2有三个http模块介入请求、阶段3也有一个http模块介入请求。则ngx_http_init_phase_handlers这个函数调用后,从ngx_http_phase_t   phases[11]数组转换到ngx_http_phase_handler_t    handlers数组的过程如下图所示:

nginx怎么处理http请求

//初始化请求的各个阶段 
static ngx_int_t ngx_http_init_phase_handlers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf) 
{ 
  //11个http请求阶段,每一个阶段都可以有多个http模块介入。 
  //这里统计11个节点一共有多个少http模块。以便下面开辟空间 
  for (i = 0; i < ngx_http_log_phase; i++)  
  { 
    n += cmcf->phases[i].handlers.nelts; 
  } 
  //开辟空间,存放介入11个处理阶段的所有http模块的回调 
  ph = ngx_pcalloc(cf->pool,n * sizeof(ngx_http_phase_handler_t) + sizeof(void *)); 
  cmcf->phase_engine.handlers = ph; 
  n = 0; 
 
  //对于每一个http处理阶段,给该阶段中所有介入的http模块赋值 
  for (i = 0; i < ngx_http_log_phase; i++) 
  { 
    h = cmcf->phases[i].handlers.elts; 
 
    switch (i)  
    { 
      case ngx_http_server_rewrite_phase://根据请求的uri查找location之前,修改请求的uri阶段 
        if (cmcf->phase_engine.server_rewrite_index == (ngx_uint_t) -1)  
        { 
          cmcf->phase_engine.server_rewrite_index = n; //重定向模块在数组中的位置 
        } 
        checker = ngx_http_core_rewrite_phase;   //每一个阶段的checker回调 
        break; 
      case ngx_http_find_config_phase://根据请求的uri查找location阶段(只能由http框架实现) 
        find_config_index = n; 
        ph->checker = ngx_http_core_find_config_phase; 
        n++; 
        ph++; 
        continue; 
      case ngx_http_rewrite_phase:  //根据请求的rui查找location之后,修改请求的uri阶段 
        if (cmcf->phase_engine.location_rewrite_index == (ngx_uint_t) -1) 
        { 
          cmcf->phase_engine.location_rewrite_index = n; 
        } 
        checker = ngx_http_core_rewrite_phase; 
        break; 
      case ngx_http_post_rewrite_phase: //ngx_http_rewrite_phase阶段修改rul后,防止递归修改uri导致死循环阶段 
        if (use_rewrite)  
        { 
          ph->checker = ngx_http_core_post_rewrite_phase; 
          ph->next = find_config_index;//目的是为了地址重写后,跳转到ngx_http_find_config_phase阶段,根据 
                        //url重写查找location 
          n++; 
          ph++; 
        } 
        continue; 
      case ngx_http_access_phase:     //是否允许访问服务器阶段 
        checker = ngx_http_core_access_phase; 
        n++; 
        break; 
      case ngx_http_post_access_phase:  //根据ngx_http_access_phase阶段的错误码,给客户端构造响应阶段 
        if (use_access)  
        { 
          ph->checker = ngx_http_core_post_access_phase; 
          ph->next = n; 
          ph++; 
        } 
        continue; 
      case ngx_http_try_files_phase:   //try_file阶段 
        if (cmcf->try_files)  
        { 
          ph->checker = ngx_http_core_try_files_phase; 
          n++; 
          ph++; 
        } 
        continue; 
      case ngx_http_content_phase:    //处理http请求内容阶段,大部分http模块最愿意介入的阶段 
        checker = ngx_http_core_content_phase; 
        break; 
      default: 
        //ngx_http_post_read_phase,  
        //ngx_http_preaccess_phase,  
        //ngx_http_log_phase三个阶段的checker方法 
        checker = ngx_http_core_generic_phase; 
    } 
    n += cmcf->phases[i].handlers.nelts; 
    //每一个阶段中所介入的所有http模块,同一个阶段中的所有http模块有唯一的checker回调, 
    //但handler回调每一个模块自己实现 
    for (j = cmcf->phases[i].handlers.nelts - 1; j >=0; j--)  
    { 
      ph->checker = checker;     
      ph->handler = h[j]; 
      ph->next = n; 
      ph++; 
    } 
  } 
  return ngx_ok; 
}

四、http阶段的checker回调

        在11个http处理阶段中,每一个阶段都有一个checker函数,当然有些阶段的checker函数是相同的。对每一个处理阶段,介入这个阶段的所有http模块都共用同一个checker函数。这些checker函数的作用是调度介入这个阶段的所有http模块的handler方法,或者切换到一下个http请求阶段。下面分析下ngx_http_post_read_phase,ngx_http_preaccess_phase,ngx_http_log_phase三个阶段的checker方法。

//ngx_http_post_read_phase,  
//ngx_http_preaccess_phase,  
//ngx_http_log_phase三个阶段的checker方法 
//返回值: ngx_ok,http框架会将控制权交还给epoll模块 
ngx_int_t ngx_http_core_generic_phase(ngx_http_request_t *r,ngx_http_phase_handler_t *ph) 
{ 
  ngx_int_t rc; 
  //调用http模块的处理方法,这样这个http模块就介入到了这个请求阶段 
  rc = ph->handler(r); 
  //跳转到下一个http阶段执行 
  if (rc == ngx_ok)         
  { 
    r->phase_handler = ph->next; 
    return ngx_again; 
  } 
   
  //执行本阶段的下一个http模块 
  if (rc == ngx_declined)      
  { 
    r->phase_handler++; 
    return ngx_again; 
  } 
 
  //表示刚执行的handler无法在这一次调度中处理完这一个阶段, 
  //需要多次调度才能完成 
  if (rc == ngx_again || rc == ngx_done)  
                       
  { 
    return ngx_ok; 
  } 
  //返回出错 
  /* rc == ngx_error || rc == ngx_http_... */ 
  ngx_http_finalize_request(r, rc); 
 
  return ngx_ok; 
}
本文内容来源于互联网,如有侵权请联系删除。
作者最新文章
编程开发
下一篇: 测试测试3333ww222
相关文章 更多
C++动态数组初始化怎么写?常用语句与代码示例
C++动态数组初始化怎么写?常用语句与代码示例

深入解析C++中动态数组的初始化机制,涵盖new操作符的不同用法、基本类型与类对象的初始化差异,以及为何在现代C++开发中应优先使用std::vector。

Nginx Windows版安装、启动与验证完整指南
Nginx Windows版安装、启动与验证完整指南

本教程针对Windows环境,详解Nginx稳定版(如1.24.0)的下载、解压、启动及验证流程。核心步骤包括:下载官方压缩包至英文目录,使用start nginx启动,通过localhost访问默认页面,并利用tasklist和nginx -t命令确认进程状态及配置语法。涵盖端口冲突排查、配置重载及停止服务的标准操作,适用于本地开发环境搭建与基础运维验证。

using namespace 使用中遇到的问题怎么解决
using namespace 使用中遇到的问题怎么解决

命名空间的基本概念与常见引入问题在C++等编程语言中,命名空间(namespace)是一种将代码标识符(如变量、函数、类名)封装在特定名称下的机制,其主要目的是避免命名冲突,尤其是在大型项目或使用多个第三方库时。使用“using namespace”指令可以将指定命名空间中的所有名称引入当前作用域,

c语言函数递归 实操经验总结:这些技巧很实用
c语言函数递归 实操经验总结:这些技巧很实用

理解递归的基本原理在C语言中,递归是一种函数调用自身的编程技术。要掌握它,首先需要理解其核心思想:将一个复杂的大问题,分解为一个或几个与原问题相似但规模更小的子问题,直到子问题足够简单,可以直接求解。这个过程通常包含两个关键部分:递归出口和递归体。递归出口定义了问题何时不再继续分解,即最简单、可直接

c语言函数递归 怎么选?常见方案对比分析
c语言函数递归 怎么选?常见方案对比分析

递归函数的基本概念与适用场景在C语言编程中,递归是一种函数调用自身的编程技巧。它并非适用于所有问题,但在处理某些具有自相似结构的问题时,能提供极其清晰和优雅的解决方案。递归的核心思想是将一个大规模问题分解为一个或多个同类型但规模更小的子问题,直到子问题简单到可以直接求解。典型的适用场景包括树形结构的

Objective-C 内存管理入门:从 alloc 到 dealloc 的生命周期详解
Objective-C 内存管理入门:从 alloc 到 dealloc 的生命周期详解

理解内存管理的基石在Objective-C的编程世界中,内存管理是开发者必须掌握的核心技能之一。它直接关系到应用的性能、稳定性与资源利用效率。与一些采用自动垃圾回收机制的语言不同,Objective-C在很长一段时间里,依赖一套基于引用计数的、需要开发者部分介入的管理规则。这套规则的核心思想是明确的

如何正确使用 dealloc 以避免 iOS 应用中的内存泄漏
如何正确使用 dealloc 以避免 iOS 应用中的内存泄漏

理解 dealloc 的角色与时机在 iOS 应用开发中,内存管理是保障应用性能与稳定性的基石。dealloc 方法是 Objective-C 中对象生命周期结束时的关键回调,它标志着对象即将被系统回收内存。正确理解其触发时机至关重要:当一个对象的引用计数降为零时,运行时系统会自动调用该对象的 de

深入理解 Objective-C 中的 dealloc 方法:内存管理核心机制
深入理解 Objective-C 中的 dealloc 方法:内存管理核心机制

内存管理的基石在Objective-C的世界里,内存管理是开发者必须掌握的核心技能之一。作为一门在手动引用计数(MRC)时代诞生的语言,Objective-C要求程序员对对象的生命周期有清晰的认识。dealloc方法正是这一生命周期中至关重要的终点站。它是一个实例方法,当对象的引用计数降为零时,系统

理解 native2ascii:Java 国际化开发中的字符编码工具
理解 native2ascii:Java 国际化开发中的字符编码工具

native2ascii 工具的基本定位在Ja va应用程序的国际化与本地化开发过程中,处理非拉丁字符集是一个常见且关键的环节。Ja va内部使用Unicode字符集来统一表示全球各种语言的文字,但其属性文件(.properties)在历史上要求使用ASCII编码,或者更准确地说,要求非ASCII字

如何使用 native2ascii 转换中文字符为 Unicode 转义序列
如何使用 native2ascii 转换中文字符为 Unicode 转义序列

理解 native2ascii 工具的基本用途在软件开发,特别是涉及国际化处理的场景中,开发者常常需要处理不同编码的文本资源。native2ascii 是 Ja va 开发工具包(JDK)中提供的一个命令行实用程序,其主要功能是将包含本地字符编码(非ASCII字符)的文件,转换为包含 Unicode

查看更多
精品专题 更多
装机必备
装机必备

正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

Windows
Windows

正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

macOS软件
macOS软件

正软商城macOS软件专区,精选适用于Mac电脑的办公、设计、影音、效率、开发和系统工具,提供软件功能介绍、macOS兼容版本、正版授权及购买下载服务。

Mac软件 更多
灵活计算器
灵活计算器
macOS/iOS/Android

灵活计算器是一款笔记式算数应用,支持实时计算、动态关联和云端同步功能。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

赤友清理大师
赤友清理大师
macOS

赤友清理大师是一款为 Mac 设计的智能清理优化工具,可精准扫描垃圾、大文件、重复文件等,释放磁盘空间。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

极度公式
极度公式
Windows/macOS/Linux

极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

WINDOWS 更多
Windows 10
Windows 10
Windows

Windows 10 是一款微软推出的经典操作系统,拥有硬件兼容性与多任务处理能力。它更偏向把系统状态查看和常用调节动作放在一起,适合需要持续观察和微调设备状态的场景。

极度公式
极度公式
Windows/macOS/Linux

极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

密码键盘
密码键盘
Windows/macOS/iOS/Android

密码键盘是一款兼具安全性与便捷性的高效密码管理器。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。