Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add base URL canonicalization to algorithm #457

Merged
merged 21 commits into from
Jun 29, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions aspen/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@
from __future__ import print_function
from __future__ import unicode_literals

from aspen import serve, Website
from aspen import serve, website

if __name__ == '__main__':
website = Website()
serve(website)
serve(website.Website())
4 changes: 4 additions & 0 deletions aspen/algorithms/website.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ def raise_200_for_OPTIONS(request):
raise Response(200)


def redirect_to_base_url(website, request):
website.canonicalize_base_url(request)


def dispatch_request_to_filesystem(website, request):

if website.list_directories:
Expand Down
2 changes: 1 addition & 1 deletion aspen/auth/cookie.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@

# Public config knobs
# ===================
# Feel free to set these in, e.g., configure-aspen.py
# Feel free to modify for your application.

NAME = "auth"
DOMAIN = None
Expand Down
2 changes: 1 addition & 1 deletion aspen/auth/httpdigest.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def send_403(self, html, extraheaders):
## make a generator of containers that aspen will like

def inbound_responder(*args, **kw):
""" This should be used in your configure-aspen.py like so:
""" This should be used in your startup script like so:

import aspen.auth.httpdigest as digestauth

Expand Down
168 changes: 38 additions & 130 deletions aspen/configuration/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,73 +13,50 @@
import mimetypes
import os
import sys
import traceback
import pkg_resources
from collections import defaultdict

import aspen
import aspen.logging
from aspen.configuration import parse
from aspen.configuration.exceptions import ConfigurationError
from aspen.configuration.options import OptionParser, DEFAULT
from aspen.utils import ascii_dammit
from aspen.exceptions import ConfigurationError
from aspen.utils import ascii_dammit, BaseURLCanonicalizer
from aspen.typecasting import defaults as default_typecasters
import aspen.body_parsers


# Defaults
# ========
# The from_unicode callable converts from unicode to whatever format is
# required by the variable, raising ValueError appropriately. Note that
# name is supposed to match the options in our optparser. I like it wet.
default_indices = lambda: ['index.html', 'index.json', 'index',
'index.html.spt', 'index.json.spt', 'index.spt']

# 'name': (default, from_unicode)
KNOBS = \
{ 'configuration_scripts': (lambda: [], parse.list_)
, 'project_root': (None, parse.identity)
, 'logging_threshold': (0, int)
, 'www_root': (None, parse.identity)


# Extended Options
# 'name': (default, from_unicode)
, 'changes_reload': (False, parse.yes_no)
, 'charset_dynamic': ('UTF-8', parse.charset)
, 'charset_static': (None, parse.charset)
, 'indices': ( lambda: ['index.html', 'index.json', 'index'] +
['index.html.spt', 'index.json.spt', 'index.spt']
, parse.list_
)
, 'list_directories': (False, parse.yes_no)
, 'media_type_default': ('text/plain', parse.media_type)
, 'media_type_json': ('application/json', parse.media_type)
, 'renderer_default': ('stdlib_percent', parse.renderer)
, 'show_tracebacks': (False, parse.yes_no)
{ 'base_url': (None, parse.identity)
, 'changes_reload': (False, parse.yes_no)
, 'charset_dynamic': ('UTF-8', parse.charset)
, 'charset_static': (None, parse.charset)
, 'indices': (default_indices, parse.list_)
, 'list_directories': (False, parse.yes_no)
, 'logging_threshold': (0, int)
, 'media_type_default': ('text/plain', parse.media_type)
, 'media_type_json': ('application/json', parse.media_type)
, 'project_root': (None, parse.identity)
, 'renderer_default': ('stdlib_percent', parse.renderer)
, 'show_tracebacks': (False, parse.yes_no)
, 'www_root': (None, parse.identity)
}

DEFAULT_CONFIG_FILE = 'configure-aspen.py'

# Configurable
# ============
# Designed as a singleton.

class Configurable(object):
"""Mixin object for aggregating configuration from several sources.
"""

protected = False # Set to True to require authentication for all
# requests.

@classmethod
def from_argv(cls, argv):
"""return a Configurable based on the passed-in arguments list
"""
configurable = cls()
configurable.configure(argv)
return configurable
This is implemented in such a way that we get helpful log output: we
iterate over settings first, not over contexts first (defaults,
environment, kwargs).

"""

def _set(self, name, hydrated, flat, context, name_in_context):
"""Set value at self.name, calling value if it's callable.
"""Set value at self.name, calling hydrated if it's callable.
"""
if aspen.is_callable(hydrated):
hydrated = hydrated() # Call it if we can.
Expand Down Expand Up @@ -122,11 +99,8 @@ def set(self, name, raw, from_unicode, context, name_in_context):
args = (name, hydrated, value, context, name_in_context)
return self._set(*args)

def configure(self, argv):
"""Takes an argv list, and gives it straight to optparser.parse_args.

The argv list should not include the executable name.

def configure(self, **kwargs):
"""Takes a dictionary of strings/unicodes to strings/unicodes.
"""

# Do some base-line configuration.
Expand All @@ -143,17 +117,12 @@ def configure(self, argv):

self.typecasters = default_typecasters

# Parse argv.
# ===========

opts, args = OptionParser().parse_args(argv)


# Configure from defaults, environment, and command line.
# =======================================================
# Configure from defaults, environment, and kwargs.
# =================================================

msgs = ["Reading configuration from defaults, environment, and "
"command line."] # can't actually log until configured
"kwargs."] # can't actually log until configured

for name, (default, func) in sorted(KNOBS.items()):

Expand All @@ -171,21 +140,20 @@ def configure(self, argv):
, envvar
))

# set from the command line
value = getattr(opts, name)
if value is not DEFAULT:
# set from kwargs
value = kwargs.get(name)
if value is not None:
msgs.append(self.set( name
, value
, func
, "command line option"
, "--"+name
, "kwargs"
, name
))


# Set some attributes.
# ====================


def safe_getcwd(errorstr):
try:
# If the working directory no longer exists, then the following
Expand All @@ -202,7 +170,6 @@ def safe_getcwd(errorstr):
raise ConfigurationError(errorstr)



# LOGGING_THRESHOLD
# -----------------
# This is initially set to -1 and not 0 so that we can tell if the user
Expand All @@ -225,7 +192,7 @@ def safe_getcwd(errorstr):
cwd = safe_getcwd("Could not get a current working "
"directory. You can specify "
"ASPEN_PROJECT_ROOT in the environment, "
"or --project_root on the command line.")
"or project_root in kwargs.")
self.project_root = os.path.join(cwd, self.project_root)

self.project_root = os.path.realpath(self.project_root)
Expand All @@ -235,12 +202,6 @@ def safe_getcwd(errorstr):
users_mimetypes = os.path.join(self.project_root, 'mime.types')
mimetypes.knownfiles += [users_mimetypes]

# configure-aspen.py
configure_aspen_py = os.path.join( self.project_root
, DEFAULT_CONFIG_FILE
)
self.configuration_scripts.append(configure_aspen_py) # last word

# PYTHONPATH
sys.path.insert(0, self.project_root)

Expand All @@ -249,7 +210,7 @@ def safe_getcwd(errorstr):
self.www_root = safe_getcwd("Could not get a current working "
"directory. You can specify "
"ASPEN_WWW_ROOT in the environment, "
"or --www_root on the command line.")
"or www_root in kwargs.")

self.www_root = os.path.realpath(self.www_root)

Expand All @@ -260,6 +221,9 @@ def safe_getcwd(errorstr):
self.media_type_json: aspen.body_parsers.jsondata
}

# install a base URL canonicalizer
self.canonicalize_base_url = BaseURLCanonicalizer(self.base_url)

# load renderers
self.renderer_factories = {}
for name in aspen.BUILTIN_RENDERERS:
Expand Down Expand Up @@ -292,7 +256,6 @@ def safe_getcwd(errorstr):
if not mimetypes.inited:
mimetypes.init()

self.run_config_scripts()
self.show_renderers()

def show_renderers(self):
Expand All @@ -316,58 +279,3 @@ def show_renderers(self):
aspen.log_dammit(msg % self.renderer_default)
sys.excepthook(*default_renderer.info)
raise default_renderer


def run_config_scripts(self):
# Finally, exec any configuration scripts.
# ========================================
# The user gets self as 'website' inside their configuration scripts.
default_cfg_filename = None
if self.project_root is not None:
default_cfg_filename = os.path.join(self.project_root, DEFAULT_CONFIG_FILE)

for filepath in self.configuration_scripts:
if not filepath.startswith(os.sep):
if self.project_root is None:
raise ConfigurationError("You must set project_root in "
"order to specify a configuratio"
"n_script relatively.")
filepath = os.path.join(self.project_root, filepath)
filepath = os.path.realpath(filepath)
try:
execfile(filepath, {'website': self})
except IOError, err:
# Re-raise the error if it happened inside the script.
if err.filename != filepath:
raise

# I was checking os.path.isfile for these, but then we have a
# race condition that smells to me like a potential security
# vulnerability.

## determine if it's a default configscript or a specified one
cfgtype = "configuration"
if filepath == default_cfg_filename:
cfgtype = "default " + cfgtype
## pick the right error mesage
if err.errno == errno.ENOENT:
msg = ("The %s script %s doesn't seem to exist.")
elif err.errno == errno.EACCES:
msg = ("It appears that you don't have permission to read "
"the %s script %s.")
else:
msg = ("There was a problem reading the %s script %s:")
msg += os.sep + traceback.format_exc()
## do message-string substitutions
msg = msg % (cfgtype, filepath)
## output the message
if not "default" in cfgtype:
# if you specify a config file, it's an error if there's a problem
raise ConfigurationError(msg)
else:
# problems with default config files are okay, but get logged
aspen.log(msg)




22 changes: 0 additions & 22 deletions aspen/configuration/exceptions.py

This file was deleted.

Loading