import re
from onegov.server import errors
from typing import Any, TYPE_CHECKING
if TYPE_CHECKING:
from _typeshed.wsgi import WSGIEnvironment, StartResponse
from collections.abc import Iterable
from re import Pattern
[docs]
class Application:
""" WSGI applications inheriting from this class can be served by
onegov.server.
"""
#: If the host passed by the request is not localhost, then it is
#: checked against the allowed_hosts expression. If it doesn't match,
#: the request is denied.
[docs]
allowed_hosts_expression: 'Pattern[str] | None' = None
#: Additional allowed hosts may be added to this set. Those are not
#: expressions, but straight hostnames.
[docs]
allowed_hosts: set[str]
#: The namespace of the application, set before the application is
#: configured in :meth:`configure_application`.
#: Use :meth:`alias` instead of manipulating this dictionary.
[docs]
_aliases: dict[str, str]
[docs]
def __call__(
self,
environ: 'WSGIEnvironment',
start_respnose: 'StartResponse'
) -> 'Iterable[bytes]':
raise NotImplementedError
[docs]
def set_application_id(self, application_id: str) -> None:
""" Sets the application id before __call__ is called. That is, before
each request.
The application id consists of two values, delimitd by a '/'. The first
value is the namespace of the application, the second value is the
last fragment of the base_path.
That is `namespace/app` for all requests in the following config::
{
'applications': [
{
path: '/app',
namespace: 'namespace'
}
]
}
And `namespace/blog` for a `/sites/blog` request in the following
config::
{
'applications': [
{
path: '/sites/*',
namespace: 'namespace'
}
]
}
"""
assert application_id.startswith(self.namespace + '/')
self.application_id = application_id
[docs]
def set_application_base_path(self, base_path: str) -> None:
""" Sets the base path of the application before __call__ is called.
That is, before each request.
The base_path of static applications is equal to path the application
was configured to run under.
The base_path of wildcard applications includes the path matched
by the wildcard. For example, if the application is configured to run
under `/sites/*`, the base_path would be `/sites/blog` if
`/sites/blog/login` was requested.
"""
self.application_base_path = base_path
[docs]
def is_allowed_hostname(self, hostname: str) -> bool:
""" Called at least once per request with the given hostname.
If True is returned, the request with the given hostname is allowed.
If False is returned, the request is denied.
You usually won't need to override this method, as
:attr:`allowed_hosts_expression` and :attr:`allowed_hosts` already
gives you a way to influence its behavior.
If you do override, it's all on you though (except for localhost
requests).
"""
if self.allowed_hosts_expression:
if self.allowed_hosts_expression.match(hostname):
return True
return hostname in self.allowed_hosts
[docs]
def is_allowed_application_id(self, application_id: str) -> bool:
""" Called at least once per request with the given application id.
If True is returned, the request with the given application_id is
allowed. If False is returned, the request is denied.
By default, all application ids are allowed.
"""
return True
[docs]
def alias(self, application_id: str, alias: str) -> None:
""" Adds an alias under which this application is available on the
server.
The alias only works for wildcard applications - it has no effect
on static applications!
This is how it works:
An application running under `/sites/main` can be made available
under `/sites/blog` by running `self.alias('main', 'blog')`.
Aliases must be unique, so this method will fail with a
:class:`onegov.server.errors.ApplicationConflictError` if an alias
is already in use by another application running under the same path.
Note that an application opened through an alias will still have
the application_id set to the actual root, not the alias. So
`/sites/blog` in the example above would still lead to an
application_id of `main` instead of `blog`.
This is mainly meant to be used for dynamic DNS setups. Say you have
an application available through test.onegov.dev (pointing to
`/sites/test`). You now want to make this site available through
example.org. You can do this as follows::
self.allowed_hosts.add('example.org')
self.alias('example_org')
If your server has a catch-all rule that sends unknown domains to
`/sites/domain_without_dots`, then you can add domains dynamically
and the customer just points the DNS to your ip address.
"""
if alias in self._aliases:
raise errors.ApplicationConflictError(
"the alias '{}' is already in use".format(alias))
self._aliases[alias] = application_id
[docs]
def handle_exception(
self,
exception: BaseException,
environ: 'WSGIEnvironment',
start_response: 'StartResponse'
) -> 'Iterable[bytes]':
""" Default exception handling - this can be used to return a different
response when an unhandled exception occurs inside a request or before
a request is handled by the application (when any of the above methods
are called).
By default we just raise the exception.
Typically, returning an error is what you might want to do::
def handle_exception(exception, environ, start_response):
if isinstance(exception, UnderMaintenanceError):
error = webob.exc.HTTPInternalServerError()
return error(environ, start_response)
"""
raise exception