Skip to content

Commit

Permalink
redirect/handle old-style index and SmartHTTP requests
Browse files Browse the repository at this point in the history
  • Loading branch information
s-ol committed Aug 11, 2022
1 parent 1befb64 commit 9470d0b
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 9 deletions.
60 changes: 51 additions & 9 deletions klaus/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
jinja2_autoescape_builtin = False
except ImportError:
jinja2_autoescape_builtin = True
import flask
from flask import Flask, request, redirect
import httpauth
import dulwich.web
from dulwich.errors import NotGitRepository
Expand All @@ -15,7 +15,44 @@
KLAUS_VERSION = utils.guess_git_revision() or "1.5.2"


class Klaus(flask.Flask):
class KlausRedirects(Flask):
def __init__(self, repos):
Flask.__init__(self, __name__)

for namespaced_name in repos:
self.setup_redirects('/' + namespaced_name)
if namespaced_name.count('/') == 1:
self.setup_redirects('/' + namespaced_name, '/~' + namespaced_name)

def setup_redirects(self, route, pattern=None):
if not pattern:
pattern = route

def redirect_root():
return redirect(route + '/-/' + request.query_string.decode(), 301)
def redirect_rest(path):
return redirect(route + '/-/' + path + request.query_string.decode(), 301)
def redirect_git():
return redirect(route + '.git' + request.query_string.decode(), 301)

self.add_url_rule(
pattern + '/',
endpoint=pattern + '_root',
view_func=redirect_root,
)
self.add_url_rule(
pattern + '/<path:path>',
endpoint=pattern + '_rest',
view_func=redirect_rest,
)
self.add_url_rule(
pattern + '/info/refs',
endpoint=pattern + '_git',
view_func=redirect_git,
)


class Klaus(Flask):
jinja_options = {
"extensions": [] if jinja2_autoescape_builtin else ["jinja2.ext.autoescape"],
"undefined": jinja2.StrictUndefined,
Expand All @@ -31,7 +68,7 @@ def __init__(self, repo_paths, site_name, use_smarthttp, ctags_policy="none"):
self.valid_repos = {repo.namespaced_name: repo for repo in valid_repos}
self.invalid_repos = {repo.namespaced_name: repo for repo in invalid_repos}

flask.Flask.__init__(self, __name__)
Flask.__init__(self, __name__)

self.setup_routes()

Expand All @@ -55,6 +92,8 @@ def create_jinja_environment(self):
return env

def setup_routes(self):
redirects = {}

# fmt: off
for endpoint, rule in [
('repo_list', '/'),
Expand Down Expand Up @@ -169,18 +208,21 @@ def make_app(
use_smarthttp,
ctags_policy,
)
app.wsgi_app = utils.ChainedApps(
app,
KlausRedirects(app.valid_repos)
)
app.wsgi_app = utils.ProxyFix(app.wsgi_app)

if use_smarthttp:
# `path -> Repo` mapping for Dulwich's web support
dulwich_backend = dulwich.server.DictBackend(
{
"/" + namespaced_name + '.git': repo
for namespaced_name, repo in app.valid_repos.items()
}
)
dulwich_repos = {}
for namespaced_name, repo in app.valid_repos.items():
dulwich_repos["/" + namespaced_name + '.git'] = repo

# Dulwich takes care of all Git related requests/URLs
# and passes through everything else to klaus
dulwich_backend = dulwich.server.DictBackend(dulwich_repos)
dulwich_wrapped_app = dulwich.web.make_wsgi_chain(
backend=dulwich_backend,
fallback_app=app.wsgi_app,
Expand Down
42 changes: 42 additions & 0 deletions klaus/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import binascii
import os
import re
import sys
import time
import datetime
import mimetypes
Expand Down Expand Up @@ -103,6 +104,47 @@ def __call__(self, environ, start_response):
return self.app(environ, start_response)


class ChainedApps(object):
"""WSGI middleware to chain two or more Flask apps.
The request is passed to the next app if a response has a 404 status."""

def __init__(self, *apps):
self.apps = apps

def __call__(self, environ, start_response):
# this method is almost verbatim flask.Flask.wsgi_app(),
# except for the for/continue statements.
for app in self.apps:
ctx = app.request_context(environ)
error = None
try:
try:
ctx.push()
response = app.full_dispatch_request()
except Exception as e:
error = e
response = app.handle_exception(e)
except: # noqa: B001
error = sys.exc_info()[1]
raise

if response.status_code == 404:
# pass through 404 codes
continue

return response(environ, start_response)
finally:
if "werkzeug.debug.preserve_context" in environ:
environ["werkzeug.debug.preserve_context"](_cv_app.get())
environ["werkzeug.debug.preserve_context"](_cv_request.get())

if error is not None and app.should_ignore_error(error):
error = None

ctx.pop(error)


def timesince(when, now=time.time):
"""Return the difference between `when` and `now` in human readable form."""
return naturaltime(now() - when)
Expand Down

0 comments on commit 9470d0b

Please sign in to comment.