NaviServer - programmable web server
4.99  5.0

[ Main Table Of Contents | Table Of Contents | Keyword Index ]

revproxy(n) 5.0.0a revproxy "NaviServer Module"

Name

revproxy - Reverse Proxy Module for NaviServer (Version 0.23)

Table Of Contents

Synopsis

Description

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:

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.

Backend Connection Methods

The revproxy module supports two backend connection methods:

  1. 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)

  2. 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.

  3. 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.

Reverse Proxy Server with backendconnection ns_http+ns_connchan

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.

CONFIGURATION

The reverse proxy module can be configured by extending a standard NaviServer configuration file. An overview:

Example: Using Request Filters (Here: Pre-Authentication)

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 ""}}
   }
 }

Example: Using a Request Handler

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.

Choosing the Backend Connection Method

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
 }

ADVANCED CUSTOMIZATION

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.

revproxy::upstream -target value ?-backend_response_callback value? ?-backendconnection value? ?-connecttimeout value? ?-exception_callback value? ?-regsubs value? ?-targethost value? ?-timeout value? ?-url_rewrite_callback value? ?-validation_callback value?

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
 }

ENSURING NETWORK DRIVERS ARE LOADED

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
 }

FULL CONFIGURATION EXAMPLE

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.

REQUIREMENTS

See Also

ns_connchan, ns_http, ns_register, ns_register_filter, ns_register_proc, ns_register_tcl

Keywords

HTTP, HTTP-client, HTTPS, configuration, filter, handler, logging, module, nsf, proxy, request, reverse proxy