We wanted to use UNIX sockets for py4web instead of ports and we finally developed a configuration for this that works. Unix sockets are more secure and for us were much easier as we didn't need to manage our port ranges and use iptables.
The main difficulty we had was the _dashboard application which is an SPA application. It want to be served from the root, but we needed a URL prefix since we had multiple py4web instances from the same host. For this reason it was necessary to create a custom wsgi. I couldn't find any url_prefix options which i could use with gunicorn and sockets. If such an option exists then please let me know. AI told us this wasn't possible.
Our configuration is webseal/trustbuilder => Apache reverse proxy on Linux and py4web running gunicorn.
After a lot of trial and error, we came up with a working configuration.
I am sharing the basic structure for others who may want to use sockets with py4web:
Note: Our py4web is conda installed and you will need to install gunicorn in your environment.
This is the wsgi file contents:
from py4web.core import wsgi
APPS_FOLDER = "<path_to_py4web>/apps"
PREFIX = "<URL_prefix>"
from py4web.core import wsgi
app = wsgi(
apps_folder="<path_to_py4web>/apps",
password_file="<path_to_py4web>/password.txt",
dashboard_mode="full" # Very important!!
)
def application(environ, start_response):
raw_path = environ.get("PATH_INFO") or "/"
print("RAW PATH:", raw_path, flush=True)
if raw_path.startswith(PREFIX):
new_path = raw_path[len(PREFIX):] or "/"
else:
new_path = raw_path
if not new_path.startswith("/"):
new_path = "/" + new_path
environ["SCRIPT_NAME"] = PREFIX
environ["PATH_INFO"] = new_path
print("FINAL:", environ["SCRIPT_NAME"], environ["PATH_INFO"], flush=True)
return app(environ, start_response)
This is the apache reverse proxy configuration:
AllowEncodedSlashes NoDecode
# -------------------------------
# Logging
# -------------------------------
SetEnvIf Request_URI "^<url_prefix>" log_py4web
CustomLog <path_to_log_file>/p4web-access.log combined env=log_py4web
SetEnvIf Request_URI "^<url_prefix>" log_py4web
CustomLog <path_to_log>/p4web-access.log combined env=log_py4web
# =========================================================
# ROOT passthrough (REQUIRED for _dashboard SPA)
# =========================================================
ProxyPass "/_dashboard/" \
"unix:<path_to_sockets_dir>/py4web.sock|http://<ip apache is listening on>/_dashboard/"
ProxyPassReverse "/_dashboard/" \
"unix:<path_to_sockets_dir>/py4web.sock|http://<ip apache is listening on>/_dashboard/"
# =========================================================
# ROOT passthrough (REQUIRED for _dashboard SPA)
# =========================================================
ProxyPass "/static/" \
"unix:<path_to_sockets_dir>/py4web.sock|http://<ip apache is listening on>/static/"
ProxyPassReverse "/static/" \
"unix:<path_to_sockets_dir>/py4web.sock|http://<ip apache is listening on>/static/"
# =========================================================
# Non-SPA PY4WEB APPS
# =========================================================
ProxyPass "<url_prefix>" \
"unix:<path_to_sockets_dir>/py4web.sock|http://<ip apache is listening on>/"
ProxyPassReverse "<url_prefix>" \
"unix:<path_to_sockets_dir>/py4web.sock|http://<ip apache is listening on>/"
To run this with gunicorn from the command-line:
gunicorn my_wsgiwrapper:application \
--bind unix:<path_to_sockets>/py4web.sock \
--workers 1 \
--log-level debug \
--capture-output \
--timeout 120