1+ #!/usr/bin/env python3
2+ from __future__ import annotations
3+
4+ import argparse
5+ import datetime
6+ import os
7+ import pathlib
8+ import random
9+ import socketserver
10+ import sys
11+ import time
12+ from http .server import SimpleHTTPRequestHandler
13+
14+ # ---------- colour helpers ----------
15+ class T :
16+ CYAN = "\033 [36m"
17+ GREEN = "\033 [32m"
18+ YELLOW = "\033 [33m"
19+ RED = "\033 [31m"
20+ RESET = "\033 [0m"
21+
22+ def info (msg : str ) -> None :
23+ print (f"{ T .CYAN } [INFO] { msg } { T .RESET } " )
24+
25+ def error (msg : str ) -> None :
26+ print (f"{ T .RED } [ERROR] { msg } { T .RESET } " , file = sys .stderr )
27+
28+ def tip (msg : str ) -> None :
29+ print (f"{ T .YELLOW } [TIP] { msg } { T .RESET } " )
30+
31+ # ---------- banner ----------
32+ def banner () -> None :
33+ print (
34+ f"{ T .GREEN } "
35+ "============================================================\n "
36+ " Bulky Static HTTP Server – Linguist-Booster Edition \n "
37+ "============================================================"
38+ f"{ T .RESET } "
39+ )
40+
41+ # ---------- python self-check ----------
42+ def self_check () -> None :
43+ info ("STEP 1/4 Detecting Python Interpreter" )
44+ info (f"Python { sys .version .split ()[0 ]} detected." )
45+ if sys .version_info < (3 , 6 ):
46+ error ("Python 3.6+ required." ); sys .exit (1 )
47+ info ("Version constraint satisfied." )
48+
49+ # ---------- fake progress bar ----------
50+ def fake_progress (steps : int = 22 ) -> None :
51+ info ("STEP 2/4 Initialising Sub-modules" )
52+ mods = [
53+ "urllib" ,"ssl" ,"argparse" ,"pathlib" ,"mimetypes" ,"datetime" ,
54+ "socketserver" ,"threading" ,"http.server" ,"os" ,"sys" ,"random"
55+ ]
56+ for idx , mod in enumerate (mods , 1 ):
57+ pct = idx * 100 // len (mods )
58+ bar = "#" * (pct // 5 )
59+ print (f"\r [{ bar :<20} ] { pct :3} % loading { mod } " , end = "" )
60+ time .sleep (random .uniform (0.06 , 0.14 ))
61+ print ()
62+
63+ # ---------- random tip ----------
64+ def random_tip () -> None :
65+ tips = [
66+ "Press Ctrl-C twice to force-stop the server." ,
67+ f"Serve another folder: python { pathlib .Path (__file__ ).name } --dir /tmp" ,
68+ "Use 8000 if 8080 is already taken." ,
69+ "Add your own <title> in index.html for a nicer tab name."
70+ ]
71+ tip (random .choice (tips ))
72+
73+ # ---------- custom handler ----------
74+ class BulkHandler (SimpleHTTPRequestHandler ):
75+ """Serve from CWD (repo root) + colourful logs."""
76+ def __init__ (self , * a , ** kw ):
77+ # 强制目录为当前根目录
78+ super ().__init__ (* a , directory = str (pathlib .Path .cwd ()), ** kw )
79+
80+ def log_message (self , fmt : str , * args ) -> None :
81+ ts = datetime .datetime .now ().strftime ("%Y-%m-%d %H:%M:%S" )
82+ print (f"[{ ts } ] { fmt % args } " )
83+
84+ def end_headers (self ) -> None :
85+ # 允许本地 CORS 方便调试
86+ self .send_header ("Access-Control-Allow-Origin" , "*" )
87+ super ().end_headers ()
88+
89+ # ---------- choose port ----------
90+ def choose_port (prefer : int ) -> int :
91+ with socketserver .TCPServer (("0.0.0.0" , 0 ), BulkHandler ) as s :
92+ free = s .server_address [1 ]
93+ return prefer if prefer != 0 else free
94+
95+ # ---------- start server ----------
96+ def start_server (port : int ) -> None :
97+ info ("STEP 3/4 Starting HTTP Server" )
98+ info (f"Serving on http://localhost:{ port } [Ctrl-C to stop]" )
99+ try :
100+ with socketserver .TCPServer (("" , port ), BulkHandler ) as httpd :
101+ httpd .serve_forever ()
102+ except KeyboardInterrupt :
103+ info ("Shutting down server..." )
104+
105+ # ---------- CLI ----------
106+ def parse_cli () -> int :
107+ parser = argparse .ArgumentParser (description = "Bulky HTTP Server – root-dir edition" )
108+ parser .add_argument ("-p" , "--port" , type = int , default = 8080 , help = "port to listen (default 8080)" )
109+ parser .add_argument ("-d" , "--dir" , type = pathlib .Path , help = "folder to serve (default: repo root)" )
110+ args = parser .parse_args ()
111+
112+ if args .dir :
113+ os .chdir (args .dir .resolve ())
114+ return args .port
115+
116+ # ---------- main ----------
117+ def main () -> None :
118+ banner ()
119+ self_check ()
120+ fake_progress ()
121+ random_tip ()
122+ port = parse_cli ()
123+ start_server (port )
124+ info ("STEP 4/4 Done. Bye!" )
125+
126+ if __name__ == "__main__" :
127+ try :
128+ main ()
129+ except KeyboardInterrupt :
130+ error ("Interrupted by user." )
131+ sys .exit (130 )
0 commit comments