Skip to content

Dev Web Server

Tarun Aditya Thurlapati edited this page Apr 24, 2020 · 3 revisions

Web server Implementation

refer Python Web Server DOCS

In concise:

  1. http.server (python module or class which is imported):
    This module defines classes for implementing HTTP servers (Web servers).
  2. Classes in http.server:
    • class http.server.HTTPServer(server_address, RequestHandlerClass) : implements the server i.e. its a server instance
    • class http.server.BaseHTTPRequestHandler(request, client_address, server): This class is used to handle the HTTP requests that arrive at the server. By itself, it cannot respond to any actual HTTP requests; it must be inherited to handle each request method (e.g. GET or POST). BaseHTTPRequestHandler provides a number of class and instance variables, and methods for use by child class. For more info about the implementables (variables and functions) of this abstract class, go HERE

this is the class that we inherit as part of Our custom WebServer class and then implement the GET and POST functions using custom funcs (do_GET() and do_POST())

Code Explanation - WebServer.py

These lines are for debugging / production environment switching:

if __name__ == "__main__":    
    import MasterControl
else:
    from . import MasterControl

If the app is debugged using staring from WebServer.py, then we need the import to be direct (import MasterControl), but when init.py imports WebServer.py, the import must be relative, i.e. like from . import MasterControl

Imported packages:

from sys import exit as ExitProgram
from http.server import *
import os
from pathlib import Path

Global variables:

httpd = None
PORT = 8182
CurrentDir = Path(os.path.dirname(__file__))

This marks the beginning of the web server class:

class WebServer(BaseHTTPRequestHandler):

This is where all handling of requests to do work will be executed, that is, within an instance of this class, which is THIS It must be noted that this class is a handler class, which derives (inherits) from the BaseHTTPRequestHandler class and implements the predefined do_GET and do_POST functions as per our requirement, and IT ITSELF BECOMES A HANDLER CLASS. (which handles requests!)

This is do_GET:

All get requests made by the web page go through here:

    def do_GET(self):

IF there is an error, a 404 file not found is sent back to the browser

HTML File handling:

In this part of the code, HTML File requests are handled

        if 'html' in self.path or self.path == '/':
            try:
                    '''
This part here check for what file is requested and sends it. But, if the request is for 
index page and if the DB has been initiated, an error is returned. similarly, if request is 
for todo page and database is not created it returns an error.
also, '/', '' and '/index.html' return the same page i.e. index.html
                    '''
                self.send_response(200)
                self.send_header('Content-type','text/html')
            except:
                file_to_open = "File not found"
                self.send_response(404)
            self.end_headers()
            self.wfile.write(bytes(file_to_open, 'utf-8'))

This part handles JS

When HTML files are requested, their internal code directs the browser to also get the JS code file mentioned int here to use it in the web page:

        if '/JS' in self.path:
            try:
                file_to_open = open(....).read() #JS file is opened here
                self.send_response(200)
                self.send_header('Content-type','application/javascript')
            except:
                file_to_open = "File not found"
                self.send_response(404)
            self.end_headers()
            self.wfile.write(bytes(file_to_open, 'utf-8'))

This part handles CSS

When HTML files are requested, their internal code directs the browser to also get the CSS code file mentioned int here to apply it on the web page:

        if '/CSS' in self.path:
            try:
                file_to_open = open(....).read() #CSS file is opened here
                self.send_response(200)
                self.send_header('Content-type','text/css')
            except:
                file_to_open = "File not found"
                self.send_response(404)
            self.end_headers()
            self.wfile.write(bytes(file_to_open, 'utf-8'))

This part handles Export File Requests

When the user asks to export the file, the file is read in rb mode, i.e. read as bytes and sent back as an octet-stream which is fancy way of saying bits

        if self.path.endswith("Temp.db"):
            self.path = 'Models\Temp.db'
            try:
                MasterControl.WriteToTempDB()
                with open(os.path.join(CurrentDir,self.path), mode='rb') as file: # b is important -> binary
                    fileContent = file.read()
                self.send_response(200)
                self.send_header('Content-type','application/octet-stream')
            except:
                fileContent = "File not found"
                self.send_response(404)
            self.end_headers()
            self.wfile.write(fileContent)

Handles sending back todo list for display:

        if self.path.endswith("ListData"):
            try:
                fileContent = MasterControl.GetListDataAsString()
                self.send_response(200)
            except:
                fileContent = "Somethings is wrong. Sorry!"
                self.send_response(404)
            self.end_headers()
            self.wfile.write(bytes(fileContent,'utf-8'))
        return

This is do_POST:

  1. All post requests made by the web page go through here: there responseText variable is the one which is sent back to the browser. by default it is "Ignore this message!".
  2. When the POST request is made, a string is sent with the request values, and some other data, which is given to the server as bytes in self.rfile file. So, we read the file only upto the amount of data that we are supposed to use, i.e. upto the contentLength of the file which is provided to us by the POST request of the browser.
  3. Then, multiple if statements check for what request has been made:

These statements handle db create request

    def do_POST(self):
        responseText = "Ignore this message!"
        contentLength = int(self.headers['Content-Length']) # <-- Gets the size of data
        if self.path.endswith("NewToDoDataBase"):
            #goes to MasterControl 
        if self.path.endswith("Temp.db"):
            #goes to MasterControl

In MasterControl.py, for

  • NewToDoDataBase - we go here
  • Temp.db - we go here

after the functions are executed and returned, the request response is sent back to browser

  1. Each of these if statements are used for LIST and TASK manipulation, by getting the manipulation data from the server and then passing it on to MasterControl.py: example:
        if self.path.endswith("Edit"):
            Data = self.rfile.read(contentLength).decode('utf-8')
            print("~Request string: "+Data)
            responseText = MasterControl.EditItem(Data)

List manipulation via do_POST

  1. The if statements have been showed only for one example, the rest of the statements call the given links in MasterControl.py: a. Edit task b. Delete task c. Done or undone task d. Add task e. Edit List Name f. Delete List g. Add List

        if self.path.endswith("DestroyToDoDBFrame"):
            #goes to MasterControl
        if self.path.endswith("ShutDownServer"):
            print("Shutdown command received from webpage")
            ExitProgram() #This has been imported
        if self.path.endswith("DispToDoDataFrame"):
            MasterControl.DispToDoDataFrame()
        self.send_response(200)
        self.end_headers()
        self.wfile.write(bytes(responseText,'utf-8'))
  1. self.send_response(200) makes sure code is successfully read and sent, self.end_headers() end the request response headers and self.wfile.write(bytes(responseText,'utf-8')) send the response to the server by writing it to the wfile which is built into the web server class handler.

This is the Start function, which creates an instance of the web server and runs it:

  1. Here, we make a server and pass webserver class to it as the handler class, since it handles the custom code we wrote
def Start():
    global httpd
    httpd = HTTPServer(('localhost', PORT), WebServer)
  1. here httpd.serve_forever() runs the server and httpd.socket.close() shuts it down. We try to serve forever, and if a keyboard interrupt is received it shuts down and returns to init.py
    try:
        httpd.serve_forever()

From here the function moves to answering requests HERE

    except (KeyboardInterrupt,SystemExit): #this is the ctrl + C interrupt
    	print(MasterControl.Shutdown(CurrentDir))
    	httpd.socket.close()

This is a default error

    except:
        print("~There was an error in running the server")