• Nginx配置指令的执行顺序


    Nginx指令顺序:set与echo

    location /test {
         set $a 32;
         echo $a;
    
         set $a 56;
         echo $a;
    }
    
    # set 指令就是在 rewrite 阶段运行的,而 echo 指令就只会在 content 阶段运行
    # rewrite 阶段总是在 content 阶段之前执行
    #结果server-rewrite 阶段
    $ curl 'http://localhost:8080/test
    56
    56
    

    Nginx指令阶段

    Nginx 处理请求的过程一共划分为 11 个阶段,按照执行顺序依次是 post-read、server-rewrite、find-config、rewrite、post-rewrite、preaccess、access、post-access、try-files、content 以及 log.

    • server-rewrite 阶段

      • set(在service配置块中)
    • rewrite 阶段

      • ngx_rewrite(标准模块, 所以指令),set 在location配置块中
      • ngx_set_misc模块(set_unescape_uri )
      • ngx_lua模块(set_by_lua)
      • ngx_array_var
      • ngx_encrypted_session
      • ngx_headers_more( more_set_input_headers, 指令总是运行在rewrite阶段的末尾)
      • ngx_lua(rewrite_by_lua,指令总是运行在rewrite阶段的末尾)
    • access 的请求处理阶段

      • ngx_access(标准模块,所以指令:allow, deny,多条配置指令之间是按顺序执行,第一条满足条件的指令就不再执行后续)
      • ngx_auth_request 指令
      • ngx_lua 模块(access_by_lua指令)
    location /hello {
        allow 127.0.0.1;
        deny all;
    
        echo "hello world";
    }
    
    #如果是从本地访问的,则首先匹配 allow 127.0.0.1 这一条语句,于是 Nginx 就继续往下执行其他模块的指令以及后续的处理阶段;而如果是从其他机器访问,则首先匹配的则是 deny all 这一条语句,即拒绝所有地址,它会导致 403 错误页立即返回给客户端。
    
    #结果:
    #从本机访问
    $ curl 'http://localhost:8080/hello'
    hello world
    #从另一台机器访问
    $ curl 'http://192.168.1.101:8080/hello'
    403 Forbidden
    
    # 完成标准模块相同的功能, 性能没标准模块好
    location /hello {
        access_by_lua '
            if ngx.var.remote_addr == "127.0.0.1" then
                return
            end
    
            ngx.exit(403)
        ';
    
        echo "hello world";
    }
    

    综合:

    location /test {
        # rewrite phase
        set $age 1;
        rewrite_by_lua "ngx.var.age = ngx.var.age + 1";
    
        # access phase
        deny 10.32.168.49;
        access_by_lua "ngx.var.age = ngx.var.age * 3";
    
        # content phase
        echo "age = $age";
    }
    
    #结果:
    $ curl 'http://localhost:8080/test'
    age = 6
    

    set 指令来自 ngx_rewrite 模块,运行于 rewrite 阶段;
    而 rewrite_by_lua 指令来自 ngx_lua 模块,运行于 rewrite 阶段的末尾;
    接下来, deny 指令来自 ngx_access 模块,运行于 access 阶段;
    再下来, access_by_lua 指令同样来自 ngx_lua 模块,运行于 access 阶段的末尾;
    最后,我们的老朋友 echo 指令则来自 ngx_echo 模块,运行在 content 阶段。

    • content 阶段
      • ngx_echo模块(所有指令:echo,echo_exec,echo_location)
      • ngx_proxy 模块的proxy_pass指令
      • ngx_lua模块(content_by_lua)

    在 rewrite 和 access 这两个阶段,多个模块的配置指令可以同时使用,譬如上例中的 set 指令和 rewrite_by_lua 指令同处 rewrite 阶段,而 deny 指令和 access_by_lua 指令则同处 access 阶段。但通常不适用于 content 阶段

    Nginx 模块在向 content 阶段注册配置指令时,本质上是在当前的 location 配置块中注册所谓的“内容处理程序”(content handler)。每一个 location 只能有一个“内容处理程序”,因此,当在 location 中同时使用多个模块的 content 阶段指令时,只有其中一个模块能成功注册“内容处理程序”, 即只能解析一条相同的指令。

    location /test {
         echo hello;
         content_by_lua 'ngx.say("world")';
     }
    
    #结果:
    $ curl 'http://localhost:8080/test'
    world 
    
    #这里要么解析echo,要么解析content_by_lua , 一般是解析最好一条指令
    

    在 content_by_lua 内联的 Lua 代码中调用两次 ngx.say 函数,而不是在当前 location 中使用两次 content_by_lua 指令。 但echo 可以使用多次。

    location /test {
        echo hello;
        echo world;
    }
    #结果:
    $ curl 'http://localhost:8080/test'
    hello
    world
    
    
    location /test {
        content_by_lua 'ngx.say("hello")';
        content_by_lua 'ngx.say("world")';
    }
    
    # 这个配置在 Nginx 启动时就会报错
    # [emerg] "content_by_lua" directive is duplicate ...
    
    
    location /test {
        content_by_lua 'ngx.say("hello") ngx.say("world")';
    }
    #结果:
    $ curl 'http://localhost:8080/test'
    hello
    world
    

    在 ngx_proxy 模块返回的内容前后,不能通过 ngx_echo 模块的 echo 指令输出。必须改用改用 ngx_echo 模块提供的 echo_before_body 和 echo_after_body 这两条配置指令

    location /test {
        echo_before_body "before...";
        proxy_pass http://127.0.0.1:8080/foo;
        echo_after_body "after...";
    }
    
    location /foo {
        echo "contents to be proxied";
    }
    
    #结果:
    $ curl 'http://localhost:8080/test'
    before...
    contents to be proxied
    after...
    

    配置指令 echo_before_body 和 echo_after_body 之所以可以和其他模块运行在 content 阶段的指令一起工作,是因为它们运行在 Nginx 的“输出过滤器”中。Nginx 在输出响应体数据时都会调用“输出过滤器”,所以 ngx_echo 模块才有机会在“输出过滤器”中对 ngx_proxy 模块产生的响应体输出进行修改(即在首尾添加新的内容)。

    Nginx阶段Content中默认服务模块

    当一个 location 中未使用任何 content 阶段的指令,没有模块注册“内容处理程序”时,Nginx 一般会在 content 阶段安排三个这样的静态资源服务模块,依次是 ngx_index 模块, ngx_autoindex 模块,以及 ngx_static 模块。

    • ngx_index 模块
    #当用户请求 / 地址时,Nginx 就会自动在 root 配置指令指定的文件系统目录下依次寻找 index.htm 和 index.html 这两个文件
    
    # 如果存在,同样发起“内部跳转”到 /index.htm, /index.html;如果件仍然不存在,则放弃处理权给 content 阶段的下一个模块。
    
    location / {
        root /var/www/; # root 配置指令只起到了声明“文档根目录”的作用
        index index.htm index.html;
    }
    

    echo_exec 指令和 rewrite 指令可以发起“内部跳转”。这种跳转会自动修改当前请求的 URI,并且重新匹配与之对应的 location 配置块,再重新执行 rewrite、access、content 等处理阶段。有别于 HTTP 协议中定义的基于302和301响应的“外部跳转”,最终用户的浏览器的地址栏也不会发生变化,依然是原来的 URI 位置。而 ngx_index 模块一旦找到了 index 指令中列举的文件之后,就会发起这样的“内部跳转”,仿佛用户是直接请求的这个文件所对应的 URI 一样。

    location / {
        root /var/www/;
        index index.html;
    }
    
    location /index.html {
        set $a 32;
        echo "a = $a";
    }
    
    #结果:
    $ curl 'http://localhost:8080/'
    a = 32
    
    • ngx_autoindex 模块

    运行在 ngx_index 模块之后的 ngx_autoindex 模块就可以用于自动生成这样的“目录索引”网页

    # 如果/var/www/index.html不存在将会自动生成一个目录索引文件命名为index.html
    location / {
        root /var/www/;
        index index.html;
        autoindex on;
    }
    
    • ngx_static 模块

    这个模块主要实现服务静态文件的功能。比方说,一个网站的静态资源,包括静态 .html 文件、静态 .css 文件、静态 .js 文件、以及静态图片文件等等,全部可以通过这个模块对外服务。 ngx_index 模块虽然可以在指定的首页文件存在时发起“内部跳转”,但真正把相应的首页文件服务出去(即把该文件的内容作为响应体数据输出,并设置相应的响应头),还是得靠这个 ngx_static 模块来完成。

    location / {
        root /var/www/;
    }
    
    #在本机的 /var/www/ 目录下创建两个文件,一个文件叫做 index.html,内容是一行文本 this is my home;另一个文件叫做 hello.html,内容是一行文本 hello world. 同时注意这两个文件的权限设置,确保它们都对运行 Nginx worker 进程的系统帐户可读。
    
    #结果:
    $ curl 'http://localhost:8080/index.html'
    this is my home
    
    $ curl 'http://localhost:8080/hello.html'
    hello world
    

    location / 中没有使用运行在 content 阶段的模块指令,于是也就没有模块注册这个 location 的“内容处理程序”,处理权便自动落到了在 content 阶段“垫底”的那 3 个静态资源服务模块。首先运行的 ngx_index 和 ngx_autoindex 模块先后看到当前请求的 URI,/index.html 和 /hello.html,并不以 / 结尾,于是直接弃权,将处理权转给了最后运行的 ngx_static 模块。ngx_static 模块根据 root 指令指定的“文档根目录”(document root),分别将请求 URI /index.html 和 /hello.html 映射为文件系统路径 /var/www/index.html 和 /var/www/hello.html,在确认这两个文件存在后,将它们的内容分别作为响应体输出,并自动设置 Content-Type、Content-Length 以及 Last-Modified 等响应头。

    所有阶段

    • post-read阶段
      • ngx_realip 模块提供的 set_real_ip_from 和 real_ip_header 这两条配置指令
    # 这里的配置是让 Nginx 把那些来自 127.0.0.1 的所有请求的来源地址,都改写为请求头 X-My-IP 所指定的值。同时该例使用了标准内建变量 $remote_addr 来输出当前请求的来源地址,以确认是否被成功改写。
    
    server {
        listen 8080;
    
        set_real_ip_from 127.0.0.1;
        real_ip_header   X-My-IP;
    
        location /test {
            set $addr $remote_addr;
            echo "from: $addr";
        }
    }
    
    # 结果:
    $ curl -H 'X-My-IP: 1.2.3.4' localhost:8080/test
    from: 1.2.3.4
    
    $ curl localhost:8080/test
    from: 127.0.0.1
    
    $ curl -H 'X-My-IP: abc' localhost:8080/test
    from: 127.0.0.1
    

    也可以通过 set_real_ip_from 指令指定一个 IP 网段(利用 (三) 中介绍过的“CIDR 记法”)。此外,同时配置多个 set_real_ip_from 语句也是允许的,这样可以指定多个受信任的来源地址或地址段。下面是一个例子:

    set_real_ip_from 10.32.10.5;
    set_real_ip_from 127.0.0.0/24;
    

    用途:
    当 Nginx 处理的请求经过了某个 HTTP 代理服务器的转发时,这个模块就变得特别有用。当原始的用户请求经过转发之后,Nginx 接收到的请求的来源地址无一例外地变成了该代理服务器的 IP 地址,于是 Nginx 以及 Nginx 背后的应用就无法知道原始请求的真实来源。所以,一般我们会在 Nginx 之前的代理服务器中把请求的原始来源地址编码进某个特殊的 HTTP 请求头中(例如上例中的 X-My-IP 请求头),然后再在 Nginx 一侧把这个请求头中编码的地址恢复出来。这样 Nginx 中的后续处理阶段(包括 Nginx 背后的各种后端应用)就会认为这些请求直接来自那些原始的地址,代理服务器就仿佛不存在一样。正是因为这个需求,所以 ngx_realip 模块才需要在第一个处理阶段,即 post-read 阶段,注册处理程序,以便尽可能早地改写请求的来源。

    • server-rewrite 阶段

    当ngx_rewrite模块的配置指令直接书写在server配置块中时,基本上都是运行在server-rewrite阶段

    server {
        listen 8080;
    
        location /test {
            set $b "$a, world";
            echo $b;
        }
    
        set $a hello;
    }
    
    #结果:
    $ curl localhost:8080/test
    hello, world
    

    由于 server-rewrite 阶段位于 post-read 阶段之后,所以 server 配置块中的 set 指令也就总是运行在 ngx_realip 模块改写请求的来源地址之后。

    server {
        listen 8080;
    
        set $addr $remote_addr;
    
        set_real_ip_from 127.0.0.1;
        real_ip_header   X-Real-IP;
    
        location /test {
            echo "from: $addr";
        }
    }
    
    #结果:
    $ curl -H 'X-Real-IP: 1.2.3.4' localhost:8080/test
    from: 1.2.3.4
    
    #虽然 set 指令写在了 ngx_realip 的配置指令之前,但仍然晚于 ngx_realip 模块执行。所以 $addr 变量在 server-rewrite 阶段被 set 指令赋值时,从 $remote_addr 变量读出的来源地址已经是经过改写过的了。
    
    • find-config 阶段

    这个阶段并不支持 Nginx 模块注册处理程序,而是由 Nginx 核心来完成当前请求与 location 配置块之间的配对工作,在此阶段之前,请求并没有与任何 location 配置块相关联。因此,对于运行在 find-config 阶段之前的 post-read 和 server-rewrite 阶段来说,只有 server 配置块以及更外层作用域中的配置指令才会起作用。这就是为什么只有写在 server 配置块中的 ngx_rewrite 模块的指令才会运行在 server-rewrite 阶段,这也是为什么前面所有例子中的 ngx_realip 模块的指令也都特意写在了 server 配置块中,以确保其注册在 post-read 阶段的处理程序能够生效。

    • rewrite 阶段

    Nginx 已经在 find-config 阶段完成了当前请求与 location 的配对,所以从 rewrite 阶段开始,location 配置块中的指令便可以产生作用

    当 ngx_rewrite 模块的指令用于 location 块中时,便是运行在这个 rewrite 阶段。另外, ngx_set_misc 模块的指令也是如此,还有 ngx_lua 模块的 set_by_lua 指令和 rewrite_by_lua 指令也不例外。

    • post-rewrite 阶段

    这个阶段也像 find-config 阶段那样不接受 Nginx 模块注册处理程序,而是由 Nginx 核心完成 rewrite 阶段所要求的“内部跳转”操作(如果 rewrite 阶段有此要求的话)。

    已经介绍过了“内部跳转”的概念,同时演示了如何通过 echo_exec 指令或者 rewrite 指令来发起“内部跳转”

    server {
        listen 8080;
    
        location /foo {
            set $a hello;
            rewrite ^ /bar;
        }
    
        location /bar {
            echo "a = [$a]";
        }
    }
    

    这里在 location /foo 中通过 rewrite 指令把当前请求的 URI 无条件地改写为 /bar,同时发起一个“内部跳转”,最终跳进了 location /bar 中。这里比较有趣的地方是“内部跳转”的工作原理。

    “内部跳转”本质上其实就是把当前的请求处理阶段强行倒退到 find-config 阶段,以便重新进行请求 URI 与 location 配置块的配对。

    比如上例中,运行在 rewrite 阶段的 rewrite 指令就让当前请求的处理阶段倒退回了 find-config 阶段。由于此时当前请求的 URI 已经被 rewrite 指令修改为了 /bar,所以这一次换成了 location /bar 与当前请求相关联,然后再接着从 rewrite 阶段往下执行。

    倒退回 find-config 阶段的动作并不是发生在 rewrite 阶段,而是发生在后面的 post-rewrite 阶段。上例中的 rewrite 指令只是简单地指示 Nginx 有必要在 post-rewrite 阶段发起“内部跳转”。

    这个设计对于 Nginx 初学者来说,或许显得有些古怪:“为什么不直接在 rewrite 指令执行时立即进行跳转呢?”答案其实很简单,那就是为了在最初匹配的 location 块中支持多次反复地改写 URI,例如:

    location /foo {
        rewrite ^ /bar;
        rewrite ^ /baz;
    
        echo foo;
    }
    
    location /bar {
        echo bar;
    }
    
    location /baz {
        echo baz;
    }
    
    #结果:
    $ curl localhost:8080/foo
    baz
    

    这里在 location /foo 中连续把当前请求的 URI 改写了两遍:第一遍先无条件地改写为 /bar,第二遍再无条件地改写为 /baz. 而这两条 rewrite 语句只会最终导致 post-rewrite 阶段发生一次“内部跳转”操作,从而不至于在第一次改写 URI 时就直接跳离了当前的 location 而导致后面的 rewrite 语句没有机会执行。

    如果在 server 配置块中直接使用 rewrite 配置指令对请求 URI 进行改写,则不会涉及“内部跳转”,因为此时 URI 改写发生在 server-rewrite 阶段,早于执行 location 配对的 find-config 阶段。

    server {
        listen 8080;
    
        rewrite ^/foo /bar;
    
        location /foo {
            echo foo;
        }
    
        location /bar {
            echo bar;
        }
    }
    
    #结果:
    $ curl localhost:8080/foo
    bar
    

    我们在 server-rewrite 阶段就把那些以 /foo 起始的 URI 改写为 /bar,而此时请求并没有和任何 location 相关联,所以 Nginx 正常往下运行 find-config 阶段,完成最终的 location 匹配。如果我们请求上例中的 /foo 接口,那么 location /foo 根本就没有机会匹配,因为在第一次(也是唯一的一次)运行 find-config 阶段时,当前请求的 URI 已经被改写为 /bar,从而只会匹配 location /bar.

    • preaccess 阶段

    标准模块 ngx_limit_req 和 ngx_limit_zone 就运行在此阶段,前者可以控制请求的访问频度,而后者可以限制访问的并发度。
    前面提到的标准模块 ngx_realip 其实也在这个阶段注册了处理程序。

    #把 ngx_realip 的配置指令放在了 location 配置块中
    server {
        listen 8080;
    
        location /test {
            set_real_ip_from 127.0.0.1;
            real_ip_header X-Real-IP;
    
            echo "from: $remote_addr";
        }
    }
    
    #结果:
    $ curl -H 'X-Real-IP: 1.2.3.4' localhost:8080/test
    from: 1.2.3.4
    

    此例最重要的区别在于把 ngx_realip 的配置指令放在了 location 配置块中。前面我们介绍过,Nginx 匹配 location 的动作发生在 find-config 阶段,而 find-config 阶段远远晚于 post-read 阶段执行,所以在 post-read 阶段,当前请求还没有和任何 location 相关联。在这个例子中,因为 ngx_realip 的配置指令都写在了 location 配置块中,所以在 post-read 阶段, ngx_realip 模块的处理程序没有看到任何可用的配置信息,便不会执行来源地址的改写工作了。

    为了解决这个难题, ngx_realip 模块便又特意在 preaccess 阶段注册了处理程序,这样它才有机会运行 location 块中的配置指令。

    ngx_realip 模块的这个解决方案还是存在漏洞

    server {
        listen 8080;
    
        location /test {
            set_real_ip_from 127.0.0.1;
            real_ip_header X-Real-IP;
    
            set $addr $remote_addr;
            #是 set 语句读取 $remote_addr 变量时产生的。信息中的字符串 "127.0.0.1" 便是 $remote_addr 当时读出来的值。
            echo "from: $addr";
        }
    }
    
    #结果:
    $ curl -H 'X-Real-IP: 1.2.3.4' localhost:8080/test
    from: 127.0.0.1
    

    我们在 rewrite 阶段将 $remote_addr 的值保存到了用户变量 $addr 中,然后再输出。因为 rewrite 阶段先于 preaccess 阶段执行,所以当 ngx_realip 模块尚未在 preaccess 阶段改写来源地址时,最初的来源地址就已经在 rewrite 阶段被读取了。建议是:尽量在 server 配置块中配置 ngx_realip 这样的模块,以避免上面介绍的这种棘手的例外情况。

    • access 阶段

    标准模块 ngx_access、第三方模块 ngx_auth_request 以及第三方模块 ngx_lua 的 access_by_lua 指令就运行在这个阶段。

    • post-access 阶段。

    这个阶段也和 post-rewrite 阶段类似,并不支持 Nginx 模块注册处理程序,而是由 Nginx 核心自己完成一些处理工作。post-access 阶段主要用于配合 access 阶段实现标准 ngx_http_core 模块提供的配置指令 satisfy 的功能。

    多个 Nginx 模块注册在 access 阶段的处理程序, satisfy 配置指令可以用于控制它们彼此之间的协作方式。比如模块 A 和 B 都在 access 阶段注册了与访问控制相关的处理程序,那就有两种协作方式,一是模块 A 和模块 B 都得通过验证才算通过,二是模块 A 和模块 B 只要其中任一个通过验证就算通过。第一种协作方式称为 all 方式(或者说“与关系”),第二种方式则被称为 any 方式(或者说“或关系”)。默认情况下,Nginx 使用的是 all 方式。

    location /test {
        satisfy all;
    
        deny all;
        access_by_lua 'ngx.exit(ngx.OK)';
    
        echo something important;
    }
    
    #结果:
    $ curl localhost:8080/test
        <html>
        <head><title>403 Forbidden</title></head>
        <body bgcolor="white">
        <center><h1>403 Forbidden</h1></center>
        <hr><center>nginx</center>
        </body>
        </html>
    

    这里,我们在 /test 接口中同时配置了 ngx_access 模块和 ngx_lua 模块,这样 access 阶段就由这两个模块一起来做检验工作。

    然而,如果我们把上例中的 satisfy all 语句更改为 satisfy any。

    location /test {
        satisfy any;
    
        deny all;
        access_by_lua 'ngx.exit(ngx.OK)';
    
        echo something important;
    }
    
    #结果:
    $ curl localhost:8080/test
    something important
    

    因为在 any 方式下,access 阶段只要有一个模块通过了验证,就会认为请求整体通过了验证,而在上例中, ngx_lua 模块的 access_by_lua 语句总是会通过验证的。

    在配置了 satisfy any 的情况下,只有当 access 阶段的所有模块的处理程序都拒绝访问时,整个请求才会被拒。

    • try-files 阶段

    这个阶段专门用于实现标准配置指令 try_files 的功能,并不支持 Nginx 模块注册处理程序。由于 try_files 指令在许多 FastCGI 应用的配置中都有用到

    try_files 指令接受两个以上任意数量的参数,每个参数都指定了一个 URI. 这里假设配置了 N 个参数,则 Nginx 会在 try-files 阶段,依次把前 N-1 个参数映射为文件系统上的对象(文件或者目录),然后检查这些对象是否存在。一旦 Nginx 发现某个文件系统对象存在,就会在 try-files 阶段把当前请求的 URI 改写为该对象所对应的参数 URI(但不会包含末尾的斜杠字符,也不会发生 “内部跳转”)。如果前 N-1 个参数所对应的文件系统对象都不存在,try-files 阶段就会立即发起“内部跳转”到最后一个参数(即第 N 个参数)所指定的 URI.

    root /var/www/;
    
    location /test {
        try_files /foo /bar/ /baz;
        echo "uri: $uri";
    }
    
    location /foo {
        echo foo;
    }
    
    location /bar/ {
        echo bar;
    }
    
    location /baz {
        echo baz;
    }
    
    

    这里通过 root 指令把“文档根目录”配置为 /var/www/,如果你系统中的 /var/www/ 路径下存放有重要数据,则可以把它替换为其他任意路径,但此路径对运行 Nginx worker 进程的系统帐号至少有可读权限。我们在 location /test 中使用了 try_files 配置指令,并提供了三个参数,/foo、/bar/ 和 /baz. 根据前面对 try_files 指令的介绍,我们可以知道,它会在 try-files 阶段依次检查前两个参数 /foo 和 /bar/ 所对应的文件系统对象是否存在。

    假设现在 /var/www/ 路径下是空的,则第一个参数 /foo 映射成的文件 /var/www/foo 是不存在的;同样,对于第二个参数 /bar/ 所映射成的目录 /var/www/bar/ 也是不存在的。于是此时 Nginx 会在 try-files 阶段发起到最后一个参数所指定的 URI(即 /baz)的“内部跳转”。对于 try_files 的前 N-1 个参数,Nginx 只会检查文件系统,而不会去执行 URI 与 location 之间的匹配。

    $ curl localhost:8080/test
    baz
    

    当 try_files 指令处理到它的最后一个参数时,总是直接执行“内部跳转”,而不论其对应的文件系统对象是否存在。

    try_files 指令本质上只是有条件地改写当前请求的 URI,而这里说的“条件”其实就是文件系统上的对象是否存在。当“条件”都不满足时,它就会无条件地发起一个指定的“内部跳转”。当然,除了无条件地发起“内部跳转”之外, try_files 指令还支持直接返回指定状态码的 HTTP 错误页。

    try_files /foo /bar/ =404;
    

    还支持直接返回指定状态码的 HTTP 错误页,例如:

    这行配置是说,当 /foo 和 /bar/ 参数所对应的文件系统对象都不存在时,就直接返回 404 Not Found 错误页。注意这里它是如何使用等号字符前缀来标识 HTTP 状态码的。

    作者:T&D
    Q Q:335749143
    邮箱:tanda.arch#gmail.com(@替换#)
    出处:http://www.cnblogs.com/one-villager/
    * 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

  • 相关阅读:
    MyBatis 学习笔记
    JavaEE路径陷阱之getRealPath
    Java路径问题最终解决方案—可定位所有资源的相对路径寻址
    Hibernate4.3.10通过slf4j使用log4j
    Hibernate关联关系映射
    SpringMVC 学习笔记
    Spring 学习笔记
    Hibernate 学习笔记
    Struts2 学习笔记
    vue element tree组件,根据不同的状态显示不同的字体颜色
  • 原文地址:https://www.cnblogs.com/one-villager/p/nginx_directive_order.html
Copyright © 2020-2023  润新知