Add a debug mode and add regexp support for the mapping

* mandaye/config.py: add a generic filter for biblio
 * mandaye/dispatchers/auth.py: improve error management
 * mandaye/dispatchers/default.py: improve mapping support
 * mandaye/filters/vincennes.py: improve error management
 * mandaye/response.py: add debug support and improve the responses
 * mandaye/server.py: improve bad gateway suppport
This commit is contained in:
Jérôme Schneider 2011-09-09 22:26:57 +02:00
parent 7afc5c247d
commit 516df263c4
9 changed files with 147 additions and 79 deletions

2
TODO
View File

@ -1,8 +1,8 @@
* Improve README.rst
* Move the default filters into the mapping
* Add a debug mode
* Mapping should match the url
* Manage a folder with the configurations
* Finish authentification management
* Merge __init__ and init in the dispatchers

View File

@ -3,6 +3,9 @@
host = "127.0.0.1"
port = 8088
debug = True
# Needed if ssl is activate
ssl = False
keyfile = ""
@ -16,10 +19,17 @@ template_directory = "mandaye/templates"
db_url = 'sqlite:///test.db'
from filters.vincennes import biblio_html_login_page
from filters.vincennes import biblio_html
from filters.default import MandayeFilter
biblio_mapping = {
'/mandaye/login': {
r'/': {
'on_response_filters': [{
'type': 'data',
'filter': biblio_html
}]
},
r'/mandaye/login$': {
'method': 'GET',
'response': {
'dispatcher_method': 'login',
@ -35,7 +45,7 @@ biblio_mapping = {
}
}
},
'/mandaye/connection': {
r'/mandaye/connection$': {
'method': 'GET',
'response': {
'dispatcher_method': 'connection',
@ -46,7 +56,7 @@ biblio_mapping = {
}
}
},
'/sezhame/page/connexion-abonne': {
r'/sezhame/page/connexion-abonne$': {
'method': 'GET',
'on_response_filters': [{
'type': 'data',
@ -58,6 +68,7 @@ biblio_mapping = {
from dispatchers.default import Dispatcher
from dispatchers.vincennes import VincennesDispatcher
# hosts = {'lamereasyl42': r'/': {'dispatcher': MyDispatcherBiatch, 'mapping': dick}}
hosts = {
'sfd.local:8088': [
(r'/', Dispatcher('http://www.sfdiabete.org')),

View File

@ -21,20 +21,20 @@ class AuthFormDispatcher(Dispatcher):
"""
login = self.local_auth.get_current_login()
if not login:
return _401('Invalid login')
return _401('Access denied: invalid token')
if not values.has_key('form_action') \
or not values.has_key('form_values') \
or not values.has_key('username_field') \
or not values.has_key('password_field'):
logging.warning('Invalid values for AuthFormDispatcher.login')
return _502
return _500(self.env['PATH_INFO'],
'Invalid values for AuthFormDispatcher.login')
headers = values.get('form_headers')
if not headers:
headers = { 'Content-Type': 'application/x-www-form-urlencoded' }
params = values['form_values']
# TODO: find the login / password in the destaut or redirect
params[values['username_field']] = '754443'
params[values['password_field']] = 'lecteur'
params[values['password_field']] = ''
params = urllib.urlencode(params)
conn = httplib.HTTPConnection(self.target.hostname)
conn.request("POST", values.get('form_action'), params, headers)

View File

@ -1,9 +1,10 @@
import logging
import re
from urlparse import urlparse
from mandaye.filters.default import MandayeFilter
from mandaye.response import _502
from mandaye.response import _500
# TODO: add an external url mapping
@ -12,7 +13,7 @@ class Dispatcher(object):
It allows you to launch the right filter on the reqest and the response
"""
def __init__(self, target_url, mapping=None):
def __init__(self, target_url, mapping=dict()):
""" target_url: the full url of your destination
"""
self.target = urlparse(target_url)
@ -24,18 +25,33 @@ class Dispatcher(object):
self.env['target'] = self.target
self.filter = MandayeFilter(self.env, self.target)
path = self.env['PATH_INFO']
if self.mapping and \
self.mapping.has_key(path) and \
self.mapping[path].has_key('method') and \
self.mapping[path]['method'] == self.env['REQUEST_METHOD']:
self.req_mapping = self.mapping[path]
else:
self.req_mapping = None
self.req_mapping = self._parse_mapping(self.mapping)
def _call_dispatcher_hook(self, hookname, *args):
if not self.req_mapping:
return None
hook = self.req_mapping.get(hookname)
def _parse_mapping(self, mapping):
""" parse the mapping on every request
"""
req_mapping = {
'on_request_hook': None,
'on_request_filters': [],
'on_response_hook': None,
'on_response_filters': [],
'response': None,
}
for path in mapping.iterkeys():
if re.match(path, self.env['PATH_INFO']):
cmapping = self.mapping[path]
if not cmapping.has_key('method') \
or cmapping['method'] == self.env['REQUEST_METHOD']:
for hookname in req_mapping:
if cmapping.has_key(hookname):
if isinstance(req_mapping[hookname], list):
req_mapping[hookname].extend(cmapping[hookname])
else:
req_mapping[hookname] = cmapping[hookname]
return req_mapping
def _call_dispatcher_hook(self, hook, *args):
if hook and hook.has_key('dispatcher_method'):
try:
method = getattr(self, hook['dispatcher_method'])
@ -53,7 +69,7 @@ class Dispatcher(object):
""" Return the destination url
"""
# Disable reverse proxy if we have a static response
if self.req_mapping and self.req_mapping.has_key('response'):
if self.req_mapping['response']:
return None
# TODO: manage redirect here
url = self.target.geturl() + self.env['PATH_INFO']
@ -65,10 +81,10 @@ class Dispatcher(object):
def get_response(self, request):
""" Called if you have a response hook for this request
"""
response = self._call_dispatcher_hook('response', request)
response = self._call_dispatcher_hook(self.req_mapping['response'],
request)
if not response:
return _500("The response dispatcher hook failed for URL %s" %\
self.env["PATH_INFO"])
return _500(self.env["PATH_INFO"], "The response hook failed")
return response
def mod_request(self, request):
@ -79,19 +95,20 @@ class Dispatcher(object):
request.headers = self.filter.request_headers(request.headers)
request.cookies = self.filter.request_cookies(request.cookies)
request.msg = self.filter.request_msg(request.msg)
# Loading specific filters
if self.req_mapping and self.req_mapping.has_key('on_request_filters'):
for filter in self.req_mapping['on_request_filters']:
if filter['type'] == "data":
request.msg = filter['filter'](request.msg, self.env)
elif filter['type'] == "cookies":
request.cookies = filter['filter'](request.cookies, self.env)
elif filter['type'] == "headers":
request.headers = filter['filter'](request.headers, self.env)
for filter in self.req_mapping['on_request_filters']:
if filter['type'] == "data":
request.msg = filter['filter'](request.msg, self.env)
elif filter['type'] == "cookies":
request.cookies = filter['filter'](request.cookies, self.env)
elif filter['type'] == "headers":
request.headers = filter['filter'](request.headers, self.env)
# Calling hook function
new_request = self._call_dispatcher_hook('on_request_hook', request)
if new_request:
return new_request
if self.req_mapping['on_request_hook']:
return self._call_dispatcher_hook(
self.req_mapping['on_request_hook'], request)
return request
def mod_response(self, request, response):
@ -99,21 +116,25 @@ class Dispatcher(object):
request: the Mandaye request
response: MandayeResponse object with cookies, headers and HTML
you can modify the cookies and the HTTP headers """
# Loading defaults filters
response.headers = self.filter.response_headers(response.headers)
response.cookies = self.filter.response_cookies(response.cookies)
response.msg = self.filter.response_msg(response.msg)
# Loading specific filters
if self.req_mapping and self.req_mapping.has_key('on_response_filters'):
for filter in self.req_mapping['on_response_filters']:
if filter['type'] == "data":
response.msg = filter['filter'](response.msg, self.env)
elif filter['type'] == "cookies":
response.cookies = filter['filter'](response.cookies, self.env)
elif filter['type'] == "headers":
response.headers = filter['filter'](response.headers, self.env)
new_response = self._call_dispatcher_hook('on_response_hook', request, response)
if new_response:
return new_response
for filter in self.req_mapping['on_response_filters']:
if filter['type'] == "data":
response.msg = filter['filter'](response.msg, self.env)
elif filter['type'] == "cookies":
response.cookies = filter['filter'](response.cookies, self.env)
elif filter['type'] == "headers":
response.headers = filter['filter'](response.headers, self.env)
# Calling hook function
if self.req_mapping['on_response_hook']:
return self._call_dispatcher_hook(
self.req_mapping['on_response_hook'], request, response)
return response

View File

@ -2,6 +2,8 @@
Dispatcher for the Vincennes authentifications
"""
import base64
import logging
import traceback
from Crypto.Cipher import AES
@ -34,12 +36,17 @@ class VincennesAuth(object):
query = self._parse_qs(self.env['QUERY_STRING'])
if query.has_key('token'):
# TODO: catch exceptions
token = query['token']
token = base64.b64decode(token)
cipher = AES.new(config.secret, AES.MODE_CFB)
decode = cipher.decrypt(token)
info = eval(decode[16:])
return info['pseudo']
try:
token = query['token']
token = base64.b64decode(token)
cipher = AES.new(config.secret, AES.MODE_CFB)
decode = cipher.decrypt(token)
info = eval(decode[16:])
return info['pseudo']
except Exception, e:
if config.debug:
traceback.print_exc()
return None
return None
class VincennesDispatcher(AuthFormDispatcher):

View File

@ -51,7 +51,7 @@ class MandayeFilter(object):
""" headers: HTTPHeader instance
return a HTTPHeader object
"""
blacklist = ['transfer-encoding']
blacklist = ['transfer-encoding', 'content-length']
for key, value in headers.iteritems():
if key == 'location':
location = value[0].replace(self.target.hostname,

View File

@ -10,3 +10,9 @@ def biblio_html_login_page(data, env):
data)
return data
def biblio_html(data, env):
""" Global html filter the Vincenne library
This fix the fucking absolute url of the biblio site
"""
data = data.replace(env["target"].hostname, env["HTTP_HOST"])
return data

View File

@ -1,9 +1,13 @@
import logging
import traceback
from cgi import escape
from mako.lookup import TemplateLookup
from mako.template import Template
from mandaye.config import template_directory
from mandaye.http import HTTPResponse, HTTPHeader
from mandaye.config import template_directory, debug
mylookup = TemplateLookup(directories=[template_directory])
@ -11,6 +15,13 @@ def serve_template(templatename, **kwargs):
mytemplate = mylookup.get_template(templatename)
return mytemplate.render(**kwargs)
def _get_html_traceback(exception, path):
traceback.print_exc()
tb_str = traceback.format_exc()
msg = "<h2>%s %s</h2><pre>%s</pre>" % \
(path, exception, escape(tb_str))
return msg
def _302(location, cookies=None):
headers = HTTPHeader({'Location': [location],
@ -24,29 +35,46 @@ def _401(msg):
return HTTPResponse(401, 'Unauthorized', headers,
serve_template("response.html", title=title, body=msg))
def _404(url):
def _404(path):
logging.waring("[404]: %s not found" % path)
title = "Not Found"
body = "The requested URL %s was not found on this server." % url
body = "The requested URL %s was not found on this server." % path
headers = HTTPHeader({'Content-Type': ['text/html']})
return HTTPResponse(404, 'Not found', headers,
serve_template("response.html", title=title, body=body))
def _500(msg=None):
if not msg:
msg = "The server encountered an internal error or misconfiguration \
and was unable to completeyour request.Please contact the server \
administrator and inform them of the time the error occurred, and \
anything you might have done that may have caused the error. \
More information about this error may be availablein the server error log."
def _500(path, msg, exception=None):
""" Return a 500 error
path: the path of this error
exception: this exception instance if any """
logging.critical("[500] %s: %s" % (path, msg))
title = 'Internal Server Error'
headers = HTTPHeader({'Content-Type': ['text/html']})
if debug:
if exception:
msg += _get_html_traceback(exception, path)
else:
msg=msg
else:
msg = "The server encountered an internal error or misconfiguration \
and was unable to complete your request. Please contact the server \
administrator and inform them of the time the error occurred."
return HTTPResponse(500, 'Internal Server Error', headers,
serve_template("response.html", title=title, body=msg))
def _502(msg):
def _502(path, target_hostname, exception=None):
""" Return a 502 error
path: the path of this error
exception: this exception instance if any """
logging.critical("[502] error while reading %s:\n" % path)
title = 'Bad Gateway'
headers = HTTPHeader({'Content-Type': ['text/html']})
msg = "The server can't join the target server or misconfiguration \
and was unable to complete your request. Please contact the server \
administrator and inform them of the time the error occurred."
if debug and exception:
msg = "<h2>Target hostname: %s</h2>" % target_hostname
msg += _get_html_traceback(exception, path)
return HTTPResponse(502, 'Bad Gateway', headers,
serve_template("response.html", title=title, body=msg))

View File

@ -1,10 +1,10 @@
from gevent import monkey
# gevent patching
monkey.patch_all()
import Cookie
import config
import logging
import urllib
import urllib2
import re
@ -81,6 +81,9 @@ class MandayeApp(object):
mrequest = self._get_request()
# Calling the dispatcher hook for the request
mrequest = self.dispatcher.mod_request(mrequest)
if not mrequest:
return self.on_response(start_response,
_500(self.env["PATH_INFO"], "Empty request"))
url = self.dispatcher.get_target_url()
if not url:
return self.on_response(start_response,
@ -95,16 +98,8 @@ class MandayeApp(object):
try:
opener = urllib2.build_opener(MandayeErrorHandler, MandayeRedirectHandler)
response = opener.open(req)
except Exception:
# TODO: enable this only if we are in debug and cleaning the code
ex = sys.exc_info()[1]
path = self.env['PATH_INFO']
sys.stderr.write('error while reading %s:\n' % path)
traceback.print_exc()
tb = traceback.format_exc()
error_str = escape(str(ex) or ex.__class__.__name__ or 'Error')
response = _502("<h2>%s %s</h2><pre>%s</pre>" % \
(escape(path), error_str, escape(tb)))
except Exception, e:
response = _502(self.env['PATH_INFO'], req.get_host(), e)
return self.on_response(start_response, response)
else:
mresponse = HTTPResponse()
@ -113,8 +108,8 @@ class MandayeApp(object):
return self.on_response(start_response, response)
def on_response(self, start_response, response):
'''response: an instance of HTTPResponse
'''
""" start_response: wsgi start_response
response: an instance of HTTPResponse """
response.headers.addsetcookies(response.cookies)
start_response('%d %s' % (response.code, response.status),
response.headers.items())