revproxy - Reverse Proxy Module for NaviServer (Version 0.23)
The revproxy module provides a reverse proxy solution for NaviServer. A reverse proxy server receives HTTP requests from outside and forwards these requests to some internal servers (backend servers), which are typically not reachable directly from the clients. The reverse proxy modules allows you to forward incoming requests to one or more backend systems, via HTTP or HTTPS connections. The module also supports WebSockets (including secure WebSockets).
You can configure revproxy to selectively forward requests based on HTTP method and URL patterns. This enables a hybrid setup where part of the site is served locally by NaviServer, and another part is handled by external backend services. Additionally, you can invoke revproxy::upstream directly in server-side pages (e.g., .vuh in OpenACS or ADP pages) to restrict backend access to authenticated or authorized users (e.g., admins).
The revproxy functionality integrates into NaviServer’s request handling workflow in three primary ways:
As a filter (ns_register_filter) — invoked early in the request lifecycle
As a request handler (ns_register_proc) — runs after filters (typically after authentication and permission checks)
Directly in server-side code — for fine-grained or conditional invocation
Choose the approach (filter, request handler, or direct invocation) that best suits your site’s security and performance requirements, such as whether to proxy requests before or after user authentication.
The revproxy module supports two backend connection methods:
ns_connchan (Classical Method, Tcl based)
Fully Event-driven approach suitable for streaming HTML and WebSockets
Supports background operation (does not block connection thread)
Proven stability and robustness
May be required for requests that cannot currently be handled by ns_http (e.g., certain WebSocket upgrade requests)
No persistent connections supported
Partial reads and writes and error conditions have to be handled on the Tcl level (complex)
ns_http (Experimental, C based)
Partial read/write operations are handled in C for improved efficiency
Supports persistent connections (NaviServer 5.0+) for repeated requests to the same backend
Integrates with writer threads, scaling well under heavy load
Provides separate logging and statistics for backend connections
For certain types of requests (e.g., the websocket upgrade request), automatic fall back to ns_connchan possible.
Does not work well for streaming requests, since request data is only available after the request has finished.
ns_http+ns_connchan (Experimental)
Uses ns_http for request data sending and ns_connchan for response spooling
Supports persistent connections and request streaming
Supports background operation (does not block connection thread)
By default, you can set backendconnection globally via the configuration file. It is also possible to override the backend connection method per handler instance by specifying the option -backendconnection to revproxy::upstream.
Figure 1 shows the interaction between a client, the reverse proxy server and the backend server, in particular, when the backendconnection is set to ns_http+ns_connchan. The incoming request is always processed by the network driver of NaviServer (nssock or nsssl). After the request is processed, we have the request header in the form of an ns_set and the request body either as spool file or as string. At this time, the URL rewrite callbacj and later the validation callback is fired (see ADVANCED CUSTOMIZATION) for more details). The backendconnection defines then, how the request is sent to the backend, and how the results are received and delivered to the client. In the case of ns_http+ns_connchan, the response headers from the backend server are sent the client before the response body is received. Also, the response body is forwarded to the client incrementally.
Figure 1: Reverse Proxy Server with configuration ns_http+ns_connchan
On the contrary, when the backendconnection is ns_http, forwarding to the client happens after the full request has been received by the reverse proxy. When ns_connchan is used, the transfer is also incrementally, but with the drawbacks mentioned above.
The reverse proxy module can be configured by extending a standard NaviServer configuration file. An overview:
Load the revproxy module
ns_section "ns/server/$server/modules" { ns_param revproxy tcl }
Decide whether to register filters or request handlers
Optionally set global or per-handler parameters (such as connection method, timeouts, etc.)
Register a preauth filter and remove the prefix /shiny before forwarding requests to https://my.backend.com/:
ns_section "ns/server/$server/module/revproxy" { ns_param register { ns_register_filter preauth GET /shiny/* ::revproxy::upstream -target https://my.backend.com/ -regsubs {{/shiny ""}} ns_register_filter preauth POST /shiny/* ::revproxy::upstream -target https://my.backend.com/ -regsubs {{/shiny ""}} } }
Forward GET /doc/* requests to two backend servers with a 20-second connect timeout:
ns_section ns/server/default/module/revproxy { set target {http://server1.local:8080/ http://server2.local:8080/} ns_param register [subst { ns_register_proc GET /doc { ::revproxy::upstream proc -connecttimeout 2s -target [list ${target}] } }] }
This configuration uses simple load distribution across multiple backends (round-robin). Failover checks (health checks) are currently not performed.
Specify a default backend connection type globally or override it in individual handlers:
ns_section ns/server/default/module/revproxy { ns_param backendconnection ns_http ;# default is ns_connchan }
The core command for proxying is revproxy::upstream, which accepts seveal parameters to control behavior. Note that multiple proxy handler instances can be defined, each with distinct parameters.
Defines a proxying rule for -target. The value of this required parameter is a list of one or more backend URLs. Without further definitions, the incoming URL path is appended to the specified target, but it can be altered via option -regsubs, Furthermore, the URL can also be rewritten more generally by the -url_rewrite_callback.
The option -backend_response_callback can be used to modify backend response headers. This callback has no default value. Example signature:
nsf::proc ::my_backend_response_callback { -url -responseHeaders -status } { # Modify or remove header fields in "responseHeaders" as needed }
The option -backendconnection can be used to define the per-handler connection method. It overrides the global backendconnection setting.
The option -exception_callback can be used to generate custom error pages on failures. Default implementation: ::revproxy::exception
nsf::proc ::revproxy::exception { {-status 503} {-msg ""} -error -url {-frontendChan ""} } { if {$msg eq ""} { ns_log warning "revproxy exception backend with URL '$url' failed with status $status" set msg "Backend error: [ns_quotehtml $error]" } if {$frontendChan ne ""} { switch $status { 502 {set phrase "Bad Gateway"} 503 {set phrase "Service Unavailable"} 504 {set phrase "Gateway Timeout"} default {set phrase Error} } ns_connchan write $frontendChan "HTTP/1.0 $status $phrase\r\n\r\n$status $phrase: $url" } else { ns_returnerror $status $msg } }
The option -regsubs can be use to provide a list of regsub patterns for rewriting incoming URLs.
The option -targethost specifies the host header field for the requests to the backend server. If this parameter is not specified or set to the empty string, the host header field received from the client is also sent to the backend server.
The option -connecttimeout specifies the time limit for connection setup to the backend server. (default: 1s)
The option -timeout specifies the time limit for read, or write operations (default: 10s)
The option -url_rewrite_callback can be used to dynamically compute the final upstream URL. This can be used in cases, where the option -regsubs is not sufficient. Default implementation: ::revproxy::rewrite_url
nsf::proc ::revproxy::rewrite_url { -target -url {-query ""}} { set newUrl [string trimright $target /]/[string trimleft $url /.] if {$query ne ""} {append newUrl ?$query} return $newUrl }
The option -validation_callback can be used to perform a test on the final rewriting of the URL and request headers. This callback receives the -url and -request, and has the complete control over the proxying request. It can still modify the headers, the HTTP method and the request data. The request dict contains keys like headers, binary, and either content or contentfile for uploaded request data. The callback has no default. Example signature:
nsf::proc ::my_validation_callback { -url -request } { # Check final URL and request data provided by the "request" dict. # Return unmodified or updated dict, or "" to abort. # .... return $request }
To use HTTP or HTTPS in backend connections, ensure the appropriate drivers (nssock for HTTP, nsssl for HTTPS) are loaded. If you wish to disable a particular listening port (e.g., HTTPS) while still using HTTPS for backend requests, configure it to listen on port 0:
ns_section ns/modules { ns_param nsssl https } ns_section ns/module/https { ns_param port 0 }
Below is a sample configuration file illustrating a possible reverse proxy setup. Save this file (e.g., "/usr/local/ns/conf/nsd-config-revproxy.tcl") and start NaviServer with the appropriate shell/environment variables:
######################################################################## # Sample configuration file for NaviServer with reverse proxy setup. ######################################################################## set home [file dirname [file dirname [info nameofexecutable]]] ######################################################################## # Per default, the reverse proxy server uses the following # configuration variables. Their values can be overloaded on startup # via environment variables with the "nsd_" prefix, when starting the # server, e.g., setting a different "revproxy_target" via: # # nsd_revproxy_target=https://localhost:8445 /usr/local/ns/bin/nsd -f ... # ######################################################################## dict set defaultConfig httpport 48000 dict set defaultConfig httpsport 48443 dict set defaultConfig revproxy_target http://127.0.0.1:8080 dict set defaultConfig revproxy_backendconnection ns_connchan dict set defaultConfig address 0.0.0.0 ns_configure_variables "nsd_" $defaultConfig ns_section ns/parameters { ns_param home $home ns_param tcllibrary tcl ns_param serverlog error.log } ns_section ns/servers { ns_param default "Reverse proxy" } ns_section ns/modules { if {$httpsport ne ""} { ns_param https nsssl } if {$httpport ne ""} { ns_param http nssock } } ns_section ns/module/http { ns_param port $httpport ns_param address $address ns_param maxupload 1MB ns_param writerthreads 1 } ns_section ns/module/https { ns_param port $httpsport ns_param address $address ns_param maxupload 1MB ns_param writerthreads 1 ns_param ciphers "ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!RC4" ns_param protocols "!SSLv2:!SSLv3" ns_param certificate /usr/local/ns/etc/server.pem } ns_section ns/module/http/servers { ns_param defaultserver default ns_param default localhost ns_param default [ns_info hostname] } ns_section ns/module/https/servers { ns_param defaultserver default ns_param default [ns_info hostname] ns_param default localhost } ######################################################################## # Settings for the "default" server ######################################################################## ns_section ns/server/default { ns_param connsperthread 1000 ns_param minthreads 5 ns_param maxthreads 100 ns_param maxconnections 100 ns_param rejectoverrun true } ns_section ns/server/default/fastpath { # # From where to serve pages that are not proxied to a backend. # ns_param pagedir pages-revproxy } ns_section ns/server/default/modules { ns_param nslog nslog ns_param revproxy tcl } ns_section ns/server/default/module/revproxy { ns_param backendconnection $revproxy_backendconnection ns_param verbose 1 set usefilter 0 if {$usefilter} { ns_param register [subst { ns_register_filter postauth GET /* ::revproxy::upstream -target [list $revproxy_target] ns_register_filter postauth POST /* ::revproxy::upstream -target [list $revproxy_target] }] } else { # # Here, we register additionally nsstats.tcl to the URL space to # get statistics from the proxy-server itself. This assumes, # that nsstats.{tcl,adp} has been installed into the pages # directory of the proxy server. On public sites, you might wish to # remove this handler or protect nsstats.tcl with a password. # ns_param register [subst { ns_register_tcl GET /nsstats.tcl ns_register_proc GET /* { ::revproxy::upstream proc -target [list ${revproxy_target}] } ns_register_proc POST /* { ::revproxy::upstream proc -target [list ${revproxy_target}] } }] } } ns_section ns/server/default/httpclient { # # Activate persistent connections for ns_http and request logging (with connection reuse statistics) # ns_param keepalive 5s ns_param logging on ns_param logfile httpclient-revproxy.log } set ::env(RANDFILE) $home/.rnd set ::env(HOME) $home set ::env(LANG) en_US.UTF-8 # ns_log notice "END OF CONFIG"
Invoke NaviServer with this configuration file, for example:
nsd_revproxy_target=https://localhost:8445 \ nsd_httpport=48000 \ /usr/local/ns/bin/nsd -f -u nsadmin -g nsadmin -t /usr/local/ns/conf/nsd-config-revproxy.tcl 2>&1
In general, there are many way how to use the revproxy framework. The following examples shows, how to define a virtual server accepting requests with the host header field cvs.local (e.g., add this domain name as alias to your host and use the name in the URL). By defining the reverse proxy as a virtual server, one can specify different resource limits (number of connection threads, upload limits), different log files, different backend connection methods, etc.
Add this snippet to your NaviServer configuration and adjust the URLs/ports/names according to your needs. As it is, it will use the reverse proxy for HTTP/HTTPS requests addressed to cvs.local to the backend server 127.0.0.1:8060.
######################################################################## # Virtual Server definition for e.g. handling CVS browser ######################################################################## ns_section ns/servers { ns_param cvs "Reverse proxy to CVS repository" } ns_section ns/module/http/servers { ns_param cvs cvs.local } ns_section ns/module/https/servers { ns_param cvs cvs.local } ns_section ns/server/cvs/httpclient { ns_param keepalive 5s ;# default: 0s } ns_section ns/server/cvs/modules { ns_param revproxy tcl } ns_section "ns/server/cvs/module/revproxy" { ns_param backendconnection $revproxy_backendconnection ns_param verbose 0 ns_param register { ns_register_proc GET /* {::revproxy::upstream proc -target http://127.0.0.1:8060/} ns_register_proc POST /* {::revproxy::upstream proc -target http://127.0.0.1:8060/} } }
There are many configuration options possible. One could make the default server the reverse proxy, and use other servers for other purposes. One could also combine mass virtual hosting with the reverse proxy by defining appropriate mapping rules.
nsf (Next Scripting Framework) from http://next-scripting.org/
ns_connchan, ns_http, ns_register, ns_register_filter, ns_register_proc, ns_register_tcl