Flask 1: Ports, Routes, Handlers, Endpoints and Parameterized Endpoints

In this reading, we'll get you started learning about Flask, a web application framework. You'll also learn about using virtualenv, a very important tool for working with Python.

If you need to brush up on your Python, I strongly suggest this Python 101. If you are feeling good about your Python skills from CS 111, there are a few topics at the end of that Python 101 that are not typically covered in CS 111. You can skip ahead to stuff not in CS 111

Web Application Frameworks

Why use Flask or any Web Application Framework versus coding in plain Python? In general, why use a web application framework?

A web application framework systematizes some important but routine aspects of web applications. Most modern web applications share the following features:

  • They use some kind of templating system for creating the responses. This allows separating the computed, varying parts of the response from the static HTML and other front-end stuff. (These are sometimes called the views; see below.)
  • They handle a number of different use cases (ways that the app is used) in a single program.
  • Often, these different use cases are accessed via different URLs. For example, the IMDB has one URL for each person and one for each movie, but of course these are generated by software when requested. In general a web application comprises a set of URLs. The web framework has just one main back-end script (plus, of course, supporting modules and files).
  • The different use cases are distinguished by some initial logic that analyzes the incoming request and decides how to handle it. A web framework centralizes that logic. This logic is also called routing. (It is also sometimes called the controller; see below.)

Frameworks usually involve a long-term process (in the sense of an operating system process). That process lasts across many requests, so templates don't have to be re-read from disk each time, and other persistent information can be retained in memory.

Model-View-Controller

Web frameworks are often described as fitting into the Model-View-Controller (MVC) paradigm. In that analysis:

  • The database is the model.
  • The logic to determine the use case and how to handle it is the controller.
  • The routes and templates that determine what the user sees are called the views.

This terminology is helpful if you already understand MVC, but if you don't, it's useless. Make of it what you will.

Background Concepts

There are a few background concepts that we'll run into when using Flask, so I'd like to describe those first.

Process

A process, in computer science, is a program that is currently executing. Programs that are executing have memory allocated to them that they can store useful stuff in. We all know that if our laptop runs out of battery power or crashes, certain things don't get saved, such as the changes we made to a MS Word document or to a file being edited by Emacs. That's because the changes were in memory but not (yet) saved to disk. (Some applications save to crash recovery files and other kinds of safety nets; don't let those features confuse the issue.)

Some processes run quickly and exit right away. For example, an ls command runs for less than a second. Other processes stick around for long periods. (I might have Chrome, Firefox, and Emacs all running for weeks on my computer.)

Flask is more like the latter: a long-running process. Indeed, it might run for weeks. Essentially, Flask starts an infinite loop, where the loop listens for web requests and handles them. When we develop and debug our apps, we'll probably run Flask for minutes or hours, killing and restarting it as needed to load new code and such.

Flask and Apache

Flask is used in two distinct ways:

  • development mode. In development mode, often running just on a single person's laptop, the Flask process is used instead of Apache, in the sense of handling all the web traffic. So, you don't need to have Apache running at all.
  • production mode, also called deployed mode. In this mode, web requests to the server (such as Tempest) go to Apache, which recognizes that they should be handled by your Flask process, and it relays the request to Flask.

In CS 304, we will just be using development mode while we, um, develop our applications. Occasionally, a project team wants to "deploy" their finished app, making it available to the community, in which case we can transition to production mode. But that's an option, not a requirement.

Ports

A server computer like Tempest has a number of processes listening to the network. Each process listens on a different port. A port is just a 16-bit number from 1 to 65536. You can think of a port as a numbered door (the French word for door is porte) to the machine. Incoming network packets specify which port they should go to, and then they're handled by the process listening on that port.

Ports numbered less than 1024 are reserved for the operating system. Here are some standard services and the ports they listen on:

  • 22: ssh (this is how you ssh to a machine)
  • 25: sendmail
  • 80: http (Apache)
  • 443: https (Apache using secure connections)
  • 3306: MySQLd (the server)

There are many others; it's not important to remember any of these numbers. The point is that different services listen on different ports. Think of them as different offices in town hall: you go to different offices to pay your taxes, schedule a building inspection, complain about trash collection, etc.

If Apache is running on Tempest listening on port 80, and you run your Flask application on Tempest, what port is your Flask application running on? It can't listen on port 80 without interfering with the normal web services that Tempest provides. (You wouldn't be able to open port 80 anyhow, since it's a special port number.) It can't listen on any other ports that are in use, either.

By default, Flask runs on port 5000 in development mode. That works fine if you're running on your own laptop. But if all of the students in CS 304 are running their Flask applications on Tempest, we can't all use port 5000 without all of us intefering with each other.

Consequently, we all need a way to open a port on Tempest that is unique to us. Fortunately, each user on a Unix system has a user id (UID) as well as a username. The UID is a unique number that will be perfect to keep us all separate. (If you're curious, you can find out your UID by logging in and using the id command.) We'll write our Flask programs to open up a port number corresponding to our UID. So if your UID is 6543, you'll open port 6543.

When using a web browser, if you want to send your web requests to a port other than the default HTTP port (80), simply add a colon and the port number to the URL. For example, when you're developing with Flask on your own machine, you might use a URL like this:

http://localhost:5000/hello

The Flask documentation will have examples like that a lot.

When we develop our Flask apps, instead of localhost and port 5000, we'll each use URLs like this, where the 6543 is our UID and hence our port:

http://cs.wellesley.edu:6543/hello

The CS 304 course account has UID 1942, so when I login to the CS 304 course account and run my Flask app in development mode, it opens port 1942 and I access it at:

http://cs.wellesley.edu:1942/hello

This will be tricky for the first day, and then it'll become completely routine.

VPN and SSH Tunnel

The previous section works great from on-campus, but fails if we are off-campus (which is true for me when I work from home, and for many of you when you are off-campus for cross-registered classes, visiting home, etc.). That's because the campus firewall only allows a handful of ports to be open "through" the firewall (ports 22, 80, and 443 for example, but not 1942).

There are two choices we have:

  • Use the VPN. That changes your laptop to be inside the campus firewall, and you can access your app on the port with your UID, as described above. This is relatively easy and works. It can be a little slow (traffic goes back and forth to campus extra times because of the VPN), but I've never really noticed much lag. On the other hand, I live 3 miles from campus, not on the other side of the world.
  • Use an SSH tunnel. The SSH program can set up a "tunnel" through the firewall, relaying traffic to port XYZ on Tempest to port ABC on your laptop.

The SSH incantation to set up a relay from 1942 on tempest to 8080 on my laptop is the following command, which I run in a terminal on my laptop, not on tempest:

ssh -L 8080:localhost:1942 anderson@cs.wellesley.edu

That logs me into my personal account (which I then ignore) and also sets up the SSH tunnel. I then go to my laptop's browser and access this URL:

localhost:8080

(8080, like 5000 and 8000, is commonly used for this purpose, but the number doesn't matter. But there's also no reason to get creative.)

This is described in our FAQ as well: Flask from off-campus

There's also a video on our videos page.

I use SSH tunneling most of the time. I've even written a shell alias so that I don't have to remember the incantation. Ask me how if you're curious.

Flask

Since Flask is a common, well-used web framework, good documentation already exists for it. To prepare for class, please read the following tutorials. (The first links to the next and so forth.)

The Flask Environment page has a small error in it. To activate the virtual environment, do

source venv/bin/activate

Yes, this is similar to the source command in MySQL. Both mean "read code from the specified file". Here, we are reading shell commands, not SQL statements.

Flask Summary

There is a bit more after this summary, so don't stop reading yet.

Here's a summary/recap of the external readings. This section won't substitute for them, but might make a useful refresher when you're looking for some information. (You should not feel obliged to memorize anything in this course.)

  • Flask is a web application (micro) framework.
  • It uses Jinja2 as its templating engine, to combine dynamic data and static templates into dynamically built web pages.
  • Flask works with both Python 2.7 and Python 3. We will be using Python 3 in CS 304.

Virtualenv

Virtualenv is an important general-purpose Python tool, that is not part of Flask or even related to Flask. It has to do with Python coding in a modern environment. I have a separate reading on virtualenv. You must also read that.

Here's a relevant summary about virtualenv in the context of Flask:

  • We will use virtualenv so that you can install Python packages if needed.
  • Virtualenv install Python packages from the web into a folder in your account, so they are separate from anyone else's. Indeed, you could have several virtualenvs, for different projects and such.
  • Virtualenv is already installed on Tempest; no need to install the command.
  • You create (once) a virtualenv by doing virtualenv venv; that creates a directory called venv and puts some initial stuff in it.
  • Whenever you want to use that virtualenv, you do source venv/bin/activate
  • Then you can install flask (once) by doing pip install flask

We will set up our virtual environments in class, together, but it helps to understand what we are doing beforehand.

Basic App

Here is a "hello world" Flask app. You can download it from hello.py

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello World!'

if __name__ == '__main__':
    import os
    port = os.getuid()
    app.debug = True
    app.run('0.0.0.0',port)

That file is a little different from the TutorialsPoint example, because there's some Python code to determine your UID (a number) and open that port. It also opens an external network inteface (so you can connect from outside Tempest) and turns on debug mode. None of this will affect us much.

Routing

Routing is the mapping of URLs to Python functions. The user clicks a link, browser sends the URL, Flask's routing runs your function. Thus, the user runs your Flask function. It's like magic.

The example above routes (directs, sends) the shortest possible URL, namely / to your hello_world function.

If we also want to say a greeting if the user sends the /hello URL, we can do this:

@app.route('/hello/')
def greeting():
    # is this too pompous?
    return 'Greetings and Felicitations' 

Note that the URL in the app.route() function, like /hello/ above, is also often called an endpoint, since it's the destination of a web request.

Variable Rules

All of the preceding had one URL mapping to one function. What if you want a set of URLS, based on some pattern to go to a function? In fact, Flask will also parse the URL according to the pattern, giving you the pieces as arguments to your function.

The example in the reading is good. You can append your name to the URL. Here's the code:

@app.route('/greetme/<user>')
def custom_greeting(user):
    return 'Hello {name}, nice to meet you!'.format(name=user)

The code above uses the Python .format method, which you probably did not learn in CS 111. See string interpolation for a quick review. You can also see references like GeeksforGeeks and W3Schools

If my browser sends the following URL to Flask:

/greetme/Scott

It will get back (and display in the browser):

Hello Scott, nice to meet you!

Important note: There is a one to one correspondence between the parameters in the URL and the parameters of the handler function. If the URL contains N pieces of information that the handler needs, those are passed in that 1:1 way.

For example, if we have a web application that allows us to look up newspapers by year, month and day, all contained in the URL, we might define this as follows:

@app.route('/paper-by-date/<year>/<month>/<day>/')
def lookup_by_date(year, month, day):
    ...

Example URLs:

  • /paper-by-date/1961/5/6/ this looks up the paper for May 6th, 1961, George Clooney's birthday.
  • /paper-by-date/1961/5/ this doesn't match that route. It might match some other route, or it might match no route (resulting in a error), but it doesn't match this route.
  • /paper-by-date/5/6/1961/ this invokes the handler function with 5 for the year, 6 for the month, and 1961 for the day. This is unlikely to work.

So, to belabor the point, if the handler has N arguments, there have to be N parameters (each in angle brackets) in the route or endpoint. Conversely, if the route/endpoint has N parameters in it, the handler has to accept N arguments.

Note that the name of the handler doesn't have to be the same as the URL. In the example, above, the URL was paper-by-date and Python functions can't have hyphens in their names. The handler function is named lookup_by_date. Nevertheless, there is a 1:1 correspondence of routes (URLs) to handler functions.

Heck, we could even use Flask to do simple arithmetic with information in the URL:

@app.route('/subtract/<x>/<y>/')
def subtract(x, y):
    z = int(x) - int(y)
    return '{x} - {y} is {z}'.format(x=x, y=y, z=z)

Canonical URLS

If you define a route like this, without a trailing slash:

@app.route('/hello')
def greeting():
    return 'Greetings and Felicitations' 

Flask will map /hello and only that to your function. If the browser sends a URL with a trailing slash, /hello/, it gets a 404.

But if you define a route with a trailing slash:

@app.route('/hello/')
def greeting():
    return 'Greetings and Felicitations' 

Then both URLS, with and without the trailing slash, map to that function.

So, generally, prefer a trailing slash.

Templates

We will talk more about true templates in a later class, but for now, we'll just get started by talking about returning an HTML page from a route/handler. (The URL is typically referred to as a route or endpoint and the function as a handler, since it handles those requests).

The example we've been using is fine, but it doesn't have any HTML in it. But the response is received by the browser and shown to the user, so it could have HTML and such in it:

@app.route('/hello/')
def greeting():
    # definitely too pompous
    return '''<h1>Hail</h1>
              <p>Greetings and Felicitations, <em>honored</em> guest!</p>'''

However, such an approach doesn't scale. It clutters up our Python files with a bunch of HTML (and maybe CSS and JavaScript), much of which is static and probably should be shared among different routes. We will address all those issues when we get to true templating. Meanwhile, we'll talk about how to hide all that HTML in a separate file.

A Flask app calls these templates and puts them in a subfolder of the app called templates. Let's put our app in a folder called greeting_app. Our Flask application now looks like:

greeting_app/
    app.py
    templates/
        greet.html

The greet.html file is an ordinary HTML file. Later, we'll see how to make it dynamic. For now, it's purely static. The interesting part of our app.py now becomes:

from flask import (Flask, render_template)
app = Flask(__name__)

@app.route('/')
def hello_world():
    return render_template('greet.html')

Notice two small changes. The handler has changed so that instead of returning some HTML directly, it returns the value of the render_template function and refers to a file in the templates folder. The second change is that we have to import that function from the flask module (see line 1).

Our templates/greet.html is just plain HTML:

<!doctype html>
<head>
    <meta charset="utf-8">
    <!-- for mobile-friendly pages -->
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name=author content="Scott D. Anderson">
    <meta name=description content="">
    <meta name=keywords content="">
    <title>Greeting</title>
    <style>
      h1 { color: purple; }
    </style>
</head>
<body>

<h1>Hail</h1>

<!-- definitely *way* too pompous -->

<p>Greetings and Felicitations, <em>honored</em> guest!</p>

</body>
</html>

That's all for now. You know all you need to know for the first flask assignment: forms

Flask Summary

This introduction to Flask covered a lot of ground and a lot of new concepts. We'll see these many times over the next few weeks, so if it's overwhelming right now, rest assured that you'll get lots of practice and review.

Here are some of the things we learned:

  • Flask is a Python module that we import into our Python program
  • Flask turns our running python program into a tiny web server (a program that receives and responds to web requests).
  • Flask handles a lot of the routine parts of web applications for us, making our lives as web developers much easier.
  • Our Flask web server runs for a longish amount of time (minutes to hours) while we are working and debugging. A long-running program is called a process.
  • Our Flask process listens for web requests on a particular port: a number on the server.
  • Each process has to use a different port, so I've adopted the convention that each of us will use our user id (UID) as our port number.
  • One of the important things that Flask does for us is routing: looking at the URL in the web request and invoking one of our functions to handle that request.
  • We tell Flask which function handles which route by using the @app.route(url) decorator function. We decorate the handler function by preceding its definition with the @app.route(url) to specify the url that should be routed to that handler function. See below.
  • The url or endpoint is often a fixed string, like /about/ for the "About" page or /login/ for the login page, but it can also be parameterized, which means that a set of matching URLs are all handled by one handler function. Some examples:

Examples of defining handlers:

# this maps our homepage to the minimal endpoint, namely /
@app.route('/')
def home_page():
    return '<h1>Welcome!</h1> <p>Welcome to my app.</p>'

@app.route('/about/')
def about():
    # the about page is static, but long, so it's in a file
    return render_template('about.html') 

# this is a parameterized endpoint. It handles all profile pages

@app.route('/profile/<username>')
def profile_page(username):
   # look up info about this user and then render their custom profile page
   data = database.lookup(username)
   render_template('profile.html', info = data)

Decorators

Flask makes extensive use of Python's decorator syntax. You can ignore the theory and just copy the syntax, but if you're curious, this Primer on Python Decorators is excellent.