Port server.py to Python 3 and modern Twisted APIs
* Update Python 2 syntax for Python 3 compatibility * Replace deprecated `imp` module usage with `importlib` * Update Twisted request handling for Python 3 `bytes`/`str` behavior * Preserve Flipnote Hatena proxy request routing * Keep server listening on port 8080 * Update dynamic Hatena resource loading * Improve startup/error handling * Maintain compatibility with the existing database, static files, and Hatena endpoints
This commit is contained in:
1 parent
fe44430a46
commit
687a2bcb43
1 file changed
+134
-118
@@ -1,150 +1,166 @@
|
|||||||
#Settings:
|
#!/usr/bin/env python3
|
||||||
useWSGI = False#not fully tested and WILL NOT support multible instances/workers with the plaintext database
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import atexit
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
|
||||||
|
# Settings retained from the original project.
|
||||||
|
useWSGI = False # Historical name: this creates a Twisted service application.
|
||||||
port = 8080
|
port = 8080
|
||||||
|
|
||||||
#import:
|
|
||||||
print "Importing modules...",
|
|
||||||
from twisted.web import server#filehost
|
|
||||||
from twisted.internet import reactor
|
|
||||||
if useWSGI: from twisted.application import internet, service
|
|
||||||
|
|
||||||
import sys, time, os, atexit
|
def _force_project_working_directory():
|
||||||
print "Done!"
|
base = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
if base:
|
||||||
|
os.chdir(base)
|
||||||
|
|
||||||
|
|
||||||
|
def _import_twisted():
|
||||||
|
try:
|
||||||
|
from twisted.internet import reactor
|
||||||
|
from twisted.web import server
|
||||||
|
except ImportError as exc:
|
||||||
|
raise SystemExit(
|
||||||
|
"Twisted is required to run the server. Install dependencies with: "
|
||||||
|
"python3 -m pip install -r requirements.txt"
|
||||||
|
) from exc
|
||||||
|
return reactor, server
|
||||||
|
|
||||||
#set working directory
|
|
||||||
if os.path.dirname(__file__):
|
|
||||||
os.chdir(os.path.dirname(__file__))
|
|
||||||
else:
|
|
||||||
for i in sys.path:
|
|
||||||
path = os.path.split(i)[0]
|
|
||||||
if not os.path.exists(os.path.join(path, "server.py")): continue
|
|
||||||
if not os.path.exists(os.path.join(path, "hatena.py")): continue
|
|
||||||
if not os.path.exists(os.path.join(path, "DB.py")): continue
|
|
||||||
os.chdir(path)
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
print "Can't force working directory, may fail crash!"
|
|
||||||
|
|
||||||
#Logging
|
|
||||||
class Log:
|
class Log:
|
||||||
class filesplit:#a file object writing to two outputs
|
class filesplit:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.files = []
|
self.files = []
|
||||||
|
|
||||||
def write(self, data):
|
def write(self, data):
|
||||||
for i in self.files: i.write(data)
|
for output in self.files:
|
||||||
def flush(self):#ipython needs this
|
output.write(data)
|
||||||
pass
|
return len(data)
|
||||||
def __init__(self):
|
|
||||||
|
def flush(self):
|
||||||
|
for output in self.files:
|
||||||
|
output.flush()
|
||||||
|
|
||||||
|
def __init__(self, reactor):
|
||||||
|
self.reactor = reactor
|
||||||
minutes, seconds = map(int, time.strftime("%M %S").split(" "))
|
minutes, seconds = map(int, time.strftime("%M %S").split(" "))
|
||||||
minutes = 59 - minutes
|
minutes = 59 - minutes
|
||||||
seconds = 59 - seconds
|
seconds = 59 - seconds
|
||||||
reactor.callLater(60*minutes + seconds + 5, self.HandleUpdate)
|
reactor.callLater(60 * minutes + seconds + 5, self.HandleUpdate)
|
||||||
reactor.callLater(60*5, self.AutoFlush)
|
reactor.callLater(60 * 5, self.AutoFlush)
|
||||||
|
self._open_handles()
|
||||||
#make year folder
|
|
||||||
if not os.path.exists("logs/"+time.strftime("%Y")):
|
|
||||||
os.mkdir("logs/"+time.strftime("%Y"))
|
|
||||||
|
|
||||||
#make month folder
|
|
||||||
if not os.path.exists("logs/"+time.strftime("%Y/%B")):
|
|
||||||
os.mkdir("logs/"+time.strftime("%Y/%B"))
|
|
||||||
|
|
||||||
self.Activityhandle = open(time.strftime("logs/%Y/%B/%d %B activity.log"),"a")
|
|
||||||
self.Errorhandle = open(time.strftime("logs/%Y/%B/%d %B error.log"),"a")
|
|
||||||
|
|
||||||
self.stderr = sys.stderr
|
self.stderr = sys.stderr
|
||||||
sys.stderr = self.filesplit()
|
sys.stderr = self.filesplit()
|
||||||
sys.stderr.files.append(self.stderr)
|
sys.stderr.files.extend((self.stderr, self.Errorhandle))
|
||||||
sys.stderr.files.append(self.Errorhandle)
|
self.write("Server startup...", True)
|
||||||
|
|
||||||
self.write("Server startup...\n", True)
|
def _open_handles(self):
|
||||||
def HandleUpdate(self):#Automatically updates the handles for new filenames every hour
|
directory = time.strftime("logs/%Y/%B")
|
||||||
reactor.callLater(60*60, self.HandleUpdate)
|
os.makedirs(directory, exist_ok=True)
|
||||||
|
self.Activityhandle = open(
|
||||||
|
time.strftime("logs/%Y/%B/%d %B activity.log"), "a", encoding="utf-8"
|
||||||
|
)
|
||||||
|
self.Errorhandle = open(
|
||||||
|
time.strftime("logs/%Y/%B/%d %B error.log"), "a", encoding="utf-8"
|
||||||
|
)
|
||||||
|
|
||||||
print time.strftime("[%H:%M:%S] Handle update")
|
def HandleUpdate(self):
|
||||||
|
self.reactor.callLater(60 * 60, self.HandleUpdate)
|
||||||
#make year folder
|
print(time.strftime("[%H:%M:%S] Handle update"))
|
||||||
if not os.path.exists("logs/"+time.strftime("%Y")):
|
old_error = self.Errorhandle
|
||||||
os.mkdir("logs/"+time.strftime("%Y"))
|
|
||||||
|
|
||||||
#make month folder
|
|
||||||
if not os.path.exists("logs/"+time.strftime("%Y/%B")):
|
|
||||||
os.mkdir("logs/"+time.strftime("%Y/%B"))
|
|
||||||
|
|
||||||
self.close()
|
|
||||||
|
|
||||||
self.Activityhandle = open(time.strftime("logs/%Y/%B/%d %B activity.log"),"a")
|
|
||||||
self.Errorhandle = open(time.strftime("logs/%Y/%B/%d %B error.log"),"a")
|
|
||||||
|
|
||||||
sys.stderr.files[1] = self.Errorhandle
|
|
||||||
def AutoFlush(self):#Automatically flushes the files 5 minutes between
|
|
||||||
reactor.callLater(60*5, self.AutoFlush)
|
|
||||||
self.flush()
|
|
||||||
def flush(self):#Flushes to the files
|
|
||||||
self.Activityhandle.flush()
|
|
||||||
os.fsync(self.Activityhandle.fileno())
|
|
||||||
|
|
||||||
self.Errorhandle.flush()
|
|
||||||
os.fsync(self.Errorhandle.fileno())
|
|
||||||
def close(self):
|
|
||||||
self.Activityhandle.close()
|
self.Activityhandle.close()
|
||||||
self.Errorhandle.close()
|
old_error.close()
|
||||||
#=====
|
self._open_handles()
|
||||||
|
if isinstance(sys.stderr, self.filesplit) and len(sys.stderr.files) > 1:
|
||||||
|
sys.stderr.files[1] = self.Errorhandle
|
||||||
|
|
||||||
|
def AutoFlush(self):
|
||||||
|
self.reactor.callLater(60 * 5, self.AutoFlush)
|
||||||
|
self.flush()
|
||||||
|
|
||||||
|
def flush(self):
|
||||||
|
for handle in (self.Activityhandle, self.Errorhandle):
|
||||||
|
handle.flush()
|
||||||
|
os.fsync(handle.fileno())
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
for handle in (self.Activityhandle, self.Errorhandle):
|
||||||
|
if not handle.closed:
|
||||||
|
handle.close()
|
||||||
|
|
||||||
def write(self, String, Silent=False):
|
def write(self, String, Silent=False):
|
||||||
|
line = str(String).rstrip("\n")
|
||||||
if not Silent:
|
if not Silent:
|
||||||
print time.strftime("[%H:%M:%S]"), String
|
print(time.strftime("[%H:%M:%S]"), line)
|
||||||
self.Activityhandle.write(time.strftime("[%H:%M:%S] ") + String + "\n")
|
self.Activityhandle.write(time.strftime("[%H:%M:%S] ") + line + "\n")
|
||||||
|
|
||||||
Print = write
|
Print = write
|
||||||
Log = Log()
|
|
||||||
|
|
||||||
#init database:
|
|
||||||
print "Initializing flipnote database...",
|
|
||||||
import DB
|
|
||||||
print "Done!"
|
|
||||||
|
|
||||||
#Setup hatena server:
|
|
||||||
print "Setting up hatena site...",
|
|
||||||
import hatena
|
|
||||||
hatena.ServerLog = Log
|
|
||||||
site = server.Site(hatena.Setup())
|
|
||||||
print "Done!"
|
|
||||||
|
|
||||||
|
|
||||||
#make the hatena server accept proxy connections:
|
|
||||||
print "Setting up proxy hack...",
|
|
||||||
silent = True
|
|
||||||
old_buildProtocol = site.buildProtocol
|
|
||||||
def buildProtocol(self, addr):
|
|
||||||
protocol = old_buildProtocol(addr)
|
|
||||||
|
|
||||||
protocol.new_recv_buffer= []
|
def create_site():
|
||||||
|
_force_project_working_directory()
|
||||||
|
reactor, twisted_server = _import_twisted()
|
||||||
|
|
||||||
old_dataReceived = protocol.dataReceived
|
print("Initializing flipnote database...", end=" ", flush=True)
|
||||||
def dataReceived(self, data):
|
import DB # noqa: F401 - initialization is intentional
|
||||||
#i'll assume the GET request doesn't get fragmented, which should be safe with a mtu at 1500, a crash doesn't matter really anyway. too much work for a simple twisted upgrade on a dropped project
|
print("Done!")
|
||||||
for check, repl in (("GET http://flipnote.hatena.com", "GET "), ("POST http://flipnote.hatena.com", "POST ")):
|
|
||||||
|
log = Log(reactor)
|
||||||
|
|
||||||
|
print("Setting up hatena site...", end=" ", flush=True)
|
||||||
|
import hatena
|
||||||
|
hatena.ServerLog = log
|
||||||
|
|
||||||
|
class ProxyCompatibleSite(twisted_server.Site):
|
||||||
|
"""Accept the absolute-form request target sent by HTTP proxy clients."""
|
||||||
|
|
||||||
|
def buildProtocol(self, addr):
|
||||||
|
protocol = super().buildProtocol(addr)
|
||||||
|
old_data_received = protocol.dataReceived
|
||||||
|
|
||||||
|
def data_received(data):
|
||||||
|
for check, repl in (
|
||||||
|
(b"GET http://flipnote.hatena.com", b"GET "),
|
||||||
|
(b"POST http://flipnote.hatena.com", b"POST "),
|
||||||
|
):
|
||||||
if check in data:
|
if check in data:
|
||||||
data = data.replace(check, repl)
|
data = data.replace(check, repl)
|
||||||
old_dataReceived(data)
|
return old_data_received(data)
|
||||||
funcType = type(protocol.dataReceived)
|
|
||||||
protocol.dataReceived = funcType(dataReceived, protocol, protocol.__class__)
|
protocol.dataReceived = data_received
|
||||||
return protocol
|
return protocol
|
||||||
funcType = type(site.buildProtocol)
|
|
||||||
site.buildProtocol = funcType(buildProtocol, site, server.Site)
|
|
||||||
print "Done!"
|
|
||||||
|
|
||||||
#run!
|
site = ProxyCompatibleSite(hatena.Setup())
|
||||||
print "Server start!\n"
|
print("Done!")
|
||||||
if useWSGI:
|
return reactor, site, log
|
||||||
#probably doesn't work
|
|
||||||
application = service.Application('web')
|
|
||||||
sc = service.IServiceCollection(application)
|
|
||||||
internet.TCPServer(port, site).setServiceParent(sc)
|
|
||||||
|
|
||||||
atexit.register(Log.write, String="Server shutdown", Silent=True)
|
|
||||||
else:
|
def main():
|
||||||
reactor.listenTCP(port, site)#Hey listen!~
|
print("Importing modules...", end=" ", flush=True)
|
||||||
|
_force_project_working_directory()
|
||||||
|
reactor, site, log = create_site()
|
||||||
|
print("Server start!\n")
|
||||||
|
|
||||||
|
if useWSGI:
|
||||||
|
from twisted.application import internet, service
|
||||||
|
|
||||||
|
application = service.Application("web")
|
||||||
|
internet.TCPServer(port, site).setServiceParent(service.IServiceCollection(application))
|
||||||
|
atexit.register(log.write, String="Server shutdown", Silent=True)
|
||||||
|
return application
|
||||||
|
|
||||||
|
reactor.listenTCP(port, site)
|
||||||
|
try:
|
||||||
reactor.run()
|
reactor.run()
|
||||||
|
finally:
|
||||||
|
log.write("Server shutdown", True)
|
||||||
|
log.flush()
|
||||||
|
return None
|
||||||
|
|
||||||
#dunn
|
|
||||||
Log.write("Server shutdown", True)
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in new issue
Block a user
