This project is intended to function as a basic web server with a command-line interface for easy use. It's written in Python 3, with the help of the cmd
module for the CLI itself and the _thread
module (I know, I know) to run the listening/responding process without interfering with the CLI.
I am wondering (with the exception of _thread
, which I do intend to fix very soon) how well it conforms to best practices and how readable it is. Performance is not a big issue.
import socket
import cmd
import os.path
import _thread # TODO: Switch from _thread to threading
import json
version = "0.3.0"
settings = {}
serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serverSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
def loadSettings():
global settings
with open('.linprconfig', 'r') as configFile:
settings = json.load(configFile)
def makeErrorResponse(errorNumber):
with open("htdocs/error/default.html") as defaultErrorTemplateFile:
defaultErrorTemplate = defaultErrorTemplateFile.readlines()
formattedErrorTemplate = ""
for line in defaultErrorTemplate:
formattedErrorTemplate += (line.replace("{}", str(errorNumber)))
return "HTTP/1.0 " + str(errorNumber) + "\n\n", formattedErrorTemplate,
def makeResponse(filename):
if filename == "/": filename = "/index.html"
try:
with open(os.path.join(settings["general"]["contentRoot"], filename[1:])) as requestedFile: # slice removes leading /
try:
return 'HTTP/1.0 200 OK\n\n', requestedFile.read(),
except UnicodeDecodeError: # it's not a text-based file
requestedFile = open(os.path.join(settings["general"]["contentRoot"], filename[1:]), 'rb') # reopen as binary
return 'HTTP/1.0 200 OK\n\n', requestedFile.read(),
except (FileNotFoundError, IsADirectoryError):
return makeErrorResponse(404)
def listen():
try:
while True:
clientConnection, clientAddress = serverSocket.accept()
# Get the client request and split it up
request = clientConnection.recv(1024).decode()
try:
headers = request.split('\n')
# Parse the request
command = headers[0]
if command.strip(" ") == "": continue
responseHeaders = makeResponse(command.split()[1])[0]
responseContent = makeResponse(command.split()[1])[1]
clientConnection.sendall(responseHeaders.encode())
try:
clientConnection.sendall(responseContent.encode())
except AttributeError: # it's a bytes object, so no .encode() method
clientConnection.sendall(responseContent)
clientConnection.close()
except Exception as e:
print("Error caught and 500 returned: "+ str(e))
responseHeaders = makeErrorResponse(500)[0]
responseContent = makeErrorResponse(500)[1]
clientConnection.sendall(responseHeaders.encode())
try:
clientConnection.sendall(responseContent.encode())
except AttributeError: # it's a bytes object, so no .encode() method
clientConnection.sendall(responseContent)
clientConnection.close()
except (KeyboardInterrupt, EOFError, SystemExit):
pass # this hides ugly messages on exit
class CommandLine(cmd.Cmd):
def preloop(self):
self.host = None
self.port = None
self.prompt = "LINPR " + version + " >"
def do_startup(self, s):
s = s.split(" ")
try:
self.host = s[0]
self.port = s[1]
except IndexError:
self.host = "0.0.0.0"
self.port = 80
serverSocket.bind((self.host, int(self.port)))
def help_startup(self):
print("Bind to a user-specified host and port. "
"When host and port are unsupplied, use the defaults 0.0.0.0 and 80.")
def do_listen(self, s):
print("Listening on port " + str(self.port))
serverSocket.listen(1)
_thread.start_new_thread(listen, ())
def help_listen(self):
print("Listen for any incoming connections and automatically respond to them with the requested resource.")
def do_exit(self, s):
print("Exiting...")
try:
serverSocket.close()
except NameError: # server_socket hasn't been created yet
pass
return True
def help_exit(self):
print("Exits LINPR and shuts down the server.")
def emptyline(self):
pass
loadSettings()
interpreter = CommandLine()
try:
interpreter.cmdloop(intro="Welcome to LINPR " + version + ". Type 'help' for help, or 'help cmd' for info on the 'cmd' command.")
except (KeyboardInterrupt, EOFError, SystemExit):
print("\n\nUse 'exit' to exit.")
CommandLine.do_exit(interpreter, "")