Type a domain name and see DNS in action
It can take some time for output to appear. Please wait!
This widget uses the DNS resolver I developed in this blog series. The first step to get this working was to wrap it some sort of http server and get it hosted.
I had three key requirements from this project
The first and third requirements made deployment an exciting challenge that I will get into more detail later.
|---DNSWhy/
|---|-----*
|---main.py
|---heroku.yml
|---Dockerfile
|---requirements.py
Flask is a web application framework for creating web apps using python. I had to use very little of the functionality provided because my project would have just one GET
endpoint that returns the resolution output. For calling the other program I just used subprocess.check_output
that runs it with domain name supplied as a path parameter and returns the STDOUT output.
Finally, we also need CORS header Access-Control-Allow-Origin
set because this application will be hosted on a heroku server and my blog is hosted at archit.me
. With all that out of the way the final code for main.py
is just
from flask import Flask
import subprocess
app = Flask(__name__)
@app.route('/<domain>')
def hello(domain):
try:
return subprocess.check_output(["python", "DNSWhy/main.py" ,domain]).decode('ascii')
except Exception as e:
return "Error. Check domain!"
@app.after_request
def after_request(response):
response.headers.add('Access-Control-Allow-Origin', '*')
response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization')
response.headers.add('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS')
return response
if __name__== "__main__":
app.run()
Just a small footnote. While Flask does come with an http server, it actually recommends using some other server for production workloads. Keeping inline with this recommendation I decided to use gunicorn so the way to run this program is actually -
gunicorn main:app
Heroku build process in its usual way is not really compatible with the requirement 1 that I had set. Usually for a python based program, heroku will create a slug with a python installation, dependencies as defined in requirements.txt
and the rest of the application. Slugs are similar to docker images. Now when it’s time to actually run the image, Heroku will use the command defined in Procfile
. No where does this let you actually influence the build process of the slug which is necessary to clone the DNSWhy repository. There were three ways to get around this problem
There’s a fourth way aswell that would be including the dependency fetch step in Procfile
but this is a bad way to do achieve this. Please don’t do this because you will add a significant start time.
I wanted to use Heroku’s github integration so that only left the third option of docker deployment. This was pretty easy to configure because all I needed was a python installation and a git clone. Instead of using Procfile
I could just specify CMD in the Dockerfile
.
FROM python:3.10
COPY main.py .
COPY requirements.txt .
RUN apt-get -y update
RUN apt-get update && apt-get install -y python3 python3-pip
RUN pip3 install -r requirements.txt
RUN git clone https://github.com/archit120/DNSWhy.git
CMD gunicorn main:app
Along with the Dockerfile
another file named heroku.yml
is necessary which tells heroku that the web type dyno is using this Dockerfile
.
build:
docker:
web: Dockerfile
With all that out of the way I pushed it on to my github. Set up the heroku integration and loaded up https://dnswhy-flask.herokuapp.com/archit.me hoping to see some nice DNS output but instead got greeted with an error that no server is running. Looking at the logs I realize that heroku is unable to figure out that I want a docker deployment. This was quickly fixed by running
> heroku stack:set container
And this time it worked perfectly! The final step was to write the javascript demo for this post. With everything set in place I used a little jquery to hook it all up and display the output.
$("#dns-btn").on("click", function(x) {
$.get("https://dnswhy-flask.herokuapp.com/"+$("#dns-input").val(), function(x) {
$("#dns-output").text(x);
});
})