Package gluon :: Module streamer
[hide private]
[frames] | no frames]

Source Code for Module gluon.streamer

  1  #!/usr/bin/env python 
  2  # -*- coding: utf-8 -*- 
  3   
  4  """ 
  5  This file is part of the web2py Web Framework 
  6  Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> 
  7  License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) 
  8  """ 
  9   
 10  import os 
 11  import stat 
 12  import time 
 13  import re 
 14  import errno 
 15  import rewrite 
 16  from gluon.http import HTTP 
 17  from gluon.contenttype import contenttype 
 18   
 19   
 20  regex_start_range = re.compile('\d+(?=\-)') 
 21  regex_stop_range = re.compile('(?<=\-)\d+') 
 22   
 23  DEFAULT_CHUNK_SIZE = 64 * 1024 
 24   
 25   
26 -def streamer(stream, chunk_size=DEFAULT_CHUNK_SIZE, bytes=None):
27 offset = 0 28 while bytes is None or offset < bytes: 29 if not bytes is None and bytes - offset < chunk_size: 30 chunk_size = bytes - offset 31 data = stream.read(chunk_size) 32 length = len(data) 33 if not length: 34 break 35 else: 36 yield data 37 if length < chunk_size: 38 break 39 offset += length 40 stream.close()
41 42
43 -def stream_file_or_304_or_206( 44 static_file, 45 chunk_size=DEFAULT_CHUNK_SIZE, 46 request=None, 47 headers={}, 48 status=200, 49 error_message=None 50 ):
51 if error_message is None: 52 error_message = rewrite.THREAD_LOCAL.routes.error_message % 'invalid request' 53 try: 54 fp = open(static_file) 55 except IOError, e: 56 if e[0] == errno.EISDIR: 57 raise HTTP(403, error_message, web2py_error='file is a directory') 58 elif e[0] == errno.EACCES: 59 raise HTTP(403, error_message, web2py_error='inaccessible file') 60 else: 61 raise HTTP(404, error_message, web2py_error='invalid file') 62 else: 63 fp.close() 64 stat_file = os.stat(static_file) 65 fsize = stat_file[stat.ST_SIZE] 66 modified = stat_file[stat.ST_MTIME] 67 mtime = time.strftime('%a, %d %b %Y %H:%M:%S GMT', time.gmtime(modified)) 68 headers.setdefault('Content-Type', contenttype(static_file)) 69 headers.setdefault('Last-Modified', mtime) 70 headers.setdefault('Pragma', 'cache') 71 headers.setdefault('Cache-Control', 'private') 72 73 # if this is a normal response and not a respnse to an error page 74 if status == 200: 75 if request and request.env.http_if_modified_since == mtime: 76 raise HTTP(304, **{'Content-Type': headers['Content-Type']}) 77 78 elif request and request.env.http_range: 79 start_items = regex_start_range.findall(request.env.http_range) 80 if not start_items: 81 start_items = [0] 82 stop_items = regex_stop_range.findall(request.env.http_range) 83 if not stop_items or int(stop_items[0]) > fsize - 1: 84 stop_items = [fsize - 1] 85 part = (int(start_items[0]), int(stop_items[0]), fsize) 86 bytes = part[1] - part[0] + 1 87 try: 88 stream = open(static_file, 'rb') 89 except IOError, e: 90 if e[0] in (errno.EISDIR, errno.EACCES): 91 raise HTTP(403) 92 else: 93 raise HTTP(404) 94 stream.seek(part[0]) 95 headers['Content-Range'] = 'bytes %i-%i/%i' % part 96 headers['Content-Length'] = '%i' % bytes 97 status = 206 98 # in all the other cases (not 304, not 206, but 200 or error page) 99 if status != 206: 100 enc = request.env.http_accept_encoding 101 if enc and 'gzip' in enc and not 'Content-Encoding' in headers: 102 gzipped = static_file + '.gz' 103 if os.path.isfile(gzipped) and os.path.getmtime(gzipped) >= modified: 104 static_file = gzipped 105 fsize = os.path.getsize(gzipped) 106 headers['Content-Encoding'] = 'gzip' 107 headers['Vary'] = 'Accept-Encoding' 108 try: 109 stream = open(static_file, 'rb') 110 except IOError, e: 111 # this better does not happer when returning an error page ;-) 112 if e[0] in (errno.EISDIR, errno.EACCES): 113 raise HTTP(403) 114 else: 115 raise HTTP(404) 116 headers['Content-Length'] = fsize 117 bytes = None 118 if request and request.env.web2py_use_wsgi_file_wrapper: 119 wrapped = request.env.wsgi_file_wrapper(stream, chunk_size) 120 else: 121 wrapped = streamer(stream, chunk_size=chunk_size, bytes=bytes) 122 raise HTTP(status, wrapped, **headers)
123