add HttpConn

This commit is contained in:
ed 2019-06-06 08:18:00 +02:00
parent e7dc7c9997
commit c2b270dcea
4 changed files with 119 additions and 85 deletions

1
.gitignore vendored
View file

@ -9,6 +9,7 @@ buildenv/
build/ build/
dist/ dist/
*.rst *.rst
.env/
# sublime # sublime
*.sublime-workspace *.sublime-workspace

View file

@ -10,97 +10,87 @@ import jinja2
from .__init__ import * from .__init__ import *
from .util import * from .util import *
if PY2: if not PY2:
from cStringIO import StringIO as BytesIO
else:
unicode = str unicode = str
from io import BytesIO as BytesIO
class HttpCli(object): class HttpCli(object):
def __init__(self, sck, addr, args, auth, log_func): """
self.s = sck Spawned by HttpConn to process one http transaction
self.addr = addr """
self.args = args
self.auth = auth
self.sr = Unrecv(sck) def __init__(self, conn):
self.conn = conn
self.s = conn.s
self.addr = conn.addr
self.args = conn.args
self.auth = conn.auth
self.sr = conn.sr
self.bufsz = 1024 * 32 self.bufsz = 1024 * 32
self.workload = 0
self.ok = True self.ok = True
self.log_func = log_func self.log_func = conn.log_func
self.log_src = "{} \033[36m{}".format(addr[0], addr[1]).ljust(26) self.log_src = conn.log_src
with open(self.respath("splash.html"), "rb") as f:
self.tpl_mounts = jinja2.Template(f.read().decode("utf-8"))
def respath(self, res_name):
return os.path.join(E.mod, "web", res_name)
def log(self, msg): def log(self, msg):
self.log_func(self.log_src, msg) self.log_func(self.log_src, msg)
def run(self): def run(self):
while self.ok: try:
try: headerlines = read_header(self.sr)
headerlines = read_header(self.sr) except:
except: return False
self.ok = False
return
self.headers = {} self.headers = {}
try: try:
mode, self.req, _ = headerlines[0].split(" ") mode, self.req, _ = headerlines[0].split(" ")
except: except:
self.log("bad headers:\n" + "\n".join(headerlines)) self.log("bad headers:\n" + "\n".join(headerlines))
self.s.close() return False
return
for header_line in headerlines[1:]: for header_line in headerlines[1:]:
k, v = header_line.split(":", 1) k, v = header_line.split(":", 1)
self.headers[k.lower()] = v.strip() self.headers[k.lower()] = v.strip()
self.uname = "*" self.uname = "*"
if "cookie" in self.headers: if "cookie" in self.headers:
cookies = self.headers["cookie"].split(";") cookies = self.headers["cookie"].split(";")
for k, v in [x.split("=", 1) for x in cookies]: for k, v in [x.split("=", 1) for x in cookies]:
if k != "cppwd": if k != "cppwd":
continue continue
v = unescape_cookie(v) v = unescape_cookie(v)
if v == "x": if v == "x":
break break
if not v in self.auth.iuser: if not v in self.auth.iuser:
msg = u'bad_cpwd "{}"'.format(v) msg = u'bad_cpwd "{}"'.format(v)
nuke = u"Set-Cookie: cppwd=x; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT" nuke = u"Set-Cookie: cppwd=x; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT"
self.loud_reply(msg, headers=[nuke]) self.loud_reply(msg, headers=[nuke])
return return True
self.uname = self.auth.iuser[v] self.uname = self.auth.iuser[v]
if self.uname: if self.uname:
self.rvol = self.auth.vfs.user_tree(self.uname, readable=True) self.rvol = self.auth.vfs.user_tree(self.uname, readable=True)
self.wvol = self.auth.vfs.user_tree(self.uname, writable=True) self.wvol = self.auth.vfs.user_tree(self.uname, writable=True)
print(self.rvol) print(self.rvol)
print(self.wvol) print(self.wvol)
try: try:
if mode == "GET": if mode == "GET":
self.handle_get() self.handle_get()
elif mode == "POST": elif mode == "POST":
self.handle_post() self.handle_post()
else: else:
self.loud_reply(u'invalid HTTP mode "{0}"'.format(mode)) self.loud_reply(u'invalid HTTP mode "{0}"'.format(mode))
except Pebkac as ex: except Pebkac as ex:
self.loud_reply(str(ex)) self.loud_reply(str(ex))
return False
def panic(self, msg): return self.ok
self.log("client disconnected ({0})".format(msg).upper())
self.ok = False
self.s.close()
def reply(self, body, status="200 OK", mime="text/html", headers=[]): def reply(self, body, status="200 OK", mime="text/html", headers=[]):
# TODO something to reply with user-supplied values safely # TODO something to reply with user-supplied values safely
@ -186,7 +176,7 @@ class HttpCli(object):
with open(fn, "wb") as f: with open(fn, "wb") as f:
self.log("writing to {0}".format(fn)) self.log("writing to {0}".format(fn))
sz, sha512 = hashcopy(self, p_data, f) sz, sha512 = hashcopy(self.conn, p_data, f)
if sz == 0: if sz == 0:
break break
@ -247,5 +237,5 @@ class HttpCli(object):
self.s.send(buf) self.s.send(buf)
def tx_mounts(self): def tx_mounts(self):
html = self.tpl_mounts.render(this=self) html = self.conn.tpl_mounts.render(this=self)
self.reply(html.encode("utf-8")) self.reply(html.encode("utf-8"))

45
copyparty/httpconn.py Normal file
View file

@ -0,0 +1,45 @@
#!/usr/bin/env python
# coding: utf-8
from __future__ import print_function
import os
import time
import jinja2
from .__init__ import *
from .util import Unrecv
from .httpcli import HttpCli
class HttpConn(object):
"""
spawned by HttpSrv to handle an incoming client connection,
creates an HttpCli for each request (Connection: Keep-Alive)
"""
def __init__(self, sck, addr, args, auth, log_func):
self.s = sck
self.addr = addr
self.args = args
self.auth = auth
self.sr = Unrecv(sck)
self.workload = 0
self.ok = True
self.log_func = log_func
self.log_src = "{} \033[36m{}".format(addr[0], addr[1]).ljust(26)
with open(self.respath("splash.html"), "rb") as f:
self.tpl_mounts = jinja2.Template(f.read().decode("utf-8"))
def respath(self, res_name):
return os.path.join(E.mod, "web", res_name)
def run(self):
while True:
cli = HttpCli(self)
if not cli.run():
self.s.close()
return

View file

@ -4,13 +4,13 @@ from __future__ import print_function
import threading import threading
from .httpcli import * from .httpconn import *
from .authsrv import * from .authsrv import *
class HttpSrv(object): class HttpSrv(object):
""" """
handles incoming connections (parses http and produces responses) handles incoming connections using HttpConn to process http,
relying on MpSrv for performance (HttpSrv is just plain threads) relying on MpSrv for performance (HttpSrv is just plain threads)
""" """
@ -41,20 +41,18 @@ class HttpSrv(object):
def thr_client(self, sck, addr, log): def thr_client(self, sck, addr, log):
"""thread managing one tcp client""" """thread managing one tcp client"""
cli = HttpConn(sck, addr, self.args, self.auth, log)
with self.mutex:
self.clients[cli] = 0
self.workload += 50
if not self.workload_thr_alive:
self.workload_thr_alive = True
thr = threading.Thread(target=self.thr_workload)
thr.daemon = True
thr.start()
try: try:
# TODO HttpConn between HttpSrv and HttpCli
# to ensure no state is kept between http requests
cli = HttpCli(sck, addr, self.args, self.auth, log)
with self.mutex:
self.clients[cli] = 0
self.workload += 50
if not self.workload_thr_alive:
self.workload_thr_alive = True
thr = threading.Thread(target=self.thr_workload)
thr.daemon = True
thr.start()
cli.run() cli.run()
finally: finally: