Return to the main page

SVR.JS documentation


Welcome to the official documentation of SVR.JS!

This documentation is in the public domain.

First use

System requirements

Minimum

Installation

Using SVR.JS installer (installer packages made in April 5, 2024 and later; GNU/Linux only)

The command for SVR.JS installation is available in the SVR.JS home page. First off, switch to GNU/Linux tab, then copy the command below the tab. After starting the installer, you will be prompted to select the type of SVR.JS installation. After selecting the type, SVR.JS installer will install Node.JS, SVR.JS and create SVR.JS service. During installation, you may be prompted for the installation of dependencies. Once the installation is done, the server is started at http://localhost.

File structure will look like this:

SVR.JS installer will also install these commands:

Using SVR.JS installer (installer packages made before April 5, 2024; GNU/Linux only)

SVR.JS now has a brand new installer for GNU/Linux. First, download SVR.JS installer, then unpack your SVR.JS installer zip archive. After unpacking the installer, download SVR.JS zip archive (not installer), copy it to the installer directory and rename it to “svrjs.zip“. Then run SVR.JS installer using sudo bash installer.sh. After starting the installer, you will be prompted to select the type of OS (type of GNU/Linux distribution). After selecting the type, SVR.JS installer will install Node.JS, SVR.JS and create SVR.JS service. During installation, you may be prompted for the installation of dependencies. Once the installation is done, restart your server OS or type systemctl start svrjs or /etc/init.d/svrjs start to start SVR.JS and get a web server on http://localhost.

File structure will look like this:

SVR.JS installer will also install these commands:

Using create-svrjs-server tool

To install SVR.JS using create-svrjs-server, first install the utility using npm install -g svrjs. Then create a directory, which will contain SVR.JS. Change your working directory to a new one, and run one of those commands:

After downloading and installing SVR.JS to your working directory, run node svr.js for SVR.JS 3.x or node svr_new.js for earlier versions or bun run svr.js if you’re using Bun instead of Node.JS.

You will then see this message:

1
2
3
4
Decompressing modules...  
Deleting SVR.JS stub...
Decompressing SVR.JS...
Restart SVR.JS to get server interface.

After running this command again, you’ll get a web server on http://localhost.

Using Docker

To install SVR.JS via Docker, first pull the image using docker pull svrjs/svrjs command, or docker pull svrjs/svrjs:lts command, if you wish to install LTS version of SVR.JS. Then create and start the Docker container using docker run --name mysvrjs -d -p 80:80 --restart=always svrjs/svrjs command (replace mysvrjs with desired Docker container name). The file structure is the same, as it would be installed via SVR.JS installer. Once the installation is done, the server is started at http://localhost.

Manual installation

To install SVR.JS manually, first unpack your SVR.JS zip archive you downloaded from SVR.JS download page. Then change your working directory to one containing SVR.JS script stub, and run node svr.js for SVR.JS 3.x or node svr_new.js for earlier versions or bun run svr.js if you’re using Bun instead of Node.JS.

You will then see this message:

1
2
3
4
Decompressing modules...  
Deleting SVR.JS stub...
Decompressing SVR.JS...
Restart SVR.JS to get server interface.

After running this command again, you’ll get a web server on http://localhost, which looks like this:

SVR.JS console

After installation

When you visit localhost, the page will look like this:

SVR.JS running for first time

If you see this page, then SVR.JS installation is successful. You can now replace default server pages (index.html, tests.html, licenses, serverSideScript.js) with custom ones, and remove default mods! If you don’t see this page, then there was a problem when installing SVR.JS.

Features

Static file handling

Security

Configuration and customization

Compression and content delivery

Authentication and access control

Gateway interfaces

Additional functionality

SVR.JS files

SVR.JS utilities

SVR.JS 3.0.0 and later comes with several utilities:

SVR.JS commands

SVR.JS comes with a console interface that provides several built-in commands for managing the server and interacting with it.

Available commands:

These commands provide an easy and convenient way to manage and control the SVR.JS server directly from the console.

Updating SVR.JS

Using SVR.JS updater (included in SVR.JS installer package; GNU/Linux only)

If you installed SVR.JS using installer packages in April 5, 2024 and later, you can just run sudo svrjs-updater command to update SVR.JS to latest version. Once the update is done, restart your server OS or type systemctl start svrjs or /etc/init.d/svrjs start to restart SVR.JS.

For older installer packages, SVR.JS can be updated using SVR.JS updater by changing working directory to one containing SVR.JS updater, and running sudo bash updater.sh (make sure svrjs.zip file contains new version of SVR.JS). Once the update is done, restart your server OS or type systemctl start svrjs or /etc/init.d/svrjs start to restart SVR.JS.

Using create-svrjs-server tool

SVR.JS can be updated using create-svrjs-server tool by changing working directory to one containing SVR.JS, and running one of those commands:

Then you can run node svr.js or bun run svr.js to extract new version of SVR.JS and new Node.JS modules.

Manual updating

SVR.JS can be updated manually by extracting svr.js, modules.compressed and svr.compressed files from archive containing new version of SVR.JS to directory, to which older version of SVR.JS is installed (if you installed SVR.JS using SVR.JS installer, it is /usr/lib/svrjs). Then you can run node svr.js or bun run svr.js to extract new version of SVR.JS and new Node.JS modules.

Common problems

Bun support

SVR.JS is currently partially compatible with Bun JavaScript runtime. In the benchmark shown below, SVR.JS on Bun 1.1 has around 2.8 times more requests per second and over 5 times lower maximum latency than SVR.JS on Node.JS 18.19.1 LTS.

SVR.JS on Bun is faster than SVR.JS on Node.JS

However, SVR.JS on Bun currently has these limitations:

Configuration

SVR.JS can be configured by modifying config.json file.

config.json properties

The config.json file contains various properties that you can customize to configure SVR.JS according to your specific requirements. Below are the available properties:

General Configuration

SSL Configuration

Domain and Redirect Configuration

Error Pages and Logging Configuration

HTTP Configuration

Security Configuration

Virtual Host Configuration

Miscellaneous Configuration

Deprecated and Removed Properties

The following properties are deprecated or removed in newer versions of SVR.JS, and modifying them might not have any effect on the server:

Example Configuration

Here’s an example config.json file illustrating some of the available properties:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
{
"port": 8080,
"pubport": 80,
"sport": 8443,
"spubport": 443,
"domain": "example.com",
"wwwroot": "/var/www/html",
"wwwredirect": true,
"page404": "custom_404.html",
"enableLogging": true,
"enableDirectoryListing": true,
"enableCompression": true,
"enableHTTP2": true,
"enableETag": true,
"secure": true,
"cert": "path/to/certificate.crt",
"key": "path/to/private.key",
"exposeServerVersion": false,
"exposeModsInErrorPages": false,
"disableServerSideScriptExpose": true,
"enableIPSpoofing": true,
"allowStatus": false,
"useWebRootServerSideScript": false,
"rewriteMap": [
{
"definingRegex": "/^\\/serverSideScript\\.js(?:$|[#?])/",
"replacements": [
{
"regex": "/^\\/serverSideScript\\.js($|[#?])/",
"replacement": "/NONEXISTENT_PAGE$1"
}
]
},
{
"definingRegex": "/^\\/old-url$/",
"replacements": [
{
"regex": "/^\\/old-url$/",
"replacement": "/new-url"
}
]
}
],
"customHeaders": {
"X-Frame-Options": "DENY",
"X-Content-Type-Options": "nosniff"
}
}

CLI options

Page customization

You can easily customize the appearance of pages served by SVR.JS by adding custom head and foot sections. These sections can be linked to every .html file served by SVR.JS.

To customize the head, create a head.html or .head (SVR.JS 3.0.0 or newer) file in the web root directory. For the foot, create a foot.html or .foot (SVR.JS 3.0.0 or newer) file in the web root directory. Both custom head and foot sections will also be linked to directory listings if the enableDirectoryListingWithDefaultHead property in config.json is set to true.

For individual directory listings, you can add custom head and foot sections as well. Create a HEAD.html or .dirhead (SVR.JS 3.0.0 or newer) file for the head section, and a FOOT.html or .dirfoot (SVR.JS 3.0.0 or newer) file for the foot section in the specific directory you want to customize. You can also add a description of the directory (in HTML format) by creating a .maindesc file in the respective directory. You can even apply CSS styles to directory listing table (using #directoryListing CSS selector and custom directory listing head and foot).

You can add index page to web root or directories in web root with one of those file names: index.html, index.htm and index.xhtml.

From SVR.JS 3.5.0, you can change directory listing icons by replacing the icons in the .dirimages directory located in the web root. If an icon is not present in the .dirimages directory in the web root, SVR.JS will fall back to using icons in the .dirimages directory in the same directory as the SVR.JS script.

Custom error pages

You can configure SVR.JS to serve custom error pages by adding .<errorcode> (SVR.JS 3.0.0 or newer) or <errorcode>.html pages. For the 404 error, you can specify it by changing the page404 property in config.json. From SVR.JS 3.8.0 onwards, you can use errorPages property in config.json to specify path to each custom error page.

When designing custom error pages, you can make use of the following placeholders or templates:

User management

You can manage users for HTTP authentication in SVR.JS by using svrpasswd.js tool (SVR.JS 3.0.0 or newer). Usage is node svrpasswd.js [-h] [--help] [-?] [/h] [/?] [-x] [-a|--add|-d|--delete] <username>. Command-line options:

In SVR.JS 3.7.0 and newer, you can choose between 3 password hashing algorithms:

HTTP authentication

You can add HTTP basic authentication by including a 401 code (with scode property set to 401) entry in the nonStandardCodes property of config.json. To enable HTTP basic authentication, you need to specify the URL you want to restrict in the url or regex property of the entry. Additionally, you can set the authentication realm in the realm property. If the realm is not specified, the default realm is “SVR.JS HTTP Basic Authorization“. The encoding used for authentication will always be UTF-8.

By default, SVR.JS enables brute force protection for HTTP authentication, so you don’t need to worry about potential brute force attacks against the authentication mechanism.

From SVR.JS 3.8.0 onwards, you can specify a list of allowed users in the userList property.

Redirects

Setting up HTTP redirects is simple with SVR.JS. You can add a 301 or 302 code (with scode property set to 301 or 302) entry to the nonStandardCodes property in config.json. The entry should specify the source URL for the redirect in the url or regular expression string for the redirect in regex property (for example "/\\/blog($|[#?\\/].*)/"), and the destination URL in the location property (for regular expressions, you can use for example “$1“ for contents of first capturing group). Destination location can be relative to current site (for example /blogs) or a full URL (for example https://blog.example.com).

Please be cautious when setting up redirects to avoid redirect loops, as they can cause unintended behavior and potentially impact the performance of your server.

From SVR.JS 3.8.0 onwards, you can specify a hostname for which this redirect applies in host property. If you want to move your website to new address, you can add redirect to new website and specify host property to be host name of the old website (for example oldsite.example), location property to be a location of new website with “$1“ appended (for example “https://newsite.example$1“) and regex property to be "/(.*)/" (regular expression, which matches everything to capturing group).

URL rewriting

You can set up URL rewriting by adding entries to rewriteMap property in config.json file. See config.json properties” section for URL rewrite rules syntax.

Log viewing

To make log viewing easier, SVR.JS 3.x and later included it’s log viewer utility under logviewer.js. SVR.JS log viewer is interactive.

You can also manually view logs, and highlight them using SVR.JS log highlighter utility under loghighlight.js (SVR.JS 3.0.0 or newer). Usage: &lt;some process&gt; | node loghighlight.js [-h] [--help] [-?] [/h] [/?].

SVR.JS stores it’s logs in log directory. Server logs look like this:

[2023-07-04T18:50:54.610Z] SERVER MESSAGE [Request Id: c0ffd0]: Somebody connected to port 80...
[2023-07-04T18:50:54.611Z] SERVER REQUEST MESSAGE [Request Id: c0ffd0]: Client ::ffff:127.0.0.1:33670 wants content in localhost/leak.svr
[2023-07-04T18:50:54.611Z] SERVER REQUEST MESSAGE [Request Id: c0ffd0]: Client uses Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:99.0) Gecko/20100101 Firefox/99.0
[2023-07-04T18:50:54.625Z] SERVER RESPONSE ERROR MESSAGE [Request Id: c0ffd0]: There was an error while processing the request!
[2023-07-04T18:50:54.625Z] SERVER RESPONSE ERROR MESSAGE [Request Id: c0ffd0]: Stack:
[2023-07-04T18:50:54.625Z] SERVER RESPONSE ERROR MESSAGE [Request Id: c0ffd0]: RangeError [ERR_INVALID_OPT_VALUE]: The value "4294967296" is invalid for option "size"
[2023-07-04T18:50:54.625Z] SERVER RESPONSE ERROR MESSAGE [Request Id: c0ffd0]:   at Function.allocUnsafe (buffer.js:290:3)
[2023-07-04T18:50:54.625Z] SERVER RESPONSE ERROR MESSAGE [Request Id: c0ffd0]:   at /home/ubuntu/svr.js.3.3.3/temp/serverSideScript.js:70:18
[2023-07-04T18:50:54.625Z] SERVER RESPONSE ERROR MESSAGE [Request Id: c0ffd0]:   at /home/ubuntu/svr.js.3.3.3/temp/modloader/primitiveanalytics.tar.gz/index.js:28:23
[2023-07-04T18:50:54.625Z] SERVER RESPONSE ERROR MESSAGE [Request Id: c0ffd0]:   at modExecute (/home/ubuntu/svr.js.3.3.3/svr.js:2981:9)
[2023-07-04T18:50:54.625Z] SERVER RESPONSE ERROR MESSAGE [Request Id: c0ffd0]:   at Server.reqhandler (/home/ubuntu/svr.js.3.3.3/svr.js:3741:11)
[2023-07-04T18:50:54.625Z] SERVER RESPONSE ERROR MESSAGE [Request Id: c0ffd0]:   at Server.emit (events.js:198:13)
[2023-07-04T18:50:54.625Z] SERVER RESPONSE ERROR MESSAGE [Request Id: c0ffd0]:   at parserOnIncoming (_http_server.js:691:12)
[2023-07-04T18:50:54.625Z] SERVER RESPONSE ERROR MESSAGE [Request Id: c0ffd0]:   at HTTPParser.parserOnHeadersComplete (_http_common.js:111:17)
[2023-07-04T18:50:54.626Z] SERVER RESPONSE ERROR MESSAGE [Request Id: c0ffd0]: Server responded with 500 code.
[2023-07-04T18:50:54.630Z] SERVER MESSAGE [Request Id: c0ffd0]: Client disconnected.
[2023-07-04T18:50:54.699Z] SERVER MESSAGE [Request Id: 1be453]: Somebody connected to port 80...
[2023-07-04T18:50:54.699Z] SERVER REQUEST MESSAGE [Request Id: 1be453]: Client ::ffff:127.0.0.1:33670 wants content in localhost/favicon.ico
[2023-07-04T18:50:54.700Z] SERVER REQUEST MESSAGE [Request Id: 1be453]: Client uses Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:99.0) Gecko/20100101 Firefox/99.0
[2023-07-04T18:50:54.709Z] SERVER MESSAGE [Request Id: 1be453]: Client disconnected.
[2023-07-04T18:50:54.710Z] SERVER RESPONSE MESSAGE [Request Id: 1be453]: Server responded with 200 code.
[2023-07-04T18:50:54.712Z] SERVER RESPONSE MESSAGE [Request Id: 1be453]: Client successfully recieved content.

First on the line is timestamp. Second is message type, optionally with request ID. Last is message contents.

Environment variables

SVR.JS 3.12.0 and newer

You can configure environment variables by configuring environmentVariables property in config.json.

Older SVR.JS versions

SVR.JS seamlessly passes through externally set environment variables to mods and server-side JavaScript, allowing you to customize and control your application’s behavior based on these variables.

If you have a start-up script or use the command line to run SVR.JS, you can easily set environment variables before launching the server. Here’s an example of how you can do it in a bash script:

1
2
3
export NODE_ENV=production
export OPENAI_API_KEY=redacted
node svr.js

In this example, we’re setting two environment variables, NODE_ENV and OPENAI_API_KEY, before running the node svr.js command. These environment variables will be accessible within your mods and server-side JavaScript, allowing you to utilize them to configure and adapt your application as needed.

If you have installed SVR.JS using SVR.JS installer then you may modify /etc/init.d/svrjs script (do_start method) like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
do_start()
{
if [ ! -f "$lockfile" ] ; then
echo -n $"Starting $servicename: "
runuser -l "$user" -c "export GIT_HTTP_EXPORT_ALL=1; export GIT_PROJECT_ROOT=/var/lib/git; $nodejs $server > /dev/null &" && echo_success || echo_failure
RETVAL=$?
echo
[ $RETVAL -eq 0 ] && touch "$lockfile"
else
echo "$servicename is locked."
RETVAL=1
fi
}

If you have used SVR.JS installer on GNU/Linux distribution that uses systemd, then you may add Environment directives in Service section in systemd service file (/etc/systemd/system/svrjs.service) like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[Unit]
Description=SVR.JS web server
After=network.target

[Service]
Type=simple
User=svrjs
ExecStart=/usr/bin/env node /usr/lib/svrjs/svr.js
Environment=GIT_HTTP_EXPORT_ALL=1
Environment=GIT_PROJECT_ROOT=/var/lib/git
Restart=on-failure

[Install]
WantedBy=multi-user.target

Using environment variables can be a powerful way to manage different configurations for development, staging, and production environments, configure your CGI web applications, or to securely store sensitive information like API keys and passwords.

Remember that when you set environment variables externally, they will be available to all instances of SVR.JS running on your system. Exercise caution when using sensitive information as environment variables, and ensure that they are properly secured and protected.

By leveraging environment variables, you can enhance the flexibility and security of your SVR.JS application and streamline your deployment process.

CGI/SCGI/JSGI/PHP

In order to use CGI with SVR.JS, you need to install RedBrick mod. For SCGI you need to install OrangeCircle, while for JSGI you need to install YellowSquare mod. Download these mods.

CGI and PHP via RedBrick

RedBrick supports running CGI programs and PHP files (RedBrick 2.3.0 and newer) in cgi-bin directory. RedBrick 2.5.0 and newer support running CGI programs and PHP files outside cgi-bin directory. You can configure file extensions outside of cgi-bin directory handled by RedBrick in redbrick-scriptexts.json file in SVR.JS installation directory like this:

1
2
3
4
[
".php",
".cgi"
]

RedBrick custom interpreters (from RedBrick 2.3.2; in earlier versions it is broken) can be configured in redbrick-interpreters.json file in SVR.JS install directory like this:

1
2
3
4
5
6
7
8
{
".pl": ["perl"],
".py": ["python"],
".sh": ["bash"],
".pyw": ["python"],
".rb": ["ruby"],
".php": ["php-cgi"]
}

RedBrick 2.3.0 and newer support PHP-CGI, while RedBrick 2.3.1 and newer support URL rewriting. RedBrick 2.3.6 and newer work with Windows (older ones always threw an 500 error while trying to execute CGI scripts on Windows). RedBrick 2.4.3 and newer work with web root outside SVR.JS installation directory (older ones need config.json file in web root with valid JSON data; not necessarily related to config.json in SVR.JS installation directory)

RedBrick 2.4.1 and newer allows to disable some default interpreter configuration using “null” like this:

1
2
3
4
5
6
{
".pl": null,
".py": null,
".rb": null,
".exe": null
}

As of RedBrick 2.4.3, there are default interpreter configurations for .pl, .py, .sh, .ksh, .csh, .rb and .php files. If server is running on Windows, then there will be additional default interpreter configuration for .exe, .bat, .cmd (dropped in RedBrick 2.5.0) and .vbs files.

SVR.JS currently supports PHP-CGI through RedBrick mod. PHP is currently supported only inside cgi-bin directory in SVR.JS web root. RedBrick 2.5.0 and newer supports PHP outside of cgi-bin directory. You need to modify PHP configuration file (usually at /etc/php/<php version>/cgi/php.ini) and set cgi.force_redirect property to 0, otherwise PHP-CGI will not work and just display a warning about PHP-CGI binary being compiled with force-cgi-redirect enabled. It’s recommended to use directories outside of cgi-bin for user uploads and downloads (so that RedBrick will not treat uploaded scripts with shebang and ELF binary files as CGI applications and try to execute them, potentially resulting in hacker-uploaded malware infections, remote code execution vulnerabilities or 500 Internal Server Errors).

For security reasons, you may disable directory listing for cgi-bin (and also other directories) through disableDirectoryListing or disableDirectoryListingVHost options in SVR.JS configuration.

SCGI via OrangeCircle

OrangeCircle can be configured in orangecircle-config.json file in SVR.JS install directory like this:

1
2
3
4
5
{
"path": "/scgi",
"host": "localhost",
"port": 4000
}

OrangeCircle 1.0.7 and newer work with web root outside SVR.JS installation directory (older ones need config.json file in web root with valid JSON data; not necessarily related to config.json in SVR.JS installation directory)

JSGI via YellowSquare

YellowSquare supports running JSGI scripts only in jsgi-bin directory. YellowSquare runs JSGI scripts, that are either with .jsgi or .jsgi.js extension. Every change in JSGI application requires a restart of SVR.JS in order to be applied.

YellowSquare 1.0.3 and newer work with web root outside SVR.JS installation directory (older ones need config.json file in web root with valid JSON data; not necessarily related to config.json in SVR.JS installation directory)

For security reasons, you may disable directory listing for jsgi-bin (and also other directories) through disableDirectoryListing or disableDirectoryListingVHost options in SVR.JS configuration.

FastCGI/PHP-FPM

In order to use FastCGI with SVR.JS, you need to install GreenRhombus mod. Download the mod.

GreenRhombus notes

GreenRhombus’ path and FastCGI server address can be configured in greenrhombus-config.json file in the SVR.JS install directory.

Example configuration (with FastCGI server listening with port):

1
2
3
4
5
{
"path": "/fastcgi",
"host": "localhost",
"port": 7000
}

Example configuration (with FastCGI server listening on socket):

1
2
3
4
{
"path": "/fastcgi",
"socketPath": "/run/fastcgi.sock"
}

You can configure file extensions outside of path specified in greenrhombus-config.json file handled by GreenRhombus in greenrhombus-scriptexts.json file in SVR.JS installation directory like this:

1
2
3
[
".php"
]

PHP-FPM

GreenRhombus supports running PHP files through PHP-FPM. If you want to use GreenRhombus only for PHP-FPM, configure greenrhombus-config.json like this (in this case we’re using socket in /run/php/php8.2-fpm.sock; you can check it in PHP-FPM configuration file, e.g. /etc/php/8.2/fpm/pool.d/www.conf; configure it without path property):

1
2
3
{
"socketPath": "/run/php/php8.2-fpm.sock"
}

And configure greenrhombus-scriptexts.json like this:

1
2
3
[
".php"
]

PHP-FPM may run on different user than SVR.JS web server, so you may need to set permissions for the user, which PHP-FPM runs on.

Forward proxy notes

In order to use SVR.JS as a forward proxy, you need to install forward-proxy-mod SVR.JS mod. Download this mod.

If you are using forward-proxy-mod, then local IP addresses and localhost are also accessible from the proxy. You may block these using a firewall, if you don’t want these to be accessible from the proxy

Reverse proxy configuration

In order to use SVR.JS as a reverse proxy, you need to install reverse-proxy-mod SVR.JS mod. Download this mod.

Configuration file of reverse-proxy-mod is reverse-proxy-config.json inside SVR.JS installation directory. Keys of configuration object are domain names (or paths from reverse-proxy-mod 1.1.1), for which it’s settings apply. Values are object with those properties:

reverse-proxy-mod 1.1.0 and newer support HTTP upgrades (including WebSocket).

If you’re using per-host URL rewrite rules and running multiple sites on one SVR.JS instance (instead of proxying them all to specific web servers; assuming that you’re using SVR.JS 3.8.0 or newer; shared hosting), use paths referring to URL rewrite destinations instead of domain names. However if you’re planning to use VPSes (virtualized servers) or run different web server instances and use SVR.JS with reverse-proxy-mod as a reverse proxy for them, use domain names instead.

If you’re using SVR.JS just as a reverse proxy (for VPSes or other web server instances, and not serving websites from proxy itself), set disableServerSideScriptExpose to false, set web root to outside SVR.JS installation directory, empty out rewriteMap, nonStandardCodes, enableDirectoryListingVHost, customHeadersVHost, wwwrootPostfixesVHost, wwwrootPostfixPrefixesVHost to [], empty out customHeaders to {}, set disableTrailingSlashRedirects to true, set allowDoubleSlashes to true, and set disableToHTTPSRedirect to true, in order to avoid interference involving SVR.JS web server (use this configuration when proxy itself doesn’t use SVR.JS server-side JavaScript not including SVR.JS mods).

Virtual hosts

When you’re not planning to use SVR.JS server-side JavaScript or SVR.JS mods implementing individual web applications and plan to use SVR.JS similarly to Apache, nginx or IIS (static-only, PHP, CGI or JSGI), you can use virtual host-like functionality (name-based; IP-based from SVR.JS 3.14.1 and newer) with SVR.JS 3.8.0 or newer.

You can set up custom error pages, URL rewriting rules and non-standard status code settings per-host like this (assuming that you want to also include CGI support through RedBrick):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
{
"users": [],
"port": 80,
"pubport": 80,
"page404": "404.html",
"blacklist": [],
"nonStandardCodes": [
{
"url": "/anothersite.example/dl",
"location": "/download",
"host": "anothersite.example",
"scode": 301
},
{
"url": "/anothersite.example/downloads",
"location": "/download",
"host": "anothersite.example",
"scode": 301
}
],
"enableCompression": true,
"customHeaders": {},
"enableHTTP2": false,
"enableLogging": true,
"enableDirectoryListing": true,
"enableDirectoryListingWithDefaultHead": false,
"serverAdministratorEmail": "[no contact information]",
"stackHidden": false,
"enableRemoteLogBrowsing": false,
"exposeServerVersion": true,
"disableServerSideScriptExpose": true,
"rewriteMap": [
{
"definingRegex": "/^(?!\\/(?:\\.dirimages|cgi-bin)(?:$|[\\/#?]))/",
"host": "website.example",
"replacements": [
{
"regex": "/(.*)/",
"replacement": "/website.example$1"
}
]
},
{
"definingRegex": "/^\\/cgi-bin(?:$|[\\/#?])/",
"host": "website.example",
"replacements": [
{
"regex": "/^\\/cgi-bin($|[\\/#?].*)/",
"replacement": "/cgi-bin/website.example$1"
}
]
},
{
"definingRegex": "/^(?!\\/(?:\\.dirimages|cgi-bin)(?:$|[\\/#?])|\\/index(?:$|[\\/#?]))/",
"host": "anothersite.example",
"replacements": [
{
"regex": "/(.*)/",
"replacement": "/anothersite.example$1"
}
]
},
{
"definingRegex": "/^\\/index(?:$|[\\/#?])/",
"host": "anothersite.example".
"replacements": [
{
"regex": "/^\\/index/",
"replacement": "/anothersite.example/"
}
]
},
{
"definingRegex": "/^\\/cgi-bin(?:$|[\\/#?])/",
"host": "anothersite.example",
"replacements": [
{
"regex": "/^\\/cgi-bin($|[\\/#?].*)/",
"replacement": "/cgi-bin/anothersite.example$1"
}
]
},
{
"definingRegex": "/^\\/(?:cgi-bin\\/)?(?:website\\.example|anothersite\\.example)(?:$|[\\/#?])/",
"replacements": [
{
"regex": "/(.*)/",
"replacement": "/NONEXISTENT_PAGE"
}
]
}
],
"allowStatus": true,
"dontCompress": [
"/.*\\.ipxe$/",
"/.*\\.img$/",
"/.*\\.iso$/"
],
"enableIPSpoofing": false,
"secure": false,
"sni": {},
"disableNonEncryptedServer": false,
"disableToHTTPSRedirect": false,
"enableETag": true,
"disableUnusedWorkerTermination": false,
"rewriteDirtyURLs": true,
"errorPages": [
{
"scode": 404,
"path": "oops.html",
"host": "website.example"
}
],
"customHeadersVHost": [
{
"host": "website.example",
"headers": {
"X-Some-Header": "some-value"
}
}
],
"enableDirectoryListingVHost": [
{
"host": "website.example",
"enable": false
}
]
}

If you’re using SVR.JS 3.14.0 or newer, you can use this configuration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
{
"users": [],
"port": 80,
"pubport": 80,
"page404": "404.html",
"blacklist": [],
"nonStandardCodes": [
{
"url": "/anothersite.example/dl",
"location": "/download",
"host": "anothersite.example",
"scode": 301
},
{
"url": "/anothersite.example/downloads",
"location": "/download",
"host": "anothersite.example",
"scode": 301
}
],
"enableCompression": true,
"customHeaders": {},
"enableHTTP2": false,
"enableLogging": true,
"enableDirectoryListing": true,
"enableDirectoryListingWithDefaultHead": false,
"serverAdministratorEmail": "[no contact information]",
"stackHidden": false,
"enableRemoteLogBrowsing": false,
"exposeServerVersion": true,
"disableServerSideScriptExpose": true,
"rewriteMap": [
{
"definingRegex": "/^\\/anothersite.example\\/index(?:$|[\\/#?])/",
"host": "anothersite.example".
"replacements": [
{
"regex": "/^\\/anothersite.example\\/index/",
"replacement": "/anothersite.example/"
}
]
},
],
"allowStatus": true,
"dontCompress": [
"/.*\\.ipxe$/",
"/.*\\.img$/",
"/.*\\.iso$/"
],
"enableIPSpoofing": false,
"secure": false,
"sni": {},
"disableNonEncryptedServer": false,
"disableToHTTPSRedirect": false,
"enableETag": true,
"disableUnusedWorkerTermination": false,
"rewriteDirtyURLs": true,
"errorPages": [
{
"scode": 404,
"path": "oops.html",
"host": "website.example"
}
],
"customHeadersVHost": [
{
"host": "website.example",
"headers": {
"X-Some-Header": "some-value"
}
}
],
"enableDirectoryListingVHost": [
{
"host": "website.example",
"enable": false
}
],
"wwwrootPostfixesVHost": [
{
"host": "website.example",
"postfix": "website.example",
"skipRegex": "/^\\/.dirimages(?:$|[\\/#?]))/"
},
{
"host": "anothersite.example",
"postfix": "anothersite.example",
"skipRegex": "/^\\/.dirimages(?:$|[\\/#?]))/"
}
{
"postfix": "NONEXISTENT_SITE"
}
],
"wwwrootPostfixPrefixesVHost": [
"/cgi-bin"
]
}

You need to then create this directory structure in SVR.JS web root:

If you want to still use SVR.JS server-side JavaScript (not including SVR.JS mods implementing specific web applications) and virtual hosts simultaneously, path checks need to correspond to rewritten URLs (those processed by URL rewriting engine).

For some SVR.JS mods, path settings may correspond to URLs processed by URL rewriting engine.

It is not recommended to use global custom head and foot (head.html, foot.html, .head, .foot) with this setup, because they will apply to all virtual hosts (doesn’t include custom heads and feet for directory listings [.dirhead, .dirfoot], which apply to only one directory).

Client-initiated secure renegotiation

Client-initiated secure renegotiation may pose DoS risks. However, Node.JS (JS runtime on which SVR.JS is running on) has built-in protection against DoS attacks caused by client-initiated secure renegotiation. Such attacks can be detected by looking for ERR_TLS_SESSION_ATTACK errors in server log.

Mods

Mods in SVR.JS are custom modules that can extend the server’s functionality. Using mods, you can extend SVR.JS functionality to suit your specific requirements and customize the server’s behavior to handle different types of requests.

Installing mods

To install mod to SVR.JS, copy the mod to mods directory inside SVR.JS installation directory. SVR.JS searches this directory for mods, loads and executes them in alphabetical order (by mod file name). If you want have mods to be executed in specific order, add numeric prefix to mod file name, for example “01-redbrick.cgi.2.3.3.tar.gz“ and “00-easywaf.integration.1.1.2.tar.gz“.

Mod format

SVR.JS mods are tar archives with gzip compression, they work in SVR.JS 2.x and newer

SVR.JS 1.x used custom svrmodpack archives with gzip compression (they work in all SVR.JS versions), but this format is deprecated for new mods, and may be no longer supported in future versions of SVR.JS, since svrmodpack is not maintained anymore. All current SVR.JS mods are now in tar.gz format. SVR.JS 3.13.0 dropped support for svrmodpack.

Mod loading order

Startup

  1. Search for mods
  2. For each mod (sorted alphabetically by mod file name):
    1. Prepare temporary directory for extracted mod contents
    2. Extract mod contents
    3. Initialize mod, and add mod along with mod info to list
  3. Load server-side JavaScript:
    1. Create mod file from server-side JavaScript
    2. Initialize “mod”, and add “mod” to list

Execution (on each server request)

  1. Initialize SVR.JS variables
  2. Invoke mods and server-side JavaScript (mods sorted alphabetically by mod file name)
  3. Load SVR.JS main callback (if it’s not affected by mods and server-side JavaScript)

Mod files

  1. index.js - main script for mod
  2. mod.info - information about mod
  3. other files necessary for the mod

Mod development

Mods in SVR.JS have two methods:

This method is required (if it is not present, SVR.JS will simply return 500 Internal Server Error on all requests with error message in error stack similar to “TypeError: modO.callback is not a function“).

Required in order for function returned from callback method to be invoked for request URLs beginning with “http://“ or with “https://“ (proxy through GET or POST method, non-proxy requests have request URLs beginning with “/“). You should implement a proxy URL check in callback method, if you’re going to use proxyCallback and callback methods at once, or else your SVR.JS mod may be vulnerable to access control bypass attacks (SVR.JS doesn’t enforce URL rewriting, custom headers and non-standard codes for proxy requests to avoid interference of its access controls with proxy mods).

These methods are defined inside Mod.prototype object. Both methods return a function, which will be executed in SVR.JS.

__dirname and . in require() function both refer to directory, to which mod contents are extracted.

The reference to file in the SVR.JS installation directory is __dirname + "/../../../filename" (replace filename with your desired file name).

Current working directory (process.cwd()) is SVR.JS web root.

A typical index.js file for a mod may look like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//Requires go here
function Mod() {}
Mod.prototype.callback = function callback(req, res, serverconsole, responseEnd, href, ext, uobject, search, defaultpage, users, page404, head, foot, fd, elseCallback, configJSON, callServerError, getCustomHeaders, origHref, redirect, parsePostData) {
return function () {
//Mod contents go here
if (href == "/hello.svr") {
serverconsole.resmessage("Sent Hello World message!");
res.writeHead(200, "OK", {
"Content-Type": "text/plain"
});
res.end("Hello World!");
} else {
elseCallback();
}
}
}

//OPTIONAL: proxyCallback method
//Uncomment code below, if you want to use proxyCallback method.
//But then you'll need to implement proxy request URL check for callback method.

/*
Mod.prototype.proxyCallback = function proxyCallback(req, socket, head, configJSON, serverconsole, elseCallback) {
return function () {
//Just pass elseCallback
elseCallback();
}
}
*/

module.exports = Mod; //SVR.JS mod exports

The mod.info file (in JSON format) contains metadata about the mod, such as its name and version:

1
2
3
4
{
"name": "The Example Mod",
"version": "0.1.0"
}

Server-side JavaScript

Another way to expand SVR.JS functionality is through server-side JavaScript located in serverSideScript.js file inside SVR.JS web root (or locaten in SVR.JS installation directory if you’re running SVR.JS 3.9.0 or newer, and you have set useWebRootServerSideScript property to false). Server-side JavaScript allows you to create various web applications using JavaScript, Node.JS and SVR.JS API.

Predefined objects

When working with server-side JavaScript in SVR.JS, you have access to several predefined objects that can greatly enhance your scripting capabilities. These objects are available for use without requiring any additional imports or installations.

Additionally, there is an option to control the automatic execution of the elseCallback function. By default, automatic execution is enabled. You can use the disableEndElseCallbackExecute option to disable this behavior if needed.

By leveraging these predefined objects, you can streamline your server-side JavaScript code and build powerful applications using SVR.JS.

Predefined methods

See methods in SVR.JS API in non-proxy section

Additionally SVR.JS server-side JavaScript has filterHeaders(headers) method, that filters out invalid request headers.

SVR.JS 3.8.0 and newer have additionally two methods:

SSJS development

__dirname and . in require() function both refer to temp directory in SVR.JS.

Current working directory (process.cwd()) is SVR.JS web root.

If you want to divide server-side JavaScript into several files, you can do one of those:

Example code:

1
2
3
4
5
6
7
8
9
10
disableEndElseCallbackExecute = true; //Without "var", else it will not work!!!
if (href == "/hello.svr") {
serverconsole.resmessage("Sent Hello World message!");
res.writeHead(200, "OK", {
"Content-Type": "text/plain"
});
res.end("Hello World!");
} else {
elseCallback();
}

Migration to SVR.JS

If you have previously built your web application using the Node.JS http library, Express framework, or Koa framework, you can easily migrate that code to SVR.JS server-side JavaScript.

From Node.JS http library

For applications built with the Node.JS http library, you can simply copy the contents of the request event listener to the SVR.JS server-side JavaScript. However, make sure to consider the disableEndElseCallbackExecute option to ensure proper execution flow.

1
2
3
4
5
6
7
8
9
10
11
12
disableEndElseCallbackExecute = true; //Without "var", else it will not work!!!

// Node.JS http library request event listener code goes here.
if(req.url == "/" && req.method == "GET") {
res.writeHead(200, "OK", {
"Content-Type": "text/plain"
});
res.end("Hello World!");
return;
}

elseCallback(); // Optionally, invoke main SVR.JS callback.

From Express Framework

If your application is built using the Express framework, you can easily migrate it to SVR.JS. You can mix Express methods with SVR.JS methods for more flexibility.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
disableEndElseCallbackExecute = true; //Without "var", else it will not work!!!

var express = require("express");
// Other requires go here.

var app = express(); // Initialize express app

// Express application code goes here!
app.get("/", function (req, res) {
res.send("Hello World!");
});

app.use(elseCallback); // Optionally, if you want the main SVR.JS callback.

app(req, res); // Invoke Express handler

From Koa Framework

Migrating from the Koa framework to SVR.JS is also straightforward. Here’s an example of how you can do it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
disableEndElseCallbackExecute = true; //Without "var", else it will not work!!!

var koa = require("koa");
// Other requires go here.

var app = new koa(); // Initialize Koa app

// Koa application code goes here!
app.use(function (ctx, next) {
if (ctx.method != "GET" || ctx.path != "/") {
next(); // Koa router could be used...
} else {
ctx.body = "Hello World!";
}
});

// Optionally, if you want the main SVR.JS callback (not recommended by Koa, as it passes Node.JS req and res objects).
app.use(function (ctx) {
ctx.respond = false;
elseCallback(ctx.req, ctx.res);
});

(app.callback())(req, res); // Invoke Koa handler

By migrating your web application to SVR.JS, you can take advantage of its features and performance enhancements, while still preserving your existing codebase.

SVR.JS API

SVR.JS has its API for both mods and server-side JavaScript that expands its functionality. SVR.JS API extends vanilla Node.JS HTTP API.

Error handling

When a JavaScript error is thrown outside of event callbacks, SVR.JS will return a 500 error to the client. Inside event callbacks, SVR.JS will simply crash.

Incorrect Error Handling:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//XXX WARNING!!! IT WILL CRASH THE SVR.JS!!!
//It also contains path traversal vulnerability!

disableEndElseCallbackExecute = true; //Without "var", else it will not work!!!

var headers = getCustomHeaders();
headers["Content-Type"] = "text/html; charset=utf8";

if(href == "/page.svr") {
fs.readFile(".template", function(err, data) {
if(err) throw err; //EVIL!!!
var id = uobject.query.id;
if(!id) id = "index";
if(fs.existsSync("pages/" + id + ".html")) {
fs.readFile("pages/" + id + ".html", function(err2, data2)) {
if(err2) throw err2; //EVIL TWO!!!
res.writeHead(200,"OK", headers);
res.end(data.toString().replace("{websiteContents}",data2.toString()));
});
} else {
callServerError(404);
}
});
} else if(href == "/") {
redirect("/page.svr");
} else {
elseCallback();
}

Instead, you should handle errors gracefully using callServerError function:

Correct Error Handling:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//Much better!

disableEndElseCallbackExecute = true; //Without "var", else it will not work!!!

var headers = getCustomHeaders();
headers["Content-Type"] = "text/html; charset=utf8";

if(href == "/page.svr") {
if(fs.existsSync(".template")) {
fs.readFile(".template", function(err, data) {
if(err) {
callServerError(500,err);
return;
}
var id = uobject.query.id;
if(!id) id = "index";
id = id.replace(/\\/g,"/").replace(/(?:\/|^)\.\.(?=(\/|$))/g,"$1").replace(/\/+/g,"/") //Poor mans path sanitiation
if(fs.existsSync("pages/" + id + ".html")) {
fs.readFile("pages/" + id + ".html", function(err2, data2)) {
if(err2) {
callServerError(500,err2);
return;
}
res.writeHead(200,"OK", headers);
res.end(data.toString().replace("{websiteContents}",data2.toString()));
});
} else {
callServerError(404);
}
});
} else {
callServerError(500, new Error("Server is misconfigured - .template file missing"));
}
} else if(href == "/") {
redirect("/page.svr");
} else {
elseCallback();
}

By using callServerError, you can handle errors effectively and provide appropriate error responses to the client, preventing SVR.JS from crashing due to unhandled exceptions.

Non-proxy API

This API is exposed both to mods and server-side JavaScript. This API also includes proxy requests, which don’t use CONNECT method. It’s possible to determine, if the request comes from the proxy, by checking if req.url begins with “http://“ or with “https://“ (unlike non-proxy requests, for which req.url begins with “/“) like this:

1
var isProxy = (req.url && req.url.match(/^https?:\/\//));

SVR.JS applies mods for request URLs beginning with “http://“ or with “https://“ (proxy through GET or POST method, non-proxy requests have request URLs beginning with “/“) only if Mod.prototype.proxyCallback method is present (not possible with SVR.JS server-side JavaScript).

req

req object is almost same, as req object in Node.JS

Differences:

res

res object is almost same, as res object in Node.JS

Differences:

serverconsole.climessage(message)

Parameters:

Sends CLI message to server console.

serverconsole.reqmessage(message)

Parameters:

Sends request message to server console.

serverconsole.resmessage(message)

Parameters:

Sends response message to server console.

serverconsole.errmessage(message)

Parameters:

Sends response error message to server console.

serverconsole.locerrmessage(message)

Parameters:

Sends local error message to server console.

serverconsole.locwarnmessage(message)

Parameters:

Sends local warning message to server console.

serverconsole.locmessage(message)

Parameters:

Sends local message to server console.

responseEnd(body)

Parameters:

Sends response message (along with custom head and foot) specified by body parameter.

href

Path name of resource defined in the request. Alias for uobject.pathname.

ext

Extension of resource defined in the request.

uobject

Parsed Url object created by url.parse() method (includes parsed query string).

SVR.JS 3.3.1 and newer include hostname of the server (3.3.1 to 3.14.x use wrapper over WHATWG URL API; 3.15.0 and newer use custom URL parser), older versions don’t.

Query string of URL. Alias for uobject.search

defaultpage

WARNING! DEPRECATED IN SVR.JS 3.X OR NEWER

In SVR.JS 2.x it was alias for configJSON.defaultpage. In SVR.JS 3.x for backward compability it’s always “index.html“.

users

WARNING! DEPRECATED

Alias for configJSON.users

page404

Alias for configJSON.page404

HTML head read from either .head or head.html file.

HTML foot read from either .foot or foot.html file.

fd

WARNING! This property has currently no use and it’s reserved for new SVR.JS functions. Currently this property is an empty string.

elseCallback()

Invokes next SVR.JS mod callback, SVR.JS server-side JavaScript callback or main SVR.JS callback.

configJSON

Added in SVR.JS 3.0.0

Parsed object of config.json file.

SVR.JS 3.4.0 and newer has version property, that corresponds to server version, and productName property, which always is “SVR.JS”.

callServerError(errorCode[, extName][, stack][, ch])

Added in SVR.JS 3.0.0

Parameters:

Invokes HTTP error code. If it’s unavailable, invokes 501 error code.

getCustomHeaders()

Added in SVR.JS 3.0.0

Returns: Object property contains custom headers.

This methods retrieves custom headers from config.json file. Returned object additionally includes Server header.

origHref

Added in SVR.JS 3.0.0

Original path name before URL rewriting.

redirect(dest[, isTemporary][, keepMethod][, headers])

Added in SVR.JS 3.0.0

Parameters:

Redirects HTTP client to specific destination.

parsePostData([options], callback)

Added in SVR.JS 3.0.0

Parameters:

A wrapper over formidable library, which is used for parsing request bodies of POST requests.

authUser

Added in SVR.JS 3.14.2

The name of authenticated HTTP user. If the user wasn’t authenticated, the property would be null.

If you want to check if the request is authenticated in SVR.JS versions older than 3.14.2, you can use function shown below, that checks for an applicable 401 non-standard code in the server configuration:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function checkIfThereIsA401Rule() {
var actually401 = false;

function createRegex(regex) {
var regexObj = regex.split("/");
if (regexObj.length == 0) throw new Error("Invalid regex!");
var modifiers = regexObj.pop();
regexObj.shift();
var searchString = regexObj.join("/");
return new RegExp(searchString, modifiers);
}

if(configJSON.nonStandardCodes) {
configJSON.nonStandardCodes.every(function (nonscode) {
if (nonscode.scode == 401) {
if (nonscode.regex && (req.url.match(createRegex(nonscode.regex)) || href.match(createRegex(nonscode.regex)))) {
actually401 = true;
return true;
} else if (nonscode.url && (nonStandardCodes[i].url == href || (os.platform() == "win32" && nonStandardCodes[i].url.toLowerCase() == href.toLowerCase()))) {
actually401 = true;
return true;
}
}
return false;
});
}
return actually401;
}

Proxy API

Added in SVR.JS 3.4.21, 3.7.0

This API is exposed only to mods. It is invoked, when client connects with the server using CONNECT method.

This API was present from SVR.JS 3.0.0, however SVR.JS version older than 3.4.21 or 3.7.0 had a bug, which involves calling non-proxy callback, instead of proxy one. A workaround involves calling proxy callback over non-proxy one, when request uses CONNECT method (insert at beginning of non-proxy callback):

1
2
3
4
if(!res.writeHead) {
Mod.prototype.proxyCallback(req, res, serverconsole, responseEnd, href, ext)();
return;
}

req

Added in SVR.JS 3.4.21, 3.7.0

req object is the same, as req object in Node.JS

socket

Added in SVR.JS 3.4.21, 3.7.0

socket object is the same, as socket object in Node.JS

head

Added in SVR.JS 3.4.21, 3.7.0

head object is the same, as head object in Node.JS

configJSON

Added in SVR.JS 3.4.21, 3.7.0

See configJSON in non-proxy API

serverconsole

Added in SVR.JS 3.4.21, 3.7.0

See serverconsole in non-proxy API

elseCallback()

Added in SVR.JS 3.4.21, 3.7.0

See elseCallback in non-proxy API