Files
copyparty/copyparty/httpsrv.py

206 lines
6.1 KiB
Python
Raw Normal View History

2019-05-26 16:30:19 +00:00
# coding: utf-8
2019-06-12 16:39:43 +00:00
from __future__ import print_function, unicode_literals
2019-05-26 16:30:19 +00:00
import os
import sys
2019-06-07 08:54:41 +00:00
import time
import base64
import socket
2019-05-26 16:30:19 +00:00
import threading
try:
import jinja2
except ImportError:
print(
"""\033[1;31m
you do not have jinja2 installed,\033[33m
choose one of these:\033[0m
* apt install python-jinja2
* {} -m pip install --user jinja2
* (try another python version, if you have one)
* (try copyparty.sfx instead)
""".format(
os.path.basename(sys.executable)
)
)
sys.exit(1)
2020-05-13 00:39:29 +02:00
from .__init__ import E, MACOS
2021-07-08 23:35:28 +02:00
from .util import spack
2021-06-07 20:09:18 +02:00
from .httpconn import HttpConn
2019-05-26 16:30:19 +00:00
class HttpSrv(object):
"""
2019-06-06 08:18:00 +02:00
handles incoming connections using HttpConn to process http,
2019-05-26 16:30:19 +00:00
relying on MpSrv for performance (HttpSrv is just plain threads)
"""
def __init__(self, broker, is_mp=False):
2019-07-01 02:42:29 +02:00
self.broker = broker
self.is_mp = is_mp
2019-07-01 02:42:29 +02:00
self.args = broker.args
self.log = broker.log
2021-06-11 23:01:13 +02:00
self.asrv = broker.asrv
2019-05-26 16:30:19 +00:00
self.disconnect_func = None
2019-05-28 20:49:58 +00:00
self.mutex = threading.Lock()
2019-05-26 16:30:19 +00:00
self.clients = {}
self.workload = 0
self.workload_thr_alive = False
self.cb_ts = 0
self.cb_v = 0
2019-05-26 16:30:19 +00:00
env = jinja2.Environment()
env.loader = jinja2.FileSystemLoader(os.path.join(E.mod, "web"))
self.j2 = {
x: env.get_template(x + ".html")
for x in ["splash", "browser", "browser2", "msg", "md", "mde"]
}
cert_path = os.path.join(E.cfg, "cert.pem")
if os.path.exists(cert_path):
self.cert_path = cert_path
else:
self.cert_path = None
2019-05-26 16:30:19 +00:00
def accept(self, sck, addr):
"""takes an incoming tcp connection and creates a thread to handle it"""
2021-03-21 16:19:45 +01:00
if self.args.log_conn:
self.log("%s %s" % addr, "|%sC-cthr" % ("-" * 5,), c="1;30")
2021-06-08 20:14:23 +02:00
thr = threading.Thread(
target=self.thr_client,
args=(sck, addr),
name="httpsrv-{}-{}".format(addr[0].split(".", 2)[-1][-6:], addr[1]),
)
2019-05-26 16:30:19 +00:00
thr.daemon = True
thr.start()
def num_clients(self):
with self.mutex:
return len(self.clients)
2019-05-28 19:36:42 +00:00
def shutdown(self):
2021-06-18 00:30:37 +02:00
clients = list(self.clients.keys())
for cli in clients:
try:
cli.shutdown()
except:
pass
self.log("httpsrv-n", "ok bye")
2019-05-28 19:36:42 +00:00
2019-07-01 02:42:29 +02:00
def thr_client(self, sck, addr):
2019-05-26 16:30:19 +00:00
"""thread managing one tcp client"""
2019-07-11 18:26:24 +00:00
sck.settimeout(120)
2019-07-01 02:42:29 +02:00
cli = HttpConn(sck, addr, self)
2019-06-06 08:18:00 +02:00
with self.mutex:
self.clients[cli] = 0
2019-05-26 16:30:19 +00:00
if self.is_mp:
self.workload += 50
if not self.workload_thr_alive:
self.workload_thr_alive = True
2021-06-08 20:14:23 +02:00
thr = threading.Thread(
target=self.thr_workload, name="httpsrv-workload"
)
thr.daemon = True
thr.start()
2019-05-26 16:30:19 +00:00
2021-06-18 00:30:37 +02:00
fno = sck.fileno()
2019-06-06 08:18:00 +02:00
try:
2021-03-21 16:19:45 +01:00
if self.args.log_conn:
self.log("%s %s" % addr, "|%sC-crun" % ("-" * 6,), c="1;30")
2019-05-26 16:30:19 +00:00
cli.run()
2021-06-18 00:30:37 +02:00
except (OSError, socket.error) as ex:
if ex.errno not in [10038, 10054, 107, 57, 9]:
self.log(
"%s %s" % addr,
"run({}): {}".format(fno, ex),
c=6,
)
2019-05-26 16:30:19 +00:00
finally:
2021-06-08 09:40:49 +02:00
sck = cli.s
2021-03-21 16:19:45 +01:00
if self.args.log_conn:
self.log("%s %s" % addr, "|%sC-cdone" % ("-" * 7,), c="1;30")
2019-07-03 22:25:51 +00:00
try:
2021-06-18 00:30:37 +02:00
fno = sck.fileno()
2019-07-03 22:25:51 +00:00
sck.shutdown(socket.SHUT_RDWR)
sck.close()
except (OSError, socket.error) as ex:
2020-05-13 00:39:29 +02:00
if not MACOS:
self.log(
"%s %s" % addr,
2021-06-18 00:30:37 +02:00
"shut({}): {}".format(fno, ex),
2021-03-06 17:38:56 +01:00
c="1;30",
2020-05-13 00:39:29 +02:00
)
if ex.errno not in [10038, 10054, 107, 57, 49, 9]:
2020-04-14 22:42:43 +00:00
# 10038 No longer considered a socket
2020-05-15 00:00:49 +02:00
# 10054 Foribly closed by remote
2020-04-14 22:42:43 +00:00
# 107 Transport endpoint not connected
# 57 Socket is not connected
# 49 Can't assign requested address (wifi down)
2020-04-14 22:42:43 +00:00
# 9 Bad file descriptor
2019-07-03 22:25:51 +00:00
raise
finally:
with self.mutex:
del self.clients[cli]
if self.disconnect_func:
self.disconnect_func(addr) # pylint: disable=not-callable
2019-05-26 16:30:19 +00:00
def thr_workload(self):
"""indicates the python interpreter workload caused by this HttpSrv"""
# avoid locking in extract_filedata by tracking difference here
while True:
time.sleep(0.2)
with self.mutex:
if not self.clients:
# no clients rn, termiante thread
self.workload_thr_alive = False
self.workload = 0
return
total = 0
with self.mutex:
for cli in self.clients.keys():
now = cli.workload
delta = now - self.clients[cli]
if delta < 0:
# was reset in HttpCli to prevent overflow
delta = now
total += delta
self.clients[cli] = now
self.workload = total
def cachebuster(self):
if time.time() - self.cb_ts < 1:
return self.cb_v
with self.mutex:
if time.time() - self.cb_ts < 1:
return self.cb_v
v = E.t0
try:
with os.scandir(os.path.join(E.mod, "web")) as dh:
for fh in dh:
inf = fh.stat(follow_symlinks=False)
v = max(v, inf.st_mtime)
except:
pass
2021-07-08 23:35:28 +02:00
v = base64.urlsafe_b64encode(spack(b">xxL", int(v)))
self.cb_v = v.decode("ascii")[-4:]
self.cb_ts = time.time()
return self.cb_v