nginx 代码分析listen 和request请求的流程

本文深入探讨Nginx的listen机制,包括如何配置不同端口和服务名称,以及解析其内部实现原理,从ngx_http_add_listen函数到ngx_http_init_connection等关键步骤。

listen 针对于server的端口进行监听,server的配置可以比较复杂如:

server {
        listen       192.168.8.81:80;
        server_name  vae.test1.com *.test1.com;<!--hosts文件配置的地址-->
        index index.html;
        location /test1 {
           root /usr/local/nginx/html/;
        }
    }
    server {
        listen       127.0.0.1:80;
        server_name  vae.test1.com *.test1.com;<!--hosts文件配置的地址-->
        index index.html;
        location /test1 {
           root /usr/local/nginx/html/;
        }
    }

    server {
        listen       80;
        server_name  vae.test1.com;<!--hosts文件配置的地址-->
        index index.html;
        location /test1 {
           root /usr/local/nginx/html/;
        }
    }
server {
        listen       80;
        server_name  vae.test2.com;
        index index.html;
        location /test2 {
            root /usr/local/nginx/html/;
        }
    }
server {
        listen       8081;
        server_name  vae.test3.com;
        index index.html;
        location /test3 {
            root /usr/local/nginx/html/;
        }
    }

//完成之后需要重新启动下nginx命令:nginx -s reload,每次修改nginx.conf文件都需要重启

host配置:

#   127.0.0.1       localhost
#   ::1             localhost
192.168.8.81 vae.test1.com
192.168.8.81 vae.test2.com
192.168.8.81 vae.test3.com

ngx_http_add_listen 函数将解析http中的server的地址和端口,以port为粒度,添加addr(即1.1.1.1:80、2.2.2.2:80和80,会放在相同的cmcf->port[i]中)。
时序图和数据结构关系图:
这里写图片描述

那么在cmcf中的存储例子:

这里写图片描述

然后,就是在ngx_http_optimize_servers 中优化后加入cf->cycle->listening中(待完善)

接着在ngx_init_cycle –> ngx_open_listening_sockets中打开和监听所有的端口创建socket

在启动过程中的 ngx_worker_process_init中调用ngx_modules[event]->init_process(cycle) 初始化connections 并设置事件的回调 ,以ngx_epoll_moudle为例:

  • ngx_event_process_init中 listen流程
/* 初始化用来管理所有定时器的红黑树,用于事件的处理轮询 */
if (ngx_event_timer_init(cycle->log) == NGX_ERROR) {
    return NGX_ERROR;
 }
 ....
 //分配和初始化cycle->connections,设置每个c的读写事件event
cycle->connections = ngx_alloc(sizeof(ngx_connection_t) * cycle->connection_n, cycle->log);
c = cycle->connections;
cycle->read_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n,cycle->log);
cycle->write_events = ngx_alloc(sizeof(ngx_event_t) * cycle->connection_n,cycle->log);
do {
     i--;
     c[i].data = next;
     c[i].read = &cycle->read_events[i]; //关联c[i]和rev wev
     c[i].write = &cycle->write_events[i];
     c[i].fd = (ngx_socket_t) -1;
     next = &c[i];
 } while (i);
 //针对监听的每个端口,分配connecting,并设置回调函数,加入到epoll池中
 //c = ngx_cycle->free_connections;
 ls = cycle->listening.elts;
    for (i = 0; i < cycle->listening.nelts; i++) {
        c = ngx_get_connection(ls[i].fd, cycle->log);
        ....
        rev->handler = ngx_event_accept;
        if (ngx_add_event(rev, NGX_READ_EVENT, 0) == NGX_ERROR) {
            return NGX_ERROR;
        }
    }
  • ngx_event_accept请求的处理流程
    -这里涉及到ngx_use_accept_mutex的问题,用于解决多进程之间对请求的均衡处理(后续理解)
//epoll 中接收到请求后的回调,建立socket连接
void ngx_event_accept(ngx_event_t *ev)
{
    lc = ev->data;
    ls = lc->listening;
    s = accept(lc->fd, (struct sockaddr *) sa, &socklen);
    if (ngx_use_accept_mutex) {//进程竞争锁
        if (ngx_accept_mutex_held) {
            ngx_shmtx_unlock(&ngx_accept_mutex);
            ngx_accept_mutex_held = 0;
            }
            ngx_accept_disabled = 1;
    } else {
        ngx_add_timer(ev, ecf->accept_mutex_delay);
    }
    c = ngx_get_connection(s, ev->log);
}
//加入到epoll中等待read事件
 if (ngx_add_conn && (ngx_event_flags & NGX_USE_EPOLL_EVENT) == 0) {
        if (ngx_add_conn(c) == NGX_ERROR) {
            ngx_close_accepted_connection(c);
             return;
        }
 }
 //在ngx_http_add_listening时ls->handler = ngx_http_init_connection;设置回调
 ls->handler(c);//初始化这个对话connection
void ngx_http_init_connection(ngx_connection_t *c)
{//初始化一个request的connection
    //设置这个listen的addr_conf和conf_ctx,当数据可读时候用于解析
    hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t));
    if (hc == NULL) {
        ngx_http_close_connection(c);
        return;
    }
    c->data = hc;
    ....
    rev = c->read;//初始化read的epoll毁掉函数
    rev->handler = ngx_http_wait_request_handler;
    c->write->handler = ngx_http_empty_handler;
    //设置这个链接的超时timer
    ngx_add_timer(rev, c->listening->post_accept_timeout);
}

具体的时序图:

这里写图片描述

  • ngx_http_wait_request_handler

当epoll中此connection可读,调用ngx_http_wait_request_handler(ngx_event_t *rev)
关于rev和connection中的关键数据data的关系

//request 中的main_conf/srv_conf/loc_conf
ngx_http_wait_request_handler(ngx_event_t *rev)
{
    n = c->recv(c, b->last, size);
    ....    
    c->data = ngx_http_create_request(c); //这里创建request
    {
        r->main_conf = hc->conf_ctx->main_conf;
        r->srv_conf = hc->conf_ctx->srv_conf;
        r->loc_conf = hc->conf_ctx->loc_conf;
    }

}
  1. ngx_get_connection中 rev->data = c;

  2. ngx_http_init_connection(*c) -> ngx_http_wait_request_handler(rev) -> c->data = ngx_http_create_request(rev)

这里写图片描述

//设置request 的connect到recv中:
ngx_http_process_request_line(ngx_event_t *rev)
{
    ngx_connection_t    *c;
    ngx_http_request_t  *r;

    c = rev->data; //ngx_get_connection中 rev->data = c;
    r = c->data;  
    //这里的data设置:ngx_http_init_connection(*c) -> ngx_http_wait_request_handler(rev) -> c->data = ngx_http_create_request(rev)
}

最后当请求request过来的时候就能够从rev中获取到request并且里面有相应的ngx_http_core_loc_conf_t

ngx_http_core_find_location 回顾上一篇文章中,根据url和ngx_http_core_loc_conf_t 的三叉排序树或者正则数组 来获取正确模块的loc_conf。

最后就可以调用模块的content_handler函数传入req->locf 完成整请求(待完善)

后续会针对初始化event 和 请求的流程写详细一点,这里只是从listen出发后续展开。

<think>我们正在解决Nginx代理请求时出现的"Invalid CORS request"错误。根据引用内容,核心问题在于跨域请求配置不正确。以下是针对此问题的系统化解决方案: ### 核心问题分析 1. **Origin头不匹配**:浏览器发送的`Origin`头(如`http://yumbo.top`)与Nginx代理后的实际地址(如`http://yumbo.top:8081`)在端口或协议上不一致,导致CORS校验失败[^1]。 2. **预检请求(OPTIONS)处理不当**:对于非简单请求(如POST),浏览器会先发送OPTIONS预检请求,若Nginx未正确处理,则会导致403错误[^1]。 3. **Host头传递错误**:Nginx代理配置中`proxy_set_header Host`设置不当,导致后端接收的Host信息与预期不符[^1]。 ### 解决方案 #### 1. 基础CORS配置(解决90%问题) ```nginx server { listen 80; server_name your_domain.com; location /api/ { # 1. 代理到后端服务 proxy_pass http://backend_server:port; # 2. 关键头设置:传递原始HostOrigin proxy_set_header Host $http_host; # 保留客户端原始Host(含端口) proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Origin $http_origin; # 传递原始Origin[^1] # 3. CORS响应头配置 add_header 'Access-Control-Allow-Origin' '$http_origin' always; add_header 'Access-Control-Allow-Credentials' 'true' always; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always; add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type' always; # 4. 显式处理OPTIONS预检请求 if ($request_method = 'OPTIONS') { add_header 'Access-Control-Max-Age' 1728000; add_header 'Content-Type' 'text/plain; charset=utf-8'; add_header 'Content-Length' 0; return 204; } } } ``` #### 2. 特殊场景处理 **场景1:端口不一致问题** 若前端域名`http://yumbo.top`访问后端`http://yumbo.top:8081`,需强制统一Origin: ```nginx proxy_set_header Origin "http://yumbo.top"; # 覆盖为不带端口的域名 ``` **场景2:POST请求403而GET正常** 确保OPTIONS请求被正确处理: ```nginx location /api/ { ... # 显式处理OPTIONS方法(需在location块内) if ($request_method = 'OPTIONS') { add_header 'Access-Control-Allow-Origin' '$http_origin'; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range'; add_header 'Access-Control-Max-Age' 1728000; add_header 'Content-Type' 'text/plain; charset=utf-8'; add_header 'Content-Length' 0; return 204; } } ``` #### 3. Host头最佳实践 | 配置方式 | 示例 | 适用场景 | |---------|------|---------| | `$host` | `yumbo.top` | 后端不依赖端口 | | `$host:$proxy_port` | `yumbo.top:8081` | 需明确端口 | | `$http_host` | `yumbo.top:8080` | 保留客户端原始端口[^1] | --- ### 调试技巧 1. **检查请求头** 在Nginx中添加日志记录: ```nginx log_format cors_debug '$remote_addr - $http_origin - $http_host - "$request"'; access_log /var/log/nginx/cors.log cors_debug; ``` 2. **手动测试预检请求** 使用curl验证OPTIONS响应: ```bash curl -X OPTIONS -H "Origin: http://your-frontend.com" \ -H "Access-Control-Request-Method: POST" \ -I http://your-nginx-proxy/api ``` 3. **验证响应头** 确保响应包含: ``` Access-Control-Allow-Origin: http://your-frontend.com Access-Control-Allow-Methods: POST ``` --- ### 常见陷阱 1. **`add_header`在if块中不生效** 添加`always`参数: ```nginx add_header 'Header-Name' 'Value' always; ``` 2. **携带Cookie时的CORS配置** 必须指定具体域名(不能用`*`)并开启凭证: ```nginx add_header 'Access-Control-Allow-Origin' 'http://your-frontend.com' always; add_header 'Access-Control-Allow-Credentials' 'true' always; ``` > **关键提示**:95%的"Invalid CORS request"错误通过正确设置`Origin``Host`头解决[^1],剩余5%需检查OPTIONS请求处理。 §§相关问题§§ 1. 如何让Nginx支持多个域名的CORS配置? 2. 在Kubernetes Ingress中如何配置CORS? 3. 当Nginx作为多层代理时如何传递CORS头? 4. 如何解决CORS请求携带Cookie的问题?</think>### 解决Nginx代理请求出现"Invalid CORS request"错误的完整方案 #### 核心问题分析 1. **Origin头不匹配** 浏览器发送的`Origin`头(如`http://yumbo.top`)与Nginx代理后的实际地址(如`http://yumbo.top:8081`)在端口或协议上不一致[^1]。 2. **预检请求处理缺失** POST等非简单请求会触发OPTIONS预检请求,但Nginx未正确处理[^1]。 3. **Host头传递错误** 代理配置中`proxy_set_header Host`设置不当,导致后端接收错误的主机信息[^1]。 --- ### 完整解决方案 #### 1. 基础CORS配置(解决90%问题) ```nginx server { listen 80; server_name your_domain.com; location /api/ { # 1. 代理到后端服务 proxy_pass http://backend_server:port; # 2. 关键头设置 proxy_set_header Host $http_host; # 保留客户端原始Host(含端口) proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Origin $http_origin; # 传递原始Origin[^1] # 3. CORS响应头配置 add_header 'Access-Control-Allow-Origin' '$http_origin' always; add_header 'Access-Control-Allow-Credentials' 'true' always; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE' always; add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type' always; # 4. 显式处理OPTIONS预检请求 if ($request_method = 'OPTIONS') { add_header 'Access-Control-Max-Age' 1728000; add_header 'Content-Type' 'text/plain; charset=utf-8'; add_header 'Content-Length' 0; return 204; } } } ``` #### 2. 特殊场景处理 **场景1:端口不一致问题** ```nginx # 强制统一Origin(解决端口差异) proxy_set_header Origin "http://yumbo.top"; # 覆盖为不带端口的域名 ``` **场景2:POST请求403而GET正常** ```nginx # 在OPTIONS块中添加完整CORS头 if ($request_method = 'OPTIONS') { add_header 'Access-Control-Allow-Origin' '$http_origin'; add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS'; ... # 其他头 return 204; } ``` **场景3:部分接口正常(如login)而其他失败** ```nginx # 确保所有接口路径都应用CORS配置 location ~ ^/(api|auth|data)/ { ... # 应用相同的CORS配置 } ``` #### 3. Host头最佳实践 | 配置方式 | 结果示例 | 适用场景 | |---------|---------|---------| | `proxy_set_header Host $host;` | `yumbo.top` | 后端不依赖端口 | | `proxy_set_header Host $host:$proxy_port;` | `yumbo.top:8081` | 需明确端口 | | `proxy_set_header Host $http_host;` | `yumbo.top:8080` | 保留客户端原始端口[^1] | --- ### 调试步骤 1. **检查请求头** ```bash curl -I -H "Origin: http://your-frontend.com" http://your-nginx-proxy/api ``` 2. **验证预检请求** ```bash curl -X OPTIONS -H "Origin: http://your-frontend.com" \ -H "Access-Control-Request-Method: POST" \ http://your-nginx-proxy/api ``` 3. **查看Nginx日志** 添加调试日志格式: ```nginx log_format cors_debug '$remote_addr - $http_origin - $http_host - "$request"'; access_log /var/log/nginx/cors.log cors_debug; ``` --- ### 常见问题解决 1. **`add_header`不生效** 添加`always`参数: ```nginx add_header 'Access-Control-Allow-Origin' '$http_origin' always; ``` 2. **携带Cookie的问题** ```nginx add_header 'Access-Control-Allow-Credentials' 'true' always; # 必须指定具体域名,不能用* add_header 'Access-Control-Allow-Origin' 'http://your-frontend.com' always; ``` 3. **SpringBoot后端已配置CORS仍失败** Nginx配置优先级高于后端,需在Nginx层统一处理跨域。 > **关键提示**:通过正确设置`proxy_set_header Origin`显式处理`OPTIONS`请求,可解决95%的"Invalid CORS request"错误[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值