Files

165 lines
5.4 KiB
Python
Raw Permalink Normal View History

2015-01-22 19:41:44 +01:00
from twisted.internet import defer
from twisted.python import log
from twisted.web import http, resource, server, static
class NoRangeStaticProducer(static.NoRangeStaticProducer):
@defer.inlineCallbacks
def resumeProducing(self):
if not self.request:
return
2015-01-22 19:41:44 +01:00
data = yield defer.maybeDeferred(self.fileObject.read, self.bufferSize)
2015-11-18 11:40:11 +01:00
if not self.request:
return
2015-01-22 19:41:44 +01:00
if data:
# this .write will spin the reactor, calling .doWrite and then
# .resumeProducing again, so be prepared for a re-entrant call
self.request.write(data)
else:
self.request.unregisterProducer()
self.request.finish()
self.stopProducing()
2015-01-22 19:41:44 +01:00
class SingleRangeStaticProducer(static.SingleRangeStaticProducer):
@defer.inlineCallbacks
def resumeProducing(self):
if not self.request:
return
2017-12-17 19:38:53 +01:00
data = yield defer.maybeDeferred(self.fileObject.read, min(self.bufferSize, self.size - self.bytesWritten))
2015-11-18 11:40:11 +01:00
if not self.request:
return
2015-01-22 19:41:44 +01:00
if data:
self.bytesWritten += len(data)
# this .write will spin the reactor, calling .doWrite and then
# .resumeProducing again, so be prepared for a re-entrant call
self.request.write(data)
2015-01-22 19:41:44 +01:00
if self.request and self.bytesWritten == self.size:
self.request.unregisterProducer()
self.request.finish()
self.stopProducing()
2015-01-22 19:41:44 +01:00
class MultipleRangeStaticProducer(static.MultipleRangeStaticProducer):
@defer.inlineCallbacks
def resumeProducing(self):
if not self.request:
return
2015-01-22 19:41:44 +01:00
data = []
dataLength = 0
done = False
while dataLength < self.bufferSize:
if self.partBoundary:
dataLength += len(self.partBoundary)
data.append(self.partBoundary)
self.partBoundary = None
2017-12-17 19:38:53 +01:00
p = yield defer.maybeDeferred(self.fileObject.read, min(self.bufferSize - dataLength, self._partSize - self._partBytesWritten))
2015-01-22 19:41:44 +01:00
self._partBytesWritten += len(p)
dataLength += len(p)
data.append(p)
if self.request and self._partBytesWritten == self._partSize:
try:
self._nextRange()
except StopIteration:
done = True
break
2015-11-18 11:40:11 +01:00
if not self.request:
return
2015-01-22 19:41:44 +01:00
self.request.write(''.join(data))
2015-01-22 19:41:44 +01:00
if done:
self.request.unregisterProducer()
self.request.finish()
2015-11-17 22:41:14 +01:00
self.stopProducing()
2015-01-22 19:41:44 +01:00
self.request = None
2015-01-22 19:41:44 +01:00
class FilelikeObjectResource(static.File):
isLeaf = True
contentType = None
fileObject = None
encoding = 'bytes'
def __init__(self, fileObject, size, contentType='bytes'):
self.contentType = contentType
self.fileObject = fileObject
self.fileSize = size
resource.Resource.__init__(self)
def _setContentHeaders(self, request, size=None):
if size is None:
size = self.getFileSize()
if size:
request.setHeader('content-length', str(size))
if self.contentType:
request.setHeader('content-type', self.contentType)
if self.encoding:
request.setHeader('content-encoding', self.encoding)
def makeProducer(self, request, fileForReading):
"""
Make a L{StaticProducer} that will produce the body of this response.
This method will also set the response code and Content-* headers.
@param request: The L{Request} object.
@param fileForReading: The file object containing the resource.
@return: A L{StaticProducer}. Calling C{.start()} on this will begin
producing the response.
"""
byteRange = request.getHeader('range')
if byteRange is None or not self.getFileSize():
self._setContentHeaders(request)
request.setResponseCode(http.OK)
return NoRangeStaticProducer(request, fileForReading)
try:
parsedRanges = self._parseRangeHeader(byteRange)
except ValueError:
log.msg("Ignoring malformed Range header %r" % (byteRange,))
self._setContentHeaders(request)
request.setResponseCode(http.OK)
return NoRangeStaticProducer(request, fileForReading)
if len(parsedRanges) == 1:
offset, size = self._doSingleRangeRequest(
request, parsedRanges[0])
self._setContentHeaders(request, size)
return SingleRangeStaticProducer(
request, fileForReading, offset, size)
else:
rangeInfo = self._doMultipleRangeRequest(request, parsedRanges)
return MultipleRangeStaticProducer(
request, fileForReading, rangeInfo)
def getFileSize(self):
return self.fileSize
def render_GET(self, request):
"""
Begin sending the contents of this L{File} (or a subset of the
contents, based on the 'range' header) to the given request.
"""
request.setHeader('accept-ranges', 'bytes')
producer = self.makeProducer(request, self.fileObject)
if request.method == 'HEAD':
2015-11-17 22:41:14 +01:00
self.fileObject.close()
2015-01-22 19:41:44 +01:00
return ''
producer.start()
# and make sure the connection doesn't get closed
return server.NOT_DONE_YET
render_HEAD = render_GET