讲透 Nginx Location 匹配的前前后后

1,032 阅读7分钟

Nginx的 location 指令用于匹配客户端请求的 URI,并定义了如何处理匹配成功的请求。它通常用于配置不同 URI 路径下的不同处理方式,如转发请求给后端服务器或提供静态文件服务。可根据需要配置多个 location 块以覆盖不同的URI匹配情况。

名词

  • 文件型 URI:指对服务器上某个文件的访问形式,通常不以斜杠 / 结尾,目的无非就是对文件的查看、下载等;

    例如 localhost/foo

  • 目录型 URI:指对服务器上某个目录的访问形式,通常以斜杠 / 结尾,目的无非就是浏览目录列表(目录索引服务)或目录(项目)中的默认页面;

    例如 localhost/foo/

  • Nginx 外部重定向:给客户端返回一个重定向 301 状态码的响应。需要客户端手动重定向,通常如果使用浏览器的话会自动跳转;

  • Nginx 内部重定向:Nginx 内部改写客户端访问的 URI,对改写后的 URI 进行重新匹配,客户端无感知;

配置语法

语法

location @name { ... }

location [ = | ~ | ~* | ^~ ] uri { ... }

默认值

上下文

server, location

命名 Location

使用 @ 命名一个 location,外部无法访问,只有在内部跳转时(例如 try_fileserror_page 等)使用。

例如:请求 URI 资源不存在时跳转 @notfound 命名 location,返回 404 状态码。

location /foo {
    root ...;
    try_files $uri $uri/ @notfound;
}

location @notfound {
    return 404;
}

匹配规则修饰符

  • 默认(空白):字符串前缀匹配,只要请求 URI 开头部分与配置 uri 相同即可;

  • ^~:字符串前缀匹配,与默认空白规则相似,只不过会阻断正则,可以理解 ^~ 为非正则,优先级比空白符更高;

  • =:精确匹配,请求 URI 与 配置 uri 全等;

  • :区分大小写的正则匹配,如果运行 Nginx 的系统(如 Windows)对大小写不敏感,那么 ~*~ 相同;

  • ~*:不区分大小写的正则匹配;

前缀按照长短匹配:前缀匹配按照匹配长度决定优先级,如果多个规则全部匹配,选择匹配长度最长的。

正则按照顺序匹配:正则匹配按照配置文件中的定义顺序,最先定义的优先级更高。

匹配流程描述

  1. 首先进行精确匹配(=),匹配成功则终止其余匹配,使用该规则。
  2. 进行字符串前缀匹配(空白符与 ^~),在所有满足前缀匹配的规则中选择匹配字符串最长的规则。
  3. 如果匹配字符串最长的规则的修饰符为 ^~,则终止正则匹配,使用该规则。
  4. 如果匹配字符串最长的规则的修饰符为空白符,缓存该规则,进行正则匹配。
  5. 正则匹配,顺序按照配置文件中定义的顺序进行匹配。正则匹配成功则使用该规则。
  6. 正字匹配失败则使用 4 中缓存的规则。

精确匹配对请求处理提速:例如,频繁请求 / ,可以定义 location = /,将加快对这些请求的处理,如果精确匹配成功,将会终止其他搜索。

流程图

匹配前

匹配规则后的响应逻辑

当前仅讨论一般情况(仅配置 rootalias,未配置 indextry_filesautoindex 等)。

  • uri 参数配置为 /foo 时,例如 location /foo { ... },Nginx 将 foo 视为文件目录

    如果规则匹配成功,Nginx 会查找名为 foo 的文件是否存在:

    • 存在:将该文件作为响应返回(200 状态码);

    • 不存在:查找名为 foo 的目录是否存在:

      • 存在:重定向(301 状态码)目录型的 URI /foo/

      • 不存在:返回未找到(404 状态码);

  • uri 参数配置为 /foo/ 时,例如 location /foo/ { ... },Nginx 将 foo 仅视为目录

    如果规则匹配成功,Nginx 会查找名为 foo 的目录是否存在:

    • 不存在:返回未找到(404 状态码);
    • 存在:将当前 URI 与默认索引 index 值(index.html)拼接为新的 URI 并对其进行内部重定向,重新查找匹配的规则
      • 不存在匹配规则:返回未找到(404 状态码);
      • 存在匹配规则:返回相应的处理结果;

对于 indexautoindextry_files 对一般情况的影响请往后看。

强调两个点

  • 当访问文件型 URI 匹配到目录时,Nginx 返回重定向(301 状态码),该重定向指向对应的目录型 URI,属于外部重定向,需要客户端自行重新访问。

    例如. 客户端访问 localhost/foo 的 URI,未找到 foo 文件,但匹配到 foo 的文件夹,于是 Nginx 会返回重定向(301 状态码),要求客户端重定向到 localhost/foo/

  • 当访问目录型 URI 成功匹配时,Nginx 重新拼接包含默认索引的 URI,在 Nginx 内部重新对新的 URI 进行规则匹配,属于内部重定向。

    例如. 客户端访问 localhost/foo/ 的 URI 并且匹配到 foo 文件夹,于是 Nginx 将 URI 与默认索引值重新拼接为 localhost/foo/index.html 并再次查找匹配规则,对于客户端来说,相当于一开始访问的就是 localhost/foo/index.html

流程图

匹配后

index 配置

语法

index file ...;

默认值

index index.html;

上下文

http, server, location

index 用于指定当请求的 URI 匹配到目录时 Nginx 应该返回的默认索引文件,文件名可以包含变量,文件按指定的顺序进行检查,列表的最后一个元素可以是具有绝对路径的文件

对一般情况的影响:配置 index 将会改变一般情况中拼接内部重定向的 URI 地址。

autoindex 配置

语法

autoindex on | off;

默认值

autoindex off;

上下文

http, server, location

autoindex 定义是否开启目录索引服务的配置项。

对一般情况的影响:开启后如果目录存在默认索引文件,则依然打开默认索引文件,如果不存在默认索引文件,则会展示目录列表。

try_files 配置

语法

try_files file ... uri;

try_files file ... =code;

默认值

上下文

server, location

try_files 按指定顺序检查配置的文件或目录(结尾 /)是否存在,配置值可以使用变量(例如 $uri)。所查找的文件或目录是基于 rootalias 路径,返回第一个找到的文件或目录,如果所有的文件或目录都不存在,会进行一个内部重定向到最后一个参数

最后一个参数可以是:

  • 一个内部重定向 URI,例如 /foo/index.html
  • 一个错误代码,例如 =404
  • 命名 location,例如 @foo

对一般情况的影响:当配置了 try_files,则不会查找客户端 URI 所请求的文件或目录是否存在,而是直接按照配置顺序查找 try_files 指定的文件或目录是否存在。当文件存在时响应该文件,当目录存在时对客户端响应外部重定向(301 状态码)至该目录。

使用 try_files 模拟一般情况:

location /foo {
	root ...;
	try_files $uri $uri/ =404;
}

上例中使用 $uri 变量模拟规则匹配后,先查找所请求的文件,如果文件不存在则查找目录,如果都不存在则响应未找到(404 状态码)。

FAQ

一个请求两条访问日志

当浏览器访问某 URI 时 access.log 中同时出现两条日志,为什么?

例如. 访问 localhost/foo 的日志

localhost - - [...] "GET /foo HTTP/1.1" 301 ...
localhost - - [...] "GET /foo/ HTTP/1.1" 200 ...

答:浏览器访问 localhost/foo,当匹配该 URI 的规则的 rootalias 下存在一个名为 foo 的目录而非文件时,Nginx 首先会将该 URI 转换为 localhost/foo/ 的目录型 URI 返回给浏览器一个重定向操作(也就是第一条访问日志,响应码 301)。浏览器接收到 Nginx 的重定向响应,通常会自动进行跳转,所以再次发起 localhost/foo/ 的请求(也就是第二条访问日志),根据再次匹配的结果进行响应(比如 200、404、403 等等)。

某些规则匹配成功但结果不尽人意

在某些情况下(下面会说明某些是什么)精确或正则匹配成功,却返回未找到(404 状态码)或意想不到的结果页面,为什么?

不妨尝试如下配置:

server {
    listen	80;
    server_name	localhost;

    location = /foo {
    	root /usr/share/nginx;
    }
}

目录结构:

/usr/share/nginx
└── foo
    └── index.html

当访问 localhost/foo 时,会返回未找到 404 状态码,如果将精确匹配的等号去掉,一切都正常了,是精确匹配不会查找默认页面吗?

答:访问 localhost/foo 后 Nginx 发现匹配的是 foo 目录,首先会返回 301 状态码的重定向至 localhost/foo/ 的响应,浏览器再次请求 localhost/foo/ 的目录型 URI,Nginx 内部重新拼接 URI 为 localhost/foo/index.htmlfoo 中确实存在 index.html,一切看似都很正常,为什么会未找到?别忘记配置文件中仅有一条精确匹配的规则,Nginx 对内部重定向的 localhost/foo/index.html 会进行重新匹配,没有匹配项,所以返回未找到 404 状态码。同理如果将 = /foo 替换为 ~ /foo$ 也是一样的结果。

那么是哪些特殊情况呢?你的规则存在无法匹配 Nginx 内部重定向 URI 的情况。所以使用精确匹配与固定字符结尾(固定字符+$)的正则匹配规则时,小心这一点。

本文为原创内容,版权所有 © 2024 姚生。欢迎转载,但请注明出处,并附上原文链接。未经授权不得用于商业用途。如有疑问,请联系作者。