summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBarry Warsaw2009-05-02 18:07:38 -0400
committerBarry Warsaw2009-05-02 18:07:38 -0400
commit443aba66658347d0b747ff84ec1750cfb8c71924 (patch)
treeb60a131313455dfb8858cf01da599cb50ad48343
parentea96bbebd8594a70f534aacdab949a63bf800b22 (diff)
downloadmailman-443aba66658347d0b747ff84ec1750cfb8c71924.tar.gz
mailman-443aba66658347d0b747ff84ec1750cfb8c71924.tar.zst
mailman-443aba66658347d0b747ff84ec1750cfb8c71924.zip
-rw-r--r--buildout.cfg2
-rw-r--r--src/mailman/interfaces/rest.py6
-rw-r--r--src/mailman/rest/configuration.py9
-rw-r--r--src/mailman/rest/configure.zcml12
-rw-r--r--src/mailman/rest/publication.py67
-rw-r--r--src/mailman/rest/root.py4
-rw-r--r--src/mailman/rest/traverse.py1
-rw-r--r--src/mailman/rest/urls.py53
-rw-r--r--src/mailman/rest/webservice.py88
9 files changed, 180 insertions, 62 deletions
diff --git a/buildout.cfg b/buildout.cfg
index f2ff616a9..e13ab26ff 100644
--- a/buildout.cfg
+++ b/buildout.cfg
@@ -4,7 +4,7 @@ parts =
tags
test
unzip = true
-develop = .
+develop = . /Users/barry/projects/lazr/lazr.restful
[interpreter]
recipe = zc.recipe.egg
diff --git a/src/mailman/interfaces/rest.py b/src/mailman/interfaces/rest.py
index 697c81da4..83a458c1d 100644
--- a/src/mailman/interfaces/rest.py
+++ b/src/mailman/interfaces/rest.py
@@ -21,7 +21,7 @@ from __future__ import absolute_import, unicode_literals
__metaclass__ = type
__all__ = [
- 'IHasGet',
+ 'IResolvePathNames',
]
@@ -29,8 +29,8 @@ from zope.interface import Interface
-class IHasGet(Interface):
- """A marker interface objects that implement traversal with get()."""
+class IResolvePathNames(Interface):
+ """A marker interface objects that implement simple traversal."""
def get(name):
"""Traverse to a contained object."""
diff --git a/src/mailman/rest/configuration.py b/src/mailman/rest/configuration.py
index 9716f8503..0b142ca55 100644
--- a/src/mailman/rest/configuration.py
+++ b/src/mailman/rest/configuration.py
@@ -26,11 +26,12 @@ __all__ = [
from lazr.restful.interfaces import IWebServiceConfiguration
+from zope.interface import implements
+from mailman import version
from mailman.config import config
from mailman.rest.publication import AdminWebServicePublication
from mailman.rest.root import AdminWebServiceRootResource
-from mailman.version import VERSION
@@ -43,7 +44,7 @@ class AdminWebServiceConfiguration:
def view_permission(self):
return config.webservice.view_permission
- path_override = 'admin'
+ path_override = None
@property
def use_https(self):
@@ -51,8 +52,8 @@ class AdminWebServiceConfiguration:
return config.webservice.use_https
# This should match the major.minor Mailman version.
- service_version_uri_prefix = '3.0'
- code_revision = VERSION
+ service_version_uri_prefix = '{0.MAJOR_REV}.{0.MINOR_REV}'.format(version)
+ code_revision = version.VERSION
@property
def show_tracebacks(self):
diff --git a/src/mailman/rest/configure.zcml b/src/mailman/rest/configure.zcml
index 24712b340..edd444d0d 100644
--- a/src/mailman/rest/configure.zcml
+++ b/src/mailman/rest/configure.zcml
@@ -6,16 +6,20 @@
<include package="zope.component" file="meta.zcml"/>
<include package="zope.security" file="meta.zcml"/>
<include package="lazr.restful" file="meta.zcml"/>
+ <include package="lazr.restful" file="configure.zcml"/>
- <webservice:register module="mailman.interfaces.rest" />
+ <webservice:register module="mailman.interfaces.system" />
- <adapter factory="mailman.rest.traverse.Traverse" />
-
- <adapter factory="mailman.rest.root.AdminWebServiceRootAbsoluteURL" />
+ <adapter factory="mailman.rest.urls.AbsoluteURLMapper" />
<adapter
factory="mailman.rest.root.AdminWebServiceRootAbsoluteURL"
name="absolute_url"
/>
+ <utility
+ factory="mailman.rest.configuration.AdminWebServiceConfiguration"
+ provides="lazr.restful.interfaces.IWebServiceConfiguration">
+ </utility>
+
</configure>
diff --git a/src/mailman/rest/publication.py b/src/mailman/rest/publication.py
index b5fac2318..bc57bdd58 100644
--- a/src/mailman/rest/publication.py
+++ b/src/mailman/rest/publication.py
@@ -28,80 +28,55 @@ __all__ = [
import traceback
from lazr.restful.publisher import WebServicePublicationMixin
-from zope.component import getUtility, queryMultiAdapter
+from zope.component import queryMultiAdapter
from zope.interface import implements
-from zope.publisher.interfaces import IPublication, IPublishTraverse, NotFound
+from zope.publisher.interfaces import IPublication, NotFound
from zope.publisher.publish import mapply
-from zope.security.checker import ProxyFactory
from zope.security.management import endInteraction, newInteraction
+from mailman.interfaces.rest import IResolvePathNames
+
class Publication:
- """Very simple implementation of `IPublication`.
-
- The object pass to the constructor is returned by getApplication().
- """
+ """Very simple implementation of `IPublication`."""
implements(IPublication)
def __init__(self, application):
- """Create the test publication.
-
- The object at which traversal should start is passed as parameter.
- """
self.application = application
def beforeTraversal(self, request):
- """Sets the request as the current interaction.
-
- (It also ends any previous interaction, that's convenient when
- tests don't go through the whole request.)
- """
- endInteraction()
- newInteraction(request)
+ """See `IPublication`."""
+ pass
def getApplication(self, request):
- """Returns the application passed to the constructor."""
+ """See `IPublication`."""
return self.application
def callTraversalHooks(self, request, ob):
- """Does nothing."""
+ """See `IPublication`."""
+ pass
def traverseName(self, request, ob, name):
- """Traverse by looking at an `IPublishTraverse` adapter.
-
- The object is security wrapped.
- """
- # XXX flacoste 2009/03/06 bug=338831. This is copied from
- # zope.app.publication.publicationtraverse.PublicationTraverse.
- # This should really live in zope.publisher, we are copying because
- # we don't want to depend on zope.app stuff.
- # Namespace support was dropped.
- if name == '.':
- return ob
-
- if IPublishTraverse.providedBy(ob):
- ob2 = ob.publishTraverse(request, name)
- else:
- # self is marker.
- adapter = queryMultiAdapter(
- (ob, request), IPublishTraverse, default=self)
- if adapter is not self:
- ob2 = adapter.publishTraverse(request, name)
- else:
- raise NotFound(ob, name, request)
-
- return ProxyFactory(ob2)
+ """See `IPublication`."""
+ missing = object()
+ resolver = IResolvePathNames(ob, missing)
+ if resolver is missing:
+ raise NotFound(ob, name, request)
+ return ob.get(name)
def afterTraversal(self, request, ob):
- """Does nothing."""
+ pass
def callObject(self, request, ob):
"""Call the object, returning the result."""
+ # XXX Bad hack.
+ from zope.security.proxy import removeSecurityProxy
+ ob = removeSecurityProxy(ob)
return mapply(ob, request.getPositionalArguments(), request)
def afterCall(self, request, ob):
- """Does nothing."""
+ pass
def handleException(self, object, request, exc_info, retry_allowed=1):
"""Prints the exception."""
diff --git a/src/mailman/rest/root.py b/src/mailman/rest/root.py
index a38371770..620739287 100644
--- a/src/mailman/rest/root.py
+++ b/src/mailman/rest/root.py
@@ -27,6 +27,7 @@ __all__ = [
from lazr.restful import ServiceRootResource
+from lazr.restful.interfaces import ICollection
from zope.component import adapts
from zope.interface import implements
from zope.publisher.interfaces.browser import IDefaultBrowserLayer
@@ -34,15 +35,12 @@ from zope.traversing.browser.interfaces import IAbsoluteURL
from mailman.config import config
from mailman.core.system import system
-from mailman.interfaces.rest import IHasGet
class AdminWebServiceRootResource(ServiceRootResource):
"""The root of the Mailman RESTful admin web service."""
- implements(IHasGet)
-
def get(self, name):
"""See `IHasGet`."""
top_level = {
diff --git a/src/mailman/rest/traverse.py b/src/mailman/rest/traverse.py
index 34d747c32..2aef47709 100644
--- a/src/mailman/rest/traverse.py
+++ b/src/mailman/rest/traverse.py
@@ -40,7 +40,6 @@ class Traverse:
"""An implementation of `IPublishTraverse` that uses the get() method."""
implements(IPublishTraverse)
- adapts(IHasGet, IDefaultBrowserLayer)
def __init__(self, context, request):
self.context = context
diff --git a/src/mailman/rest/urls.py b/src/mailman/rest/urls.py
new file mode 100644
index 000000000..0cd51e5f6
--- /dev/null
+++ b/src/mailman/rest/urls.py
@@ -0,0 +1,53 @@
+# Copyright (C) 2009 by the Free Software Foundation, Inc.
+#
+# This file is part of GNU Mailman.
+#
+# GNU Mailman is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
+
+"""Module stuff."""
+
+from __future__ import absolute_import, unicode_literals
+
+__metaclass__ = type
+__all__ = [
+ 'AbsoluteURLMapper',
+ ]
+
+
+from zope.component import adapts
+from zope.interface import implements, Interface
+from zope.traversing.browser.interfaces import IAbsoluteURL
+
+
+
+class AbsoluteURLMapper:
+ """Generic absolute url mapper."""
+
+ implements(IAbsoluteURL)
+ adapts(Interface, IAbsoluteURL)
+
+ def __init__(self, context, request):
+ """Initialize with respect to a context and request."""
+ # Avoid circular imports.
+ from mailman.rest.configuration import AdminWebServiceConfiguration
+ self.webservice_config = AdminWebServiceConfiguration()
+ self.version = webservice_config.service_version_uri_prefix
+ self.schema = ('https' if self.webservice_config.use_https else 'http')
+ self.hostname = config.webservice.hostname
+
+ def __str__(self):
+ """Return the semi-hard-coded URL to the service root."""
+ return '{0.schema}://{0.hostname}/{0.version}'.format(self)
+
+ __call__ = __str__
diff --git a/src/mailman/rest/webservice.py b/src/mailman/rest/webservice.py
new file mode 100644
index 000000000..8203b0439
--- /dev/null
+++ b/src/mailman/rest/webservice.py
@@ -0,0 +1,88 @@
+# Copyright (C) 2009 by the Free Software Foundation, Inc.
+#
+# This file is part of GNU Mailman.
+#
+# GNU Mailman is free software: you can redistribute it and/or modify it under
+# the terms of the GNU General Public License as published by the Free
+# Software Foundation, either version 3 of the License, or (at your option)
+# any later version.
+#
+# GNU Mailman is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+# more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# GNU Mailman. If not, see <http://www.gnu.org/licenses/>.
+
+"""Module stuff."""
+
+from __future__ import absolute_import, unicode_literals
+
+__metaclass__ = type
+__all__ = [
+ 'AdminWebServiceApplication',
+ 'AdminWebServiceRequest',
+ 'start',
+ ]
+
+
+from wsgiref.simple_server import make_server
+
+from lazr.restful.publisher import WebServiceRequestTraversal
+from pkg_resources import resource_string
+from zope.configuration import xmlconfig
+from zope.interface import implements
+from zope.publisher.browser import BrowserRequest
+from zope.publisher.publish import publish
+
+from mailman.core.system import system
+from mailman.interfaces.rest import IResolvePathNames
+from mailman.rest.publication import AdminWebServicePublication
+
+
+
+class AdminWebServiceRequest(WebServiceRequestTraversal, BrowserRequest):
+ """A request for the admin REST interface."""
+
+
+class AdminWebServiceApplication:
+ """A WSGI application for the admin REST interface."""
+
+ implements(IResolvePathNames)
+
+ def __init__(self, environ, start_response):
+ # Create the request based on the HTTP method used.
+ method = environ.get('REQUEST_METHOD', 'GET').upper()
+ request = AdminWebServiceRequest(environ['wsgi.input'], environ)
+ request.setPublication(AdminWebServicePublication(self))
+ # Support post-mortem debugging.
+ handle_errors = environ.get('wsgi.handleErrors', True)
+ # The request returned by the publisher may in fact be different than
+ # the one passed in.
+ request = publish(request, handle_errors=handle_errors)
+ # Start the WSGI server response.
+ response = request.response
+ start_response(response.getStatusString(), response.getHeaders())
+ # Return the result body iterable.
+ return response.consumeBodyIter()
+
+ def get(self, name):
+ """Maps root names to resources."""
+ top_level = dict(
+ sys=system,
+ )
+ return top_level.get(name)
+
+
+
+def start():
+ """Start the WSGI admin REST service."""
+ zcml = resource_string('mailman.rest', 'configure.zcml')
+ xmlconfig.string(zcml)
+ server = make_server('', 8001, AdminWebServiceApplication)
+ return server
+
+
+if __name__ == '__main__':
+ start().serve_forever()