better error reporting

* use a template based on the Privoxy one
  * print a stack of the exceptions
  * link the documentation and issue tracker
This commit is contained in:
Michele Guerini Rocco 2019-08-23 01:31:58 +02:00
parent 05a6db87dd
commit 5498e1de87
Signed by: rnhmjoj
GPG Key ID: BFBAF4C975F76450
4 changed files with 176 additions and 44 deletions

View File

@ -56,9 +56,9 @@ def get_cert(name, config):
cafile: the CA file to create dummpy cert files cafile: the CA file to create dummpy cert files
certdir: the path where cert files are looked for or created certdir: the path where cert files are looked for or created
''' '''
certfile = os.path.join(config.CERTDIR, name + '.crt') certfile = os.path.join(config.certdir, name + '.crt')
if not os.path.exists(certfile): if not os.path.exists(certfile):
dummy_cert(config.CA, certfile, name) dummy_cert(config.ca, certfile, name)
return certfile return certfile

107
data/error.html Normal file
View File

@ -0,0 +1,107 @@
<!doctype html>
<html>
<head>
<meta encoding="utf-8">
<style>
* { box-sizing: border-box; }
body {
font-family: Helvetica, Arial, sans-serif;
color: black;
background: white;
margin: 1.2em;
}
article, footer {
border: 1px solid black;
width: 100%;
margin-top: 0.6em;
padding: 1em;
}
header {
height: 7em;
display: flex;
margin: 0;
}
header div {
display: flex;
align-items: center;
border: 1px solid black;
margin: 0 0.3rem;
padding: 1rem;
}
header div:first-child { margin-left: 0; }
header div:last-child { margin-right: 0; }
#status {
background: red;
color: white;
font-size: 300%;
font-weight: bolder;
}
#title {
background: #ddd;
width: 100%;
}
#message { background: #fdd; }
article { background: #eee; }
footer { background: #ccf; }
h1, h2, h3 { margin: 0; }
h1 { font-size: 140% }
h2 { font-size: 120% }
h3 { font-size: 110% }
a { text-decoration: none; }
a:link { color: #00d; }
a:visited { color: #309; }
a:active { color: #33f; }
</style>
<title>$server: Error $code</title>
</head>
<body>
<header>
<div id="status">$code</div>
<div id="title">
<h1>
This is <a href="https://maxwell.ydns.eu/git/rnhmjoj/privoxy-tls">$server</a>
on $hostname ($address), port $port
</h1>
</div>
</header>
<article id="message">
<h2>$message:</h2>
<p>
Your request for <b><a href="$url">$url</a></b> could not be fulfilled
because an error occured.
</p>
</article>
<article>
<h2>More information:</h2>
$explain
<p>Error generated on $now.</p>
</article>
<footer>
<h2>Support:</h2>
<p>
Please have a look at the
<a href="https://maxwell.ydns.eu/git/rnhmjoj/privoxy-tls">documentation</a>
to learn how to configure Privoxy TLS correctly.
</p>
<p>
If you are encountering a problem consider opening an
<a href="https://maxwell.ydns.eu/git/rnhmjoj/privoxy-tls/issues">issue</a>.
</p>
</footer>
</body>
</html>

49
main.py
View File

@ -29,16 +29,19 @@ _name = 'privoxy-tls'
class LoadConfig: class LoadConfig:
def __init__(self, configfile): def __init__(self, configfile):
self.config = configparser.ConfigParser(allow_no_value=True, self.config = configparser.ConfigParser(
inline_comment_prefixes=('#',)) allow_no_value=True, delimiters=('=',),
inline_comment_prefixes=('#',))
self.config.read(configfile) self.config.read(configfile)
self.PROXADDR = self.config['General'].get('ProxAddr') self.proxy_name = self.config['General'].get('ProxAddr')
self.FRONTPORT = int(self.config['General'].get('FrontPort')) self.front_name = self.config['General'].get('FrontAddr', 'localhost')
self.REARPORT = int(self.config['General'].get('RearPort')) self.rear_name = self.config['General'].get('RearAddr', 'localhost')
self.GeneralPROXY = self.config['General'].get('DefaultProxy') self.front_port = int(self.config['General'].get('FrontPort'))
self.LOGLEVEL = self.config['General'].get('LogLevel') self.rear_port = int(self.config['General'].get('RearPort'))
self.CA = self.config['General'].get('CACert') self.proxy = self.config['General'].get('DefaultProxy')
self.CERTDIR = self.config['General'].get('CertDir') self.loglevel = self.config['General'].get('LogLevel')
self.ca = self.config['General'].get('CACert')
self.certdir = self.config['General'].get('CertDir')
class ConnectionPools: class ConnectionPools:
@ -307,7 +310,7 @@ class FrontRequestHandler(ProxyRequestHandler):
logger.warning(f'{self.reqNum:03d} [F] {e} on ' logger.warning(f'{self.reqNum:03d} [F] {e} on '
f'"{self.command} {url}"') f'"{self.command} {url}"')
except (urllib3.exceptions.HTTPError,) as e: except (urllib3.exceptions.HTTPError,) as e:
self.sendout_error(url, 502, message="HTTPError", explain=e) self.sendout_error(url, 502, message="HTTP Error", explain=e)
logger.warning(f'{self.reqNum:03d} [F] {e} on ' logger.warning(f'{self.reqNum:03d} [F] {e} on '
f'"{self.command} {url}"') f'"{self.command} {url}"')
finally: finally:
@ -323,7 +326,7 @@ class RearRequestHandler(ProxyRequestHandler):
Supposed to be the parent proxy for Privoxy for tagged requests Supposed to be the parent proxy for Privoxy for tagged requests
Convert http request to https Convert http request to https
""" """
server_version = f'{_name} front/{__version__}' server_version = f'{_name} rear/{__version__}'
def do_METHOD(self): def do_METHOD(self):
"Convert http request to https" "Convert http request to https"
@ -409,7 +412,7 @@ class RearRequestHandler(ProxyRequestHandler):
logger.warning(f'{self.reqNum:03d} [R]{prefix} ' logger.warning(f'{self.reqNum:03d} [R]{prefix} '
f'"{self.command} {url}" {e}') f'"{self.command} {url}" {e}')
except (urllib3.exceptions.HTTPError,) as e: except (urllib3.exceptions.HTTPError,) as e:
self.sendout_error(url, 502, message="HTTPError", explain=e) self.sendout_error(url, 502, message="HTTP Error", explain=e)
logger.warning(f'{self.reqNum:03d} [R]{prefix} ' logger.warning(f'{self.reqNum:03d} [R]{prefix} '
f'"{self.command} {url}" {e}') f'"{self.command} {url}" {e}')
@ -423,16 +426,18 @@ class RearRequestHandler(ProxyRequestHandler):
def main(): def main():
urllib3.disable_warnings() urllib3.disable_warnings()
logger.setLevel(getattr(logging, config.LOGLEVEL, logging.INFO)) logger.setLevel(getattr(logging, config.loglevel, logging.INFO))
handler = logging.StreamHandler() handler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s %(message)s', datefmt='[%H:%M]') formatter = logging.Formatter('%(asctime)s %(message)s', datefmt='[%H:%M]')
handler.setFormatter(formatter) handler.setFormatter(formatter)
logger.addHandler(handler) logger.addHandler(handler)
frontserver = FrontServer(('', config.FRONTPORT), FrontRequestHandler) frontserver = FrontServer((config.front_name, config.front_port),
rearserver = RearServer(('', config.REARPORT), RearRequestHandler) FrontRequestHandler)
frontserver.config = config rearserver = RearServer((config.rear_name, config.rear_port),
for worker in (frontserver.serve_forever, rearserver.serve_forever, RearRequestHandler)
for worker in (frontserver.serve_forever,
rearserver.serve_forever,
pools.reloadConfig): pools.reloadConfig):
thread = threading.Thread(target=worker) thread = threading.Thread(target=worker)
thread.daemon = True thread.daemon = True
@ -440,10 +445,10 @@ def main():
print('=' * 40) print('=' * 40)
print(f'{_name} {__version__} (urllib3/{urllib3.__version__})') print(f'{_name} {__version__} (urllib3/{urllib3.__version__})')
print(f'Front : localhost:{config.FRONTPORT}') print(f'Front : {config.front_name}:{config.front_port}')
print(f'Privoxy : {config.PROXADDR}') print(f'Privoxy : {config.proxy_name}')
print(f'Rear : localhost:{config.REARPORT}') print(f'Rear : {config.rear_name}:{config.rear_port}')
print(f'Parent : {config.GeneralPROXY}') print(f'Proxy : {config.proxy}')
print('=' * 40) print('=' * 40)
while True: while True:
time.sleep(1) time.sleep(1)
@ -461,7 +466,7 @@ if __name__ == '__main__':
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
config = LoadConfig(CONFIG) config = LoadConfig(CONFIG)
proxpool = urllib3.ProxyManager( proxpool = urllib3.ProxyManager(
config.PROXADDR, num_pools=10, maxsize=8, config.proxy_name, num_pools=10, maxsize=8,
timeout=urllib3.util.timeout.Timeout( timeout=urllib3.util.timeout.Timeout(
connect=90.0, read=310.0)) connect=90.0, read=310.0))
pools = ConnectionPools(CONFIG) pools = ConnectionPools(CONFIG)

View File

@ -12,6 +12,8 @@ import cgi
import socket import socket
import select import select
import ssl import ssl
import string
import pathlib
from http.server import HTTPServer, BaseHTTPRequestHandler from http.server import HTTPServer, BaseHTTPRequestHandler
from socketserver import ThreadingMixIn from socketserver import ThreadingMixIn
@ -20,23 +22,36 @@ from cert import get_cert
_name = 'proxy' _name = 'proxy'
logger = logging.getLogger('__main__') logger = logging.getLogger('__main__')
message_format = '''\ data = pathlib.Path(__file__).parent / 'data'
<!doctype html> error_template = string.Template(open(data / 'error.html').read())
<html>
<head>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8"> def walk_traceback(e, n=0):
<title>Proxy Error: {code}</title> '''
</head> Produce an HTML formatted stack trace
<body> given an exception.
<h1>{code}: {message}</h1> '''
<p>The following error occurred while partial = []
trying to access <strong>{url}</strong> ul = lambda xs: ('<ul><li>'
</p> + '</li>\n<li>'.join(xs)
<p><strong>{explain}</strong></p> + '</li></ul>')
<hr>Generated on {now} by {server}.
</body> for i, arg in enumerate(e.args):
</html> name = (('<strong>'
''' + type(e).__name__
+ '</strong>') if i == 0 else '')
if isinstance(arg, str):
partial.append(
name + ' - '
+ arg.replace('<', '&lt;').replace('>', '&gt;'))
else:
partial.append(name)
if isinstance(arg, Exception):
partial.append(walk_traceback(arg, n+1))
if n == 0:
partial.append(walk_traceback(e.reason, n+1))
return ul(partial)
def read_write(socket1, socket2, max_idling=10): def read_write(socket1, socket2, max_idling=10):
@ -157,10 +172,15 @@ class ProxyRequestHandler(BaseHTTPRequestHandler):
message = shortmsg message = shortmsg
if explain is None: if explain is None:
explain = longmsg explain = longmsg
content = message_format.format(
content = error_template.substitute(
code=code, message=message, code=code, message=message,
explain=explain, url=url, explain=walk_traceback(explain),
now=datetime.today(), url=url,
hostname=self.server.server_name,
address=self.server.server_address[0],
port=self.server.server_port,
now=datetime.today().isoformat(sep=' ', timespec='seconds'),
server=self.server_version) server=self.server_version)
body = content.encode('UTF-8', 'replace') body = content.encode('UTF-8', 'replace')
self.send_response_only(code, message) self.send_response_only(code, message)