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:
parent
7afc5c247d
commit
516df263c4
2
TODO
2
TODO
|
@ -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
|
||||
|
||||
|
|
|
@ -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')),
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
||||
|
|
|
@ -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())
|
||||
|
|
Reference in New Issue