New support for nginx's X-Accel-Redirect for downloading files. Also made Files.thumbnail() non-streaming.
This commit is contained in:
parent
503625bdea
commit
3013beddbd
|
@ -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": {
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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 )
|
||||
|
|
|
@ -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 )
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
Reference in New Issue