I was trying to get a project I had done earlier up and running on this blog. (Click the picture to see it in action.)
The project relied on a CGI file written in Python being executed by the server.
I first tried a simple upload of my files to Heroku hoping that everything would just automagically work… ha! Of course it didn’t work. What follows is the series of adventures it took to get it working.
Where I insist that CGI can work on Heroku
The file structure of the project looked something like this (see the full code on Github),
icons/
index.html
auto_python.cgi
localCGIServer.py
- Maybe I just needed to add a
runtime.txt
to specify the Python version Heroku should use. Heroku defaults to python2 and the CGI server code I was using was written for python3. So I added it. Nada! - I then added a
requirements.txt
because Heroku won’t recognize it as a Python app otherwise. Nada! - I tweaked
localCGIServer.py
to use the port passed in by a user. TheProcfile
’s content (used by Heroku to initialize the app) was changed accordingly fromweb: python localCGIServer.py
toweb: python localCGIServer.py $PORT
. (See the docs for more on Heroku ports.) Still nada! - One of the articles I was reading made me suspect that the project (which worked fine locally on my Windows machine) may not be working because Heroku is linux based. To that end, I tried running the project locally on Ubuntu and - the CGI part didn’t work! We’re getting somewhere!
- I narrowed down the reason to CRLF. The article explains that CGI files with Windows style line endings (CRLF) versus linux style line endings (LF) will not work on linux servers.
- Sure enough, when I ran
file auto_python.cgi
, I gotPython script, ASCII text executable, with CRLF line terminators
- To convert the line endings from windows style to linux, I ran
fromdos auto_python.cgi
- After changing the line endings, the project worked locally on Ubuntu. If it works locally on Ubuntu then it will work on Heroku! So I deployed!
- It didn’t work =/
- At least the code was now ruled out as the problem. The problem had to be on Heroku’s end.
- Running
heroku logs
revealed this error code,sock=backend at=error code=h18 desc=“server request interrupted”
- I looked up the error code. According to the Heroku docs, “The backend socket, belonging to your app’s web process was closed before the backend returned an HTTP response.”
- Lovely! I love you too Heroku.
- Several hours of GoogleFu later, and I could not find anything on how to resolve this error.
Where I concede that CGI can't work on Heroku
At this point, I was done with anything involving CGI and Heroku in the same room! There was an answer on StackOverflow where the person recommended porting a CGI application to Flask in order to get it working in Heroku. Since I was getting nowhere with the CGI file, it was time to learn this Flask thing. I was reluctant to learn a new framework, but one way or another this app was going to go online!
Try I did,
Learn I did,
But in the end Flask it would be.
As I read and through Flask’s documentation, it was
Flask is very lightweight! And painless to use! And it’s in Python.
When porting to Flask, I didn’t change the original code (which you can find here). The main change was getting rid of auto-python.cgi
and shoving all of its code into a function called findUsers()
in Main.py
.
Gone was the localCGIServer.py
business - into a black hole where no one will ever find it.
The app’s new file structure looked like this,
static/
icons/
templates/
index.html
Main.py
Here’s the code for Main.py
import os
import re
from flask import Flask
from flask import render_template, request, Response, url_for
app = Flask(__name__)
# Home page
@app.route('/')
@app.route('/index.html')
def landingPage():
return render_template('index.html')
# Thanks page
@app.route('/thanks.html')
def thanksPage():
return render_template('thanks.html')
# Autocomplete script
@app.route('/auto_python.cgi', methods=['GET', 'POST'])
def findUsers():
if request.method == 'GET':
print("why heloo there")
# Get the query
q = str( request.args.get("to") ) # http://stackoverflow.com/q/11774265
# We will store our response HTML here
html = ''
# Our limited 'database' contains a few users
# with their username and full name
data = [
{
"user" : "amon",
"name" : "N. Equalist"
},
...
{
"user" : "sasuke",
"name" : "Sasuke Uchiha"
},
{
"user" : "vegeta",
"name" : "Saiyan Prince"
}
]
# We "search" through the data
regex = re.escape( q )
for row in data:
# Looking for users that match our auto-complete search
# stackoverflow.com/a/14225664/2354735
# stackoverflow.com/a/500870/2354735
if ( re.search(regex, row["user"], re.IGNORECASE) is not None or
re.search(regex, row["name"], re.IGNORECASE) is not None ):
html += ('<li id="' + row["user"] + '">' +
'<img class="icon" src="' + url_for('static', filename='') + 'icons/' + row["user"] + '.png"/>' +
'<div id="uDetails">' +
'<span id="username">' + row["user"] + '</span>' +
'<span> : </span>' +
'<span id="fullname">' + row["name"] + '</span>' +
'</div></li>')
# And send the "response"
return Response(html, mimetype='text/html') # http://stackoverflow.com/a/11774026
# Start Server
if __name__ == '__main__':
# app.run()
port = int(os.environ.get("PORT", 5000))
app.run(host="0.0.0.0", port=port)
All the code can be found on Github.
Porting to Flask turned out to be very straightforward! And Flask itself enjoyable to use. And it works seamlessly with Heroku.
Color me impressed!