目錄
2.2 解析HTTP请求
首頁 後端開發 php教程 nginx HTTP處理流程的淺析

nginx HTTP處理流程的淺析

Oct 16, 2018 am 11:51 AM
nginx

這篇文章帶給大家的內容是關於nginx HTTP處理流程的淺析,有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。

1.初始化伺服器

server指令用於設定virtual server,我們通常會在一台機器設定多個virtual server,監聽不同連接埠號,映射到不同檔案目錄;nginx解析使用者配置,在所有連接埠建立socket並啟動監聽。

nginx解析設定檔是由各個模組分擔處理的,每個模組註冊並處理自己關心的配置,透過模組結構體ngx_module_t的欄位ngx_command_t *commands實作;

例如ngx_http_module是一個核心模組,其commands欄位定義如下:

struct ngx_command_s {
    ngx_str_t             name;
    ngx_uint_t            type;
    char               *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
};
 
static ngx_command_t  ngx_http_commands[] = {
 
    { ngx_string("http"),
      NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,
      ngx_http_block,
     },
};
登入後複製
  • name指令名稱,解析設定檔時依名稱能符合查找;

  • type指令類型,NGX_CONF_NOARGS該配置無參數,NGX_CONF_BLOCK該配置是一個配置區塊,NGX_MAIN_CONF表示配置可以出現在哪些位元(NGX_MAIN_CONF、NGX_HTTP_SRV_CONF、NGX_HTTP_LOC_CONF); ;

  • 可以看到解析http指令的處理函數為ngx_http_block,實作如下:

    static char * ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
    {
        //解析main配置
        //解析server配置
        //解析location配置
     
        //初始化HTTP处理流程所需的handler
     
        //初始化listening
        if (ngx_http_optimize_servers(cf, cmcf, cmcf->ports) != NGX_OK) {
            return NGX_CONF_ERROR;
        }
    }
    登入後複製
  • ngx_http_optimize_servers方法循環所有配置端口,建立ngx_listening_t對象,並將其添加到conf->cycle->listening(後續操作會遍歷此數組,建立socket並監聽)。方法主要操作如下圖:

注意到這裡設定了ngx_listening_t的handler為ngx_http_init_connection,當接收到socket連線要求時,會呼叫此handler處理。

nginx HTTP處理流程的淺析那麼什麼時候啟動監聽呢?全域搜尋關鍵字cycle->listening可以找到。 main方法會呼叫ngx_init_cycle,其完成了伺服器初始化的大部分工作,其中就包含啟動監聽(ngx_open_listening_sockets)。

假設nginx使用epoll處理所有socket事件,什麼時候將監聽事件加入epoll呢?全域搜尋關鍵字cycle->listening可以找到。 ngx_event_core_module模組是事件處理核心模組,初始化此模組時會執行ngx_event_process_init函數,其中將監聽事件新增至epoll。

static ngx_int_t ngx_event_process_init(ngx_cycle_t *cycle)
{
    ls = cycle->listening.elts;
    for (i = 0; i listening.nelts; i++) {
        //设置读事件处理handler
        rev->handler = ngx_event_accept;
         
        ngx_add_event(rev, NGX_READ_EVENT, 0);
    }
}
登入後複製

注意到接收到客戶端socket連線請求事件的處理函式是ngx_event_accept。

2.HTTP請求解析

2.1 基礎結構體結構體ngx_connection_t儲存socket連接相關資訊;nginx預先建立若干個ngx_connection_t對象,儲存在全域變數ngx_cycle->free_connections,稱為連接池;當新生成socket時,會嘗試從連接池中取得空閒connection連接,如果取得失敗,則會直接關閉此socket。

指令worker_connections用於配置連接池最大連接數目,配置在events指令塊中,由ngx_event_core_module解析。

vents {
   use epoll;
   worker_connections  60000;
}
登入後複製

當nginx作為HTTP伺服器時,最大客戶端數目maxClient=worker_processes

worker_connections/2;當nginx作為反向代理伺服器時,最大客戶端數目maxClient=worker_processes

worker_connections/4 。其worker_processes為使用者配置的worker進程數目。

結構體ngx_connection_t定義如下:

struct ngx_connection_s {
    //空闲连接池中,data指向下一个连接,形成链表;取出来使用时,data指向请求结构体ngx_http_request_s
    void               *data;
    //读写事件结构体,两个关键字段:handler处理函数、timer定时器
    ngx_event_t        *read;
    ngx_event_t        *write;
 
    ngx_socket_t        fd;   //socket fd
 
    ngx_recv_pt         recv; //socket接收数据函数指针
    ngx_send_pt         send; //socket发送数据函数指针
 
    ngx_buf_t          *buffer; //输入缓冲区
 
    struct sockaddr    *sockaddr; //客户端地址
    socklen_t           socklen;
 
    ngx_listening_t    *listening; //监听的ngx_listening_t对象
 
    struct sockaddr    *local_sockaddr; //本地地址
    socklen_t           local_socklen;
 
    …………
}
登入後複製
結構體ngx_http_request_t儲存整個HTTP請求處理流程所需的所有信息,欄位非常多,這裡只進行簡要說明:

struct ngx_http_request_s {
 
    ngx_connection_t                 *connection;
 
    //读写事件处理handler
    ngx_http_event_handler_pt         read_event_handler;
    ngx_http_event_handler_pt         write_event_handler;
 
    //请求头缓冲区
    ngx_buf_t                        *header_in;
 
    //解析后的请求头
    ngx_http_headers_in_t             headers_in;
     
    //请求体结构体
    ngx_http_request_body_t          *request_body;
 
    //请求行
    ngx_str_t                         request_line;
    //解析后请求行若干字段
    ngx_uint_t                        method;
    ngx_uint_t                        http_version;
    ngx_str_t                         uri;
    ngx_str_t                         args;
 
    …………
}
登入後複製

請求行與請求體解析相對比較簡單,這裡重點講述請求頭的解析,解析後的請求頭資訊都儲存在ngx_http_headers_in_t結構體中。

ngx_http_request.c檔案中定義了所有的HTTP頭部,儲存在ngx_http_headers_in數組,數組的每個元素是一個ngx_http_header_t結構體,主要包含三個字段,頭部名稱、頭部解析後字段儲存在ngx_http_headers_in_t的偏移量,解析頭部的處理函數。

ngx_http_header_t  ngx_http_headers_in[] = {
    { ngx_string("Host"), offsetof(ngx_http_headers_in_t, host),
                 ngx_http_process_host },
 
    { ngx_string("Connection"), offsetof(ngx_http_headers_in_t, connection),
                 ngx_http_process_connection },
    …………
}
 
typedef struct {
    ngx_str_t                         name;
    ngx_uint_t                        offset;
    ngx_http_header_handler_pt        handler;
} ngx_http_header_t;
登入後複製

解析請求頭時,從ngx_http_headers_in數組中尋找請求頭ngx_http_header_t對象,呼叫處理函數handler,儲存到r->headers_in對應欄位。以解析Connection頭部為例,ngx_http_process_connection實作如下:

static ngx_int_t ngx_http_process_connection(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset)
{
    if (ngx_strcasestrn(h->value.data, "close", 5 - 1)) {
        r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE;
 
    } else if (ngx_strcasestrn(h->value.data, "keep-alive", 10 - 1)) {
        r->headers_in.connection_type = NGX_HTTP_CONNECTION_KEEP_ALIVE;
    }
 
    return NGX_OK;
}
登入後複製

輸入參數offset在此並沒有什麼作用。注意到第二個輸入參數ngx_table_elt_t,儲存了當前請求頭的鍵值對資訊:

typedef struct {
    ngx_uint_t        hash;  //请求头key的hash值
    ngx_str_t         key;
    ngx_str_t         value;
    u_char           *lowcase_key;  //请求头key转为小写字符串(可以看到HTTP请求头解析时key不区分大小写)
} ngx_table_elt_t;
登入後複製

再思考一個問題,從ngx_http_headers_in數組中查找請求頭對應ngx_http_header_t物件時,需要遍歷,每個元素都需要進行字串比較,效率低。因此nginx將ngx_http_headers_in數組轉換為哈希表,哈希表的鍵即為請求頭的key,方法ngx_http_init_headers_in_hash實現了數組到哈希表的轉換,轉換後的哈希表存儲在cmcf->headers_in_hash字段。

nginx HTTP處理流程的淺析

2.2 解析HTTP请求

第1节提到,在创建socket启动监听时,会添加可读事件到epoll,事件处理函数为ngx_event_accept,用于接收socket连接,分配connection连接,并调用ngx_listening_t对象的处理函数(ngx_http_init_connection)。

void ngx_event_accept(ngx_event_t *ev)
{
    s = accept4(lc->fd, (struct sockaddr *) sa, &socklen, SOCK_NONBLOCK);
 
    //客户端socket连接成功时,都需要分配connection连接,如果分配失败则会直接关闭此socket。
    //而每个worker进程连接池的最大连接数目是固定的,当不存在空闲连接时,此worker进程accept的所有socket都会被拒绝;
    //多个worker进程通过竞争执行epoll_wait;而当ngx_accept_disabled大于0时,会直接放弃此次竞争,同时ngx_accept_disabled减1。
    //以此实现,当worker进程的空闲连接过少时,减少其竞争epoll_wait次数
    ngx_accept_disabled = ngx_cycle->connection_n / 8 - ngx_cycle->free_connection_n;
 
    c = ngx_get_connection(s, ev->log);
 
    ls->handler(c);
}
登入後複製

socket连接成功后,nginx会等待客户端发送HTTP请求,默认会有60秒的超时时间,即60秒内没有接收到客户端请求时,断开此连接,打印错误日志。函数ngx_http_init_connection用于设置读事件处理函数,以及超时定时器。

void ngx_http_init_connection(ngx_connection_t *c)
{
    c->read = ngx_http_wait_request_handler;
    c->write->handler = ngx_http_empty_handler;
 
    ngx_add_timer(rev, c->listening->post_accept_timeout);
}
登入後複製

全局搜索post_accept_timeout字段,可以查找到设置此超时时间的配置指令,client_header_timeout,其可以在http、server指令块中配置。

函数ngx_http_wait_request_handler为解析HTTP请求的入口函数,实现如下:

static void ngx_http_wait_request_handler(ngx_event_t *rev)
{
    //读事件已经超时
    if (rev->timedout) {
        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
        ngx_http_close_connection(c);
        return;
    }
 
    size = cscf->client_header_buffer_size;   //client_header_buffer_size指令用于配置接收请求头缓冲区大小
    b = c->buffer;
 
    n = c->recv(c, b->last, size);
 
    //创建请求对象ngx_http_request_t,HTTP请求整个处理过程都有用;
    c->data = ngx_http_create_request(c);
 
    rev->handler = ngx_http_process_request_line; //设置读事件处理函数(此次请求行可能没有读取完)
    ngx_http_process_request_line(rev);
}
登入後複製

函数ngx_http_create_request创建并初始化ngx_http_request_t对象,注意这赋值语句r->header_in =c->buffer。

解析请求行与请求头的代码较为繁琐,终点在于读取socket数据,解析字符串,这里不做详述。HTTP请求解析过程主要函数调用如下图所示:

nginx HTTP處理流程的淺析

注意,解析完成请求行与请求头,nginx就开始处理HTTP请求,并没有等到解析完请求体再处理。处理请求入口为ngx_http_process_request。

3.处理HTTP请求

3.1 HTTP请求处理的11个阶段

nginx将HTTP请求处理流程分为11个阶段,绝大多数HTTP模块都会将自己的handler添加到某个阶段(将handler添加到全局唯一的数组phases中),注意其中有4个阶段不能添加自定义handler,nginx处理HTTP请求时会挨个调用每个阶段的handler;

typedef enum {
    NGX_HTTP_POST_READ_PHASE = 0, //第一个阶段,目前只有realip模块会注册handler,但是该模块默认不会运行(nginx作为代理服务器时有用,后端以此获取客户端原始ip)
  
    NGX_HTTP_SERVER_REWRITE_PHASE,  //server块中配置了rewrite指令,重写url
  
    NGX_HTTP_FIND_CONFIG_PHASE,   //查找匹配的location配置;不能自定义handler;
    NGX_HTTP_REWRITE_PHASE,       //location块中配置了rewrite指令,重写url
    NGX_HTTP_POST_REWRITE_PHASE,  //检查是否发生了url重写,如果有,重新回到FIND_CONFIG阶段;不能自定义handler;
  
    NGX_HTTP_PREACCESS_PHASE,     //访问控制,比如限流模块会注册handler到此阶段
  
    NGX_HTTP_ACCESS_PHASE,        //访问权限控制,比如基于ip黑白名单的权限控制,基于用户名密码的权限控制等
    NGX_HTTP_POST_ACCESS_PHASE,   //根据访问权限控制阶段做相应处理;不能自定义handler;
  
    NGX_HTTP_TRY_FILES_PHASE,     //只有配置了try_files指令,才会有此阶段;不能自定义handler;
    NGX_HTTP_CONTENT_PHASE,       //内容产生阶段,返回响应给客户端
  
    NGX_HTTP_LOG_PHASE            //日志记录
} ngx_http_phases;
登入後複製

nginx使用结构体ngx_module_s表示一个模块,其中字段ctx,是一个指向模块上下文结构体的指针(上下文结构体的字段都是一些函数指针);nginx的HTTP模块上下文结构体大多都有字段postconfiguration,负责注册本模块的handler到某个处理阶段。11个阶段在解析完成http配置块指令后初始化。

static char * ngx_http_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    //解析http配置块
 
    //初始化11个阶段的phases数组,注意多个模块可能注册到同一个阶段,因此phases是一个二维数组
    if (ngx_http_init_phases(cf, cmcf) != NGX_OK) {
        return NGX_CONF_ERROR;
    }
 
    //遍历索引HTTP模块,注册handler
    for (m = 0; ngx_modules[m]; m++) {
        if (ngx_modules[m]->type != NGX_HTTP_MODULE) {
            continue;
        }
 
        module = ngx_modules[m]->ctx;
 
        if (module->postconfiguration) {
            if (module->postconfiguration(cf) != NGX_OK) {
                return NGX_CONF_ERROR;
            }
        }
    }
 
    //将二维数组转换为一维数组,从而遍历执行数组所有handler
    if (ngx_http_init_phase_handlers(cf, cmcf) != NGX_OK) {
        return NGX_CONF_ERROR;
    }
}
登入後複製

以限流模块ngx_http_limit_req_module模块为例,postconfiguration方法简单实现如下:

static ngx_int_t ngx_http_limit_req_init(ngx_conf_t *cf)
{
    h = ngx_array_push(&cmcf->phases[NGX_HTTP_PREACCESS_PHASE].handlers);
     
    *h = ngx_http_limit_req_handler;  //ngx_http_limit_req_module模块的限流方法;nginx处理HTTP请求时,都会调用此方法判断应该继续执行还是拒绝请求
  
    return NGX_OK;
}
登入後複製

GDB调试,断点到ngx_http_block方法执行所有HTTP模块注册handler之后,打印phases数组

p cmcf->phases[*].handlers
p *(ngx_http_handler_pt*)cmcf->phases[*].handlers.elts
登入後複製

11个阶段注册的handler如下图所示:

nginx HTTP處理流程的淺析

3.2 11个阶段初始化

上面提到HTTP的11个处理阶段handler存储在phases数组,但由于多个模块可能注册handler到同一个阶段,使得phases是一个二维数组,因此需要转换为一维数组,转换后存储在cmcf->phase_engine字段,phase_engine的类型为ngx_http_phase_engine_t,定义如下:

typedef struct {
    ngx_http_phase_handler_t  *handlers;   //一维数组,存储所有handler
    ngx_uint_t                 server_rewrite_index;  //记录NGX_HTTP_SERVER_REWRITE_PHASE阶段handler的索引值
    ngx_uint_t                 location_rewrite_index; //记录NGX_HTTP_REWRITE_PHASE阶段handler的索引值
} ngx_http_phase_engine_t;
 
struct ngx_http_phase_handler_t {
    ngx_http_phase_handler_pt  checker;  //执行handler之前的校验函数
    ngx_http_handler_pt        handler;
    ngx_uint_t                 next;   //下一个待执行handler的索引(通过next实现handler跳转执行)
};
 
//cheker函数指针类型定义
typedef ngx_int_t (*ngx_http_phase_handler_pt)(ngx_http_request_t *r, ngx_http_phase_handler_t *ph);
//handler函数指针类型定义
typedef ngx_int_t (*ngx_http_handler_pt)(ngx_http_request_t *r);
登入後複製

数组转换函数ngx_http_init_phase_handlers实现如下:

static ngx_int_t ngx_http_init_phase_handlers(ngx_conf_t *cf, ngx_http_core_main_conf_t *cmcf)
{
    use_rewrite = cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers.nelts ? 1 : 0;
    use_access = cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers.nelts ? 1 : 0;
     
    n = use_rewrite + use_access + cmcf->try_files + 1 /* find config phase */; //至少有4个阶段,这4个阶段是上面说的不能注册handler的4个阶段
     
    //计算handler数目,分配空间
    for (i = 0; i phases[i].handlers.nelts;
    }
    ph = ngx_pcalloc(cf->pool, n * sizeof(ngx_http_phase_handler_t) + sizeof(void *));
 
    //遍历二维数组
    for (i = 0; i phases[i].handlers.elts;
 
        switch (i) {
 
        case NGX_HTTP_SERVER_REWRITE_PHASE:
            if (cmcf->phase_engine.server_rewrite_index == (ngx_uint_t) -1) {
                cmcf->phase_engine.server_rewrite_index = n;   //记录NGX_HTTP_SERVER_REWRITE_PHASE阶段handler的索引值
            }
            checker = ngx_http_core_rewrite_phase;
            break;
 
        case NGX_HTTP_FIND_CONFIG_PHASE:
            find_config_index = n;   //记录NGX_HTTP_FIND_CONFIG_PHASE阶段的索引,NGX_HTTP_POST_REWRITE_PHASE阶段可能会跳转回此阶段
            ph->checker = ngx_http_core_find_config_phase;
            n++;
            ph++;
            continue;   //进入下一个阶段NGX_HTTP_REWRITE_PHASE
  
        case NGX_HTTP_REWRITE_PHASE:
            if (cmcf->phase_engine.location_rewrite_index == (ngx_uint_t) -1) {
                cmcf->phase_engine.location_rewrite_index = n;   //记录NGX_HTTP_REWRITE_PHASE阶段handler的索引值
            }
            checker = ngx_http_core_rewrite_phase; 
            break;
 
        case NGX_HTTP_POST_REWRITE_PHASE:
            if (use_rewrite) {
                ph->checker = ngx_http_core_post_rewrite_phase;
                ph->next = find_config_index;
                n++;
                ph++;
            }
            continue;  //进入下一个阶段NGX_HTTP_ACCESS_PHASE
 
        case NGX_HTTP_ACCESS_PHASE:
            checker = ngx_http_core_access_phase;
            n++;
            break;
 
        case NGX_HTTP_POST_ACCESS_PHASE:
            if (use_access) {
                ph->checker = ngx_http_core_post_access_phase;
                ph->next = n;
                ph++;
            }
            continue;  //进入下一个阶段
 
        case NGX_HTTP_TRY_FILES_PHASE:
            if (cmcf->try_files) {
                ph->checker = ngx_http_core_try_files_phase;
                n++;
                ph++;
            }
            continue;
 
        case NGX_HTTP_CONTENT_PHASE:
            checker = ngx_http_core_content_phase;
            break;
 
        default:
            checker = ngx_http_core_generic_phase;
        }
 
        //n为下一个阶段第一个handler的索引
        n += cmcf->phases[i].handlers.nelts;
 
        //遍历当前阶段的所有handler
        for (j = cmcf->phases[i].handlers.nelts - 1; j >=0; j--) {
            ph->checker = checker;
            ph->handler = h[j];
            ph->next = n;
            ph++;
        }
    }
}
登入後複製

GDB打印出转换后的数组如下图所示,第一列是cheker字段,第二列是handler字段,箭头表示next跳转;图中有个返回的箭头,即NGX_HTTP_POST_REWRITE_PHASE阶段可能返回到NGX_HTTP_FIND_CONFIG_PHASE;原因在于只要NGX_HTTP_REWRITE_PHASE阶段产生了url重写,就需要重新查找匹配location。

nginx HTTP處理流程的淺析

3.3 处理HTTP请求

2.2节提到HTTP请求的处理入口函数是ngx_http_process_request,其主要调用ngx_http_core_run_phases实现11个阶段的执行流程;

ngx_http_core_run_phases遍历预先设置好的cmcf->phase_engine.handlers数组,调用其checker函数,逻辑如下:

void ngx_http_core_run_phases(ngx_http_request_t *r)
{
    ph = cmcf->phase_engine.handlers;
 
    //phase_handler初始为0,表示待处理handler的索引;cheker内部会根据ph->next字段修改phase_handler
    while (ph[r->phase_handler].checker) {
 
        rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]);
 
        if (rc == NGX_OK) {
            return;
        }
    }
}
登入後複製

checker内部就是调用handler,并设置下一步要执行handler的索引;比如说ngx_http_core_generic_phase实现如下:

ngx_int_t ngx_http_core_generic_phase(ngx_http_request_t *r, ngx_http_phase_handler_t *ph)
{
    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "rewrite phase: %ui", r->phase_handler);
    rc = ph->handler(r);
    if (rc == NGX_OK) {
        r->phase_handler = ph->next;
        return NGX_AGAIN;
    }
}
登入後複製

3.4 内容产生阶段

内容产生阶段NGX_HTTP_CONTENT_PHASE是HTTP请求处理的第10个阶段,一般情况有3个模块注册handler到此阶段:ngx_http_static_module、ngx_http_autoindex_module和ngx_http_index_module。

但是当我们配置了proxy_pass和fastcgi_pass时,情况会有所不同;

使用proxy_pass配置上游时,ngx_http_proxy_module模块会设置其处理函数到配置类conf;使用fastcgi_pass配置时,ngx_http_fastcgi_module会设置其处理函数到配置类conf。例如:

static char * ngx_http_fastcgi_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_core_loc_conf_t   *clcf;
    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
 
    clcf->handler = ngx_http_fastcgi_handler;
}
登入後複製

阶段NGX_HTTP_FIND_CONFIG_PHASE查找匹配的location,并获取此ngx_http_core_loc_conf_t对象,将其handler赋值给ngx_http_request_t对象的content_handler字段(内容产生处理函数)。

而在执行内容产生阶段的checker函数时,会执行content_handler指向的函数;查看ngx_http_core_content_phase函数实现(内容产生阶段的checker函数):

ngx_int_t ngx_http_core_content_phase(ngx_http_request_t *r,
    ngx_http_phase_handler_t *ph)
{
    if (r->content_handler) {  //如果请求对象的content_handler字段不为空,则调用
        r->write_event_handler = ngx_http_request_empty_handler;
        ngx_http_finalize_request(r, r->content_handler(r));
        return NGX_OK;
    }
 
    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "content phase: %ui", r->phase_handler);
 
    rc = ph->handler(r);  //否则执行内容产生阶段handler
}
登入後複製

总结

nginx处理HTTP请求的流程较为复杂,因此本文只是简单提供了一条线索:分析了nginx服务器启动监听的过程,HTTP请求的解析过程,11个阶段的初始化与调用过程。至于HTTP解析处理的详细流程,还需要读者去探索。

以上是nginx HTTP處理流程的淺析的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.聊天命令以及如何使用它們
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

怎麼查看nginx是否啟動 怎麼查看nginx是否啟動 Apr 14, 2025 pm 01:03 PM

確認 Nginx 是否啟動的方法:1. 使用命令行:systemctl status nginx(Linux/Unix)、netstat -ano | findstr 80(Windows);2. 檢查端口 80 是否開放;3. 查看系統日誌中 Nginx 啟動消息;4. 使用第三方工具,如 Nagios、Zabbix、Icinga。

linux怎麼查看nginx是否啟動 linux怎麼查看nginx是否啟動 Apr 14, 2025 pm 12:48 PM

在 Linux 中,使用以下命令檢查 Nginx 是否已啟動:systemctl status nginx根據命令輸出進行判斷:如果顯示 "Active: active (running)",則 Nginx 已啟動。如果顯示 "Active: inactive (dead)",則 Nginx 已停止。

linux怎麼啟動nginx linux怎麼啟動nginx Apr 14, 2025 pm 12:51 PM

在 Linux 中啟動 Nginx 的步驟:檢查 Nginx 是否已安裝。使用 systemctl start nginx 啟動 Nginx 服務。使用 systemctl enable nginx 啟用在系統啟動時自動啟動 Nginx。使用 systemctl status nginx 驗證啟動是否成功。在 Web 瀏覽器中訪問 http://localhost 查看默認歡迎頁面。

nginx在windows中怎麼配置 nginx在windows中怎麼配置 Apr 14, 2025 pm 12:57 PM

如何在 Windows 中配置 Nginx?安裝 Nginx 並創建虛擬主機配置。修改主配置文件並包含虛擬主機配置。啟動或重新加載 Nginx。測試配置並查看網站。選擇性啟用 SSL 並配置 SSL 證書。選擇性設置防火牆允許 80 和 443 端口流量。

怎麼啟動nginx服務器 怎麼啟動nginx服務器 Apr 14, 2025 pm 12:27 PM

啟動 Nginx 服務器需要按照不同操作系統採取不同的步驟:Linux/Unix 系統:安裝 Nginx 軟件包(例如使用 apt-get 或 yum)。使用 systemctl 啟動 Nginx 服務(例如 sudo systemctl start nginx)。 Windows 系統:下載並安裝 Windows 二進製文件。使用 nginx.exe 可執行文件啟動 Nginx(例如 nginx.exe -c conf\nginx.conf)。無論使用哪種操作系統,您都可以通過訪問服務器 IP

怎麼解決nginx跨域問題 怎麼解決nginx跨域問題 Apr 14, 2025 am 10:15 AM

解決 Nginx 跨域問題有兩種方法:修改跨域響應頭:添加指令以允許跨域請求,指定允許的方法和頭,以及設置緩存時間。使用 CORS 模塊:啟用模塊並配置 CORS 規則,允許跨域請求、方法、頭和設置緩存時間。

nginx403怎麼解決 nginx403怎麼解決 Apr 14, 2025 am 10:33 AM

如何解決 Nginx 403 Forbidden 錯誤?檢查文件或目錄權限;2. 檢查 .htaccess 文件;3. 檢查 Nginx 配置文件;4. 重啟 Nginx。其他可能原因還包括防火牆規則、SELinux 設置或應用程序問題。

怎麼把nginx訪問地址設置成服務器ip 怎麼把nginx訪問地址設置成服務器ip Apr 14, 2025 am 11:36 AM

要在 Nginx 中將訪問地址設置為服務器 IP,請:配置服務器塊,設置監聽地址(如:listen 192.168.1.10:80)設置服務器名稱(如:server_name example.com www.example.com),或將其留空以訪問服務器 IP保存並重新加載 Nginx 以應用更改

See all articles