NGINX Unit

配置§

/config 部分的 控制 API 处理 Unit 的常规配置,其中包含 监听器路由应用程序上游 等实体。

监听器§

若要接受请求,请在 config/listeners API 部分中添加一个监听器对象;该对象的名称可以是

  • 一个唯一的 IP 套接字:127.0.0.1:80[::1]:8080

  • 一个通配符,它匹配端口上的任何主机 IP:*:80

  • 在基于 Linux 的系统上,

    抽象 UNIX 套接字也可以使用:unix:@abstract_socket

注意

同样在基于 Linux 的系统上,由于内核施加的规则,通配符监听器不能与同一端口上的其他监听器重叠。例如,*:8080127.0.0.1:8080 冲突;特别是,这意味着 *:8080 不能立即127.0.0.1:8080 替换(反之亦然),而无需先将其删除。

Unit 将其收到的请求分派到监听器引用的目标。你可以将多个监听器插入一个目标,或使用一个监听器并在多个目标之间热交换。

可用的侦听器选项

选项

说明

pass(必需)

侦听器将传入请求传递到的目标。可能的备选方案

该值是 变量 - 插值;如果它在插值后与任何配置实体不匹配,则返回 404 “未找到” 响应。

forwarded

对象;配置客户端 IP 地址和协议 替换

tls

对象;定义 SSL/TLS 设置

在此,本地侦听器在端口 8300 接受请求,并通过 uri 变量 标识的 blogs 应用 目标 将其传递过去。端口 8400 上的通配符侦听器将任何主机 IP 上的请求中继到 main 路由

{
    "127.0.0.1:8300": {
        "pass": "applications/blogs$uri"
    },

    "*:8400": {
        "pass": "routes/main"
    }
}

此外,pass 值可以是 百分号编码。例如,你可以转义实体名称中的斜杠

{
    "listeners": {
         "*:80": {
             "pass": "routes/slashes%2Fin%2Froute%2Fname"
         }
    },

    "routes": {
         "slashes/in/route/name": []
    }
}

SSL/TLS 配置§

tls 对象提供以下选项

选项

说明

certificate(必需)

字符串或字符串数组;引用一个或多个 证书包,这些证书包已在之前上传,可通过侦听器实现安全通信。

conf_commands

对象;定义要为侦听器设置的 OpenSSL 配置命令

要拥有此选项,必须使用 OpenSSL 1.0.2+ 构建和运行 Unit

$ openssl version

      OpenSSL 1.1.1d  10 Sep 2019

此外,请确保你的 OpenSSL 版本支持此选项设置的命令。

session

对象;配置侦听器的 TLS 会话缓存和票证。

要使用您之前上传的证书包,请在tls对象的certificate选项中指定该证书包的名称

{
    "listeners": {
        "127.0.0.1:443": {
            "pass": "applications/wsgi-app",
            "tls": {
                "certificate": "bundle"
            }
        }
    }
}
配置多个包

从 1.23.0 版本开始,Unit 支持通过为certificate选项值提供证书包名称数组,在侦听器上配置服务器名称指示 (SNI)

{
    "*:443": {
        "pass": "routes",
        "tls": {
            "certificate": [
                "bundleA",
                "bundleB",
                "bundleC"
            ]
        }
    }
}
  • 如果连接客户端发送服务器名称,Unit 会使用匹配的证书包进行响应。

  • 如果名称匹配多个包,则完全匹配优先于通配符;如果这无济于事,则使用首先列出的包。

  • 如果没有匹配或未发送服务器名称,Unit 将使用列表中的第一个包。

要为侦听器设置自定义 OpenSSL 配置命令,请使用tls中的conf_commands对象

{
    "tls": {
        "certificate": "bundle",
        "conf_commands": {
            "ciphersuites": "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256",
            "minprotocol": "TLSv1.3"
        }
    }
}

tls中的session对象配置侦听器的会话设置

选项

说明

cache_size

整数;设置 TLS 会话缓存中的会话数。

默认值为0(禁用缓存)。

tickets

布尔值、字符串或字符串数组;配置 TLS 会话票证。

默认值为false(禁用票证)。

timeout

整数;设置 TLS 会话缓存的会话超时时间。

创建新会话时,其生命周期源自当前时间和timeout。如果请求缓存会话的时间超过其生命周期,则不会重复使用该会话。

默认值为300(5 分钟)。

示例

{
    "tls": {
        "certificate": "bundle",
        "session": {
            "cache_size": 10240,
            "timeout": 60,
            "tickets": [
                "k5qMHi7IMC7ktrPY3lZ+sL0Zm8oC0yz6re+y/zCj0H0/sGZ7yPBwGcb77i5vw6vCx8vsQDyuvmFb6PZbf03Auj/cs5IHDTYkKIcfbwz6zSU=",
                "3Cy+xMFsCjAek3TvXQNmCyfXCnFNAcAOyH5xtEaxvrvyyCS8PJnjOiq2t4Rtf/Gq",
                "8dUI0x3LRnxfN0miaYla46LFslJJiBDNdFiPJdqr37mYQVIzOWr+ROhyb1hpmg/QCM2qkIEWJfrJX3I+rwm0t0p4EGdEVOXQj7Z8vHFcbiA="
            ]
        }
    }
}

tickets选项的工作方式如下

  • 布尔值启用或禁用会话票证;使用true时,将使用随机会话票证密钥

    {
        "session": {
            "tickets": true
        }
    }
    
  • 字符串启用票证并显式设置会话票证密钥

    {
        "session": {
            "tickets": "IAMkP16P8OBuqsijSDGKTpmxrzfFNPP4EdRovXH2mqstXsodPC6MqIce5NlMzHLP"
        }
    }
    

    这可以在各个服务器之间共享密钥的情况下启用票证重复使用。

    共享密钥轮换

    如果多个 Unit 实例需要识别彼此颁发的票证(例如,在负载均衡器后面运行时),则它们应共享会话票证密钥。

    例如,考虑三台启用了 SSH 的服务器,分别名为unit*.example.com,已安装 Unit 并配置了相同的*:443侦听器。要在每台服务器上配置一组三个初始密钥

    SERVERS="unit1.example.com
    unit2.example.com
    unit3.example.com"
    
    KEY1=$(openssl rand -base64 48)
    KEY2=$(openssl rand -base64 48)
    KEY3=$(openssl rand -base64 48)
    
    for SRV in $SERVERS; do
        ssh root@$SRV  \
            curl -X PUT -d '["$KEY1", "$KEY2", "$KEY3"]' --unix-socket /path/to/control.unit.sock  \
                'https://127.0.0.1/config/listeners/*:443/tls/session/tickets/'
    done
    

    要在每台服务器上添加新密钥

    NEWKEY=$(openssl rand -base64 48)
    
    for SRV in $SERVERS; do
        ssh root@$SRV  \
            curl -X POST -d '\"$NEWKEY\"' --unix-socket /path/to/control.unit.sock  \
                'https://127.0.0.1/config/listeners/*:443/tls/session/tickets/'"
    done
    

    在添加新密钥后删除最旧的密钥

    for SRV in $SERVERS; do
        ssh root@$SRV  \
            curl -X DELETE --unix-socket /path/to/control.unit.sock  \
                'https://127.0.0.1/config/listeners/*:443/tls/session/tickets/0'
    done
    

    此方案可以在各个 Unit 实例之间安全地共享会话票证密钥。

    Unit 支持 AES256(80 字节密钥)或 AES128(48 字节密钥);字节应编码为 Base64

    $ openssl rand -base64 48
    
          LoYjFVxpUFFOj4TzGkr5MsSIRMjhuh8RCsVvtIJiQ12FGhn0nhvvQsEND1+OugQ7
    
    $ openssl rand -base64 80
    
          GQczhdXawyhTrWrtOXI7l3YYUY98PrFYzjGhBbiQsAWgaxm+mbkm4MmZZpDw0tkK
          YTqYWxofDtDC4VBznbBwTJTCgYkJXknJc4Gk2zqD1YA=
    
  • 与上述类似的字符串数组

    {
        "session": {
            "tickets": [
                "IAMkP16P8OBuqsijSDGKTpmxrzfFNPP4EdRovXH2mqstXsodPC6MqIce5NlMzHLP",
                "Ax4bv/JvMWoQG+BfH0feeM9Qb32wSaVVKOj1+1hmyU8ORMPHnf3Tio8gLkqm2ifC"
            ]
        }
    }
    

    单元使用这些密钥来解密想要恢复其会话状态的客户端提交的票证;最后一个密钥始终用于创建新的会话票证和更新之前创建的票证。

    注意

    空数组有效地禁用会话票证,与将票证设置为false相同。

IP,协议转发§

单元使用X-Forwarded-*标头字段启用转发对象及其选项

选项

说明

(必需)

字符串或字符串数组;定义基于地址的模式以获取受信任的地址。仅当请求的源 IP 是匹配项时才进行替换。

此处的特殊情况是“unix”字符串;它匹配任何 UNIX 域套接字。

client_ip

字符串;命名请求中预期的 HTTP 标头字段。它们应使用X-Forwarded-For格式,其中值是用逗号或空格分隔的 IPv4 或 IPv6 列表。

协议

字符串;定义在请求中查找的相关 HTTP 标头字段。单元希望它遵循X-Forwarded-Proto表示法,字段值本身为httphttpson

递归

布尔值;控制如何遍历client_ip字段。

默认值为false(无递归)。

注意

除了之外,转发对象还必须指定client_ip协议或两者。

警告

在 1.28.0 版本之前,单元提供了演变为转发client_ip对象

client_ip(1.28.0 之前)

转发(1.28.0 之后)

标头

client_ip

递归

递归

不适用

协议

此旧语法仍然有效,但最终将被弃用,但不会早于 1.30.0 版本。

当设置转发时,仅当请求的直接源 IP匹配选项时,单元才尊重相应的标头字段。请注意,它不仅可以使用子网,还可以使用任何基于地址的模式

{
    "forwarded": {
        "client_ip": "X-Forwarded-For",
        "source": [
            "198.51.100.1-198.51.100.254",
            "!198.51.100.128/26",
            "203.0.113.195"
        ]
    }
}

覆盖协议方案§

协议选项允许根据其指定的标头字段覆盖传入请求的协议方案。考虑以下转发配置

{
    "forwarded": {
        "protocol": "X-Forwarded-Proto",
        "source": [
            "192.0.2.0/24",
            "198.51.100.0/24"
        ]
    }
}

假设请求到达,带有以下标头字段

X-Forwarded-Proto: https

如果请求的源 IP 与 source 匹配,Unit 将把此请求视为 https 请求。

原始 IP 识别§

Unit 还支持使用 client_ip 选项识别客户端的原始 IP

{
    "forwarded": {
        "client_ip": "X-Forwarded-For",
        "recursive": false,
        "source": [
            "192.0.2.0/24",
            "198.51.100.0/24"
        ]
    }
}

假设一个请求到达,带有以下标头字段

X-Forwarded-For: 192.0.2.18
X-Forwarded-For: 203.0.113.195, 198.51.100.178

如果 recursive 设置为 false(默认值),Unit 将选择 client_ip 中命名的 最后一个 字段的最右地址作为请求的原始 IP。在本例中,对于来自 192.0.2.0/24 或 198.51.100.0/24 的请求,它设置为 198.51.100.178。

如果 recursive 设置为 true,Unit 将按相反顺序检查所有 client_ip 字段。每个字段从右到左遍历,直到找到第一个不受信任的地址;如果找到,则将其选为原始 IP。在具有 “recursive”: true 的上一个示例中,客户端 IP 将设置为 203.0.113.195,因为 198.51.100.178 也是受信任的;这简化了在多个反向代理后面工作。

路由§

config/routes 配置实体定义内部请求路由。它从 侦听器 接收请求,并通过 条件集 过滤它们,以便由 应用 处理,代理 到外部服务器或 在它们之间进行负载平衡,使用 静态内容 提供服务,回答 任意状态代码,或 重定向

在最简单的形式中,routes 是定义单个路由的数组

{
     "listeners": {
         "*:8300": {
             "pass": "routes"
         }
     },

     "routes": [
         "..."
     ]
}

另一种形式是包含一个或多个命名路由数组作为成员的对象

{
     "listeners": {
         "*:8300": {
             "pass": "routes/main"
         }
     },

     "routes": {
         "main": [
             "..."
         ],

         "route66": [
             "..."
         ]
     }
}

路由步骤§

一个 route 数组包含步骤对象作为元素;它们接受以下选项

选项

说明

action(必需)

对象;定义如何 处理 匹配请求。

match

对象;定义要匹配的步骤的 条件

传递给路由的请求按顺序遍历其步骤

  • 如果步骤中的所有 match 条件都得到满足,则遍历结束,并且执行步骤的 action

  • 如果步骤的条件未得到满足,则 Unit 继续执行路由的下一个步骤。

  • 如果路由的任何步骤都不匹配,则返回 404“未找到”响应。

警告

如果步骤省略 match 选项,则其 action 会自动发生。因此,每个路由最多使用一个此类步骤,始终将其放在最后,以避免潜在的路由问题。

临时示例

一个基本示例

{
    "routes": [
        {
            "match": {
                "host": "example.com",
                "scheme": "https",
                "uri": "/php/*"
            },

            "action": {
                "pass": "applications/php_version"
            }
        },
        {
            "action": {
                "share": "/www/static_version$uri"
            }
        }
    ]
}

此路由将所有 HTTPS 请求传递到 example.com 网站的 /php/ 子部分,传递到 php_version 应用。所有其他请求都使用 /www/static_version/ 目录中的静态内容提供服务。如果没有匹配的内容,则返回 404“未找到”响应。

一个更详细的示例,其中包含链接路由和代理

{
    "routes": {
        "main": [
            {
                "match": {
                    "scheme": "http"
                },

                "action": {
                    "pass": "routes/http_site"
                }
            },
            {
                "match": {
                    "host": "blog.example.com"
                },

                "action": {
                    "pass": "applications/blog"
                }
            },
            {
                "match": {
                    "uri": [
                        "*.css",
                        "*.jpg",
                        "*.js"
                    ]
                },

                "action": {
                    "share": "/www/static$uri"
                }
            }
        ],

        "http_site": [
            {
                "match": {
                    "uri": "/v2_site/*"
                },

                "action": {
                    "pass": "applications/v2_site"
                }
            },
            {
                "action": {
                    "proxy": "http://127.0.0.1:9000"
                }
            }
        ]
    }
}

在此处,明确定义了一个名为 main 的路由,因此 routes 是一个对象,而不是一个数组。路由的第一步将所有 HTTP 请求传递到 http_site 应用。第二步将所有目标为 blog.example.com 的请求传递到 blog 应用。最后一步为 /www/static/ 目录中的特定文件类型提供服务。如果没有任何步骤匹配,则返回 404“未找到”响应。

匹配条件§

路由步骤match 对象中的条件定义了要与请求的属性进行比较的模式

属性

与之匹配的模式

大小写敏感

参数

通过请求的 查询字符串 提供的参数;这些名称和值对已 百分比解码,且加号 (+) 已替换为空格。

Cookie

通过请求提供的 Cookie。

目标

请求的目标 IP 地址和可选端口。

标头

通过请求提供的 标头字段

主机

主机 标头字段,已转换为小写并通过移除端口号和尾随句点(如果有)进行规范化。

方法

请求行中的 方法,已大写。

查询

查询字符串,已 百分比解码,且加号 (+) 已替换为空格。

方案

URI 方案。仅接受两种模式,即 httphttps

请求的源 IP 地址和可选端口。

URI

请求目标,已 百分比解码并通过移除 查询字符串 和解析 相对引用(“.” 和 “..”、“//”)进行规范化。

参数与查询

参数查询均在查询字符串上操作,但 查询与整个字符串匹配,而 参数仅考虑键值对,例如 key1=4861&key2=a4f3

使用 参数根据查询字符串中的键值对定义条件

"arguments": {
   "key1": "4861",
   "key2": "a4f3"
}

参数顺序无关紧要:key1=4861&key2=a4f3key2=a4f3&key1=4861 被视为相同。此外,参数的多个出现都必须匹配,因此 key=4861&key=a4f3 匹配此内容

"arguments":{
    "key": "*"
}

但不是这样

"arguments":{
    "key": "a*"
}

相反,如果你的条件涉及查询字符串,但并不依赖于键值对,请使用查询

"query": [
    "utf8",
    "utf16"
]

这仅匹配形式为https://example.com?utf8https://example.com?utf16的查询字符串。

匹配解析§

要匹配,属性必须满足两个要求

  • 如果存在没有取反(!前缀)的模式,则其中至少一个与属性值匹配。

  • 没有取反模式与属性值匹配。

正式说明

此逻辑可以用集合运算来描述。假设集合U包含属性的所有可能值;集合P包含与任何没有取反的模式匹配的字符串;集合N包含与任何基于取反的模式匹配的字符串。在此方案中,匹配集合为

UP \ N 如果P ≠ ∅
U \ N 如果P = ∅

此处,请求的 URI 必须符合pattern3,但不能匹配pattern1pattern2

{
    "match": {
        "uri": [
            "!pattern1",
            "!pattern2",
            "pattern3"
        ]
    },

    "action": {
        "pass": "..."
    }
}

此外,特殊的匹配逻辑适用于参数cookie。其中每一个都可以是列出自定义命名属性及其模式的单个对象,或此类对象的数组。

要匹配单个对象,请求必须匹配对象中命名的所有属性。要匹配对象数组,匹配其项目对象中的任何单个对象就足够了。以下条件仅在请求参数包括arg1arg2,并且两者都匹配其模式时才匹配

{
    "match": {
        "arguments": {
            "arg1": "pattern",
            "arg2": "pattern"
        }
    },

    "action": {
        "pass": "..."
    }
}

对于对象数组,如果请求的参数包括匹配相应模式的arg1arg2(或两者),则条件匹配

{
    "match": {
        "arguments": [
            {
                "arg1": "pattern"
            },
            {
                "arg2": "pattern"
            }
        ]
    },

    "action": {
        "pass": "..."
    }
}

以下示例结合了所有匹配类型。此处,hostmethoduriarg1arg2cookie1cookie2以及header1header2header3必须匹配,才能执行操作host & method & uri & arg1 & arg2 & (cookie1 | cookie2) & (header1 | (header2 & header3))

{
    "match": {
        "host": "pattern",
        "method": "!pattern",
        "uri": [
            "pattern",
            "!pattern"
        ],

        "arguments": {
            "arg1": "pattern",
            "arg2": "!pattern"
        },

        "cookies": [
            {
                "cookie1": "pattern",
            },
            {
                "cookie2": "pattern",
            }
        ],

        "headers": [
            {
                "header1": "pattern",
            },
            {
                "header2": "pattern",
                "header3": "pattern"
            }
        ]
    },

    "action": {
        "pass": "..."
    }
}
对象模式示例

这需要mode=strict和 URI 查询中除access=full之外的任何access参数

{
    "match": {
        "arguments": {
            "mode": "strict",
            "access": "!full"
        }
    },

    "action": {
        "pass": "..."
    }
}

这匹配使用gzip并识别为Mozilla/5.0或将curl列为用户代理的请求

{
    "match": {
        "headers": [
            {
                "Accept-Encoding": "*gzip*",
                "User-Agent": "Mozilla/5.0*"
            },
            {
                "User-Agent": "curl*"
            }
        ]
    },

    "action": {
        "pass": "..."
    }
}

模式语法§

单个模式可以基于地址(目标)或基于字符串(其他属性)。

基于字符串的模式必须将属性与字符匹配;通配符或正则表达式修改此行为

  • 通配符模式可能包含任意组合的通配符(*),每个通配符代表任意数量的字符:How*s*that*to*you

  • 正则表达式模式以波浪号(~)开头:~^\d+\.\d+\.\d+\.\d+(转义反斜杠是JSON 要求)。正则表达式是PCRE风格的。

Percent encoding in arguments, query, and URI patterns

Argument names, non-regex string patterns in arguments, query, and uri can be percent encoded to mask special characters (! is %21, ~ is %7E, * is %2A, % is %25) or even target single bytes. For example, you can select diacritics such as Ö or Å by their starting byte 0xC3 in UTF-8:

{
    "match": {
        "arguments": {
            "word": "*%C3*"
        }
    },

    "action": {
        "pass": "..."
    }
}

Unit decodes such strings and matches them against respective request entities, decoding these as well:

{
    "routes": [
        {
            "match": {
                "query": "%7Efuzzy word search"
            },

            "action": {
                "return": 200
            }
        }
    ]
}

This condition matches the following percent-encoded request:

$ curl http://127.0.0.1/?~fuzzy%20word%20search -v

      > GET /?~fuzzy%20word%20search HTTP/1.1
      ...
      < HTTP/1.1 200 OK
      ...

Note that the encoded spaces (%20) in the request match their unencoded counterparts in the pattern; vice versa, the encoded tilde (%7E) in the condition matches ~ in the request.

String pattern examples

A regular expression that matches any .php files in the /data/www/ directory and its subdirectories. Note the backslashes; escaping is a JSON-specific requirement:

{
    "match": {
        "uri": "~^/data/www/.*\\.php(/.*)?$"
    },

    "action": {
        "pass": "..."
    }
}

Only subdomains of example.com match:

{
    "match": {
        "host": "*.example.com"
    },

    "action": {
        "pass": "..."
    }
}

Only requests for .php files located in /admin/’s subdirectories match:

{
    "match": {
        "uri": "/admin/*/*.php"
    },

    "action": {
        "pass": "..."
    }
}

Here, any eu- subdomains of example.com match except eu-5.example.com:

{
    "match": {
        "host": [
            "eu-*.example.com",
            "!eu-5.example.com"
        ]
    },

    "action": {
        "pass": "..."
    }
}

Any methods match except HEAD and GET:

{
    "match": {
        "method": [
            "!HEAD",
            "!GET"
        ]
    },

    "action": {
        "pass": "..."
    }
}

You can also combine certain special characters in a pattern. Here, any URIs match except the ones containing /api/:

{
    "match": {
        "uri": "!*/api/*"
    },

    "action": {
        "pass": "..."
    }
}

Here, URIs of any articles that don’t look like YYYY-MM-DD dates match. Again, note the backslashes; they are a JSON requirement:

{
    "match": {
        "uri": [
            "/articles/*",
            "!~/articles/\\d{4}-\\d{2}-\\d{2}"
        ]
    },

    "action": {
        "pass": "..."
    }
}

基于地址的模式定义单个 IPv4(点分十进制或CIDR)、IPv6(十六进制或CIDR)或任何UNIX 域套接字地址,这些地址必须与属性完全匹配;通配符和范围修改此行为

  • 通配符(*)只能匹配任意 IP(*:<port>)。

  • 范围(-)适用于 IP(采用各自的表示法)和端口(<start_port>-<end_port>)。

Address-based allow-deny lists

Addresses come in handy when implementing an allow-deny mechanism with routes, for instance:

"routes": [
    {
        "match": {
            "source": [
                "!192.168.1.1",
                "!10.1.1.0/16",
                "192.168.1.0/24",
                "2001:0db8::/32"
            ]
        },

        "action": {
            "share": "/www/data$uri"
        }
    }
]

See here for details of pattern resolution order; this corresponds to the following nginx directive:

location / {
    deny  10.1.1.0/16;
    deny  192.168.1.1;
    allow 192.168.1.0/24;
    allow 2001:0db8::/32;
    deny  all;

    root /www/data;
}
Address pattern examples

This uses IPv4-based matching with wildcards and ranges:

{
    "match": {
        "source": [
            "192.0.2.1-192.0.2.200",
            "198.51.100.1-198.51.100.200:8000",
            "203.0.113.1-203.0.113.200:8080-8090",
            "*:80"
        ],

        "destination": [
            "192.0.2.0/24",
            "198.51.100.0/24:8000",
            "203.0.113.0/24:8080-8090",
            "*:80"
        ]
    },

    "action": {
        "pass": "..."
    }
}

This uses IPv6-based matching with wildcards and ranges:

{
    "match": {
        "source": [
             "2001:0db8::-2001:0db8:aaa9:ffff:ffff:ffff:ffff:ffff",
             "[2001:0db8:aaaa::-2001:0db8:bbbb::]:8000",
             "[2001:0db8:bbbb::1-2001:0db8:cccc::]:8080-8090",
             "*:80"
        ],

        "destination": [
             "2001:0db8:cccd::/48",
             "[2001:0db8:ccce::/48]:8000",
             "[2001:0db8:ccce:ffff::/64]:8080-8090",
             "*:80"
        ]
    },

    "action": {
        "pass": "..."
    }
}

This matches any of the listed IPv4 or IPv6 addresses:

{
    "match": {
        "destination": [
            "127.0.0.1",
            "192.168.0.1",
            "::1",
            "2001:0db8:1::c0a8:1"
        ]
    },

    "action": {
        "pass": "..."
    }
}

Here, any IPs from the range match except 192.0.2.9:

{
    "match": {
        "source": [
            "192.0.2.1-192.0.2.10",
            "!192.0.2.9"
        ]
    },

    "action": {
        "pass": "..."
    }
}

This matches any IPs but limits the acceptable ports:

{
    "match": {
        "source": [
            "*:80",
            "*:443",
            "*:8000-8080"
        ]
    },

    "action": {
        "pass": "..."
    }
}

This matches any UNIX domain sockets:

{
    "match": {
        "source": "unix"
    },

    "action": {
        "pass": "..."
    }
}

处理操作§

如果请求匹配路由步骤的所有条件或步骤本身省略匹配对象,Unit 将使用相应的操作处理请求。互斥的操作类型为

选项

说明

详细信息

pass

请求的目标,与侦听器的pass选项相同。

监听器

proxy

HTTP 服务器的套接字地址,请求将代理到该服务器。

代理

return

HTTP 状态代码,带有上下文相关的重定向位置。

即时响应、重定向

share

使用静态内容为请求提供服务的的文件路径。

静态文件

以下其他选项适用于其中任何操作

选项

说明

详细信息

response_headers

更新即将响应的标头字段。

响应头

rewrite

更新了请求 URI,保留查询字符串。

URI 重写

一个示例

{
    "routes": [
        {
            "match": {
                "uri": [
                    "/v1/*",
                    "/v2/*"
                ]
            },

            "action": {
                "rewrite": "/app/$uri",
                "pass": "applications/app"
            }
        },
        {
            "match": {
                "uri": "~\\.jpe?g$"
            },

            "action": {
                "share": [
                    "/var/www/static$uri",
                    "/var/www/static/assets$uri"
                 ],

                "fallback": {
                     "pass": "upstreams/cdn"
                }
            }
        },
        {
            "match": {
                "uri": "/proxy/*"
            },

            "action": {
                "proxy": "http://192.168.0.100:80"
            }
        },
        {
            "match": {
                "uri": "/return/*"
            },

            "action": {
                "return": 301,
                "location": "https://www.example.com"
            }
        }
    ]
}

变量§

Unit 配置中的一些选项允许使用变量,其值在运行时计算。有许多内置变量可用

变量

说明

arg_*cookie_*header_*

存储请求参数、Cookie 和头字段的变量,例如 arg_queryTimeoutcookie_sessionIdheader_Accept_Encodingheader_* 变量的名称不区分大小写。

body_bytes_sent

响应正文中发送的字节数。

dollar

文字美元符号 ($),用于转义。

header_referer

Referer 请求头字段的内容。

header_user_agent

User-Agent 请求头字段的内容。

主机

主机 标头字段,已转换为小写并通过移除端口号和尾随句点(如果有)进行规范化。

方法

方法来自请求行。

remote_addr

请求的远程 IP 地址。

request_id

包含使用随机数据生成的字符串。可用作唯一的请求标识符。

request_line

整个请求行

request_time

请求处理时间(以毫秒为单位),格式如下:1.234

request_uri

请求目标路径包括查询,通过解析相对路径引用(“.”和“..”)并折叠相邻斜杠进行规范化。

response_header_*

存储响应头字段的变量,例如 response_header_content_type。这些变量的名称不区分大小写。

status

响应的 HTTP状态代码

time_local

本地时间,格式如下:31/Dec/1986:19:40:00 +0300

URI

请求目标路径不带查询部分,通过解析相对路径引用(“.”和“..”)并折叠相邻斜杠进行规范化。该值是百分比解码:Unit 插值请求目标路径中的所有百分比编码实体。

这些变量可与

要引用变量,请在其名称前加上美元符号字符 ($),可以选择用大括号 ({}) 将名称括起来,以将其与相邻文本分隔开或增强可见性。变量名称可以包含字母和下划线 (_),因此如果变量紧跟此类字符,请使用括号

{
    "listeners": {
        "*:80": {
            "pass": "routes/${method}_route"
        }
    },

    "routes": {
        "GET_route": [
            {
                "action": {
                    "return": 201
                }
            }
        ],

        "PUT_route": [
            {
                "action": {
                    "return": 202
                }
            }
        ],

        "POST_route": [
            {
                "action": {
                    "return": 203
                }
            }
        ]
    }
}

要引用arg_*cookie_*header_*变量,请将所需名称添加到前缀。查询字符串Type=car&Color=red产生两个变量,$arg_Type$arg_Color;Unit 还对标头字段名称中的大小写和连字符进行标准化,因此Accept-Encoding标头字段也可以称为$header_Accept_Encoding$header_accept-encoding$header_accept_encoding

注意

对于多个参数实例(想想Color=Red&Color=Blue),使用最右边的参数(Blue)。

在运行时,变量会扩展为动态计算的值(由您承担风险!)。前面的示例针对一整套路由,从传入请求的 HTTP 动词中挑选出单个请求

$ curl -i -X GET https://127.0.0.1

    HTTP/1.1 201 Created

$ curl -i -X PUT https://127.0.0.1

    HTTP/1.1 202 Accepted

$ curl -i -X POST https://127.0.0.1

    HTTP/1.1 203 Non-Authoritative Information

$ curl -i --head https://127.0.0.1  # Bumpy ride ahead, no route defined

    HTTP/1.1 404 Not Found

如果您引用不存在的变量,则将其视为为空。

示例

此配置根据请求的主机名选择静态文件位置;如果找不到任何内容,它将尝试从公共存储中检索请求的文件

{
    "listeners": {
        "*:80": {
            "pass": "routes"
        }
    },

    "routes": [
        {
            "action": {
                "share": [
                    "/www/$host$uri",
                    "/www/storage$uri"
                ]
            }
        }
    ]
}

另一个用例是使用 URI 在应用程序之间进行选择

{
    "listeners": {
        "*:80": {
            "pass": "applications$uri"
        }
    },

    "applications": {
        "blog": {
            "root": "/path/to/blog_app/",
            "script": "index.php"
        },

        "sandbox": {
            "type": "php",
            "root": "/path/to/sandbox_app/",
            "script": "index.php"
        }
    }
}

通过这种方式,请求通过其目标 URI 在应用程序之间路由

$ curl https://127.0.0.1/blog     # Targets the 'blog' app
$ curl https://127.0.0.1/sandbox  # Targets the 'sandbox' app

另一种方法将从客户端接收到的Host标头字段用于相同用途

{
    "listeners": {
        "*:80": {
            "pass": "applications/$host"
        }
    },

    "applications": {
        "localhost": {
            "root": "/path/to/admin_section/",
            "script": "index.php"
        },

        "www.example.com": {
            "type": "php",
            "root": "/path/to/public_app/",
            "script": "index.php"
        }
    }
}

您可以在字符串中使用多个变量,任意重复和放置它们。此配置根据请求的主机名和 URI 选择一个应用程序目标(支持 PHPPython 应用程序)

{
    "listeners": {
        "*:80": {
            "pass": "applications/app_$host$uri"
        }
    }
}

在运行时,对 example.com/myapp 的请求将传递到 applications/app_example.com/myapp

根据 app_session cookie 选择共享目录

{
    "action": {
        "share": "/data/www/$cookie_app_session"
    }
}

在此处,如果 share 中的 $uri 解析为目录,则由 index 决定要提供的索引文件的选择

{
    "action": {
        "share": "/www/data$uri",
        "index": "index.htm"
    }
}

在此处,重定向使用 $request_uri 变量值将请求(包括查询部分)中继到通过 HTTPS 的同一网站

{
    "action": {
        "return": 301,
        "location": "https://$host$request_uri"
    }
}

URI 重写§

所有路由步骤 操作 都支持 rewrite 选项,该选项在应用操作之前更新传入请求的 URI。它不会影响 查询,但会更改 uri$request_uri 变量

match-less 操作使用 /v1 为请求 URI 加前缀,并将其返回到路由

{
    "action": {
        "rewrite": "/v1$uri",
        "pass": "routes"
    }
}

警告

将请求 passroutes 时,避免无限循环。

此操作对请求 URI 进行规范化,并将其传递到应用程序

{
    "match": {
        "uri": [
            "/fancyAppA",
            "/fancyAppB"
        ]
    },

    "action": {
        "rewrite": "/commonBackend",
        "pass": "applications/backend"
    }
}

响应头§

所有路由步骤 操作 都支持 response_headers 选项,该选项在执行操作之前更新 Unit 响应的头字段

{
    "action": {
        "share": "/www/static/$uri",
        "response_headers": {
            "Cache-Control": "max-age=60, s-maxage=120",
            "CDN-Cache-Control": "max-age=600"
        }
    }
}

这仅适用于 2XX3XX 响应;此外,无法设置 DateServerContent-Length

此选项为 Unit 将为特定请求发送的响应的头字段设置给定的字符串值

  • 如果没有与名称关联的头字段(不分大小写),则设置该值。

  • 如果已设置具有此名称的头字段,则更新其值。

  • 如果为该值提供 null,则删除头字段。

如果执行操作并且 Unit 发出响应,则它将发送此特定操作指定的头字段。在请求的整个路由路径中的最后一个操作才会影响最终的响应头。

这些值支持 变量模板文字,从而支持任意的运行时逻辑

"response_headers": {
    "Content-Language": "`${ uri.startsWith('/uk') ? 'en-GB' : 'en-US' }`"
}

最后,还有 response_header_* 变量,它们评估为通过响应(由应用程序、上游或 Unit 本身设置)设置的头字段值;后者适用于 $response_header_connection$response_header_content_length$response_header_transfer_encoding

一种用途是更新最终响应中的头;这会扩展应用程序发出的 Content-Type

"action": {
    "pass": "applications/converter",
        "response_headers": {
            "Content-Type": "${response_header_content_type};charset=iso-8859-1"
        }
    }
}

或者,它们将与自定义日志格式一起派上用场。

即时响应、重定向§

您可以使用路由步骤操作来立即使用任意HTTP 状态代码处理某些条件。

{
    "match": {
        "uri": "/admin_console/*"
    },

    "action": {
        "return": 403
    }
}

return 操作提供以下选项

return(必需)

整数 (000–999);定义要返回的 HTTP 响应状态代码。

位置

字符串 URI;如果return 值暗示重定向,则使用。

根据其预期的语义使用代码;如果您使用自定义代码,请确保用户代理可以理解它们。

如果您指定重定向代码 (3xx),请使用return 旁边的location 选项提供目标

{
    "action": {
        "return": 301,
        "location": "https://www.example.com"
    }
}

除了丰富响应语义之外,return 还简化了允许-拒绝列表:无需使用过滤器保护每个操作,而是添加条件来尽早拒绝不需要的请求,例如

"routes": [
    {
        "match": {
            "scheme": "http"
        },

        "action": {
            "return": 403
        }
    },
    {
        "match": {
            "source": [
                "!192.168.1.1",
                "!10.1.1.0/16",
                "192.168.1.0/24",
                "2001:0db8::/32"
            ]
        },

        "action": {
            "return": 403
        }
    }
]

静态文件§

Unit 能够充当独立的 Web 服务器,有效地从本地文件系统提供静态文件;要使用此功能,请列出路由步骤操作share 选项中的文件路径。

基于share 的操作提供以下选项

share(必需)

字符串或字符串数组;列出文件路径,直到找到文件。如果找不到文件,则如果设置了fallback,则使用它。

该值是变量内插的。

索引

文件名;如果share 是目录,则尝试。如果找不到文件,则如果设置了fallback,则使用它。

默认值为index.html

后备

类似于操作的对象;如果请求无法由shareindex 提供服务,则使用它。

类型

数组MIME 类型模式;用于过滤共享文件。

chroot

目录路径名,限制可共享路径。

该值是变量内插的。

follow_symlinkstraverse_mounts

布尔值;分别打开和关闭符号链接和挂载点解析;如果设置了chroot,它们只影响chroot的内部。

两个选项的默认值为true(解析链接和挂载)。

注意

为了提供文件,Unit 的路由器进程必须能够访问它们;因此,此进程运行的帐户必须具有适当的权限分配。当从官方软件包安装 Unit 时,该进程以unit:unit运行;有关其他安装方法的详细信息,请参阅安装

考虑以下配置

{
    "listeners": {
        "*:80": {
            "pass": "routes"
        }
     },

    "routes": [
        {
            "action": {
                "share": "/www/static/$uri"
            }
        }
    ]
}

它使用变量插值:Unit 用其当前值替换$uri引用,并尝试生成路径。如果这没有产生可服务的 file,则返回 404 “未找到”响应。

警告

在 1.26.0 版本之前,Unit 使用share作为文档根目录。这是为了灵活性而更改的,所以现在share必须解析为特定文件。一个常见的解决方案是将$uri附加到文档根目录。

在 1.26 之前,上面的代码段看起来像这样

"action": {
    "share": "/www/static/"
}

请注意,URI 路径总是以斜杠开头,因此无需将目录与$uri分开;即使你这样做,Unit 也会在路径解析期间压缩相邻的斜杠,因此不会出现问题。

如果share是一个数组,则按出现顺序搜索其项,直到找到可服务的 file

"share": [
    "/www/$host$uri",
    "/www/error_pages/not_found.html"
]

此代码段首先尝试基于$host的目录;如果在那里找不到合适的文件,则尝试not_found.html文件。如果两者都无法访问,则返回 404 “未找到”响应。

最后,如果文件路径指向目录,Unit 会尝试从中提供index指示的文件。假设我们有以下目录结构和共享配置

/www/static/
├── ...
└──default.html
"action": {
    "share": "/www/static$uri",
    "index": "default.html"
}

以下请求返回default.html,即使该文件没有明确命名

$ curl https://127.0.0.1/ -v

 ...
 < HTTP/1.1 200 OK
 < Last-Modified: Fri, 20 Sep 2021 04:14:43 GMT
 < ETag: "5d66459d-d"
 < Content-Type: text/html
 < Server: Unit/1.32.1
 ...

注意

Unit 的 ETag 响应头字段使用MTIME-FILESIZE格式,其中MTIME表示文件修改时间戳,FILESIZE表示文件大小(以字节为单位),两者均为十六进制。

MIME 过滤§

若要按 MIME 类型过滤 共享 提供的文件,请定义一个字符串模式的 types 数组。它们的工作方式类似于 路由模式,但会与每个文件的 MIME 类型进行比较;仅当请求是 匹配项 时才会提供该请求

{
    "share": "/www/data/static$uri",
    "types": [
        "!text/javascript",
        "!text/css",
        "text/*",
        "~video/3gpp2?"
    ]
}

此示例配置使用 否定 阻止 JS 和 CSS 文件,但允许所有其他基于文本的 MIME 类型使用 通配符模式。此外,.3gpp.3gpp2 文件类型由 正则表达式模式 允许。

如果没有 MIME 类型与请求匹配,则会返回 403 “禁止”响应。你可以将该行为与 后备 选项配对,当返回 40x 响应时将调用该选项。

{
    "share": "/www/data/static$uri",
    "types": ["image/*", "font/*", "text/*"],
    "response_headers": {
        "Cache-Control": "max-age=1209600"
    },
    "fallback": {
        "share": "/www/data/static$uri",
    }
}

在此,对图像、字体和任何基于文本的文件的所有请求都将向响应中添加缓存控制标头。任何其他请求仍将提供文件,但这次不带标头。这对于提供不会更改的常见网页资源非常有用;网络浏览器和代理会收到通知,告知应缓存此内容。

如果请求的文件的 MIME 类型无法识别,则将其视为为空 (“”)。因此,“!”模式(“拒绝空字符串”)可用于将所有文件类型 未知 限制为 Unit

{
    "share": "/www/data/known-types-only$uri",
    "types": [
        "!"
    ]
}

如果共享路径仅指定目录名称,则 Unit 不会应用 MIME 过滤。

路径限制§

注意

若要拥有这些选项,必须在运行 Linux 内核版本 5.6+ 的系统上构建并运行 Unit。

chroot 选项将共享中的路径解析限制在某个目录中。首先,它会影响符号链接:任何尝试使用相对符号链接(如 ../../var/log)向上移动目录树的操作都会在 chroot 目录中停止,并且绝对符号链接会被视为相对于此目录以避免中断

{
    "action": {
        "share": "/www/data$uri",
        "chroot": "/www/data/"
    }
}

在此,对 /log 的请求最初解析为 /www/data/log;但是,如果那是到 /var/log/app.log 的绝对符号链接,则结果路径为 /www/data/var/log/app.log

另一个影响是,禁止对解析到 chroot 目录之外的路径的任何请求

{
    "action": {
        "share": "/www$uri",
        "chroot": "/www/data/"
    }
}

在此,对 /index.xml 的请求会引发 403 “Forbidden” 响应,因为它解析为 /www/index.xml,而该文件位于 chroot 之外。

follow_symlinkstraverse_mounts 选项设置为 false 时,禁用符号链接解析和装载点遍历(两者均默认为 true

{
    "action": {
        "share": "/www/$host/static$uri",
        "follow_symlinks": false,
        "traverse_mounts": false
    }
}

在此,整个 share 路径中的任何符号链接或装载点都会导致 403 “Forbidden” 响应。

在设置了 chroot 的情况下,follow_symlinkstraverse_mounts 仅影响 chroot 之后 路径的部分

{
    "action": {
        "share": "/www/$host/static$uri",
        "chroot": "/www/$host/",
        "follow_symlinks": false,
        "traverse_mounts": false
    }
}

在此,www/ 和内插的 $host 可以是符号链接或装载点,但它们之后的任何符号链接和装载点(包括 static/ 部分)都不会被解析。

详细信息

假设你想提供来自共享的文件,而该共享本身包含符号链接(我们假设 $host 始终解析为 localhost,并在我们的示例中使其成为符号链接),但禁用共享内的任何符号链接。

初始配置

{
    "action": {
        "share": "/www/$host/static$uri",
        "chroot": "/www/$host/"
    }
}

创建到 /www/localhost/static/index.html 的符号链接

$ mkdir -p /www/localhost/static/ && cd /www/localhost/static/
$ cat > index.html << EOF

      > index.html
      > EOF

$ ln -s index.html /www/localhost/static/symlink

如果启用了符号链接解析(无论是否使用 chroot),则针对符号链接的请求有效

$ curl https://127.0.0.1/index.html

      index.html

$ curl https://127.0.0.1/symlink

      index.html

现在将 follow_symlinks 设置为 false

{
    "action": {
        "share": "/www/$host/static$uri",
        "chroot": "/www/$host/",
        "follow_symlinks": false
    }
}

符号链接请求被禁止,这可能是预期的效果

$ curl https://127.0.0.1/index.html

      index.html

$ curl https://127.0.0.1/symlink

      <!DOCTYPE html><title>Error 403</title><p>Error 403.

最后,chroot 有什么区别?要查看,请将其删除

{
    "action": {
        "share": "/www/$host/static$uri",
        "follow_symlinks": false
    }
}

现在,“follow_symlinks”: false 影响整个共享,并且 localhost 是符号链接,因此被禁止

$ curl https://127.0.0.1/index.html

      <!DOCTYPE html><title>Error 403</title><p>Error 403.

回退操作§

最后,在 action 中,你可以在 share 旁边提供 fallback 选项。它指定如果无法从 share 路径提供请求的文件时要执行的操作

{
    "share": "/www/data/static$uri",
    "fallback": {
        "pass": "applications/php"
    }
}

由于以下原因,提供文件是不可能的

  • 请求的 HTTP 方法不是 GETHEAD

  • 该文件的 MIME 类型与types 数组 不匹配。

  • share 路径中找不到该文件。

  • 路由器进程没有足够的权限来访问该文件或基础目录。

在前面的示例中,首先尝试从/www/data/static/ 目录提供请求的文件。只有在无法提供该文件的情况下,才会将请求传递给php 应用程序。

如果fallback 本身是一个share,它还可以包含一个嵌套的fallback

{
    "share": "/www/data/static$uri",
    "fallback": {
        "share": "/www/cache$uri",
        "chroot": "/www/",
        "fallback": {
            "proxy": "http://127.0.0.1:9000"
        }
    }
}

第一个share 尝试从/www/data/static/ 提供请求;如果失败,第二个share 将尝试使用已启用的chroot/www/cache/ 路径。如果两次尝试都失败,则请求将在其他位置代理。

示例

此功能启用的一项常见用例是将对静态和动态内容的请求分离到独立的路由中。以下示例将所有针对.php 文件的请求中继到一个应用程序,并使用带有fallback 的全能静态share

{
    "routes": [
        {
            "match": {
                "uri": "*.php"
            },

            "action": {
                "pass": "applications/php-app"
            }
        },
        {
            "action": {
                "share": "/www/php-app/assets/files$uri",
                "fallback": {
                    "proxy": "http://127.0.0.1:9000"
                }
            }
        }

    ],

    "applications": {
        "php-app": {
            "type": "php",
            "root": "/www/php-app/scripts/"
        }
    }
}

对于在动态 URI 中避免使用文件名的应用程序,你可以反转此方案,列出所有要从share 中提供的静态内容类型,并在match 条件中添加一个无条件应用程序路径

{
    "routes": [
        {
            "match": {
                "uri": [
                    "*.css",
                    "*.ico",
                    "*.jpg",
                    "*.js",
                    "*.png",
                    "*.xml"
                ]
            },

            "action": {
                "share": "/www/php-app/assets/files$uri",
                "fallback": {
                    "proxy": "http://127.0.0.1:9000"
                }
            }
        },
        {
            "action": {
                "pass": "applications/php-app"
            }
        }

    ],

    "applications": {
        "php-app": {
            "type": "php",
            "root": "/www/php-app/scripts/"
        }
    }
}

如果图像文件应该在本地提供,而其他文件应该代理,请在第一个路由步骤中使用types 数组

{
    "match": {
        "uri": [
            "*.css",
            "*.ico",
            "*.jpg",
            "*.js",
            "*.png",
            "*.xml"
        ]
    },

    "action": {
        "share": "/www/php-app/assets/files$uri",
        "types": [
            "image/*"
        ],

        "fallback": {
            "proxy": "http://127.0.0.1:9000"
        }
    }
}

结合sharetypesfallback 的另一种方法通过以下紧凑模式得到体现

{
    "share": "/www/php-app/assets/files$uri",
    "types": [
        "!application/x-httpd-php"
    ],

    "fallback": {
        "pass": "applications/php-app"
    }
}

它将对 PHP 文件的显式请求转发到应用程序,同时从 share 中提供所有其他类型的文件;请注意,此处不需要match 对象来实现此效果。

代理§

Unit 的路由支持使用路由步骤的proxy 选项对套接字地址进行 HTTP 代理 操作

{
    "routes": [
        {
            "match": {
                "uri": "/ipv4/*"
            },

            "action": {
                "proxy": "http://127.0.0.1:8080"
            }
        },
        {
            "match": {
                "uri": "/ipv6/*"
            },

            "action": {
                "proxy": "http://[::1]:8080"
            }
        },
        {
            "match": {
                "uri": "/unix/*"
            },

            "action": {
                "proxy": "http://unix:/path/to/unix.sock"
            }
        }
    ]
}

如示例所示,你可以为代理目标使用 UNIX、IPv4 和 IPv6 套接字地址。

注意

HTTPS 方案尚不支持。

负载均衡§

除了将请求代理到各个服务器之外,Unit 还可以将传入请求中继到上游。上游是一组服务器,它们组成一个单一的逻辑实体,并且可以用作 监听器路由 中传入请求的传递目标。

上游在 API 的同名/config/upstreams 部分中定义

{
    "listeners": {
        "*:80": {
            "pass": "upstreams/rr-lb"
        }
    },

    "upstreams": {
        "rr-lb": {
            "servers": {
                "192.168.0.100:8080": {},
                "192.168.0.101:8080": {
                    "weight": 0.5
                }
            }
        }
    }
}

上游必须定义一个服务器对象,该对象将套接字地址列为服务器对象名称。Unit 以循环方式在 上游的服务器之间调度请求,充当负载均衡器。每个服务器对象都可以设置一个数字权重来调整它通过上游接收的请求份额。在上面的示例中,192.168.0.100:8080 接收的请求是 192.168.0.101:8080 的两倍。

权重可以指定为十进制或科学记数法中的整数或分数

{
    "servers": {
        "192.168.0.100:8080": {
            "weight": 1e1
        },

        "192.168.0.101:8080": {
            "weight": 10.0
        },

        "192.168.0.102:8080": {
            "weight": 10
        }
    }
}

最大权重为1000000,最小权重为0(此类服务器不接收请求);默认值为1

应用程序§

Unit 运行的每个应用程序在控制 API 的/config/applications 部分中定义为一个对象;它列出了应用程序的语言和设置、运行时限制、进程模型以及各种特定于语言的选项。

注意

我们的官方特定于语言的包包括应用程序配置的端到端示例,在安装包后可在/usr/share/doc/<module name>/examples/ 中找到以供参考。

在此,Unit 运行名为blogs的 PHP 应用程序的 20 个进程,这些进程存储在/www/blogs/scripts/目录中

{
    "blogs": {
        "type": "php",
        "processes": 20,
        "root": "/www/blogs/scripts/"
    }
}

应用程序对象具有许多在所有应用程序语言之间共享的选项

选项

说明

类型(必需)

应用程序类型:外部(Go 和 Node.js)、javaperlphppythonrubywasm(WebAssembly)。

除了外部wasm之外,你还可以详细说明运行时版本:“type”: “python 3”“type”: “python 3.4”,甚至“type”: “python 3.4.9rc1”。Unit 搜索其模块并使用最新的匹配模块,如果没有匹配项,则报告错误。

例如,如果你只有一个 PHP 模块 7.1.9,则它匹配“php”“php 7”“php 7.1”“php 7.1.9”。如果你有版本 7.0.2 和 7.0.23 的模块,则设置“type”: “php 7.0.2”以指定前者;否则,将使用 PHP 7.0.23。

环境

字符串值对象;要传递给应用程序的环境变量。

字符串;运行 应用程序进程 的组名。

默认值为 用户的主组。

隔离

对象;管理应用程序进程的隔离。有关详细信息,请参见 此处

限制

对象;接受两个整数选项,超时请求。它们的值控制应用程序进程的生命周期。有关详细信息,请参见 此处

进程

整数或对象;整数设置应用程序进程的静态数量,对象选项最大值备用空闲超时启用动态管理。有关详细信息,请参见 此处

默认值为 1。

stderrstdout

字符串;Unit 将应用程序输出重定向到的文件名。

默认值为 /dev/null

--no-daemon 模式下运行时,应用程序输出始终重定向到 Unit 日志文件

用户

字符串;运行 应用程序进程 的用户名。

默认值为在 构建时 或在 启动时 配置的用户名。

工作目录

字符串;应用程序的工作目录。

默认值为 Unit 的 主进程 的工作目录。

此外,你需要设置类型特定选项来运行应用程序。此 Python 应用程序 设置路径模块

{
    "type": "python 3.6",
    "processes": 16,
    "working_directory": "/www/python-apps",
    "path": "blog",
    "module": "blog.wsgi",
    "user": "blog",
    "group": "blog",
    "environment": {
        "DJANGO_SETTINGS_MODULE": "blog.settings.prod",
        "DB_ENGINE": "django.db.backends.postgresql",
        "DB_NAME": "blog",
        "DB_HOST": "127.0.0.1",
        "DB_PORT": "5432"
    }
}

进程管理§

Unit 有三个针对每个应用程序的选项,用于控制应用程序进程的行为:隔离限制进程。此外,你可以获取 API 的 /control/applications/ 部分以重新启动应用程序

# curl -X GET --unix-socket /path/to/control.unit.sock  \
      https://127.0.0.1/control/applications/app_name/restart

Unit 会优雅地处理轮换,允许旧进程处理现有请求,并启动一组新进程(由 processes 选项 定义)以接受新请求。

进程隔离§

如果 Unit 的底层操作系统支持,你可以对应用程序使用 名称空间文件系统 隔离

$ ls /proc/self/ns/

    cgroup mnt net pid ... user uts

隔离应用程序选项具有以下成员

选项

说明

automount

对象;如果启用了rootfs,则控制挂载行为。默认情况下,Unit 会自动挂载语言运行时依赖项/proc/ 处的 procfs/tmp/ 处的 tmpfs,但您可以禁用其中任何默认挂载

{
    "isolation": {
        "automount": {
            "language_deps": false,
            "procfs": false,
            "tmpfs": false
        }
    }
}

cgroup

对象;定义应用程序的cgroup

选项

说明

path(必需)

字符串;配置应用程序在cgroups v2 层次结构中的绝对或相对路径。限制会向下渗透到层次结构,因此子 cgroup 不能超过父级阈值。

gidmap

uidmap相同,但配置组 ID,而不是用户 ID。

namespaces

对象;为应用程序配置命名空间隔离方案。

可用选项(取决于系统;请查看操作系统手册以获取指导)

cgroup

为应用程序创建新的cgroup命名空间。

credential

为应用程序创建新的用户命名空间。

mount

为应用程序创建新的挂载命名空间。

network

为应用程序创建新的网络命名空间。

pid

为应用程序创建新的PID命名空间。

uname

为应用程序创建新的UTS命名空间。

上面列出的所有选项都是布尔值;要隔离应用程序,请将相应的命名空间选项设置为true;要禁用隔离,请将选项设置为false(默认)。

rootfs

字符串;要作为应用程序的新文件系统根目录使用的目录的路径名。

uidmap

用户 ID 映射对象 数组;每个数组项必须定义以下内容

容器

整数;在应用程序的命名空间中启动用户 ID 映射范围。

主机

整数;在操作系统命名空间中启动用户 ID 映射范围。

大小

整数;两个命名空间中 ID 范围的大小。

启用所有命名空间并设置用户和组 ID 映射的示例隔离对象

{
    "namespaces": {
        "cgroup": true,
        "credential": true,
        "mount": true,
        "network": true,
        "pid": true,
        "uname": true
    },

    "cgroup": {
        "path": "/unit/appcgroup"
    },

    "uidmap": [
        {
            "host": 1000,
            "container": 0,
            "size": 1000
        }
    ],

    "gidmap": [
        {
            "host": 1000,
            "container": 0,
            "size": 1000
        }
    ]
}
使用控制组§

控制组 (cgroup) 命令统一层次结构中进程组使用计算资源。Cgroup 由其在 cgroup 文件系统中的路径定义。

cgroup 对象定义 Unit 应用程序的 cgroup;其path 选项可以设置绝对值(以/开头)或相对值。如果路径在 cgroup 文件系统中不存在,Unit 会创建它。

相对路径隐式放置在 Unit 的主进程的 cgroup 中;此设置实际上将应用程序置于/<主 Unit 进程 cgroup>/production/app cgroup

{
    "isolation": {
        "cgroup": {
            "path": "production/app"
        }
    }
}

绝对路径名将应用程序置于单独的 cgroup 子树下;此配置将应用程序置于/staging/app

{
    "isolation": {
        "cgroup": {
            "path": "/staging/app"
        }
    }
}

一个基本用例是在 cgroup 上设置内存限制。首先,找到 cgroup 挂载点

$ mount -l | grep cgroup

    cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot)

接下来,检查可用控制器并设置memory.high 限制

# cat /sys/fs/cgroup//staging/app/cgroup.controllers

    cpuset cpu io memory pids

# echo 1G > /sys/fs/cgroup/staging/app/memory.high

有关更多详细信息和可能选项,请参阅管理指南

注意

为避免混淆,请注意namespaces/cgroups 选项控制应用程序的 cgroup 命名空间;而cgroup/path 选项指定 Unit 将应用程序置于其中的 cgroup。

更改根目录§

rootfs 选项将应用程序限制在您提供的目录中,使其成为新的文件系统根。要使用它,您的应用程序应具有相应的权限(在大多数情况下,实际上以root 身份运行)。

在语言模块启动应用之前,根目录已更改,因此应用的任何路径选项都应相对于新根目录。注意路径主页设置

{
    "type": "python 2.7",
    "path": "/",
    "home": "/venv/",
    "module": "wsgi",
    "isolation": {
        "rootfs": "/var/app/sandbox/"
    }
}

警告

当使用rootfs凭据设置为true

"isolation": {
    "rootfs": "/var/app/sandbox/",
    "namespaces": {
        "credential": true
    }
}

确保应用以其身份运行的用户可以访问rootfs目录。

Unit 将特定于语言的文件和目录挂载到新根目录,以便应用保持运行状态

语言

特定于语言的挂载

Java

  • JVM 的libc.so目录

  • Java 模块的主页目录

Python

Python 的sys.path目录

Ruby

  • Ruby 的头文件、解释器和库目录rubyarchhdrdirrubyhdrdirrubylibdirrubylibprefixsitedirtopdir

  • Ruby 的 gem 安装目录(gem env gemdir

  • Ruby 的整个 gem 路径列表(gem env gempath

使用“uidmap”、“gidmap”

仅当底层操作系统支持用户命名空间时,才可以使用uidmapgidmap选项。

如果省略uidmap但启用了凭据隔离,则主机命名空间中应用程序进程的有效 UID (EUID) 会映射到容器命名空间中的相同 UID;gidmap 和 GID 也分别适用。这意味着以下配置

{
    "user": "some_user",
    "isolation": {
        "namespaces": {
            "credential": true
        }
    }
}

等同于以下配置(假设some_user的 EUID 和 EGID 都等于 1000)

{
    "user": "some_user",
    "isolation": {
        "namespaces": {
            "credential": true
        },

        "uidmap": [
            {
                "host": "1000",
                "container": "1000",
                "size": 1
            }
        ],

        "gidmap": [
            {
                "host": "1000",
                "container": "1000",
                "size": 1
            }
        ]
    }
}

请求限制§

limits对象控制应用进程的请求处理,并有两个整数选项

选项

说明

请求

整数;应用进程可以处理的最大请求数。达到限制时,进程会重新启动;这可以缓解可能的内存泄漏或其他累积问题。

timeout

整数;请求超时(以秒为单位)。如果应用进程在处理请求时超过此时间,Unit 会取消请求并向客户端返回 503 “服务不可用”响应。

注意

现在,Unit 不会检测到冻结,因此挂起的进程会保留在应用的进程池中。

示例

{
    "type": "python",
    "working_directory": "/www/python-apps",
    "module": "blog.wsgi",
    "limits": {
        "timeout": 10,
        "requests": 1000
    }
}

应用程序进程§

processes 选项提供静态和动态进程管理之间的选择。如果将其设置为整数,Unit 会立即启动给定的应用进程数并保持它们而不进行扩展。

要为你的应用启用动态 prefork 模型,请提供一个包含以下选项的 processes 对象

选项

说明

idle_timeout

Unit 在终止超过 spare 的空闲进程之前等待的秒数。

max

Unit 维护的最大应用进程数(繁忙和空闲)。

默认值为 1。

spare

Unit 尝试为应用维护的最小空闲进程数。当应用启动时,将启动 spare 个空闲进程;Unit 将新请求传递给现有的空闲进程,如果 max 允许,则分叉新空闲进程以保持 spare 级别。当繁忙进程完成其工作并再次变为空闲时,Unit 会在 idle_timeout 后终止额外的空闲进程。

如果完全省略 processes,Unit 会创建 1 个静态进程。如果提供一个空对象:“processes”: {},则假定具有默认选项值的动态行为。

在此,Unit 允许最多 10 个进程,保留 5 个空闲进程,并在 20 秒后终止额外的空闲进程

{
    "max": 10,
    "spare": 5,
    "idle_timeout": 20
}

注意

有关手动应用进程重新启动的详细信息,请参阅 此处

Go§

要在 Unit 上运行 Go 应用,请修改其源代码使其对 Unit 具有感知能力并重新构建应用。

更新 Go 应用以在 Unit 上运行

Unit 使用 cgo 从 Go 调用 C 代码,因此请检查以下先决条件

  • CGO_ENABLED 变量设置为 1

    $ go env CGO_ENABLED
    
          0
    
    $ go env -w CGO_ENABLED=1
    
  • 如果你从 官方软件包 安装了 Unit,请安装开发软件包

    # apt install unit-dev
    
    # yum install unit-devel
    
  • 如果你从 源代码 安装了 Unit,请安装包含文件和库

    # make libunit-install
    

import 部分中,列出 unit.nginx.org/go 软件包

import (
    ...
    "unit.nginx.org/go"
    ...
)

unit.ListenAndServe 替换 http.ListenAndServe 调用

func main() {
    ...
    http.HandleFunc("/", handler)
    ...
    // http.ListenAndServe(":8080", nil)
    unit.ListenAndServe(":8080", nil)
    ...
}

如果您尚未执行此操作,请为您的应用初始化 Go 模块

$ go mod init example.com/app

      go: creating new go.mod: module example.com/app

安装新添加的依赖项并构建您的应用程序

$ go get unit.nginx.org/[email protected]

      go: downloading unit.nginx.org

$ go build -o app app.go

如果您将 Unit 更新到较新版本,请重复上述两个命令以重新构建您的应用。

生成的执行文件的工作方式如下

  • 当您独立运行它时,unit.ListenAndServe 调用将回退到 http 功能。

  • 当 Unit 运行它时,unit.ListenAndServe 直接与 Unit 的路由器进程通信,忽略作为其第一个参数提供的地址,而依赖于 监听器设置

接下来,在 Unit 上配置应用;除了 通用选项,您还有

选项

说明

executable(必需)

字符串;应用程序的路径名,绝对路径或相对于 working_directory 的路径。

参数

字符串数组;要传递给应用程序的命令行参数。以下示例等效于 /www/chat/bin/chat_app --tmp-files /tmp/go-cache

示例

{
    "type": "external",
    "working_directory": "/www/chat",
    "executable": "bin/chat_app",
    "user": "www-go",
    "group": "www-go",
    "arguments": [
        "--tmp-files",
        "/tmp/go-cache"
    ]
}

注意

有关基于 Go 的示例,请参阅我们的 Grafana 操作指南或基本 示例

Java§

首先,确保安装了 Unit 以及 Java 语言模块

除了 通用选项,您还有

选项

说明

webapp(必需)

字符串;应用程序的 .war 文件(已打包或未打包)的路径名。

classpath

字符串数组;指向您应用所需库的路径(可能指向目录或单个 .jar 文件)。

options

字符串数组;定义 JVM 运行时选项。

Unit 本身公开了 -Dnginx.unit.context.path 选项,其默认值为 /;使用它自定义 上下文路径

thread_stack_size

整数;工作线程的堆栈大小(以字节为单位,内存页面大小的倍数;最小值通常取决于架构)。

默认值通常取决于系统,可以使用 ulimit -s <SIZE_KB> 设置。

线程

整数;每个 应用程序进程 的工作线程数。启动时,每个应用程序进程创建此数量的线程来处理请求。

默认值为 1

示例

{
    "type": "java",
    "classpath": [
        "/www/qwk2mart/lib/qwk2mart-2.0.0.jar"
    ],

    "options": [
        "-Dlog_path=/var/log/qwk2mart.log"
    ],

    "webapp": "/www/qwk2mart/qwk2mart.war"
}

注意

有关基于 Java 的示例,请参阅我们的 JiraOpenGrokSpring Boot 操作指南或一个基本的 示例

Node.js§

首先,你需要安装 unit-http 模块 已安装。如果它是全局的,请在你的项目目录中创建符号链接

# npm link unit-http

如果你将一个 Unit 托管的应用程序移动到一个 unit-http 已全局安装的新系统中,请执行相同操作。此外,如果你稍后更新 Unit,也请根据你的 安装方法 更新 Node.js 模块。

接下来,要在 Unit 上运行你的 Node.js 应用程序,你需要配置它们。除了 通用选项 之外,你还拥有

选项

说明

executable(必需)

字符串;应用程序的路径名,绝对路径或相对于 working_directory 的路径名。

在此处提供你的 .js 路径名,并使用适当的 shebang 启动文件本身

#!/usr/bin/env node

注意

确保 chmod +x 你在此处列出的文件,以便 Unit 可以启动它。

参数

字符串数组;要传递给应用程序的命令行参数。下面的示例等效于 /www/apps/node-app/app.js --tmp-files /tmp/node-cache

示例

{
    "type": "external",
    "working_directory": "/www/app/node-app/",
    "executable": "app.js",
    "user": "www-node",
    "group": "www-node",
    "arguments": [
        "--tmp-files",
        "/tmp/node-cache"
    ]
}

你可以使用我们随 unit-http 提供的加载器模块运行 Node.js 应用程序,而无需更改其代码。根据你的 Node.js 版本应用以下应用程序配置

{
    "type": "external",
    "executable": "/usr/bin/env",
    "arguments": [
        "node",
        "--loader",
        "unit-http/loader.mjs",
        "--require",
        "unit-http/loader",
        "app.js"
    ]
}
{
    "type": "external",
    "executable": "/usr/bin/env",
    "arguments": [
        "node",
        "--require",
        "unit-http/loader",
        "app.js"
    ]
}

加载器使用其感知单元的版本覆盖httpwebsocket模块,并启动应用程序。

您还可以通过更新应用程序源代码在没有加载器的情况下运行 Node.js 应用程序。为此,在您的代码中使用unit-http代替http

var http = require('unit-http');

要使用 WebSocket 协议,您的应用程序只需要替换默认的websocket

var webSocketServer = require('unit-http/websocket').server;

注意

有关基于 Node.js 的示例,请参阅我们的ApolloExpressKoaDocker操作指南或一个基本的示例

Perl§

首先,请务必安装 Unit 以及Perl 语言模块

除了 通用选项,您还有

选项

说明

script(必需)

字符串;PSGI 脚本路径。

thread_stack_size

整数;工作线程的堆栈大小(以字节为单位,内存页面大小的倍数;最小值通常取决于架构)。

默认值通常取决于系统,可以使用 ulimit -s <SIZE_KB> 设置。

线程

整数;每个 应用程序进程 的工作线程数。启动时,每个应用程序进程创建此数量的线程来处理请求。

默认值为 1

示例

{
    "type": "perl",
    "script": "/www/bugtracker/app.psgi",
    "working_directory": "/www/bugtracker",
    "processes": 10,
    "user": "www",
    "group": "www"
}

注意

有关 Perl 的基于 Perl 的示例,请参阅我们的BugzillaCatalyst操作指南或一个基本的示例

PHP§

首先,请务必安装 Unit 以及PHP 语言模块

除了 通用选项,您还有

选项

说明

root(必需)

字符串;应用程序文件结构的基目录。所有 URI 路径都相对于它。

索引

字符串;如果未设置script,则将文件名添加到指向目录的 URI 路径。

默认值为index.php

options

对象;定义php.ini位置和选项。

script

字符串;基于root的 PHP 脚本的文件名,该脚本为应用程序的所有请求提供服务。

targets

对象;使用自定义rootscriptindex值定义应用程序部分。

indexscript选项启用两种操作模式

  • 如果设置了script,则对应用程序的所有请求都由在此选项中指定的脚本处理。

  • 否则,将根据其 URI 路径提供请求;如果它们指向目录,则使用index

可以通过options对象自定义php.ini

选项

说明

adminuser

用于额外指令的对象。admin中的值在PHP_INI_SYSTEM模式中设置,因此应用程序无法更改它们;user值在PHP_INI_USER模式中设置,可以在运行时更新

  • 这些对象会覆盖任何*.ini文件中的设置

  • admin对象只能设置列为PHP_INI_SYSTEM的设置;对于其他模式,请设置user

  • adminuser都不能设置列为仅 php.ini的指令,但disable_classesdisable_functions除外

file

字符串;具有PHP 配置指令php.ini文件的路径名。

要加载多个.ini文件,请将environmentPHP_INI_SCAN_DIR一起使用,以扫描自定义目录

{
    "applications": {
        "hello-world": {
            "type": "php",
            "root": "/www/public/",
            "script": "index.php",
            "environment": {
                "PHP_INI_SCAN_DIR": ":/tmp/php.inis/"
            }
        }
    }
}

请注意,此处前缀该值冒号是路径分隔符;它导致 PHP 扫描使用--with-config-file-scan-dir选项预先配置的目录,该目录通常为/etc/php.d/,然后扫描此处设置的目录,即/tmp/php.inis/。要跳过预先配置的目录,请删除:前缀。

注意

options中的值必须为字符串(例如,“max_file_uploads”: “4”,而不是“max_file_uploads”: 4);对于布尔标志,仅使用“0”“1”。有关PHP_INI_*模式的详细信息,请参阅PHP 文档

注意

Unit 以类似于 PHP-FPM 的方式实现fastcgi_finish_request()函数

示例

{
    "type": "php",
    "processes": 20,
    "root": "/www/blogs/scripts/",
    "user": "www-blogs",
    "group": "www-blogs",
    "options": {
        "file": "/etc/php.ini",
        "admin": {
            "memory_limit": "256M",
            "variables_order": "EGPCS"
        },

        "user": {
            "display_errors": "0"
        }
    }
}

目标§

可以为单个 PHP 应用程序配置多达 254 个单独的入口点

{
    "applications": {
        "php-app": {
            "type": "php",
            "targets": {
                "front": {
                    "script": "front.php",
                    "root": "/www/apps/php-app/front/"
                },

                "back": {
                    "script": "back.php",
                    "root": "/www/apps/php-app/back/"
                }
            }
        }
    }
}

每个目标都是一个对象,它指定root,并且可以定义indexscript,就像常规应用程序一样。目标可由侦听器和路由中的pass选项用于提供请求

{
    "listeners": {
        "127.0.0.1:8080": {
            "pass": "applications/php-app/front"
        },

        "127.0.0.1:80": {
            "pass": "routes"
        }
    },

    "routes": [
        {
            "match": {
                "uri": "/back"
            },

            "action": {
                "pass": "applications/php-app/back"
            }
        }
    ]
}

应用程序范围的设置(isolationlimitsoptionsprocesses)由应用程序中的所有目标共享。

警告

如果您指定了目标,则在应用级别不应定义索引脚本

注意

有关基于 PHP 的示例,请参阅我们的 CakePHPCodeIgniterDokuWikiDrupalLaravelLumenMatomoMediaWikiMODXNextCloudphpBBphpMyAdminRoundcubeSymfonyWordPressYii 操作指南或一个基本的 示例

Python§

首先,请确保安装了 Unit 以及 Python 语言模块

除了 通用选项,您还有

选项

说明

模块(必需)

字符串;应用的模块名称。此模块由 Unit 以通常的 Python 方式导入

可调用

字符串;基于模块的可调用项的名称,Unit 将其作为应用运行。

默认值为应用程序

主页

字符串;应用的虚拟环境的路径。绝对路径或相对于工作目录的路径。

注意

用于运行应用的 Python 版本由类型确定;为了性能,Unit 不会使用虚拟环境中的命令行解释器。

ImportError: 没有名为“encodings”的模块

在为应用设置主页后,在 Unit 的日志中看到此内容?如果解释器无法使用虚拟环境,通常会出现这种情况,可能的原因包括

  • 类型设置与虚拟环境之间的版本不匹配;检查环境的版本

    $ source /path/to/venv/bin/activate
    (venv) $ python --version
    
  • Unit 的无特权用户(通常为unit)无权访问环境的文件;分配必要的权限

    # chown -R unit:unit /path/to/venv/
    

路径

字符串或字符串数组;其他 Python 模块查找路径。这些值添加到 sys.path 之前。

前缀

字符串;WSGI 的 SCRIPT_NAME 上下文值或 ASGI 的 root_path 上下文值。应以斜杠 (/) 开头。

协议

字符串;提示 Unit 应用程序使用特定界面。可以是 asgiwsgi

targets

对象;具有 custom modulecallable 值的应用程序部分。

thread_stack_size

整数;工作线程的堆栈大小(以字节为单位,内存页面大小的倍数;最小值通常取决于架构)。

默认值通常取决于系统,可以使用 ulimit -s <SIZE_KB> 设置。

线程

整数;每个 应用程序进程 的工作线程数。启动时,每个应用程序进程创建此数量的线程来处理请求。

默认值为 1

示例

{
    "type": "python",
    "processes": 10,
    "working_directory": "/www/store/cart/",
    "path": "/www/store/",
    "home": ".virtualenv/",
    "module": "cart.run",
    "callable": "app",
    "prefix": "/cart",
    "user": "www",
    "group": "www"
}

此代码段使用 /www/store/cart/run.py 模块中的 app 可调用对象,其中 /www/store/cart/ 为工作目录,/www/store/.virtualenv/ 为虚拟环境;path 值适用于应用程序某些模块从 cart/ 子目录外部导入的情况。

您可以采用两种形式提供可调用对象。第一种形式使用 WSGI (PEP 333PEP 3333)

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/plain')])
    yield b'Hello, WSGI\n'

第二种形式在 Python 3.5+ 中受支持,使用 ASGI

async def application(scope, receive, send):

    await send({
        'type': 'http.response.start',
        'status': 200
    })

    await send({
        'type': 'http.response.body',
        'body': b'Hello, ASGI\n'
    })

注意

在 Unit 1.21.0 之前,不支持旧版 two-callable ASGI 2.0 应用程序。

根据您的需要选择一种;Unit 会尝试自动推断您的选择。如果此推断失败,请使用 protocol 选项明确设置界面。

注意

prefix 选项控制 Python 上下文中的 SCRIPT_NAME (WSGI) 或 root_path (ASGI) 设置,允许路由请求,而不管应用程序的实际路径如何。

目标§

您可以为单个 Python 应用程序配置多达 254 个单独的入口点

{
    "applications": {
        "python-app": {
            "type": "python",
            "path": "/www/apps/python-app/",
            "targets": {
                "front": {
                    "module": "front.wsgi",
                    "callable": "app"
                },

                "back": {
                    "module": "back.wsgi",
                    "callable": "app"
                }
            }
        }
    }
}

每个目标都是一个指定模块的对象,还可以定义可调用前缀,就像常规应用所做的那样。目标可由侦听器和路由中的pass 选项用于处理请求

{
    "listeners": {
        "127.0.0.1:8080": {
            "pass": "applications/python-app/front"
        },

        "127.0.0.1:80": {
            "pass": "routes"
        }
    },

    "routes": [
        {
            "match": {
                "uri": "/back"
            },

            "action": {
                "pass": "applications/python-app/back"
            }
        }
    ]
}

homepathprotocolthreadsthread_stack_size 设置由应用中的所有目标共享。

警告

如果你指定目标,则应用级别不应定义模块可调用。此外,你不能在单个应用中组合 WSGI 和 ASGI 目标。

注意

有关基于 Python 的示例,请参阅我们的 BottleDatasetteDjangoDjango ChannelsFalconFastAPIFlaskGuillotinaMailman WebMercurialMoinMoinPlonePyramidQuartResponderReview BoardSanicStarletteTracZope 操作指南或基本 示例

Ruby§

首先,确保安装 Unit 以及 Ruby 语言模块

注意

Unit 使用 Rack 接口来运行 Ruby 脚本;你也需要安装它

$ gem install rack

除了 通用选项,您还有

选项

说明

script(必需)

字符串;rack 脚本路径名,包括 .ru 扩展名,例如:/www/rubyapp/script.ru

hooks

字符串;设置在应用程序生命周期中调用的事件挂钩的.rb文件的路径名。

线程

整数;每个应用程序进程的辅助线程数。启动后,每个应用程序进程都会创建此数量的线程来处理请求。默认值为1

示例

{
   "type": "ruby",
   "processes": 5,
   "user": "www",
   "group": "www",
   "script": "/www/cms/config.ru",
   "hooks": "hooks.rb"
}

应用程序启动时会评估挂钩脚本。如果已设置,它可以定义名为on_worker_booton_worker_shutdownon_thread_booton_thread_shutdown的Ruby代码块。如果提供了这些代码块,它们将在应用程序生命周期的相应点被调用,例如

@mutex = Mutex.new

File.write("./hooks.#{Process.pid}", "hooks evaluated")
# Runs once at app load.

on_worker_boot do
   File.write("./worker_boot.#{Process.pid}", "worker boot")
end
# Runs at worker process boot.

on_thread_boot do
   @mutex.synchronize do
      # Avoids a race condition that may crash the app.
      File.write("./thread_boot.#{Process.pid}.#{Thread.current.object_id}",
                  "thread boot")
   end
end
# Runs at worker thread boot.

on_thread_shutdown do
    @mutex.synchronize do
        # Avoids a race condition that may crash the app.
        File.write("./thread_shutdown.#{Process.pid}.#{Thread.current.object_id}",
                   "thread shutdown")
    end
end
# Runs at worker thread shutdown.

on_worker_shutdown do
    File.write("./worker_shutdown.#{Process.pid}", "worker shutdown")
end
# Runs at worker process shutdown.

使用这些挂钩为应用程序添加自定义运行时逻辑。

注意

有关基于 Ruby 的示例,请参阅我们的Ruby on RailsRedmine操作指南或一个基本的示例

WebAssembly§

首先,请确保安装 Unit 以及WebAssembly 语言模块

除了 通用选项,您还有

选项

说明

组件(必需)

字符串;WebAssembly 组件路径名,包括.wasm扩展名,例如:“/var/www/wasm/component.wasm”

访问

对象;其唯一的数组成员文件系统列出了应用程序可以访问的目录

"access": {
   "filesystem": [
      "/tmp/",
      "/var/tmp/"
   ]
}

示例

{
  "listeners": {
     "127.0.0.1:8080": {
        "pass": "applications/wasm"
     }
  },
  "applications": {
     "wasm": {
        "type": "wasm-wasi-component",
        "component": "/var/www/app/component.wasm",
        "access": {
        "filesystem": [
           "/tmp/",
           "/var/tmp/"
        ]
        }
     }
  }
}

注意

一个好的基于 Rust 的第一个项目可在sunfishcode/hello-wasi-http获得。它还包括开始使用 WebAssembly、WASI 和 Rust 的所有重要步骤。

警告

unit-wasm模块已弃用。我们建议改用wasm-wasi-component,它使用标准 WASI 0.2 接口支持 WebAssembly 组件。wasm-wasi-component模块在 Unit 1.32 及更高版本中可用。

首先,请确保安装 Unit 以及WebAssembly 语言模块

除了 通用选项,您还有

选项

说明

模块(必需)

字符串;WebAssembly 模块路径名,包括.wasm扩展名,例如:applications/wasmapp/module.wasm

请求处理程序(必需)

字符串;请求处理程序函数的名称。如果您将 Unit 与官方unit-wasm软件包一起使用,则该值是特定于语言的;有关详细信息,请参阅SDK文档。否则,请使用自定义实现的名称。

运行时调用此处理程序,提供用于在应用程序中传递数据的共享内存块的地址。

malloc_handler(必需)

字符串;内存分配器函数的名称。请参阅官方 unit-wasm 包中有关特定于语言的处理程序的上述说明。

运行时在语言模块启动时调用此处理程序,以分配用于在应用程序中传递数据的共享内存块。

free_handler(必需)

字符串;内存释放器函数的名称。请参阅官方 unit-wasm 包中有关特定于语言的处理程序的上述说明。

运行时在语言模块关闭时调用此处理程序,以释放用于在应用程序中传递数据的共享内存块。

访问

对象;其唯一的数组成员 filesystem 列出了应用程序可以访问的目录

"access": {
   "filesystem": [
      "/tmp/",
      "/var/tmp/"
   ]
}

module_init_handler,

字符串;模块初始化函数的名称。如果您将 Unit 与官方 unit-wasm 配合使用,则该值特定于语言;有关详细信息,请参阅 SDK 文档。否则,请使用自定义实现的名称。

在 WebAssembly 模块初始化后,WebAssembly 语言模块在语言模块启动时调用它。

module_end_handler

字符串;模块完成函数的名称。如果您将 Unit 与官方 unit-wasm 配合使用,则该值特定于语言;有关详细信息,请参阅 SDK 文档。否则,请使用自定义实现的名称。

WebAssembly 语言模块在语言模块关闭时调用它。

request_init_handler

字符串;请求初始化函数的名称。如果您将 Unit 与官方 unit-wasm 配合使用,则该值特定于语言;有关详细信息,请参阅 SDK 文档。否则,请使用自定义实现的名称。

WebAssembly 语言模块在每个请求开始时调用它。

request_end_handler

字符串;请求完成函数的名称。如果您将 Unit 与官方 unit-wasm 配合使用,则该值特定于语言;有关详细信息,请参阅 SDK 文档。否则,请使用自定义实现的名称。

WebAssembly 语言模块在每个请求结束时(当接收到标头和请求正文时)调用它。

response_end_handler

字符串;响应完成函数的名称。如果您将 Unit 与官方 unit-wasm 配合使用,则该值特定于语言;有关详细信息,请参阅 SDK 文档。否则,请使用自定义实现的名称。

WebAssembly 语言模块在每个响应结束时(当标头和响应正文已发送时)调用它。

示例

{
    "type": "wasm",
    "module": "/www/webassembly/unitapp.wasm",
    "request_handler": "my_custom_request_handler",
    "malloc_handler": "my_custom_malloc_handler",
    "free_handler": "my_custom_free_handler",
    "access": {
        "filesystem": [
            "/tmp/",
            "/var/tmp/"
        ]
    },
    "module_init_handler": "my_custom_module_init_handler",
    "module_end_handler": "my_custom_module_end_handler",
    "request_init_handler": "my_custom_request_init_handler",
    "request_end_handler": "my_custom_request_end_handler",
    "response_end_handler": "my_custom_response_end_handler"
}

使用这些处理程序向您的应用程序添加自定义运行时逻辑;有关其用法和要求的详细讨论,请参阅 SDK 源代码和文档。

注意

有关基于 WASM 的示例,请参阅我们的 Rust 和 C 示例

设置§

Unit 具有全局设置配置对象,用于存储实例范围的偏好设置。

选项

说明

http

对象;微调来自客户端的 HTTP 请求的处理。

js_module

字符串或字符串数组;列出已启用的 njs 模块,通过 控制 API 上传。

反过来,http 选项公开以下设置

选项

说明

body_read_timeout

从客户端请求主体读取数据的最大秒数。这是连续读取操作之间的间隔,而不是读取整个主体的时长。如果 Unit 在此间隔内未从客户端收到任何数据,它将返回 408 “请求超时”响应。

默认值为 30。

discard_unsafe_fields

布尔值;控制标头字段名称解析。如果将其设置为 true,Unit 仅处理由字母数字字符和连字符组成的标头名称(请参阅 RFC 9110);否则,还允许使用以下字符:.!#$%&’*+^_`|~

默认值为 true

header_read_timeout

读取客户端请求标头的最大秒数。如果 Unit 在此间隔内未从客户端收到整个标头,它将返回 408 “请求超时”响应。

默认值为 30。

idle_timeout

保持活动连接中请求之间的最大秒数。如果在此间隔内没有新请求到达,Unit 将返回 408 “请求超时”响应并关闭连接。

默认值为 180。

log_route

布尔值;启用或禁用 路由器日志记录

默认值为 false(已禁用)。

max_body_size

客户端请求主体中的最大字节数。如果主体大小超过此值,Unit 将返回 413 “有效负载过大”响应并关闭连接。

默认值为 8388608(8 MB)。

send_timeout

作为对客户端响应传输数据的最大秒数。这是连续传输之间的间隔,而不是整个响应的时长。如果在此间隔内未向客户端发送任何数据,Unit 将关闭连接。

默认值为 30。

server_version

布尔值;如果设置为 false,Unit 将在其 Server 响应 标头字段中省略版本信息。

默认值为 true

static

对象;配置静态资产处理。有一个名为 mime_types 的单个对象选项,该选项将特定的 MIME 类型 定义为选项。它们的值可以是字符串或字符串数组;每个字符串都必须指定包含在 MIME 类型中的文件名扩展名或特定文件名。你可以覆盖默认 MIME 类型或添加新类型

# curl -X PUT -d '{"text/x-code": [".c", ".h"]}' /path/to/control.unit.sock \
       https://127.0.0.1/config/settings/http/static/mime_types
{
       "success": "Reconfiguration done."
}

默认值:.aac.apng.atom.avi.avifavifs.bin.css.deb.dll.exe.flac.gif.htm.html.ico.img.iso.jpeg.jpg.js.json.md.mid.midi.mp3.mp4.mpeg.mpg.msi.ogg.otf.pdf.php.png.rpm.rss.rst.svg.ttf.txt.wav.webm.webp.woff2.woff.xml.zip

访问日志§

要启用基本访问日志记录,请在 config 对象的 access_log 选项中指定日志文件路径。

在下面的示例中,所有请求都将记录到 /var/log/access.log

# curl -X PUT -d '"/var/log/access.log"' \
       --unix-socket /path/to/control.unit.sock \
       https://127.0.0.1/config/access_log

    {
        "success": "Reconfiguration done."
    }

默认情况下,日志以 合并日志格式 编写。CLF 行的示例

127.0.0.1 - - [21/Oct/2015:16:29:00 -0700] "GET / HTTP/1.1" 200 6022 "http://example.com/links.html" "Godzilla/5.0 (X11; Minix i286) Firefox/42"

自定义日志格式§

access_log 选项还可以设置为一个对象,以自定义日志路径及其格式

选项

说明

格式

字符串;设置日志格式。除了任意文本之外,还可以包含任何 变量 单元支持。

路径

字符串;访问日志文件的路径名。

示例

{
    "access_log": {
        "path": "/var/log/unit/access.log",
        "format": "$remote_addr - - [$time_local] \"$request_line\" $status $body_bytes_sent \"$header_referer\" \"$header_user_agent\""
    }
}

巧合的是,上述 format 是默认设置。此外,请注意,日志条目是在处理请求之后形成的。

除了 内置变量 之外,还可以使用 njs 模板 来定义日志格式

{
    "access_log": {
        "path": "/var/log/unit/basic_access.log",
        "format": "`${host + ': ' + uri}`"
    }
}

条件访问日志§

可以通过使用 if 选项动态地打开和关闭 access_log

选项

说明

如果

如果值为空、0、false、null 或 undefined,则不会记录日志。

此功能使用户能够设置条件来确定是否记录访问日志。if 选项支持字符串和 JavaScript 代码。如果其值为为空、0、false、null 或 undefined,则不会记录日志。前缀中的“!”会反转条件。

不使用 njs 的示例

{
   "access_log": {
      "if": "$cookie_session",
      "path": "..."
   }
}

将记录所有使用名为 session 的会话 cookie 的请求。

我们可以添加 ! 来反转条件。

{
   "access_log": {
      "if": "!$cookie_session",
      "path": "..."
   }
}

现在,将记录所有没有会话 cookie 的请求。

使用 njs 和模板文字的示例

{
   "access_log": {
      "if": "`${uri == '/health' ? false : true}`",
      "path": "..."
   }
}