multiple connections for http

+ better logging
This commit is contained in:
2024-02-20 17:07:11 +01:00
parent 3c1da3bb2c
commit 2fea4a95f2

118
router.py
View File

@ -1,11 +1,29 @@
import sys
import os import os
import socket import socket
import logging
from glob import glob from glob import glob
from select import select from select import select
from serial import serial_for_url from serial import serial_for_url
from subprocess import Popen, PIPE, check_output, call, DEVNULL from subprocess import Popen, PIPE, check_output, call, DEVNULL
from configparser import ConfigParser from configparser import ConfigParser
sim = False
if sys.argv[0] == 'router.py': # started manually
loglev = logging.INFO
else:
loglev = logging.WARNING
for arg in sys.argv[1:]:
if arg == '-v':
loglev = logging.DEBUG
elif arg == '-s':
sim = True
else:
raise ValueError(f'do not know {arg!r}')
logging.basicConfig(format='%(levelname)1.1s: %(message)s', level=loglev)
FILTER = "iptables -i enp4s0 -p tcp -m tcp --dport %d -j ACCEPT" FILTER = "iptables -i enp4s0 -p tcp -m tcp --dport %d -j ACCEPT"
@ -16,65 +34,56 @@ iptables -P OUTPUT ACCEPT
iptables -A INPUT -i lo -j ACCEPT iptables -A INPUT -i lo -j ACCEPT
""" """
sim = False
def unix_cmd(command): def unix_cmd(command):
if sim: if sim:
print('> %r' % command) logging.info('> %r' % command)
else: else:
print('$ %r' % command) logging.info('$ %r' % command)
return Popen(command.split(), stdout=PIPE).communicate()[0].decode() return Popen(command.split(), stdout=PIPE).communicate()[0].decode()
class IoHandler: class IoHandler:
client = None client = None
handler = None handler = None
port = None
def __init__(self, client, handler): def __init__(self, client, handler):
self.handler = handler self.handler = handler
self.client = client self.client = client
self.sentchunks = 0
self.sentbytes = 0
self.rcvdchunks = 0
self.rcvdbytes = 0
def request(self): def request(self):
try: try:
data = self.client.recv(1024) data = self.client.recv(1024)
if data: if data:
# print('<', data) logging.debug('< %r', data)
self.write(data) self.write(data)
self.sentbytes += len(data)
self.sentchunks += 1
return return
except Exception as e: except Exception as e:
print('ERROR in request: %s' % e) logging.error('ERROR in request: %r', e)
self.close() self.close()
self.handler.close_client(self) self.handler.close_client(self)
def reply(self): def reply(self):
try: try:
data = self.read() data = self.read()
# print('>', data) logging.debug('> %r', data)
self.client.sendall(data) self.client.sendall(data)
self.rcvdbytes += len(data)
self.rcvdchunks += 1
return return
except ConnectionResetError: except ConnectionResetError:
pass pass
except Exception as e: except Exception as e:
print('ERROR in reply: %s' % e) logging.error('ERROR in reply: %r', e)
self.close() self.close()
self.handler.close_client(self) self.handler.close_client(self)
class TcpHandler(IoHandler): class TcpHandler(IoHandler):
def __init__(self, client, handler): def __init__(self, client, handler):
self.socket = socket.create_connection(handler.addr) logging.info('create %r', handler.addr)
self.socket = socket.create_connection(handler.addr, timeout=5)
self.socket.settimeout(1)
self.fno = self.socket.fileno() self.fno = self.socket.fileno()
super().__init__(client, handler) super().__init__(client, handler)
logging.debug('created')
def read(self): def read(self):
data = self.socket.recv(1024) data = self.socket.recv(1024)
@ -89,7 +98,7 @@ class TcpHandler(IoHandler):
try: try:
self.socket.close() self.socket.close()
except Exception as e: except Exception as e:
print('ERROR in close: %s' % e) logging.error('ERROR in close: %r', e)
class SerialHandler(IoHandler): class SerialHandler(IoHandler):
@ -109,44 +118,6 @@ class SerialHandler(IoHandler):
self.serial.close() self.serial.close()
class InfoHandler(IoHandler):
clients = {}
def __init__(self, client, handler):
super().__init__(client, handler)
info = [f'{k} -> {v}' for k, v in AcceptHandler.routes.items()]
if AcceptHandler.handlers:
info.append('\nactive routings, statistics bytes/chunks')
info.append('fno port sent received')
for fno, h in AcceptHandler.handlers.items():
info.append(f'{fno} {h.port} {h.sentbytes:d}/{h.sentchunks:d} {h.rcvdbytes:d}/{h.rcvdchunks:d}')
info.append('')
self.client.sendall('\n'.join(info).encode('utf-8'))
self.clients[client.fileno()] = self
self.fno = None
def read(self):
return b''
def write(self, data):
pass
def close(self):
self.clients.pop(self.client.fileno())
@classmethod
def log(cls, line):
if cls.clients:
for c in cls.clients.values():
try:
c.client.sendall(line.encode('utf-8'))
c.client.sendall(b'\n')
except TimeoutError:
pass
else:
print(line)
class AcceptHandler: class AcceptHandler:
"""handler for routing """handler for routing
@ -160,10 +131,8 @@ class AcceptHandler:
reused: in this case maxcount has to be increased ... reused: in this case maxcount has to be increased ...
""" """
readers = {} readers = {}
handlers = {}
routes = {}
def __init__(self, port, addr, iocls, maxcount=1): def __init__(self, port, addr, iocls, maxcount=None):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('0.0.0.0', port)) s.bind(('0.0.0.0', port))
@ -173,20 +142,20 @@ class AcceptHandler:
self.iocls = iocls self.iocls = iocls
self.readers[s.fileno()] = self.accept self.readers[s.fileno()] = self.accept
self.port = port self.port = port
if maxcount is None:
maxcount = 8 if addr[1] == 80 else 1
self.available = maxcount self.available = maxcount
self.pending = 0 self.pending = 0
logging.info('listening at port %d for %s(%r)', port, iocls.__name__, addr)
def close_client(self, iohandler): def close_client(self, iohandler):
self.readers.pop(iohandler.fno, None) self.readers.pop(iohandler.fno, None)
client = iohandler.client
fno = client.fileno()
try: try:
self.readers.pop(fno) client = iohandler.client
self.readers.pop(client.fileno())
client.close() client.close()
InfoHandler.log(f'closed connection from port {self.port} fno {fno}')
except Exception as e: except Exception as e:
InfoHandler.log(f'{e!r} in close_client') logging.error('ERROR in close_client: %r', e)
self.handlers.pop(fno, None)
iohandler.client = None iohandler.client = None
iohandler.fno = None iohandler.fno = None
self.available += 1 self.available += 1
@ -200,23 +169,18 @@ class AcceptHandler:
return return
try: try:
client, addr = self.socket.accept() client, addr = self.socket.accept()
InfoHandler.log(f'accepted {addr} on {self.port} fno {client.fileno()}') logging.info('accepted %r on %r', addr, self.port)
handler = self.iocls(client, self) handler = self.iocls(client, self)
except Exception as e: except Exception as e:
InfoHandler.log(f'{e!r} creating {self.iocls.__name__}({self.addr})') logging.error('%r creating %s(%r)', e, self.iocls.__name__, self.addr)
client.close() client.close()
return return
self.readers[client.fileno()] = handler.request self.readers[client.fileno()] = handler.request
if handler.fno is not None:
self.readers[handler.fno] = handler.reply self.readers[handler.fno] = handler.reply
# statistics: number of chunks sent / received
handler.port = self.port
self.handlers[client.fileno()] = handler
self.available -= 1 self.available -= 1
@classmethod @classmethod
def run(cls, routes, restrict=None): def run(cls, routes, restrict=None):
cls.routes = dict(routes)
if restrict is not None: if restrict is not None:
lines = BASIC % dict(accept='DROP' if restrict else 'ACCEPT') lines = BASIC % dict(accept='DROP' if restrict else 'ACCEPT')
unix_cmd('iptables -F') unix_cmd('iptables -F')
@ -226,7 +190,6 @@ class AcceptHandler:
if restrict: if restrict:
unix_cmd(FILTER % 22) unix_cmd(FILTER % 22)
AcceptHandler(1111, None, InfoHandler, 5)
for port, dest in routes.items(): for port, dest in routes.items():
port=int(port) port=int(port)
if restrict: if restrict:
@ -242,18 +205,21 @@ class AcceptHandler:
AcceptHandler(port, (host, remoteport), TcpHandler) AcceptHandler(port, (host, remoteport), TcpHandler)
while True: while True:
try: try:
# logging.debug('select %r', list(cls.readers))
ready, _, _ = select(cls.readers, [], []) ready, _, _ = select(cls.readers, [], [])
# logging.debug('ready %r', ready)
except Exception as e: except Exception as e:
for r in cls.readers: for r in cls.readers:
try: try:
select([r], [], [], 0.1) select([r], [], [], 0.1)
except Exception as e: except Exception as e:
print(r, e) logging.error('%r in select([%d])', e, r)
raise raise
for fno in ready: for fno in ready:
cls.readers[fno]() cls.readers[fno]()
if __name__ == '__main__': if __name__ == '__main__':
parser = ConfigParser() parser = ConfigParser()
cfgfiles = glob('/root/aputools/servercfg/%s_*.cfg' % socket.gethostname()) cfgfiles = glob('/root/aputools/servercfg/%s_*.cfg' % socket.gethostname())