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
|
|
|
|
2019-06-25 21:01:00 +00:00
|
|
|
import os
|
2021-03-25 19:29:16 +01:00
|
|
|
import sys
|
2019-06-07 08:54:41 +00:00
|
|
|
import time
|
2021-07-01 20:10:02 +02:00
|
|
|
import base64
|
2019-07-02 23:36:16 +00:00
|
|
|
import socket
|
2019-05-26 16:30:19 +00:00
|
|
|
import threading
|
|
|
|
|
|
2021-03-25 19:29:16 +01:00
|
|
|
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)
|
|
|
|
|
|
2021-07-09 03:41:44 +02:00
|
|
|
from .__init__ import E, PY2, MACOS
|
|
|
|
|
from .util import spack, min_ex
|
2021-06-07 20:09:18 +02:00
|
|
|
from .httpconn import HttpConn
|
2019-05-26 16:30:19 +00:00
|
|
|
|
2021-07-09 03:41:44 +02:00
|
|
|
if PY2:
|
|
|
|
|
import Queue as queue
|
|
|
|
|
else:
|
|
|
|
|
import queue
|
|
|
|
|
|
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)
|
|
|
|
|
"""
|
|
|
|
|
|
2021-06-08 18:01:59 +02:00
|
|
|
def __init__(self, broker, is_mp=False):
|
2019-07-01 02:42:29 +02:00
|
|
|
self.broker = broker
|
2021-06-08 18:01:59 +02:00
|
|
|
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
|
|
|
|
2021-07-09 16:07:16 +02:00
|
|
|
self.name = "httpsrv-i" + str(os.getpid())
|
2019-05-28 20:49:58 +00:00
|
|
|
self.mutex = threading.Lock()
|
2021-07-09 15:49:36 +02:00
|
|
|
self.stopping = False
|
2019-05-28 20:49:58 +00:00
|
|
|
|
2021-07-09 03:41:44 +02:00
|
|
|
self.tp_nthr = 0 # actual
|
|
|
|
|
self.tp_ncli = 0 # fading
|
|
|
|
|
self.tp_time = None # latest worker collect
|
|
|
|
|
self.tp_q = None if self.args.no_htp else queue.LifoQueue()
|
|
|
|
|
|
2021-07-09 15:49:36 +02:00
|
|
|
self.srvs = []
|
2019-05-26 16:30:19 +00:00
|
|
|
self.clients = {}
|
2021-07-01 20:10:02 +02:00
|
|
|
self.cb_ts = 0
|
|
|
|
|
self.cb_v = 0
|
2019-05-26 16:30:19 +00:00
|
|
|
|
2021-03-25 19:29:16 +01:00
|
|
|
env = jinja2.Environment()
|
|
|
|
|
env.loader = jinja2.FileSystemLoader(os.path.join(E.mod, "web"))
|
|
|
|
|
self.j2 = {
|
|
|
|
|
x: env.get_template(x + ".html")
|
2021-04-03 07:56:35 +02:00
|
|
|
for x in ["splash", "browser", "browser2", "msg", "md", "mde"]
|
2021-03-25 19:29:16 +01:00
|
|
|
}
|
|
|
|
|
|
2019-06-25 21:01:00 +00:00
|
|
|
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
|
|
|
|
|
|
2021-07-09 03:41:44 +02:00
|
|
|
if self.tp_q:
|
|
|
|
|
self.start_threads(4)
|
|
|
|
|
|
|
|
|
|
t = threading.Thread(target=self.thr_scaler)
|
|
|
|
|
t.daemon = True
|
|
|
|
|
t.start()
|
|
|
|
|
|
|
|
|
|
def start_threads(self, n):
|
|
|
|
|
self.tp_nthr += n
|
|
|
|
|
if self.args.log_htp:
|
2021-07-09 16:07:16 +02:00
|
|
|
self.log(self.name, "workers += {} = {}".format(n, self.tp_nthr), 6)
|
2021-07-09 03:41:44 +02:00
|
|
|
|
|
|
|
|
for _ in range(n):
|
|
|
|
|
thr = threading.Thread(
|
|
|
|
|
target=self.thr_poolw,
|
|
|
|
|
name="httpsrv-poolw",
|
|
|
|
|
)
|
|
|
|
|
thr.daemon = True
|
|
|
|
|
thr.start()
|
|
|
|
|
|
|
|
|
|
def stop_threads(self, n):
|
|
|
|
|
self.tp_nthr -= n
|
|
|
|
|
if self.args.log_htp:
|
2021-07-09 16:07:16 +02:00
|
|
|
self.log(self.name, "workers -= {} = {}".format(n, self.tp_nthr), 6)
|
2021-07-09 03:41:44 +02:00
|
|
|
|
|
|
|
|
for _ in range(n):
|
|
|
|
|
self.tp_q.put(None)
|
|
|
|
|
|
|
|
|
|
def thr_scaler(self):
|
|
|
|
|
while True:
|
|
|
|
|
time.sleep(2 if self.tp_ncli else 30)
|
|
|
|
|
with self.mutex:
|
|
|
|
|
self.tp_ncli = max(len(self.clients), self.tp_ncli - 2)
|
|
|
|
|
if self.tp_nthr > self.tp_ncli + 8:
|
|
|
|
|
self.stop_threads(4)
|
|
|
|
|
|
2021-07-09 15:49:36 +02:00
|
|
|
def listen(self, sck):
|
|
|
|
|
self.srvs.append(sck)
|
|
|
|
|
t = threading.Thread(target=self.thr_listen, args=(sck,))
|
|
|
|
|
t.daemon = True
|
|
|
|
|
t.start()
|
|
|
|
|
|
|
|
|
|
def thr_listen(self, srv_sck):
|
|
|
|
|
"""listens on a shared tcp server"""
|
|
|
|
|
ip, port = srv_sck.getsockname()
|
|
|
|
|
fno = srv_sck.fileno()
|
2021-07-09 16:07:16 +02:00
|
|
|
msg = "subscribed @ {}:{} f{}".format(ip, port, fno)
|
|
|
|
|
self.log(self.name, msg)
|
2021-07-09 15:49:36 +02:00
|
|
|
while not self.stopping:
|
|
|
|
|
if self.args.log_conn:
|
2021-07-09 16:07:16 +02:00
|
|
|
self.log(self.name, "|%sC-ncli" % ("-" * 1,), c="1;30")
|
2021-07-09 15:49:36 +02:00
|
|
|
|
|
|
|
|
if len(self.clients) >= self.args.nc:
|
|
|
|
|
time.sleep(0.1)
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
if self.args.log_conn:
|
2021-07-09 16:07:16 +02:00
|
|
|
self.log(self.name, "|%sC-acc1" % ("-" * 2,), c="1;30")
|
2021-07-09 15:49:36 +02:00
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
sck, addr = srv_sck.accept()
|
|
|
|
|
except (OSError, socket.error) as ex:
|
2021-07-09 16:07:16 +02:00
|
|
|
self.log(self.name, "accept({}): {}".format(fno, ex), c=6)
|
2021-07-09 15:49:36 +02:00
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
if self.args.log_conn:
|
|
|
|
|
m = "|{}C-acc2 \033[0;36m{} \033[3{}m{}".format(
|
|
|
|
|
"-" * 3, ip, port % 8, port
|
|
|
|
|
)
|
|
|
|
|
self.log("%s %s" % addr, m, c="1;30")
|
|
|
|
|
|
|
|
|
|
self.accept(sck, addr)
|
|
|
|
|
|
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-07-09 03:41:44 +02:00
|
|
|
now = time.time()
|
|
|
|
|
|
|
|
|
|
if self.tp_time and now - self.tp_time > 300:
|
|
|
|
|
self.tp_q = None
|
|
|
|
|
|
|
|
|
|
if self.tp_q:
|
|
|
|
|
self.tp_q.put((sck, addr))
|
|
|
|
|
with self.mutex:
|
|
|
|
|
self.tp_time = self.tp_time or now
|
|
|
|
|
self.tp_ncli = max(self.tp_ncli, len(self.clients) + 1)
|
|
|
|
|
if self.tp_nthr < len(self.clients) + 4:
|
|
|
|
|
self.start_threads(8)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
if not self.args.no_htp:
|
|
|
|
|
m = "looks like the httpserver threadpool died; please make an issue on github and tell me the story of how you pulled that off, thanks and dog bless\n"
|
2021-07-09 16:07:16 +02:00
|
|
|
self.log(self.name, m, 1)
|
2021-07-09 03:41:44 +02:00
|
|
|
|
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()
|
|
|
|
|
|
2021-07-09 03:41:44 +02:00
|
|
|
def thr_poolw(self):
|
|
|
|
|
while True:
|
|
|
|
|
task = self.tp_q.get()
|
|
|
|
|
if not task:
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
with self.mutex:
|
|
|
|
|
self.tp_time = None
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
sck, addr = task
|
|
|
|
|
me = threading.current_thread()
|
|
|
|
|
me.name = (
|
|
|
|
|
"httpsrv-{}-{}".format(addr[0].split(".", 2)[-1][-6:], addr[1]),
|
|
|
|
|
)
|
|
|
|
|
self.thr_client(sck, addr)
|
|
|
|
|
me.name = "httpsrv-poolw"
|
|
|
|
|
except:
|
2021-07-09 16:07:16 +02:00
|
|
|
self.log(self.name, "thr_client: " + min_ex(), 3)
|
2021-07-09 03:41:44 +02:00
|
|
|
|
2019-05-26 16:30:19 +00:00
|
|
|
def num_clients(self):
|
|
|
|
|
with self.mutex:
|
|
|
|
|
return len(self.clients)
|
|
|
|
|
|
2019-05-28 19:36:42 +00:00
|
|
|
def shutdown(self):
|
2021-07-09 15:49:36 +02:00
|
|
|
self.stopping = True
|
|
|
|
|
for srv in self.srvs:
|
|
|
|
|
try:
|
|
|
|
|
srv.close()
|
|
|
|
|
except:
|
|
|
|
|
pass
|
|
|
|
|
|
2021-06-18 00:30:37 +02:00
|
|
|
clients = list(self.clients.keys())
|
|
|
|
|
for cli in clients:
|
|
|
|
|
try:
|
|
|
|
|
cli.shutdown()
|
|
|
|
|
except:
|
|
|
|
|
pass
|
|
|
|
|
|
2021-07-09 16:07:16 +02:00
|
|
|
if self.tp_q:
|
|
|
|
|
self.stop_threads(self.tp_nthr)
|
|
|
|
|
for _ in range(10):
|
|
|
|
|
time.sleep(0.05)
|
|
|
|
|
if self.tp_q.empty():
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
self.log("httpsrv-i" + str(os.getpid()), "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
|
|
|
|
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:
|
2021-07-09 15:49:36 +02:00
|
|
|
self.log("%s %s" % addr, "|%sC-crun" % ("-" * 4,), c="1;30")
|
2021-03-21 16:19:45 +01:00
|
|
|
|
2019-05-26 16:30:19 +00:00
|
|
|
cli.run()
|
|
|
|
|
|
2021-06-18 00:30:37 +02:00
|
|
|
except (OSError, socket.error) as ex:
|
2021-07-09 15:49:36 +02:00
|
|
|
if ex.errno not in [10038, 10054, 107, 57, 49, 9]:
|
2021-06-18 00:30:37 +02:00
|
|
|
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:
|
2021-07-09 15:49:36 +02:00
|
|
|
self.log("%s %s" % addr, "|%sC-cdone" % ("-" * 5,), c="1;30")
|
2021-03-21 16:19:45 +01:00
|
|
|
|
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
|
|
|
)
|
2021-07-01 20:22:12 +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
|
2021-07-01 20:22:12 +02:00
|
|
|
# 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]
|
|
|
|
|
|
2021-07-01 20:10:02 +02:00
|
|
|
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)))
|
2021-07-01 20:10:02 +02:00
|
|
|
self.cb_v = v.decode("ascii")[-4:]
|
|
|
|
|
self.cb_ts = time.time()
|
|
|
|
|
return self.cb_v
|