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:
# curl -X PUT --data-binary @http.js --unix-socket /path/to/control.unit.sock \
http://localhost/js_modules/http
Unit doesn’t enable the uploaded modules by default, so add the module’s name to settings/js_module:
# curl -X PUT -d '"http"' /path/to/control.unit.sock \
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.