Dockerize a Simple Python Server (Technical Interview Assignment)

The file runs fine on port 8080 with python3 ./message_server.py however when I attempt to containerize the server with

docker build -t message_server .

It successfully creates the container but when I run it with

docker run message_server

Nothing happens. I believe that it is an issue with my dependencies but I don’t know what my dependencies should be otherwise. You can view the Dockerfile and the imports of the python file below. I have tried adding RUN pip install HTTPServer which works along with the other imports however nothing seems to work. Any help would be Greatly appreciated. Docker gods come through!

Dockerfile:

FROM python:3
ADD message_server.py /
EXPOSE 8080
CMD [ “python3”, “./message_server.py” ]

Python file:
from http.server import HTTPServer, BaseHTTPRequestHandler
from io import BytesIO
import hashlib

PORT = 8080
hash_dict = {}

class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):

def do_GET(self):
    '''
    Recieves and processes GET request. Entry checking could be implemented further
    to do a more robust check to ensure valid requests. Implementation relies on 
    correct input. 
    '''

    messages_index = 1
    hash_index = 2
    path = self.path.split("/")
    messages_checker = path[messages_index]
    hash_val = path[hash_index]

    if messages_checker != "messages":
        self.invalid_request("invalid_path")
    elif hash_val in hash_dict:
        self.process_get_request(hash_val)
    else:
        self.invalid_request("invalid_hash")

def process_get_request(self, hash_val):
    '''
    Creates and sends a response to the client given an existing hash.
    '''

    self.send_response(200)
    self.end_headers()
    response = BytesIO()
    response.write(b'\n{\n\n "digest": "')
    response.write(str.encode(hash_dict[hash_val]))
    response.write(b'" \n\n}\n\n')
    self.wfile.write(response.getvalue())


def do_POST(self):
    '''
    Captures POST request and determines if the path is correct.
    '''
    content_length = int(self.headers['Content-Length'])
    body = self.rfile.read(content_length)
    if self.path == "/messages":
        self.construct_post_response(body)
    else:
        self.invalid_request('invalid_path')

def construct_post_response(self, body):
    #path is /messages generate response
    message_index = 1
    message = body.decode('utf-8')
    split_message = message.split(':')[1].split('"')

    if len(split_message) > 3:
        self.invalid_request("unsupported_input")
    else:
        self.send_response(200)
        self.end_headers()
        message = split_message[message_index]
        message_encoded = str.encode(message)
        hash_val = hashlib.sha256(message_encoded).hexdigest()
        hash_dict[hash_val] = message
        response = BytesIO()
        response.write(b'\n{\n\n "digest": "')
        response.write(str.encode(hash_val))
        response.write(b'" \n\n}\n\n')
        self.wfile.write(response.getvalue())
    

def invalid_request(self, error):
    '''
    Generates response for a given invalid request
    '''

    response = BytesIO()

    if error == "invalid_path":
        response.write(b'\n{\n\n "err_msg": "invalid path" \n\n}\n\n')
        self.wfile.write(response.getvalue())
    elif error == "invalid_hash":
        response.write(b'\n{\n\n "err_msg": "Message not found" \n\n}\n\n')
        self.wfile.write(response.getvalue())
    elif error == "unsupported_input":
        response.write(b'\n{\n\n "err_msg": "unsupported input" \n\n}\n\n')
        self.wfile.write(response.getvalue())

    self.send_response(404, message=None)
    self.end_headers()

print(“Running server…”)
httpd = HTTPServer((‘localhost’, 8080), SimpleHTTPRequestHandler)
httpd.serve_forever()

2 Likes

I’ll try to update this post later detailing my entire debugging / thought process because solving this question is literally the kind of work that engineers do every single day!

But here’s the answer for now:

Change the second to last line in the python server file to:

httpd = HTTPServer(('0.0.0.0', PORT), SimpleHTTPRequestHandler)
Docker can’t connect to the localhost referenced in the HTTPServer setup, so you can allow access through Docker using ‘0.0.0.0.’

I also did EXPOSE 80in the Dockerfile, but that shouldn’t really matter (it just affects the input to the docker run command as you’ll see below)

And here’s the commands I ran for docker (-p 80:8080 is important for setting the ports and -d allows you to detach the container and run it in the background, so you can input commands into the terminal as you normally would):

Brians-MBP-2:docker_image brian$ docker run -i -p 80:8080 -d python-message
5b446d88169838e8ace700467c17a780a6ce66858b427717e2831b872987d266

Brians-MBP-2:docker_image brian$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                  NAMES
5b446d881698        python-message      "python3 ./message_s…"   4 seconds ago       Up 4 seconds        0.0.0.0:80->8080/tcp   flamboyant_goodall

Brians-MBP-2:docker_image brian$ curl -X POST -H "Content-Type: application/json" -d '{"message": "foo"}' http://localhost:80/messages

{

 "digest": "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae" 

}

Brians-MBP-2:docker_image brian$ curl http://localhost:80/messages/2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae

{

 "digest": "foo" 

}

Hope this all helps :smile:!!

2 Likes