thomasvs - in flumotion/trunk: . flumotion/component/misc/httpfile
flumotion/test
flumotion-commit at lists.fluendo.com
flumotion-commit at lists.fluendo.com
Mon May 7 23:33:07 CEST 2007
Author: thomasvs
Date: Mon May 7 23:32:53 2007
New Revision: 4877
Added:
flumotion/trunk/flumotion/test/test_component_httpserver.py
Modified:
flumotion/trunk/ChangeLog
flumotion/trunk/flumotion/component/misc/httpfile/file.py
flumotion/trunk/flumotion/component/misc/httpfile/httpfile.py
flumotion/trunk/flumotion/test/Makefile.am
Log:
* flumotion/component/misc/httpfile/httpfile.py (HTTPFileStreamer.init,
HTTPFileStreamer.getDescription, HTTPFileStreamer.do_setup,
HTTPFileStreamer.do_stop, HTTPFileStreamer.do_start,
HTTPFileStreamer.requestFinished, HTTPFileStreamer.getStreamData):
Rearrange imports.
Privatize some variables.
Store some variables for resources we need to clean up.
Set self.port from what we're actually listening to, so we can
listen to port 0 then figure out which port it chose.
Make sure we can work without any loggers.
Fix handling of empty and / mount points, as well as getting
empty or / resources - fixes #567
* flumotion/test/Makefile.am:
* flumotion/test/test_component_httpserver.py (MountTest,
MountTest.setUp, MountTest.tearDown, MountTest.start,
MountTest.getURL, MountTest.testDirMountEmpty,
MountTest.testDirMountRoot, MountTest.testDirMountOnDemand,
MountTest.testFileMountEmpty, MountTest.testFileMountOnDemand):
Add tests for http-server component with various mount points
and serving either a directory or a file.
* flumotion/component/misc/httpfile/file.py (File.getChild,
File.renderAuthenticated):
Restat the file on every request, to make sure we handle changed
lengths of files.
Handle requests ending with /
Modified: flumotion/trunk/ChangeLog
==============================================================================
--- flumotion/trunk/ChangeLog (original)
+++ flumotion/trunk/ChangeLog Mon May 7 23:32:53 2007
@@ -1,3 +1,31 @@
+2007-05-07 Thomas Vander Stichele <thomas at apestaart dot org>
+
+ * flumotion/component/misc/httpfile/httpfile.py (HTTPFileStreamer.init,
+ HTTPFileStreamer.getDescription, HTTPFileStreamer.do_setup,
+ HTTPFileStreamer.do_stop, HTTPFileStreamer.do_start,
+ HTTPFileStreamer.requestFinished, HTTPFileStreamer.getStreamData):
+ Rearrange imports.
+ Privatize some variables.
+ Store some variables for resources we need to clean up.
+ Set self.port from what we're actually listening to, so we can
+ listen to port 0 then figure out which port it chose.
+ Make sure we can work without any loggers.
+ Fix handling of empty and / mount points, as well as getting
+ empty or / resources - fixes #567
+ * flumotion/test/Makefile.am:
+ * flumotion/test/test_component_httpserver.py (MountTest,
+ MountTest.setUp, MountTest.tearDown, MountTest.start,
+ MountTest.getURL, MountTest.testDirMountEmpty,
+ MountTest.testDirMountRoot, MountTest.testDirMountOnDemand,
+ MountTest.testFileMountEmpty, MountTest.testFileMountOnDemand):
+ Add tests for http-server component with various mount points
+ and serving either a directory or a file.
+ * flumotion/component/misc/httpfile/file.py (File.getChild,
+ File.renderAuthenticated):
+ Restat the file on every request, to make sure we handle changed
+ lengths of files.
+ Handle requests ending with /
+
2007-05-07 Andy Wingo <wingo at pobox.com>
* flumotion/worker/medium.py (WorkerMedium.remote_killJob): New
Modified: flumotion/trunk/flumotion/component/misc/httpfile/file.py
==============================================================================
--- flumotion/trunk/flumotion/component/misc/httpfile/file.py (original)
+++ flumotion/trunk/flumotion/component/misc/httpfile/file.py Mon May 7 23:32:53 2007
@@ -55,6 +55,10 @@
self._component = component
def getChild(self, path, request):
+ # we handle a request ending in '/' as well; this is how those come in
+ if path == '':
+ return self
+
self.restat()
if not self.isdir():
@@ -101,6 +105,9 @@
# self.restat()
self.debug('renderAuthenticated request %r' % request)
+ # make sure we notice changes in the file
+ self.restat()
+
ext = os.path.splitext(self.basename())[1].lower()
type = self.contentTypes.get(ext, self.defaultType)
@@ -109,6 +116,7 @@
return self.childNotFound.render(request)
if self.isdir():
+ self.debug("%s is a directory, can't be GET", self.path)
return self.childNotFound.render(request)
# Different headers not normally set in static.File...
Modified: flumotion/trunk/flumotion/component/misc/httpfile/httpfile.py
==============================================================================
--- flumotion/trunk/flumotion/component/misc/httpfile/httpfile.py (original)
+++ flumotion/trunk/flumotion/component/misc/httpfile/httpfile.py Mon May 7 23:32:53 2007
@@ -1,4 +1,4 @@
-# -*- Mode: Python -*-
+# -*- Mode: Python; test-case-name: flumotion.test.test_component_httpserver -*-
# vi:si:et:sw=4:sts=4:ts=4
#
# Flumotion - a streaming media server
@@ -22,17 +22,19 @@
import time
import string
+from twisted.web import resource, static, server, http
+from twisted.web import error as weberror
+from twisted.internet import defer, reactor, error
+from twisted.cred import credentials
+
from flumotion.component import component
from flumotion.common import log, messages, errors, netutils, interfaces
from flumotion.component.component import moods
from flumotion.component.misc.porter import porterclient
from flumotion.component.base import http as httpbase
-from twisted.web import resource, static, server, http
-from twisted.web import error as weberror
-from twisted.internet import defer, reactor, error
+
from flumotion.twisted import fdserver
from flumotion.twisted.compat import implements
-from twisted.cred import credentials
from flumotion.component.misc.httpfile import file
@@ -134,10 +136,10 @@
self.type = None
self.port = None
self.hostname = None
- self.loggers = []
- self.logfilter = None
+ self._loggers = []
+ self._logfilter = None
- self.description = 'On-Demand Flumotion Stream',
+ self._description = 'On-Demand Flumotion Stream',
self._singleFile = False
self._connected_clients = []
@@ -145,16 +147,20 @@
self._pbclient = None
+ self._twistedPort = None
+ self._timeoutRequestsCallLater = None
+
# store number of connected clients
self.uiState.addKey("connected-clients", 0)
self.uiState.addKey("bytes-transferred", 0)
def getDescription(self):
- return self.description
+ return self._description
def do_setup(self):
props = self.config['properties']
+ # always make sure the mount point starts with /
mountPoint = props.get('mount-point', '')
if not mountPoint.startswith('/'):
mountPoint = '/' + mountPoint
@@ -171,8 +177,8 @@
self._porterPath = props['porter-socket-path']
self._porterUsername = props['porter-username']
self._porterPassword = props['porter-password']
- self.loggers = \
- self.plugs['flumotion.component.plugs.loggers.Logger']
+ self._loggers = \
+ self.plugs.get('flumotion.component.plugs.loggers.Logger', [])
if 'bouncer' in props:
self.setBouncerName(props['bouncer'])
@@ -182,9 +188,15 @@
filter = http.LogFilter()
for f in props['ip-filter']:
filter.addIPFilter(f)
- self.logfilter = filter
-
+ self._logfilter = filter
+
def do_stop(self):
+ if self._timeoutRequestsCallLater:
+ self._timeoutRequestsCallLater.cancel()
+ self._timeoutRequestsCallLater = None
+ if self._twistedPort:
+ self._twistedPort.stopListening()
+
if self.type == 'slave' and self._pbclient:
return self._pbclient.deregisterPath(self.mountPoint)
@@ -220,22 +232,24 @@
"Can't specify porter details in master mode")
def do_start(self, *args, **kwargs):
- #root = HTTPRoot()
+ self.debug('Starting with mount point "%s"' % self.mountPoint)
root = resource.Resource()
- # TwistedWeb wants the child path to not include the leading /
- mount = self.mountPoint[1:]
# split path on / and add iteratively twisted.web resources
- children = string.split(mount, '/')
- current_resource = root
+ # Asking for '' or '/' will retrieve the root Resource's '' child,
+ # so the split on / returning a first list value '' is correct
+ children = string.split(self.mountPoint, '/')
+ parent = root
for child in children[:-1]:
res = resource.Resource()
- current_resource.putChild(child, res)
- current_resource = res
+ self.debug("Putting Resource at %s", child)
+ parent.putChild(child, res)
+ parent = res
fileResource = file.File(self.filePath, self)
self.debug("Putting File resource at %r", children[-1:][0])
- current_resource.putChild(children[-1:][0], fileResource)
+ parent.putChild(children[-1:][0], fileResource)
- reactor.callLater(self.REQUEST_TIMEOUT, self._timeoutRequests)
+ self._timeoutRequestsCallLater = reactor.callLater(
+ self.REQUEST_TIMEOUT, self._timeoutRequests)
d = defer.Deferred()
if self.type == 'slave':
@@ -257,10 +271,14 @@
else:
# File Streamer is standalone.
try:
- self.debug('Listening on %s' % self.port)
+ self.debug('Going to listen on port %d' % self.port)
iface = ""
- reactor.listenTCP(self.port, Site(root, self),
- interface=iface)
+ # we could be listening on port 0, in which case we need
+ # to figure out the actual port we listen on
+ self._twistedPort = reactor.listenTCP(self.port,
+ Site(root, self), interface=iface)
+ self.port = self._twistedPort.getHost().port
+ self.debug('Listening on port %d' % self.port)
except error.CannotListenError:
t = 'Port %d is not available.' % self.port
self.warning(t)
@@ -294,11 +312,6 @@
msg = 'slave mode, missing required property porter-%s' % k
return defer.fail(errors.ConfigError(msg))
- if props.get('mount-point', None) is not None:
- if props['mount-point'] == '/':
- return defer.fail(errors.ConfigError(
- "A mount-point of / is not supported in this release"))
-
path = props.get('path', None)
if path is None:
msg = "missing required property 'path'"
@@ -334,7 +347,7 @@
headers = request.getAllHeaders()
ip = request.getClientIP()
- if not self.logfilter or not self.logfilter.isInRange(ip):
+ if not self._logfilter or not self._logfilter.isInRange(ip):
args = {'ip': ip,
'time': time.gmtime(),
'method': request.method,
@@ -348,7 +361,7 @@
'user-agent': headers.get('user-agent', None),
'time-connected': timeConnected}
- for logger in self.loggers:
+ for logger in self._loggers:
logger.event('http_session_completed', args)
self._connected_clients.remove(request)
@@ -369,7 +382,7 @@
else:
return {
'protocol': 'HTTP',
- 'description': self.description,
+ 'description': self._description,
'url' : self.getUrl()
}
@@ -403,7 +416,6 @@
"""
Close the logfile, then reopen using the previous logfilename
"""
- for logger in self.loggers:
+ for logger in self._loggers:
self.debug('rotating logger %r' % logger)
logger.rotate()
-
Modified: flumotion/trunk/flumotion/test/Makefile.am
==============================================================================
--- flumotion/trunk/flumotion/test/Makefile.am (original)
+++ flumotion/trunk/flumotion/test/Makefile.am Mon May 7 23:32:53 2007
@@ -29,6 +29,7 @@
test_component.py \
test_component_init.py \
test_component_feed.py \
+ test_component_httpserver.py \
test_component_httpstreamer.py \
test_config.py \
test_credentials.py \
Added: flumotion/trunk/flumotion/test/test_component_httpserver.py
==============================================================================
--- (empty file)
+++ flumotion/trunk/flumotion/test/test_component_httpserver.py Mon May 7 23:32:53 2007
@@ -0,0 +1,164 @@
+# -*- Mode: Python; test-case-name: flumotion.test.test_component_httpserver -*-
+# vi:si:et:sw=4:sts=4:ts=4
+#
+# Flumotion - a streaming media server
+# Copyright (C) 2004,2005,2006,2007 Fluendo, S.L. (www.fluendo.com).
+# All rights reserved.
+
+# This file may be distributed and/or modified under the terms of
+# the GNU General Public License version 2 as published by
+# the Free Software Foundation.
+# This file is distributed without any warranty; without even the implied
+# warranty of merchantability or fitness for a particular purpose.
+# See "LICENSE.GPL" in the source distribution for more information.
+
+# Licensees having purchased or holding a valid Flumotion Advanced
+# Streaming Server license may use this file in accordance with the
+# Flumotion Advanced Streaming Server Commercial License Agreement.
+# See "LICENSE.Flumotion" in the source distribution for more information.
+
+# Headers in this file shall remain intact.
+
+from twisted.trial import unittest
+
+import common
+
+import os
+import tempfile
+
+from twisted.python import failure
+from twisted.internet import defer
+from twisted.web import client, error
+
+from flumotion.common import log
+from flumotion.component.misc.httpfile import httpfile
+
+class MountTest(log.Loggable, unittest.TestCase):
+ def setUp(self):
+ self.path = tempfile.mkdtemp(suffix=".flumotion.test")
+ A = os.path.join(self.path, 'A')
+ open(A, "w").write('test file A')
+ B = os.path.join(self.path, 'B')
+ os.mkdir(B)
+ C = os.path.join(self.path, 'B', 'C')
+ open(C, "w").write('test file C')
+
+ self.component = httpfile.HTTPFileStreamer()
+
+ def tearDown(self):
+ self.component.stop()
+ os.system('rm -r %s' % self.path)
+
+ def start(self, properties):
+ # start the component with the given properties
+ config = {
+ 'feed': [],
+ 'name': 'http-server',
+ 'parent': 'default',
+ 'avatarId': '/default/http-server',
+ 'clock-master': None,
+ 'type': 'http-server',
+ 'plugs': {},
+ 'properties': properties,
+ }
+
+ d = self.component.setup(config)
+ d.addCallback(lambda _: self.component.start())
+ return d
+
+ def getURL(self, path):
+ # path should start with /
+ return 'http://localhost:%d%s' % (self.component.port, path)
+
+ def testDirMountEmpty(self):
+ properties = {
+ u'mount-point': '',
+ u'path': self.path,
+ u'port': 0,
+ }
+ self.start(properties)
+
+ d = client.getPage(self.getURL('/A'))
+ d.addCallback(lambda r: self.assertEquals(r, 'test file A'))
+ d.addCallback(lambda r: client.getPage(self.getURL('/B/C')))
+ d.addCallback(lambda r: self.assertEquals(r, 'test file C'))
+ # getting a non-existing resource should give web.error.Error
+ d.addCallback(lambda r: client.getPage(self.getURL('/B/D')))
+ d.addErrback(lambda f: f.trap(error.Error))
+ return d
+
+ def testDirMountRoot(self):
+ properties = {
+ u'mount-point': '/',
+ u'path': self.path,
+ u'port': 0,
+ }
+ self.start(properties)
+
+ d = client.getPage(self.getURL('/A'))
+ d.addCallback(lambda r: self.assertEquals(r, 'test file A'))
+ d.addCallback(lambda r: client.getPage(self.getURL('/B/C')))
+ d.addCallback(lambda r: self.assertEquals(r, 'test file C'))
+ # getting a non-existing resource should give web.error.Error
+ d.addCallback(lambda r: client.getPage(self.getURL('/B/D')))
+ d.addErrback(lambda f: f.trap(error.Error))
+ return d
+
+ def testDirMountOnDemand(self):
+ properties = {
+ u'mount-point': '/ondemand',
+ u'path': self.path,
+ u'port': 0,
+ }
+ self.start(properties)
+
+ d = client.getPage(self.getURL('/ondemand/A'))
+ d.addCallback(lambda r: self.assertEquals(r, 'test file A'))
+ d.addCallback(lambda r: client.getPage(self.getURL('/ondemand/B/C')))
+ d.addCallback(lambda r: self.assertEquals(r, 'test file C'))
+ # getting a non-existing resource should give web.error.Error
+ d.addCallback(lambda r: client.getPage(self.getURL('/A')))
+ d.addErrback(lambda f: f.trap(error.Error))
+ d.addCallback(lambda r: client.getPage(self.getURL('/ondemand/B/D')))
+ d.addErrback(lambda f: f.trap(error.Error))
+ return d
+
+ def testFileMountEmpty(self):
+ properties = {
+ u'mount-point': '',
+ u'path': os.path.join(self.path, 'A'),
+ u'port': 0,
+ }
+ self.start(properties)
+
+ d = defer.Deferred()
+ # FIXME: what if just the server URL is requested ?
+ #d.addCallback(lambda r: client.getPage(self.getURL('')))
+ #d.addCallback(lambda r: self.assertEquals(r, 'test file A'))
+ d.addCallback(lambda r: client.getPage(self.getURL('/')))
+ d.addCallback(lambda r: self.assertEquals(r, 'test file A'))
+ # getting a non-existing resource should give web.error.Error
+ d.addCallback(lambda r: client.getPage(self.getURL('/B/D')))
+ d.addErrback(lambda f: f.trap(error.Error))
+ d.callback(None)
+ return d
+
+ def testFileMountOnDemand(self):
+ properties = {
+ u'mount-point': '/ondemand',
+ u'path': os.path.join(self.path, 'A'),
+ u'port': 0,
+ }
+ self.start(properties)
+
+ d = client.getPage(self.getURL('/ondemand'))
+ d.addCallback(lambda r: self.assertEquals(r, 'test file A'))
+ # getting a non-existing resource should give web.error.Error
+ d.addCallback(lambda r: client.getPage(self.getURL('/A')))
+ d.addErrback(lambda f: f.trap(error.Error))
+ d.addCallback(lambda r: client.getPage(self.getURL('/ondemand/B/D')))
+ d.addErrback(lambda f: f.trap(error.Error))
+ return d
+
+if __name__ == '__main__':
+ unittest.main()
More information about the flumotion-commit
mailing list