Mercurial > hg > redirector
changeset 0:af82aaec0377
initial import of redirector
author | k0s <k0scist@gmail.com> |
---|---|
date | Mon, 07 Sep 2009 15:15:48 -0400 |
parents | |
children | 55578cf505dd |
files | ini-redirector.ini redirector.ini redirector/__init__.py redirector/factory.py redirector/redirector.py redirector/redirectors.py redirector/templates/metarefresh.html redirector/templates/redirects.html sample/bar.txt sample/foo.txt sample/index.html setup.py |
diffstat | 12 files changed, 412 insertions(+), 0 deletions(-) [+] |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/ini-redirector.ini Mon Sep 07 15:15:48 2009 -0400 @@ -0,0 +1,10 @@ +[/foo.txt] +to = /bar.txt +type = 301 +created = August 1, 2009 + +[http://127.0.0.1:5521/(.*)] +to = http://localhost:5521/\1 +type = metarefresh +seconds = 10 +created = August 1, 2009
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/redirector.ini Mon Sep 07 15:15:48 2009 -0400 @@ -0,0 +1,24 @@ +#!/usr/bin/env paster + +[DEFAULT] +debug = true +email_to = jhammel@openplans.org +smtp_server = localhost +error_email_from = paste@localhost + +[server:main] +use = egg:Paste#http +host = 0.0.0.0 +port = 5521 + +[composite:main] +use = egg:Paste#urlmap +/ = redirector + +set debug = false + +[app:redirector] +paste.app_factory = redirector.factory:factory +redirector.redirector = ini-redirector +redirector.ini-redirector.ini = %(here)s/ini-redirector.ini +app.directory = %(here)s/sample \ No newline at end of file
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/redirector/__init__.py Mon Sep 07 15:15:48 2009 -0400 @@ -0,0 +1,1 @@ +#
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/redirector/factory.py Mon Sep 07 15:15:48 2009 -0400 @@ -0,0 +1,18 @@ +import os +from redirector import Redirector +from paste.httpexceptions import HTTPExceptionHandler +from paste.urlparser import StaticURLParser + +def factory(global_conf, **app_conf): + """create a sample redirector""" + assert 'app.directory' in app_conf + directory = app_conf['app.directory'] + assert os.path.isdir(directory) + keystr = 'redirector.' + args = dict([(key.split(keystr, 1)[-1], value) + for key, value in app_conf.items() + if key.startswith(keystr) ]) + app = StaticURLParser(directory) + redirector = Redirector(app, **args) + return HTTPExceptionHandler(redirector) +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/redirector/redirector.py Mon Sep 07 15:15:48 2009 -0400 @@ -0,0 +1,137 @@ +""" +redirector: a view with webob +""" + +import re + +from genshi.template import TemplateLoader +from pkg_resources import iter_entry_points +from pkg_resources import resource_filename +from urlparse import urlparse +from webob import Request, Response, exc + +class Redirector(object): + + ### class level variables + defaults = { 'auto_reload': 'False', + 'auth': 'False', + 'date_format': '%c', + 'path': '.redirect', + 'redirector': None, + 'seconds': 5, # seconds for a meta-refresh tag to redirect + } + + def __init__(self, app, **kw): + self.app = app # WSGI app + + # set instance attributes + for key in self.defaults: + setattr(self, key, kw.get(key, self.defaults[key])) + self.response_functions = { 'GET': self.get, + 'POST': self.post, + } + # bool options + for var in 'auto_reload', 'auth': + setattr(self, var, getattr(self, var).lower() == 'true') + + # pick a redirector back end + assert self.redirector + if isinstance(self.redirector, basestring): + name = self.redirector + self.redirector = iter_entry_points('redirector.redirectors', name=name).next() + # take first entry point; + # will raise StopIteration if there are none + + self.redirector = self.redirector.load() + keystr = name + '.' + kwargs = dict([(key.split(keystr, 1)[1], value) + for key, value in kw.items() + if key.startswith(keystr)]) + self.redirector = self.redirector(**kwargs) + + # genshi template loader + templates_dir = resource_filename(__name__, 'templates') + self.loader = TemplateLoader(templates_dir, + auto_reload=self.auto_reload) + + # redirect exceptions + self.status_map = dict([(key, value) + for key, value in exc.status_map.items() + if key in set([301, 302, 307])]) + + + ### methods dealing with HTTP + def __call__(self, environ, start_response): + request = Request(environ) + + path = request.path_info.strip('/').split('/') + if path and path[0] == self.path: + ### TODO: check if authorized + res = self.make_response(request) + return res(environ, start_response) + else: + + ### query redirection tables + for redirect in self.redirector.redirects(): + + _from = redirect['from'] + parsed_url = urlparse(_from) + if parsed_url[0]: + url = request.url + else: + url = request.path_info + from_re = re.compile(_from) + + # redirect on match + match = from_re.match(url) + if match: + location = from_re.sub(redirect['to'], url) + _type = redirect['type'] + if isinstance(_type, int): + raise self.status_map[_type](location=location) + else: + res = self.meta_refresh(request, redirect, location) + return res(environ, start_response) + + return self.app(environ, start_response) + + def make_response(self, request): + return self.response_functions.get(request.method, self.error)(request) + + def get_response(self, text, content_type='text/html'): + res = Response(content_type=content_type, body=text) + return res + + def get(self, request): + """ + return response to a GET requst + """ + + data = { 'redirects': self.redirector.redirects(), + 'status_map': self.status_map, + 'date_format': self.date_format } + + template = self.loader.load('redirects.html') + res = template.generate(**data).render('html', doctype='html') + return self.get_response(res) + + def post(self, request): + """ + return response to a POST request + """ + return self.get(request) + + def error(self, request): + """deal with non-supported methods""" + return exc.HTTPMethodNotAllowed("Only %r operations are allowed" % self.response_functions.keys()) + + + def meta_refresh(self, request, redirect, location): + data = { 'request': request, + 'redirect': redirect, + 'location': location, + 'seconds': redirect.get('seconds', self.seconds), + 'date_format': self.date_format } + template = self.loader.load('metarefresh.html') + res = template.generate(**data).render('html', doctype='html') + return self.get_response(res)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/redirector/redirectors.py Mon Sep 07 15:15:48 2009 -0400 @@ -0,0 +1,71 @@ +from datetime import datetime +from dateutil.parser import parse +from ConfigParser import ConfigParser + + +class TestRedirector(object): + def redirects(self): + return [ { 'from': '/foo.txt', 'to': '/bar.txt', + 'type': 301, + 'created': datetime.now(), + 'expires': None }, + { 'from': 'http://127.0.0.1:5521/(.*)', + 'to': r'http://localhost:5521/\1', + 'type': 'metarefresh', + 'seconds': 10, + 'created': datetime.now(), + 'expires': None }, + ] + def set(self, _from, to, type=301, expires=None, seconds=None, reason=None): + # test only....does not set anything + return + + def add(self, _from, to, type=301, expires=None, seconds=None): + self.set(_from, to, type, expires, seconds) + +class IniRedirector(object): + def __init__(self, ini): + self.ini = ini + + def redirects(self): + parser = ConfigParser() + parser.read(self.ini) + + redirects = [] + + for section in parser.sections(): + redirect = { 'from': section } + assert parser.has_option(section, 'to') + assert parser.has_option(section, 'type') + redirect['to'] = parser.get(section, 'to') + if parser.has_option(section, 'created'): + redirect['created'] = parse(parser.get(section, 'created')) + else: + redirect['created'] = None + _type = parser.get(section, 'type') + try: + _type = int(_type) + except ValueError: + assert _type == 'metarefresh' + redirect['type'] = _type + if parser.has_option(section, 'expires'): + redirect['expires'] = parse(parser.get(section, 'expires')) + else: + redirect['expires'] = None + if parser.has_option(section, 'reason'): + redirect['reason'] = parser.get(section, 'reason') + if parser.has_option(section, 'seconds'): + redirect['seconds'] = parser.getint(section, 'seconds') + redirects.append(redirect) + return redirects + + def set(self, _from, to, type=301, expires=None, seconds=None, reason=None): + parser = ConfigParser() + parser.read(self.ini) + raise NotImplementedError # TODO + + def add(self, _from, to, type=301, expires=None, seconds=None): + raise NotImplementedError # TODO + parser = ConfigParser() + parser.read(self.ini) + self.set(_from, to, type, expires, seconds)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/redirector/templates/metarefresh.html Mon Sep 07 15:15:48 2009 -0400 @@ -0,0 +1,25 @@ +<!DOCTYPE html + PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:py="http://genshi.edgewall.org/"> + <head> + <meta http-equiv="refresh" content="${seconds};url=${location}" /> + </head> + <body> + + <div> + You are being from ${request.url} to ${location} in ${seconds} seconds. + Please update any bookmarks to this page. + </div> + + <div py:if="redirect['expires']"> + This redirect will expire ${redirect['expires'].strftime(date_format)} + </div> + + <div py:if="redirect.get('reason')"> + ${redirect['reason']} + </div> + + </body> +</html>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/redirector/templates/redirects.html Mon Sep 07 15:15:48 2009 -0400 @@ -0,0 +1,77 @@ +<!DOCTYPE html + PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:py="http://genshi.edgewall.org/"> +<head> + <title>redirects</title> +</head> +<body> + <h1>redirects</h1> + + <!-- TODO: separate these to its own template to xi:include --> + <table> + <tr> + <th>From</th> + <th></th> + <th>To</th> + <th>Type</th> + <th>Created</th> + <th>Expires</th> + <th></th> + </tr> + + <tr py:for="redirect in redirects"> + <td>${redirect['from']}</td> + <td>→</td> + <td>${redirect['to']}</td> + <td py:choose="isinstance(redirect['type'], int)"> + <span py:when="True"> + ${str(redirect['type'])} ${status_map[redirect['type']].title} + </span> + <span py:otherwise="">meta refresh tag</span> + </td> + <td>${redirect['created'].strftime(date_format)}</td> + <td> + <span py:replace="redirect['expires'] and redirect['expires'].strftime(date_format) or 'never'"/> + </td> + <td> + <input type="submit" name="delete:${redirect['from']}" value="Remove"/> + </td> + </tr> + </table> + + <h2>Add a redirect</h2> + + <form action="post"> + <dl> + <dt>From</dt> + <dd><input type="text" name="from"/></dd> + + <dt>To</dt> + <dd><input type="text" name="to"/></dd> + + <dt>Type</dt> + <dd> + <select name="type"> + <option py:for="key in sorted(status_map.keys())" value="${key}">${key} ${status_map[key].title}</option> + <option value="metarefresh">meta-refresh page</option> + </select> + </dd> + + <dt>Expires</dt> + <dd> + <input type="text" name="expires"/> + </dd> + + <dt>Reason for this redirect</dt> + <dd> + <textarea name="reason" cols="80" rows="25"></textarea> + </dd> + </dl> + + <input type="submit" name="add" value="Add"/> + </form> + +</body> +</html>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sample/bar.txt Mon Sep 07 15:15:48 2009 -0400 @@ -0,0 +1,1 @@ +bar
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sample/foo.txt Mon Sep 07 15:15:48 2009 -0400 @@ -0,0 +1,1 @@ +foo
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/sample/index.html Mon Sep 07 15:15:48 2009 -0400 @@ -0,0 +1,9 @@ +<html> +<head> +<title>foo bar</title> +</head> +<body> +<p><a href="foo.txt">foo</a></p> +<p><a href="bar.txt">bar</a></p> +</body> +</html>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/setup.py Mon Sep 07 15:15:48 2009 -0400 @@ -0,0 +1,38 @@ +from setuptools import setup, find_packages +import sys, os + +version = "0.1" + +setup(name='redirector', + version=version, + description="WSGI middleware/app for managing redirects", + long_description=""" +""", + classifiers=[], # Get strings from http://www.python.org/pypi?%3Aaction=list_classifiers + author='Jeff Hammel', + author_email='jhammel@openplans.org', + url='http://k0s.org', + license="", + packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), + include_package_data=True, + zip_safe=False, + install_requires=[ + # -*- Extra requirements: -*- + 'WebOb', + 'Paste', + 'PasteScript', + 'genshi', + 'python-dateutil', + + ], + entry_points=""" + # -*- Entry points: -*- + [paste.app_factory] + main = redirector.factory:factory + + [redirector.redirectors] + test-redirector = redirector.redirectors:TestRedirector + ini-redirector = redirector.redirectors:IniRedirector + """, + ) +