Ticket #1493: twisted.web.static.diff

File twisted.web.static.diff, 7.0 KB (added by brubelsabs, 14 years ago)

Simple Range-Header support with a simple unit-test.

  • static.py

     
    286286        """Return file size."""
    287287        return self.getsize()
    288288
     289    def _handleRangeRequest(self, request, file):
     290        """Performs (simple) Range-Header requests.
    289291
     292        Simple means, that only the first byte range is handled.
     293
     294        @param file: file handle for the corresponding ressource
     295        @type file: file object
     296        @return: content-length and the number of the last byte to transmit.
     297        @rtype: C{int}, C{int}
     298        """
     299
     300        size = self.getFileSize()
     301        range = request.getHeader('range')
     302        try:
     303            if range is not None:
     304                bytesrange = string.split(range, '=')
     305                assert bytesrange[0] == 'bytes',\
     306                       "Syntactically invalid http range header!"
     307
     308                start, stop = string.split(bytesrange[1], ',', 1)[0].split('-')
     309               
     310                if start:
     311                    start = int(start)
     312                    if stop:
     313                        stop = int(stop)
     314                    else:
     315                        stop = size
     316                else:
     317                    lastbytes = int(stop)
     318                    if size < lastbytes:
     319                        lastbytes = size
     320                    start = size - lastbytes
     321                    stop = size
     322
     323                file.seek(start)
     324                content_length = stop - start + 1
     325
     326                if content_length <= 0:
     327                    request.setResponseCode(http.REQUESTED_RANGE_NOT_STATISFIABLE)
     328                    content_length = size
     329                    request.method = 'HEAD' # no msg body will be transferred
     330                else:
     331                    request.setResponseCode(http.PARTIAL_CONTENT)
     332                    request.setHeader('content-range', "bytes %s-%s/%s " % (str(start), str(stop), str(size)))
     333
     334                return content_length, stop
     335        except:
     336            traceback.print_exc(file=log.logfile)
     337
     338
    290339    def render(self, request):
    291340        """You know what you doing."""
    292341        self.restat()
     
    303352        if self.isdir():
    304353            return self.redirect(request)
    305354
    306         #for content-length
    307         fsize = size = self.getFileSize()
     355        request.setHeader('accept-ranges','bytes')
    308356
    309 #         request.setHeader('accept-ranges','bytes')
    310 
    311357        if self.type:
    312358            request.setHeader('content-type', self.type)
    313359        if self.encoding:
     
    325371        if request.setLastModified(self.getmtime()) is http.CACHED:
    326372            return ''
    327373
    328 # Commented out because it's totally broken. --jknight 11/29/04
    329 #         try:
    330 #             range = request.getHeader('range')
    331 #
    332 #             if range is not None:
    333 #                 # This is a request for partial data...
    334 #                 bytesrange = string.split(range, '=')
    335 #                 assert bytesrange[0] == 'bytes',\
    336 #                        "Syntactically invalid http range header!"
    337 #                 start, end = string.split(bytesrange[1],'-')
    338 #                 if start:
    339 #                     f.seek(int(start))
    340 #                 if end:
    341 #                     end = int(end)
    342 #                     size = end
    343 #                 else:
    344 #                     end = size
    345 #                 request.setResponseCode(http.PARTIAL_CONTENT)
    346 #                 request.setHeader('content-range',"bytes %s-%s/%s " % (
    347 #                     str(start), str(end), str(size)))
    348 #                 #content-length should be the actual size of the stuff we're
    349 #                 #sending, not the full size of the on-server entity.
    350 #                 fsize = end - int(start)
    351 #
    352 #             request.setHeader('content-length', str(fsize))
    353 #         except:
    354 #             traceback.print_exc(file=log.logfile)
     374        # set the stop byte, and content-length
     375        content_length = stop = self.getFileSize()
     376       
     377        if request.getHeader('range') is not None:
     378           content_length, stop = self._handleRangeRequest(request, f)
    355379
    356         request.setHeader('content-length', str(fsize))
     380        request.setHeader('content-length', str(content_length))
    357381        if request.method == 'HEAD':
    358382            return ''
    359383
    360384        # return data
    361         FileTransfer(f, size, request)
     385        FileTransfer(f, stop, request)
    362386        # and make sure the connection doesn't get closed
    363387        return server.NOT_DONE_YET
    364388
  • test/test_web.py

     
    1515    uri='http://dummy/'
    1616    method = 'GET'
    1717
    18     def getHeader(self, h):
    19         return None
     18    def getHeader(self, name):
     19        return self.headers.get(name.lower(), None)
    2020
    2121    def registerProducer(self, prod,s):
    2222        self.go = 1
     
    3636        self.protoSession = session or server.Session(0, self)
    3737        self.args = {}
    3838        self.outgoingHeaders = {}
     39        self.headers = {}
    3940
    4041    def setHeader(self, name, value):
    4142        """TODO: make this assert on write() if the header is content-length
     
    311312        child_without_ext = f.getChild('AreBelong', dreq)
    312313        self.assertNotEquals(child_without_ext, f.childNotFound)
    313314
     315    def testRangeHeaderSupport(self):
     316        """Testing if the Range-Header support works correctly.
     317        """
     318
     319        import tempfile
     320       
     321        dummy_file = tempfile.NamedTemporaryFile()
     322        payload = '\xf8u\xf3E\x8c7\xce\x00\x9e\xb6a0y0S\xf0\xef\xac\xb7\xbe\xb5\x17M\x1e\x136k{\x1e\xbe\x0c\x07\x07\t\xd0\xbckY\xf5I\x0b\xb8\x88oZ\x1d\x85b\x1a\xcdk\xf2\x1d&\xfd%\xdd\x82q/A\x10Y\x8b'
     323        dummy_file.write(payload)
     324        dummy_file.flush() # write all to disk
     325       
     326        # create a ressource out of it
     327        f = static.File(dummy_file.name)
     328        f.isLeaf = 1
     329
     330        range_req = DummyRequest([''])       
     331        range_req.uri = dummy_file.name
     332        range_req.headers['range'] = 'bytes=23-42'
     333
     334        range_req2 = DummyRequest([''])
     335        range_req2.uri = dummy_file.name
     336        range_req2.headers['range'] = 'bytes=17-'
     337
     338        range_req3 = DummyRequest([''])
     339        range_req3.uri = dummy_file.name
     340        range_req3.headers['range'] = 'bytes=-43'
     341
     342        f.render(range_req)
     343        f.render(range_req2)
     344        f.render(range_req3)
     345
     346        dummy_file.close()
     347        self.assertEqual(''.join(range_req.written), payload[23:42])
     348        self.assertEqual(''.join(range_req2.written), payload[17:])
     349        self.assertEqual(''.join(range_req3.written), payload[-43:])
     350
    314351class DummyChannel:
    315352    class TCP:
    316353        port = 80
     
    535572    clientproto = 'HTTP/1.0'
    536573    sentLength = None
    537574
    538     def __init__(self, *a, **kw):
    539         DummyRequest.__init__(self, *a, **kw)
    540         self.headers = {}
    541 
    542     def getHeader(self, h):
    543         return self.headers.get(h.lower(), None)
    544 
    545575    def getClientIP(self):
    546576        return self.client
    547577