Web Technologies/2021-2022/Laboratory 7

Web servers

edit

In the context of Web Technologies, we have described how interaction is done in a client-server architecture. With HTTP we have so far seen examples for clients (browsers, web crawlers, download clients). However, on the other part we have Web Servers which implement how HTTP requests are to be dealt with, and how responses are to be crafted, and add the information that is to be delivered to the client.

We are referring specifically to software servers, and there are some popular (Open Source) choices:

NOTE: Laboratory 1 provides some insights about HTTP.

WSGI

edit

WSGI (Web Server Gateway Interface) is a convention for allowing web servers to proxy requests to web applications.

Using WSGI is required in the context of Flask applications.

Flask-SQLAlchemy

edit

The Flask-SQLAlchemy package aims to make using SQLAlchemy ORM (Object Relational Mapper) in conjunction with Flask in an easier fashion.

As the backing DBMS for these examples, we will use SQLite.

from flask import Flask, render_template
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db = SQLAlchemy(app)

class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    age = db.Column(db.Integer)

    def __repr__(self):
        return f"User: {self.username}\nEmail: {self.email}"
    

@app.route('/')
def display_content(content=None):
    return "Hello world"


@app.route('/user/profile/<int:user_id>')
def dispay_user_profile(user_id):
    user = User.query.filter_by(id=user_id).first()
    return render_template('user_profile.html', user=user)


@app.route('/users')
def dispay_all_users():
    users = User.query.all()
    return render_template('users.html', users=users)

Creating the database and populating the database can be easily done also with Flask-SQLAlchemy:

from app import db, User, app

with app.app_context():
    db.create_all()

    # Populate database with some entries
    admin = User(username='admin', email='admin@example.com', age=42)
    regular_user = User(username='user', email='user@example.com', age=20)

    db.session.add(admin)
    db.session.add(regular_user)

    db.session.commit()

The users.html template that displays information about the user:

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8"/>
    <title>Document</title>
    
    <style>
      td, th, tr {
	  border: 1px solid black;
	  padding: 5px;
      }
    </style>
  </head>
  <body>
    <table>
      <tr>
	<th>ID</th>
	<th>Username</th>
	<th>Email</th>
	<th>Age</th>	
      </tr>
      {% for user in users %}
      <tr>
	    <td> {{ user.id }}</td>
	    <td> {{ user.username }} </td>
    	<td> {{ user.email }} </td>
    	<td> {{ user.age }} </td>
      </tr>
      {% endfor %}
    </table>
  </body>
</html>

Model-View-Controller (MVC) Architecture

edit

The MVC Architecture proposes that a web application should be structured around three different actors:

  • Models - Objects that store data (generally tightly coupled and designed to fit the schema of a database)
  • Views - Representation of data
  • Controllers - Middleware that specifies how views interact with models and vice-versa,

Why MVC?

edit
  • Separation of business logic and user interface.
  • Easily mantainable codebase.
  • Reusable components.

Deploying a Flask application to a production environment

edit

Using NGINX and uwsgi, by manual configuration:

edit

Ideally, after the development process of a Flask application, the source code will be put into a production environment which will use a web server and a WSGI for serving users.

The following sources illustrate how to install nginx and configure uWSGI in order to deploy a Flask application (assumed that you run a Debian-based GNU/Linux distribution, or WSL2).

sudo apt-get update
sudo apt-get install nginx libssl-dev

Creation of a virtual environment with all the dependencies:

virtualenv -p python3 venv

Activating the virtual environment:

source venv/bin/activate

Installing flask, flask-sqlalchemy and uwsgi packages:

pip install flask flask-sqlalchemy uwsgi

Afterward, the creation of an entrypoint for WSGI where the Flask application is triggered is required, the file wsgi-flask.py would contain:

from flaskapp import app

if __name__ == "__main__":
    app.run()

uWSGI needs a configuration file (flaskapp.ini):

[uwsgi]
module = wsgi:app

master = true
processes = 5

socket = flaskapp.sock
chmod-socket = 660

vacuum = true
logto = /path/to/your/project/logfile.log

Afterwards, nginx needs to be configured to proxy requests to WSGI:

sudo nano /etc/nginx/sites-available/flaskapp

The content of this file should include the port nginx will listen to, and point to your uWSGI socket:

server {
    listen 80;
    server_name 127.0.0.1;
    
    location / {
        include uwsgi_params;
        uwsgi_pass unix:/path/to/flask/project/flaskapp.sock;
    }
}

NOTE: Restarting NGINX is needed at this point is required in order for the configuration to take effect. Under an activated environment uwsgi needs to run (this is usually configured to happen automatically)

uwsgi --ini flaskapp.ini

Using Docker with a Dockerfile:

edit

If you would like to use Docker instead of manually deploying your application, there are some prerequisites to be done:

  1. You should create an directory named `app` in which you will copy the `main.py` and `db_creator.py` scripts.
  2. You should create a Dockerfile.

The image we will be using is the tiangolo/uwsgi-nginx-flask/image.

FROM tiangolo/uwsgi-nginx-flask

COPY ./app /app

COPY ./requirements.txt /app

RUN pip install -r /app/requirements.txt
RUN python /app/db_creator.py

We will copy our application directory into the container's `/app` directory as pointed out in the image's documentation.

We also copy our `requirements.txt` file with all the requirements needed for the application to run (you can generate this file by using the `pip freeze > requirements.txt` command while having the virtual environment active).

Then we run `pip install -r /app/requirements.txt` to install all our dependencies.

The last step is for us to run the script that will generate and populate our database using the python command.


You should have the following directory structure in order for this to work:

.
├── Dockerfile
├── app
│   ├── artifact.py
│   └── main.py
├── requirements.txt
└── venv

Then, you want to build the image using docker build:

docker build . -t "laboratory-7"

Then finally, create a container using docker run, while exposing port 80:

docker run -p 80:80 laboratory-7

Your application should now be available at localhost:80.


Useful links:

https://flask-sqlalchemy.palletsprojects.com/en/2.x/

https://www.sqlalchemy.org/

https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller

https://www.sqlite.org/index.html

https://uwsgi-docs.readthedocs.io/en/latest/