[SOLVED] Flask Web Forum API Project

100.00 $

Category:
Click Category Button to View Your Next Assignment | Homework

You will receive the following solution file(s) instantly after successful payment:

zip file icon flaskproject-odgmfp.zip (4.5 KB)
Assignment Instructions Updated Recently? Submit Below and we will provide new Solution!
Submit New Instructions
🔒 Securely Powered by:
Secure Checkout
5/5 - (4 votes)

In this project, we will implement the backend part of a web forum. Your job will be to write a server in Python that serves a variety of JSON REST API endpoints that could be used to run a web forum. (If these words don’t mean much to you, don’t worry! We’ll explain it all in due course.)

Notably, we will not be grading your code so much as grading your tests. You will write five (5) extensions beyond the baseline behavior; every extension must be thoroughly tested.

You can do this assignment alone or in a group of three.

How does the web work?

If you are familiar with web programming, feel free to skip this section, which is meant to give an overview of the technologies we’ll be using.

The “web” is a set of protocols that run on the Internet, primarily HTTP

Links to an external site.

. A client—a “web browser” of some kind—connects to a server and makes a request for a resource, specified by the path part of a URL; the server sends back a response.

A URL

Links to an external site.

is a “uniform resource locator”, and it has the form:

url       ::= scheme “:” [“//” authority] path [“?” query] [“#” fragment]

authority ::= [userinfo “@”] host [“:” port]

path      ::= “/” [name “/”]* [name]

(Square brackets man something is optional.) On the web, scheme is typically http or https; the userinfo is usually left off, and port defaults to 80 for http and 443 for https. The path part is a

In the url https://greenberg.science/papers.html, the scheme is https, the host and authority are greenberg.science, the path is /papers.html and there is no query or fragment. In the url https://en.wikipedia.org/wiki/URL#Syntax, the scheme is https, the host and authority are en.wikipedia.org, the path is /wiki/URL and the fragment is Syntax.

HTTP adds one more step of complexity to URLs: methods

Links to an external site.

. A client can GET a given URL, but it can also POST data to it, or request to DELETE the correspoding resource. Most “normal” requests just use GET, but submitting forms online uses POST. APIs like the one you’ll be writing use even more—DELETE is a commonly used method to indicate that some resource should be deleted.

HTTP requests include not just a method, but a body—data sent to that resource. The GET method’s body is optional, but some methods—like POST or PUT—expect data. The POST method is used for web forms and file uploads; the body of the request will include the form data or the file being uploaded.

The GET, HEAD, OPTIONS, and TRACE methods are meant to be “safe”—they should not alter the state of the server in any salient way. That is, using one of these methods should only execute “read only” code, modulo logging, etc.

Frontend and backend

A web site is typically broken into two parts: a frontend and a backend. The frontend handles display and user interaction; the backend handles data movement and core logic. The exact boundary between the front and backends varies from system to system. The division between frontend and backend exists not just for logical, engineering reasons, but for managerial reasons: splitting the site into teams makes for smaller, easier to manage groups of engineers.

The frontend of a website is pretty and presented to the user: HTML, JavaScript, CSS, and other technologies join together to present a usable, interactive website.

The backend of a website is the core logic: it may store data in a database, retrieve files from memory or disk, or perform computations. The backend of a website might also have the job of serving the files that the frontend uses in addition to responding to requests from the frontend.

Let’s make it concrete: consider the GradeScope. GradeScope offers an interactive display for you to enroll in a course, see the assignments, upload your files, and see your grades. The frontend works hard to present this information to you in a pretty, usable way: put the list of assignments here, the grades there, and so on. The backend not only serves you the files that run the frontend, but it also runs all of the logic: when you submit code, it triggers a run of the autograder. JavaScript in the frontend waits to hear the results of grading, loads those results from the backened, and presents them to you.

In this assignment, you will only be writing backend code: no HTML, no CSS, no need to be pretty.

API endpoints

A backend offers some particular API

Links to an external site.

for the frontend (or other sites) to use. For an API on the web, that means specifying certain an HTTP method and a URL that corresponds to some particular action on a resource. For example, Wikipedia’s API endpoint for viewing a page in the language lang is:

GET https://{{lang}}.wikipedia.org/wiki/{{page}}

(Here {{foo}} is how we specify parameters to a URL.)

So a request to GET https://en.wikipedia.org/wiki/Hot_dog loads the page for hot dogs in English.

The API endpoint for editing a page is:

POST https://{lang}.wikipedia.org/w/index.php?title={{title}}&action=submit

where the body of the request includes form data about any edits being made. Note that we’re using a query title={{title}}&action=submit as well as body data (the form data being submitted to update the page).

JSON

JSON

Links to an external site.

, or JavaScript Object Notation, is a semi-structured data format widely used for data exchange. JSON’s syntax is almost the same as Python’s notation for objects, but not quite. The API you’ll be implementing will use JSON to send and receive data.

RESTfulness

HTTP and other web protocols are stateless: the client connects, sends some number of requests, and then disconnects. It’s up to the client and the server to agree about the state of the world. If you’re not careful, it’s easy for server and client state to get out of sync. It was a real problem in the 90s and early 2000s: you add look at one flight, then another; you go back to the first flight, click “buy”, and the second flight shows up when you’re checking out… yikes!

To build a safe stateful system on top of stateless HTTP, programmers use REST

Links to an external site.

, or representational state transfer—it’s the only way to safely program the web.

In a RESTful API, there is no implicit state between requests: every request should carry enough information so that the server has the right amount context. Determining exactly what information that is can be challenging!

How do you build a backend server?

Our backend server needs to receive HTTP requests, route them to code that interprets them correctly (what does each , and sends appropriate responses.

Flask, Django, and beyond

We could go ahead and implement an HTTP server ourselves just to build our backend server… but that’d be a lot of work, and it’s hard to build a good HTTP server!

Fortunately, most programming languages have “web app frameworks” that let us write only the routing and application logic.

We recommend using Flask

Links to an external site.

, though Django

Links to an external site.

is a good alternative.

To install flask, run pip3 install flask.

Here’s a simple server; copy this into a file called app.py.

from flask import Flask

 

from secrets import randbelow

 

app = Flask(__name__)

 

@app.get(“/random/<int:sides>”)

def roll(sides):

if sides <= 0:

return { ‘err’: ‘need a positive number of sides’ }, 400

 

return { ‘num’: randbelow(sides) + 1 }

In a terminal, go to the directory with app.py in it and run flask run –reload (the –reload causes flask to restart the server when app.py is updated). When you run this command, you should see:

* Debug mode: off

WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.

* Running on http://127.0.0.1:5000

Press CTRL+C to quit

The IP address 127.0.0.1 is the localhost, i.e., your computer. We’ve defaulted to port 5000—it requires privileges to open ports below 1024.

In another terminal, run curl http://127.0.0.1:5000/random/6; you will see:

{“num”:1}

…though the number may well be different! Run the command a few times to verify that random numbers are being generated.

Note that you can have only one server listening on a given port at a time: if you try to run two, you’ll get a message like:

* Debug mode: off

Address already in use

Port 5000 is in use by another program. Either identify and stop that program, or start the server with a different port.

On macOS, try disabling the ‘AirPlay Receiver’ service from System Preferences -> Sharing.

In that case, you’ll need to stop whatever other server you have first.

Routing

Our Flask app specifies a single route: GET /random/<int:sides>. The @ sign before the function definition is called a decorator

Links to an external site.

; here it informs Flask that when a GET request comes to /random/XXX and XXX can be treated as an int, you should call the method roll() with sides=int(XXX). That is, a GET request to /random/20 will result in the call roll(sides=20). When that method runs, Flask sees the dictionary that is returned, transforms it to JSON (note the double quotes!), and returns that to the client. Here’s more detail, with all the metadata being read and written:

curl -v http://127.0.0.1:5000/random/12

*   Trying 127.0.0.1…

* TCP_NODELAY set

* Connected to 127.0.0.1 (127.0.0.1) port 5000 (#0)

> GET /random/12 HTTP/1.1

> Host: 127.0.0.1:5000

> User-Agent: curl/7.54.0

> Accept: */*

>

< HTTP/1.1 200 OK

< Server: Werkzeug/2.2.3 Python/3.9.5

< Date: Tue, 18 Apr 2023 18:18:44 GMT

< Content-Type: application/json

< Content-Length: 11

< Connection: close

<

{“num”:10}

* Closing connection 0

Notice the Content-Type: application/json on the response, indicating that Flask automatically sent JSON back. This will make your job easy!

Let’s understand the error case:

curl -v http://127.0.0.1:5000/random/0

*   Trying 127.0.0.1…

* TCP_NODELAY set

* Connected to 127.0.0.1 (127.0.0.1) port 5000 (#0)

> GET /random/0 HTTP/1.1

> Host: 127.0.0.1:5000

> User-Agent: curl/7.54.0

> Accept: */*

>

< HTTP/1.1 400 BAD REQUEST

< Server: Werkzeug/2.2.3 Python/3.9.5

< Date: Tue, 18 Apr 2023 18:20:07 GMT

< Content-Type: application/json

< Content-Length: 42

< Connection: close

<

{“err”:”need a positive number of sides”}

* Closing connection 0

Notice that we get back a JSON object describing the error, but we also get the status 400 BAD REQUEST. HTTP has a variety of status codes for use in a broad variety of situations

Links to an external site.

.

Notice that to set the status 400, we returned a tuple of a Python dictionary and a number indicating the status. If we just return a value, Flask will assume we mean 200 OK, i.e., the request was successful.

You can learn more about Flask from its quickstart tutorial

Links to an external site.

.

Baseline behavior

We’re building a web forum: users write, read, and delete posts. The baseline API has just three endpoints.

Error messages should take the form of a JSON object with at least the field err that holds a string describing the error.

Endpoint #1: create a post with POST /post

A POST request to /post should have a body consisting of a JSON object with a string-valued field called msg.

If the input isn’t a JSON object or is missing the msg field or the msg isn’t a string, you should return status 400 (indicating ‘bad request’).

You should return a JSON object with (at least) three fields:

  • id should be an integer
  • key should be a long, unique random string (which is used to later delete the post)
  • timestamp should be an ISO 8601 timestamp in UTC

This endpoint is stateful: it creates a new post that can be read or deleted (with the key).

The id should be unique: no two posts should share an id.

The key must be long enough and random enough to be secure—you should not use math.random.

The timestamp should be the time the server processed this request, i.e., when the post was created.

Check out the secrets

Links to an external site.

for generating secure random numbers and datetime

Links to an external site.

modules for working with dates.

Endpoint #2: read a post with GET /post/{{id}}

A GET request to /post/{{id}} looks up the given integer id.

If the post does not exist, it should return an error message with a 404 exit status (indicating ‘not found’).

If the post does exist, it should return a JSON object with at least three fields:

  • id should be an integer
  • timestamp should be an ISO 8601 timestamp in UTC
  • msg should be a string

The timestamp should be the same as was returned when the post was created. The msg should be the text.

Your response can include other things, but it must not include the key! The key is a secret token used to delete posts.

Endpoint #3: delete a post with DELETE /post/{{id}}/delete/{{key}}

A DELETE request to /post/{{id}}/delete/{{key}} is meant to delete the post with id id.

If the post does not exist, it should return an error message with a 404 exit status (indicating ‘not found’).

If the post exists but the key does not match the key that was randomly generated for that post, then you should return an error message with 403 exit status (indicating ‘forbidden’).

If the post exists and the key matches, then you should delete that post. This method should return the same information as PUT /post, namely:

You should return a JSON object with (at least) three fields:

  • id should be an integer
  • key should be a long, unique random string (which is used to later delete the post)
  • timestamp should be an ISO 8601 timestamp in UTC

The timestamp should be the time the post was created.

This endpoint is stateful: it deletes a post.

State and race conditions

Your backend is meant to serve many clients at once—so it’s important to avoid race conditions, where clients see an inconsistent view of the state.

The simplest way to do this is to create a Lock

Links to an external site.

object and use it to manage access to a your global state: all of your reads of your global state should happen inside a with [your lock]: context. The ‘persistence’ extension below goes a step further, using a database to store all of the data.

Testing JSON APIs

You will be primarily graded based on how you test your code: we will look at the extensions you implement in your README.md. Simply implementing a feature is worth no credit if you don’t test it or we can’t understand the tests.

So: how do you test a JSON backend API?

The simplest way to test a JSON backend API is to write down requests and your expected response; you can then write a bit of code to check that those requests yield the expected responses. But this approach doesn’t scale so well: you might need to us data from one response in another request, and before you know it you’re writing a big complicated program.

The modern approach is to use tools to help you test APIs. We recommend Postman

Links to an external site.

. Postman lets you write requests using templates, passing information between requests and testing the results. (Sadly, the tests are written in JavaScript… sorry for this added complexity!) Postman has clear and thorough documentation, and we will provide some starter tests for the baseline behavir.

Postman’s GUI is great for interactive testing; you can also use it to export a file for command-line testing, which will be necessary for us to evaluate your code. You can install the command-line tester by running npm install -g newman; if you’re missing the npm command, you need to install node and npm

Links to an external site.

. nvm

Links to an external site.

is a popular way to do that.

Support scripts for your code

We expect your submission to come with three scripts:

  • a sh script that installs any dependencies (you can assume that flask and Django are already installed)
  • a sh script that starts your server
  • a sh script runs all of your tests (you can assume that npm is already installed)

Here’s a sample setup.sh:

#!/bin/sh

 

# uncomment the line below to install some-package

# pip3 install some-package

You can depend on whatever Python packages you like, so long as you include them in your setup.shand they can be built on Gradescope.

Here’s a sample run.sh:

#!/bin/sh

 

exec flask run

This simply runs the server that is in the file app.py. (The exec makes it easier to kill the server.)

Here’s a sample test.sh:

#!/bin/sh

 

set -e # exit immediately if newman complains

trap ‘kill $PID’ EXIT # kill the server on exit

 

./run.sh &

PID=$! # record the PID

 

newman run forum_multiple_posts.postman_collection.json -e env.json # use the env file

newman run forum_post_read_delete.postman_collection.json -n 50 # 50 iterations

Here we use newman to run some exported Postman scripts. Here are the JSON files we used:

If you load our test collections into JSON, you’ll notice that we are careful to not just test successful states, but unsuccessful ones, too.

We expect your tests to run in a relatively timely fashion—not more then five or ten minutes. That is, if we run test.sh, it shouldn’t take very long for us to see that results are good!

Extensions

You must implement five extensions. Each of these offers significant leeway in terms of how, precisely, you implement it. You’ll need to document your API endpoints clearly—and to get credit, you’ll need to test them thoroughly.

Also: the core baseline behavior should still work as we describe it. That is, if a request worked in baseline, it should work with your extension. It is okay if you extend the endpoints with new behaviors so long as they don’t interfere.

These extensions are listed roughly in order of complexity/difficulty. Some of the later extensions will require you to go a little deeper into web technologies—which could be fun, but could also be hard if you’ve never done it before!

Users and user keys

The baseline behavior associates a special, private key with each post. This key is necessary to delete the post. If a user has 20 posts, they (or, rather, the frontend) must manage 20 keys… that’s quite a lot!

In this extension, you should add a notion of user with a private user key. This will modify a few existing endpoints and create some new ones.

  • Each post can be associated with a user by providing the user id and corresponding user key when creating the post.
  • Whenever you give information about a post that has an associated user, you should return the associated user id along with other data (e.g., when reading and deleting posts).
  • If a user created a post, it should be sufficient to provide the user’s key to delete the post. It should be clear whether the user is providing a post’s key or a user’s key.
  • There should be some way to create users.

User profiles (needs user)

You can only do this extension if you have already implemented users.

Add metadata to users. At least one piece of metadata should be unique, like a username or an email address; at least one should not be unique, like a real name or an avatar icon.

  • User creation must specify the unique part; it may specify the non-unique parts.
  • There should be an API endpoint to retrieve a given user’s metadata given their id or their unqiue metadata.
  • There should be an API endpoint to edit a given user’s metadata; doing so requires the user’s key.
  • When returning information about a post associated with a user, you must include the user’s unique metadata.

Threaded replies

Add a notion of reply.

  • When creating a post, it should be possible to specify a post id to which the new post is replying.
  • When returning information about a post that is a reply, include the id of the post to which it is replying.
  • When returning information about a post which has replies, include the ids of every reply to that post. (You should not include replies to replies—for that, you should implement threads.)

Date- and time-based range queries

Add an API endpoint that lets the user search for posts based on a date/time.

  • It should be possible to search with a starting date and time, and ending date and time, or both an ending date and time.
  • Your endpoint should return a list of post information (i.e., id, the message, the timestamp, and any other post information, like the user).

User-based range queries (needs user)

You can only do this extension if you have already implemented users.

Add an API endpoint that lets the user search for posts based by a given user.

  • Your endpoint should return a list of post information (i.e., id, the message, the timestamp, and any other post information, like the user).

Thread-based range queries (needs replies)

You can only do this extension if you have already implemented replies.

Add an API endpoint that lets the user search for all posts in a “thread”: that is, the transitive reply chains up and down.

Suppose post 1 is replied to by posts 2 and 3; post 2 is replied to by post 4; and post 3 is replied to by post 5. Suppose also that there is some unrelated post 6, replied to by post 7When asked for the thread for post 1, 2, 3, 4, or 5, you should return posts 1 through 5, but not post 6 or 7.

  • Your endpoint should return a list of post information (i.e., id, the message, the timestamp, and any other post information, like the user).

Fulltext search

Add an API endpoint that lets the user search for posts using a fulltext search on the contents of the post’s msg. You are free to interpret fulltext search however you like—just make sure any dependency you add is in setup.sh.

  • Your endpoint should return a list of post information (i.e., id, the message, the timestamp, and any other post information, like the user).

Moderator role

Add a privileged ‘administrator’ role which can delete posts that are not their own using a distinguished key.

If you’ve implemented users, you can make whether or not a user is a moderator part of the user’s metadata, in which case the ‘moderator key’ should be distinguished from their ordinary user key.

  • It should be sufficient to provide the moderator’s key to delete the post. It should be clear in the API whether the user is providing a post’s key or a moderator’s key.
  • There should be some way to create new moderator keys, but it should be somehow protected.

Persistence

Baseline behavior does not specify persistence: if you restart the server, you lose all of the posts. A realistic forum would hold onto posts between restarts.

In this extension, your server becomes persistent. Persistence will significantly complicate your tests, as tests will interfere with each other if the server persists between tests or even runs of the test suite. Write tests to convince us that persistence works, e.g., tests that modify the database, restart the server process, and find the modifications intact.

You do not have to use a database to be persistent, though it is of course allowed (and is considered best practice in industry). If you do use a database, make sure you install whatever packages are necessary in setup.sh and start the server on an unprivileged port in run.sh.

File upload

Make it so posts can have one or more files attached. Any binary data sent in JSON should be encoded in base 64

Links to an external site.

… which precise dialect of base 64 is up to you.

Files can be uploaded either as base64 as part of the JSON request or by using a multipart request to post the file contnt.

  • Whenever you give information about a post that has one or more associated file, you should make it clear that the files exist and give their names—but not their contents.
  • Add an API endpoint that lets you load the files from a post by name.
  • Deleting a post deletes all of its files.

Blocks and bans (needs moderator)

You can only do this extension if you have already implemented moderators.

In principle, an operation that needs a key should never fail—the frontend shouldn’t be making mistakes! If you get many failures, that’s a sign of an attack.

When a given IP address fails enough times, you should either block that IP—it can make no more requests at all—or ban it—it can no longer make any requests that require keys.

  • A block or ban should expire after some fixed amount of time. (Testing this may be difficult! If you make the amount of time configurable, you should be able to test your code without waiting, e.g., an hour.)
  • If a user is blocked or banned, it should be made explicit in the HTTP status codes.
  • Add an API endpoint where a moderator can see what blocks/bans, if any, are active. You should require a moderator key.
  • Add an API endpoint where a moderator can lift a block or ban.
  • Add an API endpoint where a moderator can create a block or ban for a given interval.

Sessions (needs users)

You can only do this extension if you have already implemented users.

Allow a user to “log in” using their key, using sessions

Links to an external site.

to track who the user is. A logged in user can use their session to not need to provide a key.

A logged in moderator must still provide the moderator key to take actions they couldn’t normally take as a user..

Cross-site request forgery prevention (needs sessions)

You can only do this extension if you have already implemented users.

CSRF

Links to an external site.

, or cross-site request forgery, is a form of attack that uses sessions to trick users into taking stateful actions. Defend against it!

Extend the ‘dangerous’ API endpoints (e.g., that edit or delete data) so that they can only be run using a CSRF token provided by approprate non-dangerous endpoints, e.g., have the GET /post/{{id}}endpoint provide the token that is needed for that user to delete that post’s identifier.

Load testing

Test the performance of your code in a way that shows us how it scales with the number of concurrent users. We recommend using throughput as the primary measure.

Your load tests should automatically produce graphs characterizing the backend’s behavior.

(NB we will be running the load test and the server on the same machine, which is not a very smart thing to do: the more load we run, the less time there is for the server to run! Consider this an exercise in writing the load test itself, even if the testing methodology is not perfect.)

What do you submit?

You will need to submit three things:

  • a md file including:
    • your name and Stevens login
    • the URL of your public GitHub repo
    • an estimate of how many hours you spent on the project
    • a description of how you tested your code
    • any bugs or issues you could not resolve
    • an example of a difficult issue or bug and how you resolved
    • a list of the five extensions you’ve chosen to implement; be sure to describe the endpoints you’ve added to support this, using a documentation format similar to ours
    • detailed summaries of your tests for each of your extensions, i.e., how to interpret your testing framework and the tests you’ve written
  • a public GitHub repo with your code in it (which you can submit directly to GradeScope), including:
    • your server code
    • a sh script that installs any dependencies (you can assume that flask and Django are already installed)
    • a sh script that starts your server
    • your tests
    • a sh script runs all of your tests (you can assume that npm is already installed)

After submission, we’re going to do two things: we’re going to run your scripts and make sure your own tests pass. Then we’re going to look through your README.md and try to understand what your extensions are and how you’ve tested them. Untested implementations are worth no credit.

Please submit just once, as a group.

 

  • flaskproject-odgmfp.zip