Scripting
NGINX Unit’s control API supports
JavaScript expressions, including function calls, in the form of
template literals
written in
NGINX JavaScript ( njs
).
They can be used with these configuration options:
- pass in listeners and actions to choose between routes, applications, app targets, or upstreams.
- response_headers values in actions to manipulate response header fields.
- rewrite in actions to enable URI rewriting.
- share and chroot in actions to control static content serving.
- location in return actions to enable HTTP redirects.
- format in the access log to customize Unit’s log output.
- if in the access log to dynamically turn Unit’s logging on and off.
As its JavaScript engine, Unit uses the njs
library,
shipped with the official packages
or
built from source.
Warning:
Unit 1.32.0 and later require njs 0.8.2.
Some request properties are exposed as njs
objects or scalars:
Name | Type | Description |
---|---|---|
args | Object | Query string arguments; Color=Blue is args.Color; can be used with Object.keys(). |
cookies | Object | Request cookies; an authID cookie is cookies.authID; can be used with Object.keys(). |
headers | Object | Request header fields; Accept is headers.Accept, Content-Encoding is headers[‘Content-Encoding’] (hyphen requires an array property accessor); can be used with Object.keys(). |
host | Scalar | Host header field, converted to lower case and normalized by removing the port number and the trailing period (if any). |
remoteAddr | Scalar | Remote IP address of the request. |
uri | Scalar | Request target, percent decoded and normalized by removing the query string and resolving relative references ("." and “..”, “//”). |
vars | Object | Unit variables; vars.method is $method. |
Template literals are wrapped in backticks. To use a literal backtick in a string,
escape it: \\` (escaping backslashes is a
JSON requirement).
The njs
snippets should be enclosed in curly brackets:
${…}.
Next, you can upload and use custom JavaScript modules with your configuration. Consider this http.js script that distinguishes requests by their Authorization header field values:
var http = {}
http.route = function(headers) {
var authorization = headers['Authorization'];
if (authorization) {
var user = atob(authorization.split(' ')[1]);
if (String(user) == 'user:password') {
return 'accept';
}
return 'forbidden';
}
return 'unauthorized';
}
export default http
To upload it to Unit’s JavaScript module storage as http, run the following command as root:
# curl -X PUT --data-binary @http.js --unix-socket /path/to/control.unit.sock \ # <Path to the remote control socket>
http://localhost/js_modules/http # <Module name in Unit's configuration>
Unit doesn’t enable the uploaded modules by default, so add the module’s name to settings/js_module running the following command as root:
curl -X PUT -d '"http"' # Module name to be enabled
/path/to/control.unit.sock \ # Path to the remote control socket
http://localhost/config/settings/js_module
Note:
Please note that the js_module option can be a string or an array; choose the appropriate HTTP method.
Now, the http.route() function can be used with Unit-supplied header field values:
{
"routes": {
"entry": [
{
"action": {
"pass": "`routes/${http.route(headers)}`"
}
}
],
"unauthorized": [
{
"action": {
"return": 401
}
}
],
"forbidden": [
{
"action": {
"return": 403
}
}
],
"accept": [
{
"action": {
"return": 204
}
}
]
}
}
Examples
This example adds simple routing logic that extracts the agent name from the
User-Agent header field to reject requests issued by curl
:
"routes": {
"parse": [
{
"action": {
"pass": "`routes/${ headers['User-Agent'].split('/')[0] == 'curl' ? 'reject' : 'default' }`"
}
}
],
"reject": [
{
"action": {
"return": 400
}
}
],
"default": [
{
"action": {
"return": 204
}
}
]
}
This example uses a series of transformations to log the request’s date, IP, URI, and all its headers:
{
"path": "/var/log/unit/access_kv.log",
"format": "`@timestamp=${new Date().toISOString()} ip=${remoteAddr} uri=${uri} ${Object.keys(headers).map(k => 'req.' + k + '=\"' + headers[k] + '\"').join(' ')}\n`"
}
The next example will add the Cache-Control Header based on the HTTP Request method:
{
"action": {
"pass": "applications/my_app",
"response_headers": {
"Cache-Control": "`${vars.method.startsWith('P') ? 'no-cache' : 'max-age=3600'}`"
}
}
}
For further reference, see the njs documentation.