witten
/
luminotes
Archived
1
0
Fork 0

New support for nginx's X-Accel-Redirect for downloading files. Also made Files.thumbnail() non-streaming.

This commit is contained in:
Dan Helfman 2009-03-18 15:11:46 -07:00
parent 503625bdea
commit 3013beddbd
5 changed files with 118 additions and 27 deletions

View File

@ -23,6 +23,7 @@ settings = {
"luminotes.https_proxy_ip": "127.0.0.2",
"luminotes.db_host": "localhost", # hostname for PostgreSQL or None (no quotes) for SQLite
"luminotes.db_ssl_mode": "allow", # "disallow", "allow", "prefer", or "require"
"luminotes.web_server": "", # "", "apache", or "nginx" to use specific server support (optional)
"luminotes.support_email": "",
"luminotes.payment_email": "",
"luminotes.rate_plans": [
@ -136,7 +137,6 @@ settings = {
"encoding_filter.on": False,
},
"/files/thumbnail": {
"stream_response": True,
"encoding_filter.on": False,
},
"/files/image": {

View File

@ -66,8 +66,8 @@ def expose( view = None, rss = None ):
cherrypy.root.report_traceback()
result = dict( error = u"An error occurred when processing your request. Please try again or contact support." )
# if the result is a generator, it's streaming data, so just let CherryPy handle it
if isinstance( result, types.GeneratorType ):
# if the result is a generator or a string, it's streaming data or just data, so just let CherryPy handle it
if isinstance( result, ( types.GeneratorType, basestring ) ):
return result
redirect = result.get( u"redirect" )
@ -83,18 +83,17 @@ def expose( view = None, rss = None ):
# try using the supplied view to render the result
try:
if view_override is None:
if rss and use_rss:
cherrypy.response.headers[ u"Content-Type" ] = u"application/xml"
return render( rss, result, encoding or "utf8" )
elif view:
return render( view, result, encoding )
elif result.get( "view" ):
result_view = result.get( "view" )
del( result[ "view" ] )
return render( result_view, result, encoding )
else:
if view_override is not None:
return render( view_override, result, encoding )
elif rss and use_rss:
cherrypy.response.headers[ u"Content-Type" ] = u"application/xml"
return render( rss, result, encoding or "utf8" )
elif view:
return render( view, result, encoding )
elif result.get( "view" ):
result_view = result.get( "view" )
del( result[ "view" ] )
return render( result_view, result, encoding )
except:
if redirect is None:
if original_error:

View File

@ -244,7 +244,7 @@ class Files( object ):
"""
Controller for dealing with uploaded files, corresponding to the "/files" URL.
"""
def __init__( self, database, users, download_products ):
def __init__( self, database, users, download_products, web_server ):
"""
Create a new Files object.
@ -254,12 +254,15 @@ class Files( object ):
@param users: controller for all users
@type download_products: [ { "name": unicode, ... } ]
@param download_products: list of configured downloadable products
@type web_server: unicode
@param web_server: front-end web server (determines specific support for various features)
@rtype: Files
@return: newly constructed Files
"""
self.__database = database
self.__users = users
self.__download_products = download_products
self.__web_server = web_server
@expose()
@weakly_expire
@ -312,9 +315,14 @@ class Files( object ):
cherrypy.response.headerMap[ u"Content-Disposition" ] = 'attachment; filename="%s"' % filename
cherrypy.response.headerMap[ u"Content-Length" ] = db_file.size_bytes
if self.__web_server == u"nginx":
cherrypy.response.headerMap[ u"X-Accel-Redirect" ] = "/download/%s" % file_id
return ""
def stream():
CHUNK_SIZE = 8192
local_file = Upload_file.open_file( file_id )
local_file.seek(0)
while True:
data = local_file.read( CHUNK_SIZE )
@ -364,9 +372,14 @@ class Files( object ):
cherrypy.response.headerMap[ u"Content-Disposition" ] = 'attachment; filename="%s"' % public_filename
cherrypy.response.headerMap[ u"Content-Length" ] = os.path.getsize( local_filename )
if self.__web_server == u"nginx":
cherrypy.response.headerMap[ u"X-Accel-Redirect" ] = "/download_product/%s" % product[ u"filename" ]
return ""
def stream():
CHUNK_SIZE = 8192
local_file = file( local_filename, "rb" )
local_file.seek(0)
while True:
data = local_file.read( CHUNK_SIZE )
@ -463,15 +476,7 @@ class Files( object ):
image.save( image_buffer, "PNG" )
image_buffer.seek( 0 )
def stream( image_buffer ):
CHUNK_SIZE = 8192
while True:
data = image_buffer.read( CHUNK_SIZE )
if len( data ) == 0: break
yield data
return stream( image_buffer )
return image_buffer.getvalue()
@expose()
@weakly_expire
@ -501,9 +506,14 @@ class Files( object ):
cherrypy.response.headerMap[ u"Content-Type" ] = db_file.content_type
if self.__web_server == u"nginx":
cherrypy.response.headerMap[ u"X-Accel-Redirect" ] = "/download/%s" % file_id
return ""
def stream():
CHUNK_SIZE = 8192
local_file = Upload_file.open_file( file_id )
local_file.seek(0)
while True:
data = local_file.read( CHUNK_SIZE )

View File

@ -57,6 +57,7 @@ class Root( object ):
database,
self.__users,
settings[ u"global" ].get( u"luminotes.download_products", [] ),
settings[ u"global" ].get( u"luminotes.web_server", "" ),
)
self.__notebooks = Notebooks( database, self.__users, self.__files, settings[ u"global" ].get( u"luminotes.https_url", u"" ) )
self.__forums = Forums( database, self.__notebooks, self.__users )

View File

@ -137,9 +137,14 @@ class Test_files( Test_controller ):
os.remove( u"products/test.exe" )
def test_download( self, filename = None, quote_filename = None, file_data = None, preview = None ):
def test_download( self, filename = None, quote_filename = None, file_data = None, preview = None, expected_file_data = None ):
self.login()
if expected_file_data is None:
expected_file_data = file_data
if file_data is None:
expected_file_data = self.file_data
self.http_upload(
"/files/upload?file_id=%s" % self.file_id,
dict(
@ -191,8 +196,17 @@ class Test_files( Test_controller ):
if u"session_storage" not in str( exc ):
raise exc
file_data = "".join( pieces )
assert file_data == ( file_data or self.file_data )
received_file_data = "".join( pieces )
assert received_file_data == expected_file_data
return result
def test_download_with_nginx( self ):
cherrypy.root.files._Files__web_server = u"nginx"
result = self.test_download( self.filename, expected_file_data = "" )
headers = result[ u"headers" ]
assert headers[ u"X-Accel-Redirect" ] == u"/download/%s" % self.file_id
def test_download_with_unicode_filename( self ):
self.test_download( self.unicode_filename )
@ -372,6 +386,44 @@ class Test_files( Test_controller ):
file_data = "".join( pieces )
assert file_data == self.file_data
def test_download_product_with_nginx( self ):
cherrypy.root.files._Files__web_server = u"nginx"
access_id = u"wheeaccessid"
item_number = u"5000"
transaction_id = u"txn"
self.login()
download_access = Download_access.create( access_id, item_number, transaction_id )
self.database.save( download_access )
result = self.http_get(
"/files/download_product?access_id=%s" % access_id,
session_id = self.session_id,
)
headers = result[ u"headers" ]
assert headers
assert headers[ u"Content-Type" ] == u"application/octet-stream"
filename = u"test.exe".encode( "utf8" )
assert headers[ u"Content-Disposition" ] == 'attachment; filename="%s"' % filename
assert headers[ u"X-Accel-Redirect" ] == u"/download_product/test.exe"
gen = result[ u"body" ]
assert isinstance( gen, types.GeneratorType )
pieces = []
try:
for piece in gen:
pieces.append( piece )
except AttributeError, exc:
if u"session_storage" not in str( exc ):
raise exc
file_data = "".join( pieces )
assert file_data == u""
def test_download_product_without_login( self ):
access_id = u"wheeaccessid"
item_number = u"5000"
@ -924,6 +976,35 @@ class Test_files( Test_controller ):
assert "".join( result[ u"body" ] ) == self.IMAGE_DATA
def test_image_with_nginx( self ):
cherrypy.root.files._Files__web_server = u"nginx"
self.login()
self.http_upload(
"/files/upload?file_id=%s" % self.file_id,
dict(
notebook_id = self.notebook.object_id,
note_id = self.note.object_id,
),
filename = self.filename,
file_data = self.IMAGE_DATA,
content_type = self.content_type,
session_id = self.session_id,
)
result = self.http_get(
"/files/image?file_id=%s" % self.file_id,
session_id = self.session_id,
)
headers = result[ u"headers" ]
assert headers
assert headers[ u"Content-Type" ] == self.content_type
assert u"Content-Disposition" not in headers
assert headers[ u"X-Accel-Redirect" ] == u"/download/%s" % self.file_id
assert "".join( result[ u"body" ] ) == u""
def test_image_with_non_image( self ):
self.login()