Web Technologies/2021-2022/Laboratory 7
Web servers
editIn 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:
- Apache2 - https://httpd.apache.org/
- NGINX - https://www.nginx.com/
- Lighttpd - https://www.lighttpd.net/
- Apache Tomcat - http://tomcat.apache.org/
NOTE: Laboratory 1 provides some insights about HTTP.
WSGI
editWSGI (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
editThe 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
editThe 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
editUsing NGINX and uwsgi, by manual configuration:
editIdeally, 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:
editIf you would like to use Docker instead of manually deploying your application, there are some prerequisites to be done:
- You should create an directory named `app` in which you will copy the `main.py` and `db_creator.py` scripts.
- 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://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller