双层 nginx 转发下的 try_files 指令 301 返回错误的 location 地址

背景信息:
使用 http://example.com/path 地址,访问服务器 path 目录下的 index.html 文件。
在单层 nginx 下,被 301 到 http://example.com/path/ 地址,可正常访问。
在双层 nginx 下,被 301 到 http://example.com:1234/path/ 地址,会访问失败。
根据环境不同,被 301 返回的地址, 协议/域名/IP/端口 等,都可能不同。

nginx 配置:

配置说明:
1. 检查 path 文件是否存在,存在则响应文件内容。
2. 检查 path/ 目录是否存在,存在则响应 301 跳转至 path/ 地址。
2.1 在新的请求中,检查 path/ 目录是否存在,存在则检查 path/index.html 文件是否存在,存在则响应文件内容。
3. 检查 /index.html 文件是否在,存在则响应文件内容。
4. 响应 404 状态码。

问题解析:
在单层 nginx 下,响应的 协议/域名/IP/端口 等,与请求一致。
在双层 nginx 下,当第一层 nginx 修改 协议/域名/IP/端口 后,转发至第二层 nginx 处理。
第二层 nginx 响应的 301 跳转地址,是基于被修改后的请求的,而第一层 nginx 不会修改这个 301 响应,原样输出到客户端后,造成访问失败。

解决方案:
最简单的方案:修改 absolute_redirect 指令为 off ,但要小心相对 Location 的兼容性。
最合理的方案:修改访问 http://example.com/path 地址为直接访问 http://example.com/path/ 地址,避免触发 301 跳转,减少一次网络请求。
有缺陷的方案:修改 try_files 指令内容为以下内容。但会造成 html 页面中用 相对路径 引用的静态资源失效。

参考资料:
try_files
absolute_redirect
server_name_in_redirect
port_in_redirect
nginx-causes-301-redirect-if-theres-no-trailing-slash
how-to-stop-nginx-301-auto-redirect-when-trailing-slash-is-not-in-uri
Nginx的301重定向处理过程分析

简单数据结构路径描述语法

适用于 JSON 结构的 path 描述语法,用字符串形式便捷描述指定数据位置。

简单类型

基本的路径描述语法。

数值类型

字面量,如:
0
1
-1

字符串类型

字面量,可选双引号,如:
string
"string"

布尔类型

字面量,如:
true
false

对象类型

点号分割,可选双引号,如:
object.key
object."key"

数组类型

中括号分割,可选数值索引或对象索引,如:
[]
[0]
[-1]
[{key:value}]
[{"key":"value"}]

组合类型

多种路径描述语法组合使用。

多层对象

多个对象类型组合,如:
object.key
object.key.key

多维数组

多个数组类型组合,如:
[][]
[0][]
[][0]
[0][0]
[{key:value}][0]
[0][{key:value}]
[{key:value}][{key:value}]

对象和数组

对象和数组组合,如:
object.key[]
object.key[].key
object.key[{key:value}]
object.key[{key:value}].key
[].key
[{key:value}].key

(扩展)运算符

基本的数据运算语法,可选小括号。

算术运算符

+ - * / % 如:
(0 + 1 - 2) * 3 / 4 % 5
"string" + "string"

比较运算符

== != > >= < <= 如:
object.key == object.key

逻辑运算符

&& || ! 如:
object.key && object.key

位运算符

& | ~ ^ << >> 如:
object.key & object.key

Markdown

 

Semver

语义化版本控制规范(SemVer)
以下关键词 MUST、MUST NOT、REQUIRED、SHALL、SHALL NOT、SHOULD、SHOULD NOT、 RECOMMENDED、MAY、OPTIONAL 依照 RFC 2119 的叙述解读。

使用语义化版本控制的软件必须(MUST)定义公共 API。该 API 可以在代码中被定义或出现于严谨的文件内。无论何种形式都应该力求精确且完整。

标准的版本号必须(MUST)采用 X.Y.Z 的格式,其中 X、Y 和 Z 为非负的整数,且禁止(MUST NOT)在数字前方补零。X 是主版本号、Y 是次版本号、而 Z 为修订号。每个元素必须(MUST)以数值来递增。例如:1.9.1 -> 1.10.0 -> 1.11.0。

标记版本号的软件发行后,禁止(MUST NOT)改变该版本软件的内容。任何修改都必须(MUST)以新版本发行。

主版本号为零(0.y.z)的软件处于开发初始阶段,一切都可能随时被改变。这样的公共 API 不应该被视为稳定版。

1.0.0 的版本号用于界定公共 API 的形成。这一版本之后所有的版本号更新都基于公共 API 及其修改内容。

修订号 Z(x.y.Z | x > 0)必须(MUST)在只做了向下兼容的修正时才递增。这里的修正指的是针对不正确结果而进行的内部修改。

次版本号 Y(x.Y.z | x > 0)必须(MUST)在有向下兼容的新功能出现时递增。在任何公共 API 的功能被标记为弃用时也必须(MUST)递增。也可以(MAY)在内部程序有大量新功能或改进被加入时递增,其中可以(MAY)包括修订级别的改变。每当次版本号递增时,修订号必须(MUST)归零。

主版本号 X(X.y.z | X > 0)必须(MUST)在有任何不兼容的修改被加入公共 API 时递增。其中可以(MAY)包括次版本号及修订级别的改变。每当主版本号递增时,次版本号和修订号必须(MUST)归零。

先行版本号可以(MAY)被标注在修订版之后,先加上一个连接号再加上一连串以句点分隔的标识符来修饰。标识符必须(MUST)由 ASCII 字母数字和连接号 [0-9A-Za-z-] 组成,且禁止(MUST NOT)留白。数字型的标识符禁止(MUST NOT)在前方补零。先行版的优先级低于相关联的标准版本。被标上先行版本号则表示这个版本并非稳定而且可能无法满足预期的兼容性需求。范例:1.0.0-alpha、1.0.0-alpha.1、1.0.0-0.3.7、1.0.0-x.7.z.92。

版本编译信息可以(MAY)被标注在修订版或先行版本号之后,先加上一个加号再加上一连串以句点分隔的标识符来修饰。标识符必须(MUST)由 ASCII 字母数字和连接号 [0-9A-Za-z-] 组成,且禁止(MUST NOT)留白。当判断版本的优先层级时,版本编译信息可(SHOULD)被忽略。因此当两个版本只有在版本编译信息有差别时,属于相同的优先层级。范例:1.0.0-alpha+001、1.0.0+20130313144700、1.0.0-beta+exp.sha.5114f85。

版本的优先层级指的是不同版本在排序时如何比较。判断优先层级时,必须(MUST)把版本依序拆分为主版本号、次版本号、修订号及先行版本号后进行比较(版本编译信息不在这份比较的列表中)。由左到右依序比较每个标识符,第一个差异值用来决定优先层级:主版本号、次版本号及修订号以数值比较,例如:1.0.0 < 2.0.0 < 2.1.0 < 2.1.1。当主版本号、次版本号及修订号都相同时,改以优先层级比较低的先行版本号决定。例如:1.0.0-alpha < 1.0.0。有相同主版本号、次版本号及修订号的两个先行版本号,其优先层级必须(MUST)透过由左到右的每个被句点分隔的标识符来比较,直到找到一个差异值后决定:只有数字的标识符以数值高低比较,有字母或连接号时则逐字以 ASCII 的排序来比较。数字的标识符比非数字的标识符优先层级低。若开头的标识符都相同时,栏位比较多的先行版本号优先层级比较高。范例:1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-alpha.beta < 1.0.0-beta < 1.0.0-beta.2 < 1.0.0-beta.11 < 1.0.0-rc.1 < 1.0.0。

参考资料:
Semantic Versioning 2.0.0
npm semver
package semver
Understanding Maven Version Numbers
Maven Artifact
Use Java 9’s own builtin Version class
How does maven sort version numbers?

在 docker 中设置 node.js 时区

CI 使用 Docker 打包前端代码,流程中有使用 Shell 和 Node.js 两种方式输出时间。
Shell 脚本用 $(date ‘+%F %T’) 输出的是东 8 区字符串 2021-05-27 14:43:23
Node.js 用 new Date().toString() 输出的是 0 时区字符串 Thu May 27 2021 06:43:09 GMT+0000 (Coordinated Universal Time)

经验证,虽然已将宿主机 /etc/localtime 挂载至 Docker 同路径,但仍需要配置环境变量 TZ=Asia/Shanghai 才可生效。
似乎是 V8 引擎在浏览器环境与服务器环境的实现方案不一致造成的。该 TZ 参数暂时不支持 Windows 但很快就会支持。

另外,顺便发现 new Date() 在解析不带时区的时间字符串时,ES5 和 ES6 有不同的规范。

参考资料:
https://github.com/nodejs/node/issues/19480
https://github.com/nodejs/node/issues/28743
https://github.com/nodejs/node/issues/4230
https://github.com/nodejs/docker-node/issues/626
https://github.com/gliderlabs/docker-alpine/issues/136
https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
https://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html
https://docs.microsoft.com/en-us/cpp/c-runtime-library/time-management?view=msvc-160
https://github.com/nodejs/node/blob/4ebb88fea5a9d83f2c4ec566e958d735f1fca138/deps/v8/src/base/platform/platform-win32.cc#L131

Apple M1 CPU 踩到的坑

  1. Homebrew 安装后未生效
    解决方案:
    在安装后手动执行(注意安装日志最后输出的提示)
    echo ‘eval $(/opt/homebrew/bin/brew shellenv)’ >> ~/.zprofile
    参考资料:
    https://github.com/Homebrew/install/blob/master/install.sh#L745
  2. Node.js Check failed: allocator->SetPermissions
    解决方案:
    升级到 14.17.0 或 15.9.0
    参考资料:
    https://github.com/nodejs/node/issues/37061#issuecomment-770863819
    https://github.com/nodejs/node/issues/37061#issuecomment-780499544
    https://github.com/nodejs/node/pull/38507
    https://github.com/nodejs/node/pull/38051
    https://github.com/nodejs/node/issues/37309