A reverse proxy
relays requests to different web services. The main advantage
is isolation of concerns: You can run different and
independent web services and serve them under a common umbrella
URL.
Proloxy is a reverse proxy that is written
in Prolog.
Proloxy uses an extensible Prolog predicate to relay
requests to different web services: For each arriving
HTTP request, Proloxy calls the predicate
request_prefix_target(+Request, -Prefix, -Target). Its
arguments are:
Prefix is prepended to relative paths in
HTTP redirects that the target service emits, so
that the next client request is again relayed to the intended
target service.
Target is the URI of the target service.
You configure Proloxy by providing a Prolog file that
contains the definition of request_prefix_target/3 and
any additional predicates and directives you need. For each web
service you want to make available, add a clause of
request_prefix_target/3 to relate an instantiated
HTTP request to a prefix and the desired target.
Each clause may use arbitrary Prolog code to analyse the request
and form the target.
When dispatching an HTTP request, Proloxy considers the clauses of
request_prefix_target/3 in the order they appear in your
configuration file and commits to the first clause that
succeeds. It relays the request to the computed target, and
then sends the target's response to the client.
For example, the following clause relays all requests to a
local web server on port 3031, passing along the original request
path. The target server can for example host the site's main page,
to be used if no other rules apply:
config.pl shows a sample
configuration file that uses Prolog rules to dispatch requests
to two different web services.
Virtual hosts
The rule language is general enough to
express virtual hosts. In the case
of name-based virtual hosts, this means that you dispatch
requests to different web services based on
the domain name that is used to access your site.
For example, to dispatch all requests of users who access your
server via your-domain.com to a web server running on
port 4040 (while leaving the path unchanged), use:
In some cases, it is convenient to respond directly with plain
text or HTML content instead of relaying the request
to a different web service. If a clause of
request_prefix_target/3 emits any text on standout output,
then this output is sent to the client as the
HTTP response. Such responses typically start with
Content-type: text/plain (or text/html),
followed by two newlines and the body of the reply. In
rules that emit output, Target must be the
atom - to avoid relaying the request to a
different service.
Proloxy provides the predicate output_from_process(+Program,
+Args) to emit process output (from stdout
and stderr) on standard output. For example, we
can configure Proloxy to show the system's uptime when the
URL /uptime is accessed:
Auxiliary programs and scripts can be conveniently invoked with
this method.
Relaying header fields
The extensible predicate transmit_header_field/1 allows you to
relay header fields that the target service emits to the client.
The argument is the name of the header field you want to
transmit if it exists in the target's response. For example, you
can put the following in config.pl:
transmit_header_field(last_modified).
The name of the header field is matched case-insensitively and
underscore (_) matches hyphen (-).
By default, Proloxy does not relay any response
header fields.
This enables HTTP Strict Transport Security (HSTS), which is useful
when running HTTPS servers.
Testing the configuration
Since each configuration file is also a valid Prolog program, you can
easily test your configuration. Consulting the Prolog program in
SWI-Prolog lets you detect syntax errors and singleton variables in
your configuration file. To test whether HTTP requests are dispatched
as you intend, query request_prefix_target/3. For example:
$ swipl config.pl
Welcome to SWI-Prolog (Multi-threaded, 64 bits, Version 7.3.14)
...
?- once(request_prefix_target([request_uri(/)], P, T)).
P = '',
T = 'http://localhost:3031/'.
?- once(request_prefix_target([request_uri('/rits/demo.html')], P, T)).
P = '/rits',
T = 'http://localhost:4040/demo.html'.
Note that:
we are using once/1 to commit to the first clause that succeeds.
we are simulating an actual HTTP request, using a list of header fields.
the answers tell us how the given HTTP requests are dispatched.
The ability to conveniently test your configuration is a nice
property, and a natural consequence of using Prolog as the
configuration language. You can also write unit tests for your
configuration and therefore easily detect regressions.
In the following, assume that your proxy rules are stored in the file
called config.pl.
To run Proloxy as a Unix daemon on the standard HTTP port (80) as
user web, use:
$ sudo swipl config.pl proloxy.pl --user=web
To run the process in the foreground and with a Prolog toplevel, use:
proloxy.service is a
sample systemd unit file that runs Proloxy on system
startup. Adapt the paths and options as needed, copy the file
to /etc/systemd/system/ and install it using:
You can run Proloxy as an HTTPS server and thus encrypt
traffic for all hosted services at once.
See LetSWICrypt for more
information.
A common use case when using HTTPS is to run a second Proloxy
instance as a regular HTTP server on port 80 to
redirect each request for http://X
to https://X. You can do this with the following
configuration file for the HTTP server:
Proloxy supports proxying of WebSocket connections.
As an example, consider making
noVNC available
via /vnc/, assuming that noVNC listens on
port 6080. The following clauses accomplish the
configuration:
WebSocket connections are automatically detected via the
Upgrade: websocket and other header fields.
Serving very large files
In typical use cases, Proloxy relays requests to other
web servers, and sends their answers to the client. The overhead
is typically negligible, since the other web services usually reside
on the same machine.
However, if a web server sends very large files in response to some
requests, Proloxy may not have enough global stack space to
collect the response.
In such cases, one solution is to configure Proloxy so that such
large files are sent directly by Proloxy, without involving a
different web service. For example, the following snippet
configures Proloxy to directly send any files (such as
ISO images) that are located in /home/web/iso, and
are accessed via /iso/.