thomasvs - in flumotion/branches/platform-3: .
flumotion/component/misc/httpfile flumotion/test
flumotion-commit at lists.fluendo.com
flumotion-commit at lists.fluendo.com
Wed May 16 11:58:01 CEST 2007
Author: thomasvs
Date: Wed May 16 11:57:57 2007
New Revision: 4955
Added:
flumotion/branches/platform-3/flumotion/test/test_component_httpserver.py
- copied unchanged from r4879, flumotion/trunk/flumotion/test/test_component_httpserver.py
flumotion/branches/platform-3/flumotion/test/test_misc_httpfile.py
- copied, changed from r4829, flumotion/trunk/flumotion/test/test_misc_httpfile.py
Modified:
flumotion/branches/platform-3/ (props changed)
flumotion/branches/platform-3/ChangeLog
flumotion/branches/platform-3/flumotion/component/misc/httpfile/file.py
flumotion/branches/platform-3/flumotion/component/misc/httpfile/httpfile.py
flumotion/branches/platform-3/flumotion/test/Makefile.am
Log:
Merged revisions 4828-4829,4836-4838,4840-4841,4843-4844,4846-4848,4852,4858,4863-4865,4870-4873,4877-4892,4894,4898,4903-4904,4911-4913 via svnmerge from
https://svn.fluendo.com/svn/flumotion/trunk
........
r4828 | thomasvs | 2007-04-25 00:06:21 +0200 (Wed, 25 Apr 2007) | 6 lines
* flumotion/test/Makefile.am:
* flumotion/test/test_misc_httpfile.py:
Add a test before refactoring.
Increases coverage for file.py from 19% to 79%.
........
r4829 | thomasvs | 2007-04-25 00:20:44 +0200 (Wed, 25 Apr 2007) | 18 lines
* flumotion/component/misc/httpfile/file.py (File, File.__init__,
File.render, File.terminateSimpleRequest, File.renderAuthenticated,
File.createSimilarFile):
Comment and document.
Remove bogus pychecker inhibition.
Privatize File._component.
Instead of commenting badly named variables, use self-documenting
names.
Be a bit more strict about range headers we accept.
Make the start/end logic shorter and easier to grok by adding
first/last variables and simplifying.
* flumotion/test/test_misc_httpfile.py
(TestTextFile.testWrongEmptyBytesRange,
TestTextFile.testWrongNoRange, TestTextFile.testWrongTypeRange,
TestTextFile.testRangeSet):
Add two tests, fix one.
........
r4877 | thomasvs | 2007-05-07 23:32:53 +0200 (Mon, 07 May 2007) | 27 lines
* 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 /
........
r4878 | thomasvs | 2007-05-07 23:55:11 +0200 (Mon, 07 May 2007) | 10 lines
* flumotion/component/misc/httpfile/file.py (File.getChild):
* flumotion/component/misc/httpfile/httpfile.py
(HTTPFileStreamer.do_setup, HTTPFileStreamer.do_start):
We still need to distinguish between the root resource being the
File resource directly, and a tree. Apparently client.getPage()
in the tests does not mimic correctly what something like wget would
do, making it hard to test for the Root resource tree being set up
correctly.
........
r4879 | thomasvs | 2007-05-08 00:31:06 +0200 (Tue, 08 May 2007) | 20 lines
* flumotion/component/misc/httpfile/file.py (File, File.__init__,
File.getChild, File.renderAuthenticated, MimedFileFactory,
MimedFileFactory.__init__, MimedFileFactory.create, FLVFile,
FLVFile.renderAuthenticated):
Add a subclass for handling FLV files.
Implement handling of the start= GET parameter, just like in
Apache and lighthttpd. Fixes #618.
Add a MimedFileFactory that allows us to create even the root
resource as a mime-type-dependent subclass.
* flumotion/component/misc/httpfile/httpfile.py (HTTPFileStreamer.init,
HTTPFileStreamer.do_start):
* flumotion/test/test_misc_httpfile.py (TestDirectory,
TestDirectory.setUp, TestDirectory.tearDown,
TestDirectory.testGetChild, TestDirectory.testFLV,
TestDirectory.finish, TestDirectory.testFLVStart,
TestDirectory.finish, TestDirectory.testFLVStartZero,
TestDirectory.finish):
Add tests for this.
........
r4898 | thomasvs | 2007-05-09 11:54:33 +0200 (Wed, 09 May 2007) | 4 lines
* flumotion/component/misc/httpfile/file.py (loadMimeTypes):
Add .flv mime type in the code.
........
r4913 | thomasvs | 2007-05-09 16:57:22 +0200 (Wed, 09 May 2007) | 14 lines
* flumotion/component/misc/httpfile/file.py (File.renderAuthenticated,
File.do_prepareBody, MimedFileFactory, FLVFile,
FLVFile.do_prepareBody, FileTransfer):
Make sure start= requests on FLV files give me the right mime type
by not writing before all headers are set.
factor out an overridable do_prepareBody in which FLV and the like
can do their manipulative trickery.
Re-fixes #618.
* flumotion/test/test_misc_httpfile.py (TestTextFile.finishCallback,
TestDirectory.finish, TestDirectory.finish, TestDirectory.finish,
TestDirectory.testFLVRangeStart, TestDirectory.finish):
Add tests. Be more assertive.
........
Modified: flumotion/branches/platform-3/ChangeLog
==============================================================================
--- flumotion/branches/platform-3/ChangeLog (original)
+++ flumotion/branches/platform-3/ChangeLog Wed May 16 11:57:57 2007
@@ -1,3 +1,78 @@
+2007-05-16 Thomas Vander Stichele <thomas at apestaart dot org>
+
+ merged from: TRUNK, 4828,4829,4877-4879,4898,4913
+
+ * flumotion/component/misc/httpfile/file.py (File.renderAuthenticated,
+ File.do_prepareBody, MimedFileFactory, FLVFile,
+ FLVFile.do_prepareBody, FileTransfer):
+ Make sure start= requests on FLV files give me the right mime type
+ by not writing before all headers are set.
+ factor out an overridable do_prepareBody in which FLV and the like
+ can do their manipulative trickery.
+ Re-fixes #618.
+
+ * flumotion/component/misc/httpfile/file.py (loadMimeTypes):
+ Add .flv mime type in the code.
+
+ * flumotion/component/misc/httpfile/file.py (File, File.__init__,
+ File.getChild, File.renderAuthenticated, MimedFileFactory,
+ MimedFileFactory.__init__, MimedFileFactory.create, FLVFile,
+ FLVFile.renderAuthenticated):
+ Add a subclass for handling FLV files.
+ Implement handling of the start= GET parameter, just like in
+ Apache and lighthttpd. Fixes #618.
+ Add a MimedFileFactory that allows us to create even the root
+ resource as a mime-type-dependent subclass.
+
+ * 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/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 /
+ * flumotion/component/misc/httpfile/file.py (File, File.__init__,
+ File.render, File.terminateSimpleRequest, File.renderAuthenticated,
+ File.createSimilarFile):
+ Comment and document.
+ Remove bogus pychecker inhibition.
+ Privatize File._component.
+ Instead of commenting badly named variables, use self-documenting
+ names.
+ Be a bit more strict about range headers we accept.
+ Make the start/end logic shorter and easier to grok by adding
+ first/last variables and simplifying.
+ * flumotion/component/misc/httpfile/file.py (File.getChild):
+ * flumotion/component/misc/httpfile/httpfile.py
+ (HTTPFileStreamer.do_setup, HTTPFileStreamer.do_start):
+ We still need to distinguish between the root resource being the
+ File resource directly, and a tree. Apparently client.getPage()
+ in the tests does not mimic correctly what something like wget would
+ do, making it hard to test for the Root resource tree being set up
+ correctly.
+
+ * flumotion/test/Makefile.am:
+ * flumotion/test/test_misc_httpfile.py:
+ Add a test before refactoring.
+ Increases coverage for file.py from 19% to 79%.
+ * 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.
+
2007-04-04 Thomas Vander Stichele <thomas at apestaart dot org>
merged from: TRUNK, 4743
Modified: flumotion/branches/platform-3/flumotion/component/misc/httpfile/file.py
==============================================================================
--- flumotion/branches/platform-3/flumotion/component/misc/httpfile/file.py (original)
+++ flumotion/branches/platform-3/flumotion/component/misc/httpfile/file.py Wed May 16 11:57:57 2007
@@ -1,4 +1,4 @@
-# -*- Mode: Python -*-
+# -*- Mode: Python; test-case-name: flumotion.test.test_misc_httpfile -*-
# vi:si:et:sw=4:sts=4:ts=4
#
# Flumotion - a streaming media server
@@ -18,39 +18,56 @@
# See "LICENSE.Flumotion" in the source distribution for more information.
# Headers in this file shall remain intact.
+
import string
import os
+from twisted.web import resource, server, http
+from twisted.web import error as weberror, static
+from twisted.internet import defer, reactor, error, abstract
+from twisted.python import filepath
+from twisted.cred import credentials
+
+from flumotion.configure import configure
from flumotion.component import component
from flumotion.common import log, messages, errors, netutils
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, server, http
-from twisted.web import error as weberror
-from twisted.internet import defer, reactor, error, abstract
-from twisted.python import filepath
from flumotion.twisted import fdserver
-from twisted.cred import credentials
-from twisted.web.static import loadMimeTypes, getTypeAndEncoding
+# add our own mime types to the ones parsed from /etc/mime.types
+def loadMimeTypes():
+ d = static.loadMimeTypes()
+ d['.flv'] = 'video/x-flv'
+ return d
-class File(resource.Resource, filepath.FilePath, log.Loggable):
- __pychecker__ = 'no-objattrs'
+# this file is inspired by/adapted from twisted.web.static
+class File(resource.Resource, filepath.FilePath, log.Loggable):
contentTypes = loadMimeTypes()
-
defaultType = "application/octet-stream"
childNotFound = weberror.NoResource("File not found.")
- def __init__(self, path, component):
+ def __init__(self, path, component, mimeToResource=None):
+ """
+ @param component: L{flumotion.component.component.BaseComponent}
+ """
resource.Resource.__init__(self)
filepath.FilePath.__init__(self, path)
- self.component = component
+ self._component = component
+ # mapping of mime type -> File subclass
+ self._mimeToResource = mimeToResource or {}
+ self._factory = MimedFileFactory(component, self._mimeToResource)
def getChild(self, path, request):
+ self.log('getChild: self %r, path %r', self, path)
+ # we handle a request ending in '/' as well; this is how those come in
+ if path == '':
+ return self
+
self.restat()
if not self.isdir():
@@ -64,7 +81,7 @@
if not fpath.exists():
return self.childNotFound
- return self.createSimilarFile(fpath.path)
+ return self._factory.create(fpath.path)
def openForReading(self):
"""Open a file and return it."""
@@ -77,34 +94,38 @@
return self.getsize()
def render(self, request):
+ self.debug('render request %r' % request)
def terminateSimpleRequest(res, request):
if res != server.NOT_DONE_YET:
request.finish()
- d = self.component.startAuthentication(request)
+ d = self._component.startAuthentication(request)
d.addCallback(self.renderAuthenticated, request)
d.addCallback(terminateSimpleRequest, request)
return server.NOT_DONE_YET
- def renderAuthenticated(self, res, request):
- """
- Now that we're authenticated (or authentication wasn't requested),
- write the file (or appropriate other response) to the client.
- We override static.File to implement Range requests, and to get access
- to the transfer object to abort it later; the bulk of this is a direct
- copy, though.
- """
+ def renderAuthenticated(self, _, request):
+ # Now that we're authenticated (or authentication wasn't requested),
+ # write the file (or appropriate other response) to the client.
+ # We override static.File to implement Range requests, and to get access
+ # to the transfer object to abort it later; the bulk of this is a direct
+ # copy of static.File.render, though.
+ # 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)
+ contentType = self.contentTypes.get(ext, self.defaultType)
if not self.exists():
- self.debug("Couldn't find resource %s", self.basename())
+ self.debug("Couldn't find resource %s", self.path)
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...
@@ -112,12 +133,14 @@
# that the client must not issue further requests.
# We do this because future requests on this server might actually need
# to go to a different process (because of the porter)
+ request.setHeader('Server', 'Flumotion/%s' % configure.version)
request.setHeader('Connection', 'close')
# We can do range requests, in bytes.
request.setHeader('Accept-Ranges', 'bytes')
- if type:
- request.setHeader('content-type', type)
+ if contentType:
+ self.debug('content type %r' % contentType)
+ request.setHeader('content-type', contentType)
try:
f = self.openForReading()
@@ -131,60 +154,137 @@
if request.setLastModified(self.getmtime()) is http.CACHED:
return ''
- tsize = fsize = size = self.getFileSize()
+ fileSize = self.getFileSize()
+ # first and last byte offset we will write
+ first = 0
+ last = fileSize - 1
+
range = request.getHeader('range')
- start = 0
if range is not None:
- # TODO: Add a unit test - or several - for this stuff.
- # We have a partial-data request...
- # Some variables... we start at byte offset 'start', end at byte
- # offset 'end'. fsize is the number of bytes we're sending. tsize
- # is the total size of the file. 'size' is the byte offset we will
- # stop at, plus 1.
- bytesrange = string.split(range, '=')
- if len(bytesrange) != 2:
+ # We have a partial data request.
+ # for interpretation of range, see RFC 2068 14.36
+ # examples: bytes=500-999; bytes=-500 (suffix mode; last 500)
+ self.log('range request, %r', range)
+ rangeKeyValue = string.split(range, '=')
+ if len(rangeKeyValue) != 2:
request.setResponseCode(http.REQUESTED_RANGE_NOT_SATISFIABLE)
return ''
- start, end = string.split(bytesrange[1], '-', 1)
+ if rangeKeyValue[0] != 'bytes':
+ request.setResponseCode(http.REQUESTED_RANGE_NOT_SATISFIABLE)
+ return ''
+
+ # ignore a set of range requests for now, only take the first
+ ranges = rangeKeyValue[1].split(',')[0]
+ l = ranges.split('-')
+ if len(l) != 2:
+ request.setResponseCode(http.REQUESTED_RANGE_NOT_SATISFIABLE)
+ return ''
+
+ start, end = l
+
if start:
- start = int(start)
- f.seek(start)
+ # byte-range-spec
+ first = int(start)
if end:
- end = int(end)
- else:
- end = size - 1
- fsize = end - start + 1
+ last = int(end)
elif end:
- lastbytes = int(end)
- if size < lastbytes:
- lastbytes = size
- start = size - lastbytes
- f.seek(start)
- fsize = lastbytes
- end = size - 1
+ # suffix-byte-range-spec
+ count = int(end)
+ # we can't serve more than there are in the file
+ if count > fileSize:
+ count = fileSize
+ first = fileSize - count
else:
+ # need at least start or end
request.setResponseCode(http.REQUESTED_RANGE_NOT_SATISFIABLE)
return ''
- size = end + 1
+ # FIXME: is it still partial if the request was for the complete
+ # file ? Couldn't find a conclusive answer in the spec.
request.setResponseCode(http.PARTIAL_CONTENT)
request.setHeader('Content-Range', "bytes %d-%d/%d" %
- (start, end, tsize))
-
- request.setHeader("Content-Length", str(fsize))
+ (first, last, fileSize))
+ # Start sending from the requested position in the file
+ if first:
+ f.seek(first)
+
+ self.do_prepareBody(request, f, first, last)
if request.method == 'HEAD':
- return ''
-
- request._transfer = FileTransfer(f, size, request)
+ return ''
+
+ request._transfer = FileTransfer(f, last + 1, request)
return server.NOT_DONE_YET
- def createSimilarFile(self, path):
- self.debug("createSimilarFile at %r", path)
- f = self.__class__(path, self.component)
- return f
+ def do_prepareBody(self, request, f, first, last):
+ """
+ I am called before the body of the response gets written,
+ and after generic header setting has been done.
+
+ I set Content-Length.
+
+ Override me to send additional headers, or to prefix the body
+ with data headers.
+ """
+ request.setHeader("Content-Length", str(last - first + 1))
+
+class MimedFileFactory(log.Loggable):
+ """
+ I create File subclasses based on the mime type of the given path.
+ """
+ contentTypes = loadMimeTypes()
+ defaultType = "application/octet-stream"
+
+ def __init__(self, component, mimeToResource=None):
+ self._component = component
+ self._mimeToResource = mimeToResource or {}
+
+ def create(self, path):
+ """
+ Creates and returns an instance of a File subclass based on the mime
+ type/extension of the given path.
+ """
+
+ self.debug("createMimedFile at %r", path)
+ ext = os.path.splitext(path)[1].lower()
+ mimeType = self.contentTypes.get(ext, self.defaultType)
+ klazz = self._mimeToResource.get(mimeType, File)
+ self.debug("mimetype %s, class %r" % (mimeType, klazz))
+ return klazz(path, self._component, mimeToResource=self._mimeToResource)
+
+class FLVFile(File):
+ """
+ I am a File resource for FLV files.
+ I can handle requests with a 'start' GET parameter.
+ This parameter represents the byte offset from where to start.
+ If it is non-zero, I will output an FLV header so the result is
+ playable.
+ """
+ header = 'FLV\x01\x01\000\000\000\x09\000\000\000\x09'
+
+ def do_prepareBody(self, request, f, first, last):
+ self.log('do_prepareBody for FLV')
+ length = last - first + 1
+
+ # if there is a non-zero start get parameter, prefix the body with
+ # our FLV header
+ # each value is a list
+ start = int(request.args.get('start', ['0'])[0])
+ # range request takes precedence over our start parsing
+ if first == 0 and start:
+ self.debug('start %d passed, seeking', start)
+ f.seek(start)
+ length = last - start + 1 + len(self.header)
+
+ request.setHeader("Content-Length", str(length))
+
+ if request.method == 'HEAD':
+ return ''
+
+ if first == 0 and start:
+ request.write(self.header)
class FileTransfer:
"""
Modified: flumotion/branches/platform-3/flumotion/component/misc/httpfile/httpfile.py
==============================================================================
--- flumotion/branches/platform-3/flumotion/component/misc/httpfile/httpfile.py (original)
+++ flumotion/branches/platform-3/flumotion/component/misc/httpfile/httpfile.py Wed May 16 11:57:57 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,17 +147,27 @@
self._pbclient = None
+ self._twistedPort = None
+ self._timeoutRequestsCallLater = None
+
+ # FIXME: maybe we want to allow the configuration to specify
+ # additional mime -> File class mapping ?
+ self._mimeToResource = {
+ 'video/x-flv': file.FLVFile,
+ }
+
# 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']
- mountPoint = props.get('mount-point', '')
+ # always make sure the mount point starts with /
+ mountPoint = props.get('mount-point', '/')
if not mountPoint.startswith('/'):
mountPoint = '/' + mountPoint
self.mountPoint = mountPoint
@@ -171,8 +183,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 +194,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 +238,33 @@
"Can't specify porter details in master mode")
def do_start(self, *args, **kwargs):
- #root = HTTPRoot()
- 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
- for child in children[:-1]:
- res = resource.Resource()
- current_resource.putChild(child, res)
- current_resource = res
- fileResource = file.File(self.filePath, self)
- self.debug("Putting File resource at %r", children[-1:][0])
- current_resource.putChild(children[-1:][0], fileResource)
+ self.debug('Starting with mount point "%s"' % self.mountPoint)
+ factory = file.MimedFileFactory(self,
+ mimeToResource=self._mimeToResource)
+ if self.mountPoint == '/':
+ self.debug('mount point / - create File resource as root')
+ # directly create a File resource for the path
+ root = factory.create(self.filePath)
+ else:
+ # split path on / and add iteratively twisted.web resources
+ # Asking for '' or '/' will retrieve the root Resource's '' child,
+ # so the split on / returning a first list value '' is correct
+ self.debug('mount point %s - creating root Resource and children',
+ self.mountPoint)
+ root = resource.Resource()
+ children = string.split(self.mountPoint[1:], '/')
+ parent = root
+ for child in children[:-1]:
+ res = resource.Resource()
+ self.debug("Putting Resource at %s", child)
+ parent.putChild(child, res)
+ parent = res
+ fileResource = factory.create(self.filePath)
+ self.debug("Putting resource %r at %r", fileResource, children[-1])
+ parent.putChild(children[-1], 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 +286,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 +327,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 +362,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 +376,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 +397,7 @@
else:
return {
'protocol': 'HTTP',
- 'description': self.description,
+ 'description': self._description,
'url' : self.getUrl()
}
@@ -403,7 +431,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/branches/platform-3/flumotion/test/Makefile.am
==============================================================================
--- flumotion/branches/platform-3/flumotion/test/Makefile.am (original)
+++ flumotion/branches/platform-3/flumotion/test/Makefile.am Wed May 16 11:57:57 2007
@@ -27,6 +27,7 @@
test_common_pygobject.py \
test_component.py \
test_component_init.py \
+ test_component_httpserver.py \
test_component_httpstreamer.py \
test_config.py \
test_credentials.py \
@@ -45,6 +46,7 @@
test_manager_depgraph.py \
test_manager_manager.py \
test_manager_worker.py \
+ test_misc_httpfile.py \
test_parts.py \
test_pb.py \
test_pbstream.py \
Copied: flumotion/branches/platform-3/flumotion/test/test_misc_httpfile.py (from r4829, flumotion/trunk/flumotion/test/test_misc_httpfile.py)
==============================================================================
--- flumotion/trunk/flumotion/test/test_misc_httpfile.py (original)
+++ flumotion/branches/platform-3/flumotion/test/test_misc_httpfile.py Wed May 16 11:57:57 2007
@@ -82,6 +82,8 @@
self.assertEquals(request.data, data)
self.assertEquals(int(request.getHeader('Content-Length') or '0'),
length)
+ self.assertEquals(request.getHeader('content-type'),
+ 'application/octet-stream')
def finishPartialCallback(self, result, request, data, start, end):
self.finishCallback(result, request, http.PARTIAL_CONTENT, data)
@@ -168,7 +170,6 @@
'a text file', 0, 10)
return fr.finishDeferred
-
def testRangeHead(self):
fr = FakeRequest(method='HEAD', headers={'range': 'bytes=2-5'})
self.assertEquals(self.resource.render(fr), server.NOT_DONE_YET)
@@ -176,4 +177,70 @@
http.PARTIAL_CONTENT, '', 4)
return fr.finishDeferred
+class TestDirectory(unittest.TestCase):
+ def setUp(self):
+ self.path = tempfile.mkdtemp()
+ h = open(os.path.join(self.path, 'test.flv'), 'w')
+ h.write('a fake FLV file')
+ h.close()
+ self.component = FakeComponent()
+ # a directory resource
+ self.resource = file.File(self.path, self.component,
+ { 'video/x-flv': file.FLVFile } )
+
+ def tearDown(self):
+ os.system('rm -r %s' % self.path)
+ def testGetChild(self):
+ fr = FakeRequest()
+ r = self.resource.getChild('test.flv', fr)
+ self.assertEquals(r.__class__, file.FLVFile)
+
+ def testFLV(self):
+ fr = FakeRequest()
+ self.assertEquals(self.resource.getChild('test.flv', fr).render(fr),
+ server.NOT_DONE_YET)
+ def finish(result):
+ self.assertEquals(fr.getHeader('content-type'), 'video/x-flv')
+ self.assertEquals(fr.data, 'a fake FLV file')
+ fr.finishDeferred.addCallback(finish)
+
+ return fr.finishDeferred
+
+ def testFLVStart(self):
+ fr = FakeRequest(args={'start': [2]})
+ self.assertEquals(self.resource.getChild('test.flv', fr).render(fr),
+ server.NOT_DONE_YET)
+ def finish(result):
+ self.assertEquals(fr.getHeader('content-type'), 'video/x-flv')
+ expected = file.FLVFile.header + 'fake FLV file'
+ self.assertEquals(fr.data, expected)
+ self.assertEquals(fr.getHeader('Content-Length'),
+ str(len(expected)))
+ fr.finishDeferred.addCallback(finish)
+
+ return fr.finishDeferred
+
+ def testFLVStartZero(self):
+ fr = FakeRequest(args={'start': [0]})
+ self.assertEquals(self.resource.getChild('test.flv', fr).render(fr),
+ server.NOT_DONE_YET)
+ def finish(result):
+ self.assertEquals(fr.getHeader('content-type'), 'video/x-flv')
+ self.assertEquals(fr.data, 'a fake FLV file')
+ fr.finishDeferred.addCallback(finish)
+ return fr.finishDeferred
+
+ def testFLVRangeStart(self):
+ # range should take precedence over start parameter
+ fr = FakeRequest(headers={'range': 'bytes=7-'}, args={'start': [2]})
+ self.assertEquals(self.resource.getChild('test.flv', fr).render(fr),
+ server.NOT_DONE_YET)
+ def finish(result):
+ self.assertEquals(fr.getHeader('content-type'), 'video/x-flv')
+ expected = 'FLV file'
+ self.assertEquals(fr.data, expected)
+ self.assertEquals(fr.getHeader('Content-Length'),
+ str(len(expected)))
+ fr.finishDeferred.addCallback(finish)
+ return fr.finishDeferred
More information about the flumotion-commit
mailing list