multiple connections for http
+ better logging
This commit is contained in:
120
router.py
120
router.py
@ -1,11 +1,29 @@
|
||||
import sys
|
||||
import os
|
||||
import socket
|
||||
import logging
|
||||
from glob import glob
|
||||
from select import select
|
||||
from serial import serial_for_url
|
||||
from subprocess import Popen, PIPE, check_output, call, DEVNULL
|
||||
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"
|
||||
|
||||
@ -16,65 +34,56 @@ iptables -P OUTPUT ACCEPT
|
||||
iptables -A INPUT -i lo -j ACCEPT
|
||||
"""
|
||||
|
||||
sim = False
|
||||
|
||||
|
||||
def unix_cmd(command):
|
||||
if sim:
|
||||
print('> %r' % command)
|
||||
logging.info('> %r' % command)
|
||||
else:
|
||||
print('$ %r' % command)
|
||||
logging.info('$ %r' % command)
|
||||
return Popen(command.split(), stdout=PIPE).communicate()[0].decode()
|
||||
|
||||
|
||||
class IoHandler:
|
||||
client = None
|
||||
handler = None
|
||||
port = None
|
||||
|
||||
def __init__(self, client, handler):
|
||||
self.handler = handler
|
||||
self.client = client
|
||||
self.sentchunks = 0
|
||||
self.sentbytes = 0
|
||||
self.rcvdchunks = 0
|
||||
self.rcvdbytes = 0
|
||||
|
||||
def request(self):
|
||||
try:
|
||||
data = self.client.recv(1024)
|
||||
if data:
|
||||
# print('<', data)
|
||||
logging.debug('< %r', data)
|
||||
self.write(data)
|
||||
self.sentbytes += len(data)
|
||||
self.sentchunks += 1
|
||||
return
|
||||
except Exception as e:
|
||||
print('ERROR in request: %s' % e)
|
||||
logging.error('ERROR in request: %r', e)
|
||||
self.close()
|
||||
self.handler.close_client(self)
|
||||
|
||||
def reply(self):
|
||||
try:
|
||||
data = self.read()
|
||||
# print('>', data)
|
||||
logging.debug('> %r', data)
|
||||
self.client.sendall(data)
|
||||
self.rcvdbytes += len(data)
|
||||
self.rcvdchunks += 1
|
||||
return
|
||||
except ConnectionResetError:
|
||||
pass
|
||||
except Exception as e:
|
||||
print('ERROR in reply: %s' % e)
|
||||
logging.error('ERROR in reply: %r', e)
|
||||
self.close()
|
||||
self.handler.close_client(self)
|
||||
|
||||
|
||||
class TcpHandler(IoHandler):
|
||||
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()
|
||||
super().__init__(client, handler)
|
||||
logging.debug('created')
|
||||
|
||||
def read(self):
|
||||
data = self.socket.recv(1024)
|
||||
@ -89,7 +98,7 @@ class TcpHandler(IoHandler):
|
||||
try:
|
||||
self.socket.close()
|
||||
except Exception as e:
|
||||
print('ERROR in close: %s' % e)
|
||||
logging.error('ERROR in close: %r', e)
|
||||
|
||||
|
||||
class SerialHandler(IoHandler):
|
||||
@ -109,44 +118,6 @@ class SerialHandler(IoHandler):
|
||||
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:
|
||||
"""handler for routing
|
||||
|
||||
@ -160,10 +131,8 @@ class AcceptHandler:
|
||||
reused: in this case maxcount has to be increased ...
|
||||
"""
|
||||
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.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
s.bind(('0.0.0.0', port))
|
||||
@ -173,20 +142,20 @@ class AcceptHandler:
|
||||
self.iocls = iocls
|
||||
self.readers[s.fileno()] = self.accept
|
||||
self.port = port
|
||||
if maxcount is None:
|
||||
maxcount = 8 if addr[1] == 80 else 1
|
||||
self.available = maxcount
|
||||
self.pending = 0
|
||||
logging.info('listening at port %d for %s(%r)', port, iocls.__name__, addr)
|
||||
|
||||
def close_client(self, iohandler):
|
||||
self.readers.pop(iohandler.fno, None)
|
||||
client = iohandler.client
|
||||
fno = client.fileno()
|
||||
try:
|
||||
self.readers.pop(fno)
|
||||
client = iohandler.client
|
||||
self.readers.pop(client.fileno())
|
||||
client.close()
|
||||
InfoHandler.log(f'closed connection from port {self.port} fno {fno}')
|
||||
except Exception as e:
|
||||
InfoHandler.log(f'{e!r} in close_client')
|
||||
self.handlers.pop(fno, None)
|
||||
logging.error('ERROR in close_client: %r', e)
|
||||
iohandler.client = None
|
||||
iohandler.fno = None
|
||||
self.available += 1
|
||||
@ -200,23 +169,18 @@ class AcceptHandler:
|
||||
return
|
||||
try:
|
||||
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)
|
||||
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()
|
||||
return
|
||||
self.readers[client.fileno()] = handler.request
|
||||
if handler.fno is not None:
|
||||
self.readers[handler.fno] = handler.reply
|
||||
# statistics: number of chunks sent / received
|
||||
handler.port = self.port
|
||||
self.handlers[client.fileno()] = handler
|
||||
self.readers[handler.fno] = handler.reply
|
||||
self.available -= 1
|
||||
|
||||
@classmethod
|
||||
def run(cls, routes, restrict=None):
|
||||
cls.routes = dict(routes)
|
||||
if restrict is not None:
|
||||
lines = BASIC % dict(accept='DROP' if restrict else 'ACCEPT')
|
||||
unix_cmd('iptables -F')
|
||||
@ -226,7 +190,6 @@ class AcceptHandler:
|
||||
if restrict:
|
||||
unix_cmd(FILTER % 22)
|
||||
|
||||
AcceptHandler(1111, None, InfoHandler, 5)
|
||||
for port, dest in routes.items():
|
||||
port=int(port)
|
||||
if restrict:
|
||||
@ -242,18 +205,21 @@ class AcceptHandler:
|
||||
AcceptHandler(port, (host, remoteport), TcpHandler)
|
||||
while True:
|
||||
try:
|
||||
# logging.debug('select %r', list(cls.readers))
|
||||
ready, _, _ = select(cls.readers, [], [])
|
||||
# logging.debug('ready %r', ready)
|
||||
except Exception as e:
|
||||
for r in cls.readers:
|
||||
try:
|
||||
select([r], [], [], 0.1)
|
||||
except Exception as e:
|
||||
print(r, e)
|
||||
logging.error('%r in select([%d])', e, r)
|
||||
raise
|
||||
for fno in ready:
|
||||
cls.readers[fno]()
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = ConfigParser()
|
||||
cfgfiles = glob('/root/aputools/servercfg/%s_*.cfg' % socket.gethostname())
|
||||
|
Reference in New Issue
Block a user