CSenshi

http server (cn - hw1)

Apr 17th, 2021 (edited)
355
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 7.39 KB | None | 0 0
  1. import os
  2. import magic
  3. import json
  4. import sys
  5. import socket
  6. import datetime
  7. import threading
  8.  
  9. from email.utils import formatdate
  10. from pprint import pprint as print
  11.  
  12.  
  13. class Config:
  14.     def __init__(self):
  15.         self.log_dir = None
  16.         self.vhosts = []
  17.         self.address_dict = {}
  18.  
  19.     def set_log_dir(self, log_dir):
  20.         self.log_dir = log_dir
  21.         if not os.path.exists(self.log_dir):
  22.             os.mkdir(self.log_dir)
  23.  
  24.     def add_vhost(self, vhost):
  25.         self.vhosts.append(vhost)
  26.         address = (vhost["ip"], vhost["port"])
  27.         if address not in self.address_dict:
  28.             self.address_dict[address] = []
  29.         self.address_dict[address] += [vhost]
  30.  
  31.     def get_unique_ip_ports(self):
  32.         return self.address_dict.keys()
  33.  
  34.     def get_address_via_host(self, host):
  35.         for address in self.address_dict:
  36.             vhost_arr = self.address_dict[address]
  37.             for vhost in vhost_arr:
  38.                 if host == vhost["vhost"]:
  39.                     return address
  40.         return None
  41.  
  42.  
  43. class Worker(threading.Thread):
  44.     def __init__(self, client_socket, client_address, server_address, config):
  45.         threading.Thread.__init__(self)
  46.         self.socket = client_socket
  47.         self.address = client_address
  48.         self.server_address = server_address
  49.         self.config = config
  50.  
  51.     def run(self):
  52.         self.request = self.socket.recv(1024)
  53.  
  54.         self.process_request()
  55.         self.generate_response()
  56.  
  57.         self.socket.send(self.response)
  58.         self.socket.close()
  59.  
  60.         self.log_request()
  61.  
  62.     def process_request(self):
  63.         decoded_request = self.request.decode().strip()
  64.         request_line, headers = decoded_request.split("\r\n", 1)
  65.  
  66.         # process request line
  67.         self._method, self._path, self._http_version = request_line.split(" ")
  68.         self._path = self._path.replace("%20", " ")
  69.  
  70.         # process headers
  71.         self._headers = headers.split("\r\n")
  72.         self._headers = [header.split(":", 1) for header in self._headers]
  73.         self._headers = {kv[0].strip().lower(): kv[1].strip() for kv in self._headers}
  74.  
  75.     def generate_response(self):
  76.         self._generate_response_headers()
  77.         self._generate_response_body()
  78.  
  79.         self.response_headers["content-length"] = len(self.response_body)
  80.  
  81.         response_headers_str = f"{self._http_version} {self._status_code}\r\n"
  82.         for key, value in self.response_headers.items():
  83.             response_headers_str += f"{key}:{value}\r\n"
  84.         response_headers_str += "\r\n"
  85.  
  86.         if self._method == "HEAD":
  87.             self.response = response_headers_str.encode()
  88.         elif self._method == "GET":
  89.             self.response = response_headers_str.encode() + self.response_body
  90.  
  91.     def log_request(self):
  92.         date = datetime.datetime.now().strftime("%a %b %d %H:%M:%S %Y")
  93.         ip = self.address[0]
  94.         host = self._headers["host"].split(":")[0]
  95.         status_code = self._status_code.split(" ")[0]
  96.         path = self._path
  97.         content_length = self.response_headers["content-length"]
  98.         user_agent = self._headers["user-agent"]
  99.  
  100.         file_name = self.config.log_dir + "/" + host + ".log"
  101.  
  102.         if status_code == '404':
  103.             file_name = self.config.log_dir + "/error.log"
  104.  
  105.         with open(file_name, "a+") as file:
  106.             file.write(
  107.                 f"[{date}] {ip} {host} {path} {status_code} {content_length} {user_agent}\n"
  108.             )
  109.  
  110.     def _generate_response_headers(self):
  111.         self.response_headers = {
  112.             "server": "lashiko-v1.0",
  113.             "date": formatdate(timeval=None, localtime=False, usegmt=True),
  114.             "connection": "keep-alive",
  115.             "content-type": "text/html",
  116.             "Accept-Ranges": " bytes",
  117.             "keep-alive": "5",
  118.             "etag": "A2E2EFC1",
  119.         }
  120.  
  121.     def _generate_response_body(self):
  122.         path = self._generate_requested_path()
  123.         if not path or not os.path.exists(path):
  124.             self.response_body = self._generate_response_body_404()
  125.         elif os.path.isfile(path):
  126.             self.response_body = self._generate_response_body_200_file(path)
  127.         else:
  128.             self.response_body = self._generate_response_body_200_dir(path)
  129.  
  130.     def _generate_response_body_404(self):
  131.         self._status_code = "404 NOT FOUND"
  132.         return b"<html><body><h1>REQUESTED DOMAIN NOT FOUND</h1></body></html>"
  133.  
  134.     def _generate_response_body_200_file(self, path):
  135.         self._status_code = "200 OK"
  136.  
  137.         self.response_headers["content-type"] = magic.Magic(mime=True).from_file(path)
  138.         with open(path, "rb") as file:
  139.             if "range" in self._headers:
  140.                 offset, length = self._get_offset_and_length_from_header(
  141.                     self._headers["range"]
  142.                 )
  143.                 file.seek(offset)
  144.                 if length:
  145.                     return file.read(length)
  146.             return file.read()
  147.  
  148.     def _generate_response_body_200_dir(self, path):
  149.         self._status_code = "200 OK"
  150.         splitted = os.path.split(path)[1:]
  151.         base_url = "/".join(splitted)
  152.         url_lists = "<br>".join(
  153.             [
  154.                 f'<a href={base_url}/{item.replace(" ", "%20")}>{base_url}/{item}</a>'
  155.                 for item in os.listdir(path)
  156.             ]
  157.         )
  158.         return f"<html><body><ul>{url_lists}</ul></body></html>".encode()
  159.  
  160.     def _generate_requested_path(self):
  161.         if ":" not in self._headers["host"]:
  162.             return None
  163.  
  164.         host, port = self._headers["host"].split(":")
  165.         address = self.config.get_address_via_host(host)
  166.  
  167.         if address != self.server_address:
  168.             return None
  169.  
  170.         return (host + "/" + self._path).replace("//", "/")
  171.  
  172.     def _get_offset_and_length_from_header(self, range_str):
  173.         bytes_str = range_str[6:]
  174.         offset, length = bytes_str.split("-")
  175.         if length:
  176.             return int(offset), int(offset)
  177.         return int(offset), None
  178.  
  179.  
  180. class Server(threading.Thread):
  181.     def __init__(self, ip, port, config):
  182.         threading.Thread.__init__(self)
  183.         self.ip = ip
  184.         self.port = port
  185.         self.config = config
  186.  
  187.     def _start_socket_connection(self):
  188.         self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  189.         # self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  190.         self.sock.bind((self.ip, self.port))
  191.         self.sock.listen(1024)
  192.  
  193.     def run(self):
  194.         self._start_socket_connection()
  195.  
  196.         while True:
  197.             conn, addr = self.sock.accept()
  198.             Worker(conn, addr, (self.ip, self.port), self.config).start()
  199.  
  200.  
  201. def start_virtual_host_servers(config):
  202.     adresses = config.get_unique_ip_ports()
  203.     for ip, port in adresses:
  204.         Server(ip, port, config).start()
  205.  
  206.  
  207. def read_config_file():
  208.     if len(sys.argv) < 2:
  209.         print("Please pass config file. Example: python main.py config.json")
  210.         exit()
  211.  
  212.     config = Config()
  213.     with open(sys.argv[1], "r") as config_file:
  214.         content = json.loads(config_file.read())
  215.         config.set_log_dir(content["log"])
  216.         for vhost in content["server"]:
  217.             config.add_vhost(vhost)
  218.         return config
  219.  
  220.  
  221. if __name__ == "__main__":
  222.     # 1. Read config file
  223.     config = read_config_file()
  224.  
  225.     # 2. Start Server
  226.     start_virtual_host_servers(config)
Add Comment
Please, Sign In to add comment