Python with apache#

For examples in this notebook I use special docker containers - docker file for container shown in python_apache_files folder. So to run examples from this section you should build image by running command docker build -t my_apache . from python_apache_files directory. This docker image will contain:

  • Installed programs:

    • nano - for easy files editing from container;

    • python3;

    • libapache2-mod-wsgi-py3 - it is the necessary programme for forking Apache with Python;

    • python3.11-venv - to configue python virtual environments;

    • curl - to try ulrs from container.

  • mkdir /var/www/application - for useful applications;

  • python3 -m venv /var/www/application/venv Python environment - it’s not necessary here, but it’s good practice to do it:

    • Installed Dash library for dealing with Web BI in Python.

Sources#

My server configuration#

Here I will show you how to start the server that contains all the examples in this notebook. More specific details about this server are described in the following sections.

ports.conf#

Config that describes which ports to listen on. By default, apache2 ports.conf only uses ports 80 and 443. But for some examples I need port 8050, so I use it as well.

%%bash
cat python_apache_files/ports.conf
# If you just change the port or add more ports here, you will likely also
# have to change the VirtualHost statement in
# /etc/apache2/sites-enabled/000-default.conf

Listen 80
Listen 8050

<IfModule ssl_module>
        Listen 443
</IfModule>

<IfModule mod_gnutls.c>
        Listen 443
</IfModule>

Launching#

By running this section on our local host, a number of sites will be hosted:

%%bash
cd python_apache_files

docker run --rm --name test_apache -d -p 81:80 -p 82:8050 my_apache &> /dev/null


docker cp ports.conf test_apache:/etc/apache2/ports.conf

# wsgi basics
docker cp wsgi_example/wsgi_basic.wsgi test_apache:/var/www/application/wsgi_basic.wsgi
docker cp wsgi_example/suburl.wsgi test_apache:/var/www/application/suburl.wsgi
docker cp wsgi_example/wsgi_basic.conf test_apache:/etc/apache2/sites-available/wsgi_basic.conf
docker exec test_apache a2ensite wsgi_basic

# specific port
docker cp use_specific_port/spec_port.wsgi test_apache:/var/www/application/spec_port.wsgi
docker cp use_specific_port/spec_port.conf test_apache:/etc/apache2/sites-available/spec_port.conf
docker exec test_apache a2ensite spec_port

# dash application
# docker exec test_apache mkdir /var/www/dash
docker cp dash/dash.wsgi test_apache:/var/www/application/dash.wsgi
docker cp dash/app.py test_apache:/var/www/application/app.py
# docker cp dash/dash.wsgi test_apache:/var/www/dash/dash.wsgi
# docker cp dash/app.py test_apache:/var/www/dash/app.py
# docker cp dash/dash.conf test_apache:/etc/apache2/sites-available/dash.conf
# docker exec test_apache a2ensite dash

# docker exec test_apache chown -R :www-data /var/www/application
# docker exec test_apache chmod -R o-rw /var/www/application


docker exec test_apache a2dissite 000-default
docker exec test_apache service apache2 reload
Enabling site wsgi_basic.
To activate the new configuration, you need to run:
  service apache2 reload
Enabling site spec_port.
To activate the new configuration, you need to run:
  service apache2 reload
Site 000-default disabled.
To activate the new configuration, you need to run:
  service apache2 reload
 * Reloading Apache httpd web server apache2
 * 

Note: for the site to work for any reason, it’s extremely important to stop apache2 default page with a2dissite 000-default command.

By runnig the following cell - sites will be stopped:

%%bash
docker stop test_apache
test_apache

WSGI basics#

WSGI is a special interface that allows Python programs and web servers to talk to each other. The site described in this section can only be accessed from root at localhost:81, so type localhost:81 in your browser.

wsgi application#

We need a WSGI application - this is a Python program that is used when you ask for a specific URL. It should have essance with name application, in basic option it can be just function. So in the following example I show exactly this option:

%%bash
cat python_apache_files/wsgi_example/wsgi_basic.wsgi
def application(environ, start_response):
    status = '200 OK'
    output = b"I'm application from root. Basic example of wsgi"

    response_headers = [('Content-type', 'text/plain'),
                        ('Content-Length', str(len(output)))]
    start_response(status, response_headers)

    return [output]

Site config#

Really important file that describe site behavior. Crusial information:

  • Should be placed in /ect/apache2/apailible-sites;

  • WSGIScriptAlias directive to describe which Python file will be used as the entry point, and which suburl of the site it will use;

  • WSGIDaemonProcess -> python-path describe which Python interpreter should be used.;

%%bash
cat python_apache_files/wsgi_example/wsgi_basic.conf
<VirtualHost *:80>
        ServerName application
        ServerAdmin webmaster@localhost
        DocumentRoot /var/www/application

        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined

        WSGIDaemonProcess application threads=5 user=www-data group=www-data python-path=/var/www/application/venv/lib/python3.11/site-packages python-home=/var/www/application/venv

        WSGIScriptAlias /suburl /var/www/application/suburl.wsgi
        WSGIScriptAlias / /var/www/application/wsgi_basic.wsgi
        
        <Directory /var/www/application>
                Order deny,allow
                Allow from all
        </Directory>

</VirtualHost>

Subulr#

If you want to have several wsgi scripts on one server, you can use suburl - url to access these pages will be like <name of site>/<sub page>. So in the central example of this notebook, /suburl is used to describe subrul:

WSGIScriptAlias /suburl /var/www/application/suburl.wsgi

Note Suburls should be written before the root url. It looks like higher level urls (which closer to root) will overwrite lower level ones. good option to test in future!

You can test the example by typing localhost:81/suburl in browser line after starting the apache web server.

Use different port#

To start the application on a different port, you need to

  • Make apache listen to it by using ports.conf in the apache program folder;

  • On the config page you should specify port 8050 in the VirtualHost directive.

So this site uses files

  • python file - just different message

%%bash
cat python_apache_files/use_specific_port/spec_port.wsgi
def application(environ, start_response):
    status = '200 OK'
    output = b"I'm application from root from specific port."

    response_headers = [('Content-type', 'text/plain'),
                        ('Content-Length', str(len(output)))]
    start_response(status, response_headers)

    return [output]
  • site config use 8050 as port in VirtualHost directive

%%bash
cat python_apache_files/use_specific_port/spec_port.conf
<VirtualHost *:8050>
        ServerName application
        ServerAdmin webmaster@localhost
        DocumentRoot /var/www/application

        ErrorLog ${APACHE_LOG_DIR}/error.log
        CustomLog ${APACHE_LOG_DIR}/access.log combined

        WSGIScriptAlias / /var/www/application/spec_port.wsgi

        <Directory /var/www/application>
                WSGIProcessGroup application
                WSGIApplicationGroup %{GLOBAL}
                Order deny,allow
                Allow from all
        </Directory>

</VirtualHost>

So this site is accessible from localhost:82 (you can see that I set port 8050 for this site, but at the docker level in which I run these examples there is a redirection from 8050 to port 82 - so on the host from will be available on port 82)

Dash application#

Dash is python data visualisation framwork based on flask. This section describe how to deploay dash application on apache2 web server.

dash application#

%%bash
cat python_apache_files/dash/app.py
from dash import Dash, html

app = Dash(__name__, requests_pathname_prefix='/dash/')
app.layout = html.Div("Hello world")

server = app.server

if __name__ == "__main__":
	app.run_server(debug=True, port=8051)

Note:

  • server = app.server is very important - it is loaded in wsgi;

  • requests_pathname_prefix='/dash/' in Dash constructor will allow to run application with suburl dash.

wsgi file#

The main purpose of the wsgi file here is to load dash.Dash as an application essence for wsgi.

%%bash
cat python_apache_files/dash/dash.wsgi
import sys
sys.path.insert(0,"/var/www/application/")
from app import server as application

Python current working directory#

The official documentation proposes to use val = os.path.dirname(__file__) - to understand what filepaht should be used.