Nginx(四):http服务器静态文件查找的实现

2023-05-16

上一篇nginx的文章中,我们理解了整个http正向代理的运行流程原理,主要就是事件机制接入,header解析,body解析,然后遍历各种checker,直到处理成功为止。

  我们以访问一个普通文件为例,nginx到底是如何找到这个文件并返回信息的呢?它会不会有些什么限制呢?

  按我们自己的理解,它应该是uri栏里截取出相应的路径,然后对应到root下,查找到相应文件,返回即可。那么,它又是如何处理html和其他的各种不同格式的文件呢?

  就让我们一起来探秘nginx文件的查找实现吧!

0. nginx 静态文件配置

  要配置静态文件处理,只需在http server中配置root路径即可。(当然了,你可以根据前缀配置许多不同的root) 

http {
    include       mime.types;
    default_type  application/octet-stream;

    #log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
    #                  '$status $body_bytes_sent "$http_referer" '
    #                  '"$http_user_agent" "$http_x_forwarded_for"';

    #access_log  logs/access.log  main;

    sendfile        on;
    #tcp_nopush     on;

    #keepalive_timeout  0;
    keepalive_timeout  65;

    #gzip  on;

    server {
        listen       8085;
        server_name  localhost;

        #charset koi8-r;

        #access_log  logs/host.access.log  main;
        
        location /hello {
            root   /www/hello;
            index  index.html index.htm;
        }

        location / {
            root   html;
            index  index.html index.htm;
        }
    }
    # 后续可以添加无数个server 扩展
}

配置简单吧,实际核心就两三行代码搞定:监听端口号 listen、访问域名 server_name、服务器根路径 root。明显这是nginx成功的原因之一。

  本文要讨论的场景是,如果我访问 http://localhost:8085/1.txt?d=xxx, nginx将如何干成这件事?

1. checker的遍历回顾

  我们先来回顾下,nginx是如何遍历各个checker的吧!

// http/ngx_http_core_module.c
// 响应客户端操作, 多阶段式操作
void
ngx_http_core_run_phases(ngx_http_request_t *r)
{
    ngx_int_t                   rc;
    ngx_http_phase_handler_t   *ph;
    ngx_http_core_main_conf_t  *cmcf;

    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);

    ph = cmcf->phase_engine.handlers;
    // 依次调用各 checker, 直到有响应 OK 的checker为止
    while (ph[r->phase_handler].checker) {
        // 每次调用 checker 之后, 内部都会将 r->phase_handler++, 即迭代下一个
        // 此处的 checker 非常之多, 是在各模块启动时, 自动向 ngx_http_core_module.main_conf 中进行注册的
        /** 
         * 定义如下:
        typedef enum {
            NGX_HTTP_POST_READ_PHASE = 0,

            NGX_HTTP_SERVER_REWRITE_PHASE,

            NGX_HTTP_FIND_CONFIG_PHASE,
            NGX_HTTP_REWRITE_PHASE,
            NGX_HTTP_POST_REWRITE_PHASE,

            NGX_HTTP_PREACCESS_PHASE,

            NGX_HTTP_ACCESS_PHASE,
            NGX_HTTP_POST_ACCESS_PHASE,

            NGX_HTTP_PRECONTENT_PHASE,

            NGX_HTTP_CONTENT_PHASE,

            NGX_HTTP_LOG_PHASE
        } ngx_http_phases;

        // 注册方式
        cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
        h = ngx_array_push(&cmcf->phases[NGX_HTTP_ACCESS_PHASE].handlers);
        *h = ngx_http_access_handler;
        */
        // 将请求信息和 handler 本身传入调用(不是面向, 只能这么做了)
        rc = ph[r->phase_handler].checker(r, &ph[r->phase_handler]);
        // 只要有一个处理成功, 则后续不再调用
        if (rc == NGX_OK) {
            return;
        }
    }
}

可以说,它的各checker是非常复杂的,各模块都可以向其中注册处理器。这也是nginx灵活性的体现。不过我们不想关注太多。

  文件的查找是在 static_module 中完成的,我们只需关注这个即可。

2. 静态文件模块的注册

  静态文件模块主要就是负责静态文件的查找处理。几乎所有的http处理模块,都是先进行注册,然后再被调用的过程。static_module 自然不例外。

// http/modules/ngx_http_static_handler.c    
static ngx_int_t
ngx_http_static_init(ngx_conf_t *cf)
{
    ngx_http_handler_pt        *h;
    ngx_http_core_main_conf_t  *cmcf;

    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
    // 获取http_core_module的配置
    // 将ngx_http_static_handler 添加到 NGX_HTTP_CONTENT_PHASE 的handlers中
    h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
    if (h == NULL) {
        return NGX_ERROR;
    }

    *h = ngx_http_static_handler;

    return NGX_OK;
}

// 下面是一些nginx的模块暴露规范,只有这样,这个模块才会被接入到整个系统中
static ngx_http_module_t  ngx_http_static_module_ctx = {
    NULL,                                  /* preconfiguration */
    ngx_http_static_init,                  /* postconfiguration */

    NULL,                                  /* create main configuration */
    NULL,                                  /* init main configuration */

    NULL,                                  /* create server configuration */
    NULL,                                  /* merge server configuration */

    NULL,                                  /* create location configuration */
    NULL                                   /* merge location configuration */
};


ngx_module_t  ngx_http_static_module = {
    NGX_MODULE_V1,
    &ngx_http_static_module_ctx,           /* module context */
    NULL,                                  /* module directives */
    NGX_HTTP_MODULE,                       /* module type */
    NULL,                                  /* init master */
    NULL,                                  /* init module */
    NULL,                                  /* init process */
    NULL,                                  /* init thread */
    NULL,                                  /* exit thread */
    NULL,                                  /* exit process */
    NULL,                                  /* exit master */
    NGX_MODULE_V1_PADDING
};

3. 文件查找实现

  经过前面的模块注册到 NGX_HTTP_CONTENT_PHASE 中,就会被nginx调用。前提是没有其他更合适的处理器的时候。而因为 static_module 是在 NGX_HTTP_CONTENT_PHASE 中,所以都会走content的处理器:

// http/ngx_http_core_module.c    
ngx_int_t
ngx_http_core_content_phase(ngx_http_request_t *r,
    ngx_http_phase_handler_t *ph)
{
    size_t     root;
    ngx_int_t  rc;
    ngx_str_t  path;

    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;
    }

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "content phase: %ui", r->phase_handler);

    rc = ph->handler(r);
    // 处理成功,则返回ok
    if (rc != NGX_DECLINED) {
        ngx_http_finalize_request(r, rc);
        return NGX_OK;
    }

    /* rc == NGX_DECLINED */

    ph++;

    if (ph->checker) {
        r->phase_handler++;
        return NGX_AGAIN;
    }

    /* no content handler was found */

    if (r->uri.data[r->uri.len - 1] == '/') {

        if (ngx_http_map_uri_to_path(r, &path, &root, 0) != NULL) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "directory index of \"%s\" is forbidden", path.data);
        }

        ngx_http_finalize_request(r, NGX_HTTP_FORBIDDEN);
        return NGX_OK;
    }

    ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "no handler found");

    ngx_http_finalize_request(r, NGX_HTTP_NOT_FOUND);
    return NGX_OK;
}


// http/modules/ngx_http_static_handler.c
static ngx_int_t
ngx_http_static_handler(ngx_http_request_t *r)
{
    u_char                    *last, *location;
    size_t                     root, len;
    ngx_str_t                  path;
    ngx_int_t                  rc;
    ngx_uint_t                 level;
    ngx_log_t                 *log;
    ngx_buf_t                 *b;
    ngx_chain_t                out;
    ngx_open_file_info_t       of;
    ngx_http_core_loc_conf_t  *clcf;
    // 仅支持 get/head/post 方法进行静态文件处理
    if (!(r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD|NGX_HTTP_POST))) {
        return NGX_HTTP_NOT_ALLOWED;
    }
    // 要求uri不能以'/'结尾,否则走其他协议
    if (r->uri.data[r->uri.len - 1] == '/') {
        return NGX_DECLINED;
    }

    log = r->connection->log;

    /*
     * ngx_http_map_uri_to_path() allocates memory for terminating '\0'
     * so we do not need to reserve memory for '/' for possible redirect
     */
    // 解析文件路径到path中
    last = ngx_http_map_uri_to_path(r, &path, &root, 0);
    if (last == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    path.len = last - path.data;

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0,
                   "http filename: \"%s\"", path.data);

    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
    // 初始化文件实例
    ngx_memzero(&of, sizeof(ngx_open_file_info_t));

    of.read_ahead = clcf->read_ahead;
    of.directio = clcf->directio;
    of.valid = clcf->open_file_cache_valid;
    of.min_uses = clcf->open_file_cache_min_uses;
    of.errors = clcf->open_file_cache_errors;
    of.events = clcf->open_file_cache_events;

    if (ngx_http_set_disable_symlinks(r, clcf, &path, &of) != NGX_OK) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    if (ngx_open_cached_file(clcf->open_file_cache, &path, &of, r->pool)
        != NGX_OK)
    {
        switch (of.err) {

        case 0:
            return NGX_HTTP_INTERNAL_SERVER_ERROR;

        case NGX_ENOENT:
        case NGX_ENOTDIR:
        case NGX_ENAMETOOLONG:

            level = NGX_LOG_ERR;
            rc = NGX_HTTP_NOT_FOUND;
            break;

        case NGX_EACCES:
#if (NGX_HAVE_OPENAT)
        case NGX_EMLINK:
        case NGX_ELOOP:
#endif

            level = NGX_LOG_ERR;
            rc = NGX_HTTP_FORBIDDEN;
            break;

        default:

            level = NGX_LOG_CRIT;
            rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
            break;
        }

        if (rc != NGX_HTTP_NOT_FOUND || clcf->log_not_found) {
            ngx_log_error(level, log, of.err,
                          "%s \"%s\" failed", of.failed, path.data);
        }

        return rc;
    }

    r->root_tested = !r->error_page;

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, "http static fd: %d", of.fd);

    if (of.is_dir) {

        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, log, 0, "http dir");

        ngx_http_clear_location(r);

        r->headers_out.location = ngx_list_push(&r->headers_out.headers);
        if (r->headers_out.location == NULL) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        len = r->uri.len + 1;

        if (!clcf->alias && r->args.len == 0) {
            location = path.data + root;

            *last = '/';

        } else {
            if (r->args.len) {
                len += r->args.len + 1;
            }

            location = ngx_pnalloc(r->pool, len);
            if (location == NULL) {
                ngx_http_clear_location(r);
                return NGX_HTTP_INTERNAL_SERVER_ERROR;
            }

            last = ngx_copy(location, r->uri.data, r->uri.len);

            *last = '/';

            if (r->args.len) {
                *++last = '?';
                ngx_memcpy(++last, r->args.data, r->args.len);
            }
        }

        r->headers_out.location->hash = 1;
        ngx_str_set(&r->headers_out.location->key, "Location");
        r->headers_out.location->value.len = len;
        r->headers_out.location->value.data = location;

        return NGX_HTTP_MOVED_PERMANENTLY;
    }

#if !(NGX_WIN32) /* the not regular files are probably Unix specific */

    if (!of.is_file) {
        ngx_log_error(NGX_LOG_CRIT, log, 0,
                      "\"%s\" is not a regular file", path.data);

        return NGX_HTTP_NOT_FOUND;
    }

#endif
    // 真正到内容输出的时候,post又是不被允许的方法
    if (r->method == NGX_HTTP_POST) {
        return NGX_HTTP_NOT_ALLOWED;
    }
    // 静态文件处理,将会忽略所有请求body参数
    rc = ngx_http_discard_request_body(r);

    if (rc != NGX_OK) {
        return rc;
    }
    // 输出文件内容到客户端
    log->action = "sending response to client";

    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = of.size;
    r->headers_out.last_modified_time = of.mtime;
    // 设置 ETag header
    if (ngx_http_set_etag(r) != NGX_OK) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }
    // content-type 设置,大概就是根据文件后缀找到相应的content-type 输出即可
    if (ngx_http_set_content_type(r) != NGX_OK) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    if (r != r->main && of.size == 0) {
        return ngx_http_send_header(r);
    }

    r->allow_ranges = 1;

    /* we need to allocate all before the header would be sent */

    b = ngx_calloc_buf(r->pool);
    if (b == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    b->file = ngx_pcalloc(r->pool, sizeof(ngx_file_t));
    if (b->file == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }
    // 发送头信息,如果是 head 请求,则到此为止
    // 该header响应会添加一些公共的请求头一并返回
   // 其中content-type是根据文件类型做的映射返回,如txt文件映射为 text/plain
   // 具体可以由用户指定,在 mime.types 中添加映射即可
    // 使用一系列的filter过滤器链进行处理
    // 当文件未发生变化时,会返回304, 即不再返回更多信息
    /**
        Accept-Ranges: bytes
        Connection: keep-alive
        Content-Length: 18
        Content-Type: text/plain
        Date: Wed, 07 Oct 2020 09:01:12 GMT
        ETag: "5f66fc46-12"
        Last-Modified: Sun, 20 Sep 2020 06:52:54 GMT
        Server: nginx/1.19.2
     */
    rc = ngx_http_send_header(r);

    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
        return rc;
    }
    // 封装要响应的文件描述符,输出响应
    b->file_pos = 0;
    b->file_last = of.size;

    b->in_file = b->file_last ? 1: 0;
    b->last_buf = (r == r->main) ? 1: 0;
    b->last_in_chain = 1;

    b->file->fd = of.fd;
    b->file->name = path;
    b->file->log = log;
    b->file->directio = of.is_directio;

    out.buf = b;
    out.next = NULL;
    // 一系列的响应过滤器处理,响应body
    /**
     * range_filter
     * copy_filter
     * output_chain -> output_chain_copy_buf
     */
    return ngx_http_output_filter(r, &out);
}

整体可以简单用一句话说明,根据uri路径,结合root路径配置,得到文件信息,响应客户端。只处理 get/head请求,对文件内容无变化情况将会返回304。上面更多的是,只限于c语言的表达能力问题,做的许多变化,无须多看。

      对于静态文件的处理,后续所有给定的参数都将被忽略掉。当然了也不是完全无用,不同参数也是有意义的,它用于检测文件是否发生变化。至于是html文件还是pdf或者txt,nginx并没有做特别的处理,它只是负责将内容返回给浏览器,浏览器做进一步解析从而达到html展现的效果。

  另外,再次需要说明的一点是,nginx的性能优势是在于其巧妙地利用系统的非阻塞io,从而提升了处理能力,而且其扩展能力非常强,配置也非常友好。但其整体流程,与其他http服务器并无二致。

对于想要了解更多细节的同学,可以点开下面的代码,查看C语言的实现细节。


// http/ngx_http_core_module.c
// 解析文件路径到path中
u_char *
ngx_http_map_uri_to_path(ngx_http_request_t *r, ngx_str_t *path,
    size_t *root_length, size_t reserved)
{
    u_char                    *last;
    size_t                     alias;
    ngx_http_core_loc_conf_t  *clcf;

    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);

    alias = clcf->alias;

    if (alias && !r->valid_location) {
        ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
                      "\"alias\" cannot be used in location \"%V\" "
                      "where URI was rewritten", &clcf->name);
        return NULL;
    }

    if (clcf->root_lengths == NULL) {

        *root_length = clcf->root.len;

        path->len = clcf->root.len + reserved + r->uri.len - alias + 1;

        path->data = ngx_pnalloc(r->pool, path->len);
        if (path->data == NULL) {
            return NULL;
        }
        // 将root根目录赋值给 path
        last = ngx_copy(path->data, clcf->root.data, clcf->root.len);

    } else {

        if (alias == NGX_MAX_SIZE_T_VALUE) {
            reserved += r->add_uri_to_alias ? r->uri.len + 1 : 1;

        } else {
            reserved += r->uri.len - alias + 1;
        }

        if (ngx_http_script_run(r, path, clcf->root_lengths->elts, reserved,
                                clcf->root_values->elts)
            == NULL)
        {
            return NULL;
        }

        if (ngx_get_full_name(r->pool, (ngx_str_t *) &ngx_cycle->prefix, path)
            != NGX_OK)
        {
            return NULL;
        }

        *root_length = path->len - reserved;
        last = path->data + *root_length;

        if (alias == NGX_MAX_SIZE_T_VALUE) {
            if (!r->add_uri_to_alias) {
                *last = '\0';
                return last;
            }

            alias = 0;
        }
    }
    // 将uri的相对路径拼接到 path 中
    last = ngx_copy(last, r->uri.data + alias, r->uri.len - alias);
    *last = '\0';

    return last;
}

// http/ngx_http_core_module.c
ngx_int_t
ngx_http_set_disable_symlinks(ngx_http_request_t *r,
    ngx_http_core_loc_conf_t *clcf, ngx_str_t *path, ngx_open_file_info_t *of)
{
#if (NGX_HAVE_OPENAT)
    u_char     *p;
    ngx_str_t   from;

    of->disable_symlinks = clcf->disable_symlinks;

    if (clcf->disable_symlinks_from == NULL) {
        return NGX_OK;
    }

    if (ngx_http_complex_value(r, clcf->disable_symlinks_from, &from)
        != NGX_OK)
    {
        return NGX_ERROR;
    }

    if (from.len == 0
        || from.len > path->len
        || ngx_memcmp(path->data, from.data, from.len) != 0)
    {
        return NGX_OK;
    }

    if (from.len == path->len) {
        of->disable_symlinks = NGX_DISABLE_SYMLINKS_OFF;
        return NGX_OK;
    }

    p = path->data + from.len;

    if (*p == '/') {
        of->disable_symlinks_from = from.len;
        return NGX_OK;
    }

    p--;

    if (*p == '/') {
        of->disable_symlinks_from = from.len - 1;
    }
#endif

    return NGX_OK;
}


// core/ngx_open_file_cache.c
// 尝试使用缓存文件
ngx_int_t
ngx_open_cached_file(ngx_open_file_cache_t *cache, ngx_str_t *name,
    ngx_open_file_info_t *of, ngx_pool_t *pool)
{
    time_t                          now;
    uint32_t                        hash;
    ngx_int_t                       rc;
    ngx_file_info_t                 fi;
    ngx_pool_cleanup_t             *cln;
    ngx_cached_open_file_t         *file;
    ngx_pool_cleanup_file_t        *clnf;
    ngx_open_file_cache_cleanup_t  *ofcln;

    of->fd = NGX_INVALID_FILE;
    of->err = 0;
    // 没有缓存,则重新打开文件
    if (cache == NULL) {

        if (of->test_only) {

            if (ngx_file_info_wrapper(name, of, &fi, pool->log)
                == NGX_FILE_ERROR)
            {
                return NGX_ERROR;
            }

            of->uniq = ngx_file_uniq(&fi);
            of->mtime = ngx_file_mtime(&fi);
            of->size = ngx_file_size(&fi);
            of->fs_size = ngx_file_fs_size(&fi);
            of->is_dir = ngx_is_dir(&fi);
            of->is_file = ngx_is_file(&fi);
            of->is_link = ngx_is_link(&fi);
            of->is_exec = ngx_is_exec(&fi);

            return NGX_OK;
        }

        cln = ngx_pool_cleanup_add(pool, sizeof(ngx_pool_cleanup_file_t));
        if (cln == NULL) {
            return NGX_ERROR;
        }
        // 打开文件
        rc = ngx_open_and_stat_file(name, of, pool->log);
        // 找到对应文件,设置 handler 为 ngx_pool_cleanup_file
        if (rc == NGX_OK && !of->is_dir) {
            cln->handler = ngx_pool_cleanup_file;
            clnf = cln->data;

            clnf->fd = of->fd;
            clnf->name = name->data;
            clnf->log = pool->log;
        }

        return rc;
    }

    cln = ngx_pool_cleanup_add(pool, sizeof(ngx_open_file_cache_cleanup_t));
    if (cln == NULL) {
        return NGX_ERROR;
    }

    now = ngx_time();

    hash = ngx_crc32_long(name->data, name->len);

    file = ngx_open_file_lookup(cache, name, hash);

    if (file) {

        file->uses++;

        ngx_queue_remove(&file->queue);

        if (file->fd == NGX_INVALID_FILE && file->err == 0 && !file->is_dir) {

            /* file was not used often enough to keep open */

            rc = ngx_open_and_stat_file(name, of, pool->log);

            if (rc != NGX_OK && (of->err == 0 || !of->errors)) {
                goto failed;
            }

            goto add_event;
        }

        if (file->use_event
            || (file->event == NULL
                && (of->uniq == 0 || of->uniq == file->uniq)
                && now - file->created < of->valid
#if (NGX_HAVE_OPENAT)
                && of->disable_symlinks == file->disable_symlinks
                && of->disable_symlinks_from == file->disable_symlinks_from
#endif
            ))
        {
            if (file->err == 0) {

                of->fd = file->fd;
                of->uniq = file->uniq;
                of->mtime = file->mtime;
                of->size = file->size;

                of->is_dir = file->is_dir;
                of->is_file = file->is_file;
                of->is_link = file->is_link;
                of->is_exec = file->is_exec;
                of->is_directio = file->is_directio;

                if (!file->is_dir) {
                    file->count++;
                    ngx_open_file_add_event(cache, file, of, pool->log);
                }

            } else {
                of->err = file->err;
#if (NGX_HAVE_OPENAT)
                of->failed = file->disable_symlinks ? ngx_openat_file_n
                                                    : ngx_open_file_n;
#else
                of->failed = ngx_open_file_n;
#endif
            }

            goto found;
        }

        ngx_log_debug4(NGX_LOG_DEBUG_CORE, pool->log, 0,
                       "retest open file: %s, fd:%d, c:%d, e:%d",
                       file->name, file->fd, file->count, file->err);

        if (file->is_dir) {

            /*
             * chances that directory became file are very small
             * so test_dir flag allows to use a single syscall
             * in ngx_file_info() instead of three syscalls
             */

            of->test_dir = 1;
        }

        of->fd = file->fd;
        of->uniq = file->uniq;

        rc = ngx_open_and_stat_file(name, of, pool->log);

        if (rc != NGX_OK && (of->err == 0 || !of->errors)) {
            goto failed;
        }

        if (of->is_dir) {

            if (file->is_dir || file->err) {
                goto update;
            }

            /* file became directory */

        } else if (of->err == 0) {  /* file */

            if (file->is_dir || file->err) {
                goto add_event;
            }

            if (of->uniq == file->uniq) {

                if (file->event) {
                    file->use_event = 1;
                }

                of->is_directio = file->is_directio;

                goto update;
            }

            /* file was changed */

        } else { /* error to cache */

            if (file->err || file->is_dir) {
                goto update;
            }

            /* file was removed, etc. */
        }

        if (file->count == 0) {

            ngx_open_file_del_event(file);

            if (ngx_close_file(file->fd) == NGX_FILE_ERROR) {
                ngx_log_error(NGX_LOG_ALERT, pool->log, ngx_errno,
                              ngx_close_file_n " \"%V\" failed", name);
            }

            goto add_event;
        }

        ngx_rbtree_delete(&cache->rbtree, &file->node);

        cache->current--;

        file->close = 1;

        goto create;
    }

    /* not found */

    rc = ngx_open_and_stat_file(name, of, pool->log);

    if (rc != NGX_OK && (of->err == 0 || !of->errors)) {
        goto failed;
    }

create:

    if (cache->current >= cache->max) {
        ngx_expire_old_cached_files(cache, 0, pool->log);
    }

    file = ngx_alloc(sizeof(ngx_cached_open_file_t), pool->log);

    if (file == NULL) {
        goto failed;
    }

    file->name = ngx_alloc(name->len + 1, pool->log);

    if (file->name == NULL) {
        ngx_free(file);
        file = NULL;
        goto failed;
    }

    ngx_cpystrn(file->name, name->data, name->len + 1);

    file->node.key = hash;

    ngx_rbtree_insert(&cache->rbtree, &file->node);

    cache->current++;

    file->uses = 1;
    file->count = 0;
    file->use_event = 0;
    file->event = NULL;

add_event:

    ngx_open_file_add_event(cache, file, of, pool->log);

update:

    file->fd = of->fd;
    file->err = of->err;
#if (NGX_HAVE_OPENAT)
    file->disable_symlinks = of->disable_symlinks;
    file->disable_symlinks_from = of->disable_symlinks_from;
#endif

    if (of->err == 0) {
        file->uniq = of->uniq;
        file->mtime = of->mtime;
        file->size = of->size;

        file->close = 0;

        file->is_dir = of->is_dir;
        file->is_file = of->is_file;
        file->is_link = of->is_link;
        file->is_exec = of->is_exec;
        file->is_directio = of->is_directio;

        if (!of->is_dir) {
            file->count++;
        }
    }

    file->created = now;

found:

    file->accessed = now;

    ngx_queue_insert_head(&cache->expire_queue, &file->queue);

    ngx_log_debug5(NGX_LOG_DEBUG_CORE, pool->log, 0,
                   "cached open file: %s, fd:%d, c:%d, e:%d, u:%d",
                   file->name, file->fd, file->count, file->err, file->uses);

    if (of->err == 0) {

        if (!of->is_dir) {
            cln->handler = ngx_open_file_cleanup;
            ofcln = cln->data;

            ofcln->cache = cache;
            ofcln->file = file;
            ofcln->min_uses = of->min_uses;
            ofcln->log = pool->log;
        }

        return NGX_OK;
    }

    return NGX_ERROR;

failed:

    if (file) {
        ngx_rbtree_delete(&cache->rbtree, &file->node);

        cache->current--;

        if (file->count == 0) {

            if (file->fd != NGX_INVALID_FILE) {
                if (ngx_close_file(file->fd) == NGX_FILE_ERROR) {
                    ngx_log_error(NGX_LOG_ALERT, pool->log, ngx_errno,
                                  ngx_close_file_n " \"%s\" failed",
                                  file->name);
                }
            }

            ngx_free(file->name);
            ngx_free(file);

        } else {
            file->close = 1;
        }
    }

    if (of->fd != NGX_INVALID_FILE) {
        if (ngx_close_file(of->fd) == NGX_FILE_ERROR) {
            ngx_log_error(NGX_LOG_ALERT, pool->log, ngx_errno,
                          ngx_close_file_n " \"%V\" failed", name);
        }
    }

    return NGX_ERROR;
}

// core/ngx_open_file_cache.c
// 打开文件
static ngx_int_t
ngx_open_and_stat_file(ngx_str_t *name, ngx_open_file_info_t *of,
    ngx_log_t *log)
{
    ngx_fd_t         fd;
    ngx_file_info_t  fi;

    if (of->fd != NGX_INVALID_FILE) {

        if (ngx_file_info_wrapper(name, of, &fi, log) == NGX_FILE_ERROR) {
            of->fd = NGX_INVALID_FILE;
            return NGX_ERROR;
        }

        if (of->uniq == ngx_file_uniq(&fi)) {
            goto done;
        }

    } else if (of->test_dir) {

        if (ngx_file_info_wrapper(name, of, &fi, log) == NGX_FILE_ERROR) {
            of->fd = NGX_INVALID_FILE;
            return NGX_ERROR;
        }

        if (ngx_is_dir(&fi)) {
            goto done;
        }
    }

    if (!of->log) {

        /*
         * Use non-blocking open() not to hang on FIFO files, etc.
         * This flag has no effect on a regular files.
         */
        // 使用非阻塞io打开文件
        fd = ngx_open_file_wrapper(name, of, NGX_FILE_RDONLY|NGX_FILE_NONBLOCK,
                                   NGX_FILE_OPEN, 0, log);

    } else {
        fd = ngx_open_file_wrapper(name, of, NGX_FILE_APPEND,
                                   NGX_FILE_CREATE_OR_OPEN,
                                   NGX_FILE_DEFAULT_ACCESS, log);
    }

    if (fd == NGX_INVALID_FILE) {
        of->fd = NGX_INVALID_FILE;
        return NGX_ERROR;
    }
    // 检测文件有效性
    if (ngx_fd_info(fd, &fi) == NGX_FILE_ERROR) {
        ngx_log_error(NGX_LOG_CRIT, log, ngx_errno,
                      ngx_fd_info_n " \"%V\" failed", name);

        if (ngx_close_file(fd) == NGX_FILE_ERROR) {
            ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
                          ngx_close_file_n " \"%V\" failed", name);
        }

        of->fd = NGX_INVALID_FILE;

        return NGX_ERROR;
    }

    if (ngx_is_dir(&fi)) {
        if (ngx_close_file(fd) == NGX_FILE_ERROR) {
            ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
                          ngx_close_file_n " \"%V\" failed", name);
        }

        of->fd = NGX_INVALID_FILE;

    } else {
        of->fd = fd;
        // 如果文件过大,则先预读取部分数据
        if (of->read_ahead && ngx_file_size(&fi) > NGX_MIN_READ_AHEAD) {
            if (ngx_read_ahead(fd, of->read_ahead) == NGX_ERROR) {
                ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
                              ngx_read_ahead_n " \"%V\" failed", name);
            }
        }

        if (of->directio <= ngx_file_size(&fi)) {
            if (ngx_directio_on(fd) == NGX_FILE_ERROR) {
                ngx_log_error(NGX_LOG_ALERT, log, ngx_errno,
                              ngx_directio_on_n " \"%V\" failed", name);

            } else {
                of->is_directio = 1;
            }
        }
    }

done:
    // 复制文件信息返回
    of->uniq = ngx_file_uniq(&fi);
    of->mtime = ngx_file_mtime(&fi);
    of->size = ngx_file_size(&fi);
    of->fs_size = ngx_file_fs_size(&fi);
    of->is_dir = ngx_is_dir(&fi);
    of->is_file = ngx_is_file(&fi);
    of->is_link = ngx_is_link(&fi);
    of->is_exec = ngx_is_exec(&fi);

    return NGX_OK;
}

如果你想学好JAVA这门技术,也想在IT行业拿高薪,可以进来看看 ,群里有:Java工程化、高性能及分布式、高性能、深入浅出。高架构。性能调优、Spring,MyBatis,Netty源码分析和大数据等多个知识点。

如果你想拿高薪的,想学习的,想就业前景好的,想跟别人竞争能取得优势的,想进阿里面试但担心面试不过的,BAT面试题:1000+BAT面试题资料汇总

看完三件事❤️

如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:

  1. 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。

  2. 关注公众号 『 java烂猪皮 』,不定期分享原创知识。

  3. 同时可以期待后续文章ing🚀

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Nginx(四):http服务器静态文件查找的实现 的相关文章

随机推荐

  • js 字符串转换成数字的三种方法, 取float型小数点后两位数的方法

    在js读取文本框或者其它表单数据的时候获得的值是字符串类型的 例如两个文本框a和b 如果获得a的value值为11 b的value值为9 那么a value要小于b value 因为他们都是字符串形式的 在网上找了一下js字符串转数字的文章
  • 二次贝塞尔曲线长度

    二次贝塞尔曲线通常以如下方式构建 xff0c 给定二维平面上的固定点P0 P1 P2 xff0c 用B t 表示该条曲线 用一个动画来演示 xff0c 可以更加清楚的表明这条曲线的构建过程 如果t变量本身线形变化的话 xff0c 这条贝塞尔
  • 损坏的主控文件表,CHKDSK被终止.如何恢复数据

    这种情况是分区结构出现异常 引起的分区错误 单击右键属性看到的是RAW格式 移动硬盘的话 xff0c 很大程度是因为强拔之类的操作 xff0c 也可能是坏道 病毒 xff0c 硬盘本身质量问题引起的 因为系统读取移动硬盘信息困难 xff0c
  • Microsoft SQL Server 2008 R2 官方简体中文正式版下载(附激活序列号密钥)

    微软官方发布的Microsoft SQL Server 2008 R2 简体中文完整版 基于SQL Server 2008提供可靠高效的智能数据平台构建而成 xff0c SQL Server 2008 R2 提供了大量新改进 xff0c 可
  • 不重装系统将系统移动到固态硬盘,并修改为C盘

    如今很多人升级电脑都会考虑换块固态硬盘 xff0c 如果重装系统 xff0c 又要重新安装一系列的软件 xff0c 拷贝一系列的数据 这里教大家怎样直接用固态硬盘代替C盘 xff0c 并且不会影响数据和软件 1 首先是在原电脑上加装新固态硬
  • python进行t检验示例

    t检验主要是针对正态总体均值的假设检验 xff0c 即检验样本的均值与某个值的差异 xff0c 或者两个样本的均值是否有差异等 其不需要事先知道总体的方差 xff0c 并且在少量样本情况下也可以进行检验 python进行t检验使用scipy
  • python-正态分布查表应用(scipy.stats.norm)

    1 公式 xff1a 正态分布概率密度公式 xff1a 可通过转换为标准正态分布 2 概率密度 xff1a 标准正态分布在某个点的概率密度可用scipy stats norm pdf计算 xff0c 下面模拟计算 5 5的概率密度 from
  • python-介绍泊松分布(poisson分布)

    一 泊松分布问题 xff1a 假设我每天接到骚扰电话的次数服从泊松分布 xff0c 并且经统计平均每天我会接到20个骚扰电话 请问 xff1a 1 我明天接到15个骚扰电话的概率 xff1f 2 我明天接到24个骚扰电话以下的概率 xff0
  • Word文档编辑技巧

    1 如何让附录显示在目录里 依次点击菜单栏的视图 工具栏 大纲 xff0c 打开大纲工具栏 选中 目录 附录 参考文献 等标题 xff0c 点击大纲工具栏的大纲级别的下拉选项 xff0c 选择 1级 xff0c 此时就完成了对 目录 等标题
  • 谈windows开发学习过程

    MFC作为一个C 43 43 类库 xff0c 而且MFC是Windows SDK的封装 为了开发windows应用更方便 xff0c 让程序员开发应用不需要过多在意细节 xff0c 专注功能上的开发 xff0c 所以推出MFC 但是实际开
  • python-指数分布介绍(scipy.stats.expon)

    一 指数分布问题 xff1a 有一种品牌的路由器 xff0c 据厂家统计知该路由器平均寿命是50000小时 xff0c 现在有2个问题 xff1a 1 去年我买了一个这样的路由器 xff0c 使用到现在已经8000小时了一点问题都没有 xf
  • python-使用LinearRegression进行简单线性拟合(线性回归)

    一元线性拟合 现有两组数据 xff0c 求y 61 a x 43 c的系数 X 61 12 46 0 25 5 22 11 3 6 81 4 59 0 66 14 53 15 49 14 43 2 19 1 35 10 02 12 93 5
  • matplotlib绘制双坐标轴(双纵轴)

    双坐标轴绘图示例 import matplotlib pyplot as plt import numpy as np plt rcParams 39 font sans serif 39 61 39 SimHei 39 用来正常显示中文标
  • pyplot画多个图,在一个图中绘制多个子图

    pyplot绘制多个子图方法 matplotlib在一个图中绘制多个子图用plt subplot 方法 import matplotlib pyplot as plt import numpy as np plt rcParams 39 f
  • mysql数据库备份及恢复

    1 mysql数据库备份 xff1a mysqldump lan db u root pmypassword h 10 49 56 01 gt data backup lan db sql 2 mysql数据库恢复 xff1a 先创建数据库
  • mysql密码过期解决办法,mysql密码永不过期

    1 进入到数据库服务器 xff0c windows需要进入到bin目录 xff0c linux应该是任意目录即可 xff08 但需要账号有权限 xff09 以windows为例 xff1a C Program Files MySQL MyS
  • java任意进制转换

    摘要 我们日常常用的是十进制 计算机是基于二进制的 xff0c 计算机常用的还有十六进制 八进制 本文主要介绍如何实现十进制和任意进制 间转换 计算思想 如果用 0123456789abcdefghijklmnopqrstuvwxyzABC
  • c++与java的应用区别

    文章目录 简介不同点关注点接口调用优化运行方式知识架构java不擅长的地方 基本就这些了 简介 一点粗鄙的总结 xff0c 肯定不全面 java我只接触了 后端部分 不同点 c 43 43 基本上你可以应用在所有的领域 xff0c 但是在后
  • HTTP基本认证(Basic Authentication)

    什么是HTTP基本认证 当打开浏览器访问网页时 xff0c 浏览器弹出一个包含用户名 密码输出项的对话框 xff0c 当用户输入用户名 密码后发送给服务器 xff0c 服务器验证通过后 xff0c 会显示相应的信息 这一认证的过程 xff0
  • Nginx(四):http服务器静态文件查找的实现

    上一篇nginx的文章中 xff0c 我们理解了整个http正向代理的运行流程原理 xff0c 主要就是事件机制接入 xff0c header解析 xff0c body解析 xff0c 然后遍历各种checker xff0c 直到处理成功为