Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 7333332

Browse files
authored
Merge pull request ckan#3756 from smotornyuk/close-temporary-files-after-request
[ckan#3752] CloseWSGIInput middleware + update in ResourceUploader
2 parents a8a8c84 + e14ede2 commit 7333332

3 files changed

Lines changed: 60 additions & 33 deletions

File tree

ckan/config/middleware/common_middleware.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
import urllib2
66
import hashlib
77
import json
8+
import cgi
89

910
import sqlalchemy as sa
11+
from webob.request import FakeCGIBody
1012

1113

1214
class RootPathMiddleware(object):
@@ -28,6 +30,32 @@ def __call__(self, environ, start_response):
2830
return self.app(environ, start_response)
2931

3032

33+
class CloseWSGIInputMiddleware(object):
34+
'''
35+
webob.request.Request has habit to create FakeCGIBody. This leads(
36+
during file upload) to creating temporary files that are not closed.
37+
For long lived processes this means that for each upload you will
38+
spend the same amount of temporary space as size of uploaded
39+
file additionally, until server restart(this will automatically
40+
close temporary files).
41+
42+
This middleware is supposed to close such files after each request.
43+
'''
44+
def __init__(self, app, config):
45+
self.app = app
46+
47+
def __call__(self, environ, start_response):
48+
wsgi_input = environ['wsgi.input']
49+
if isinstance(wsgi_input, FakeCGIBody):
50+
for _, item in wsgi_input.vars.items():
51+
if not isinstance(item, cgi.FieldStorage):
52+
continue
53+
fp = getattr(item, 'fp', None)
54+
if fp is not None:
55+
fp.close()
56+
return self.app(environ, start_response)
57+
58+
3159
class TrackingMiddleware(object):
3260

3361
def __init__(self, app, config):

ckan/config/middleware/pylons_app.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ def make_pylons_stack(conf, full_stack=True, static_files=True,
5757
for plugin in PluginImplementations(IMiddleware):
5858
app = plugin.make_middleware(app, config)
5959

60+
app = common_middleware.CloseWSGIInputMiddleware(app, config)
6061
app = common_middleware.RootPathMiddleware(app, config)
61-
6262
# Routing/Session/Cache Middleware
6363
app = RoutesMiddleware(app, config['routes.map'])
6464
# we want to be able to retrieve the routes middleware to be able to update

ckan/lib/uploader.py

Lines changed: 31 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,30 @@
1515
from ckan.common import config
1616

1717
ALLOWED_UPLOAD_TYPES = (cgi.FieldStorage, FlaskFileStorage)
18+
MB = 1 << 20
19+
1820
log = logging.getLogger(__name__)
1921

2022
_storage_path = None
2123
_max_resource_size = None
2224
_max_image_size = None
2325

2426

27+
def _copy_file(input_file, output_file, max_size):
28+
input_file.seek(0)
29+
current_size = 0
30+
while True:
31+
current_size = current_size + 1
32+
# MB chunks
33+
data = input_file.read(MB)
34+
35+
if not data:
36+
break
37+
output_file.write(data)
38+
if current_size > max_size:
39+
raise logic.ValidationError({'upload': ['File upload too large']})
40+
41+
2542
def _get_underlying_file(wrapper):
2643
if isinstance(wrapper, FlaskFileStorage):
2744
return wrapper.stream
@@ -162,22 +179,14 @@ def upload(self, max_size=2):
162179
max_size is size in MB maximum of the file'''
163180

164181
if self.filename:
165-
output_file = open(self.tmp_filepath, 'wb')
166-
self.upload_file.seek(0)
167-
current_size = 0
168-
while True:
169-
current_size = current_size + 1
170-
# MB chunks
171-
data = self.upload_file.read(2 ** 20)
172-
if not data:
173-
break
174-
output_file.write(data)
175-
if current_size > max_size:
182+
with open(self.tmp_filepath, 'wb+') as output_file:
183+
try:
184+
_copy_file(self.upload_file, output_file, max_size)
185+
except logic.ValidationError:
176186
os.remove(self.tmp_filepath)
177-
raise logic.ValidationError(
178-
{self.file_field: ['File upload too large']}
179-
)
180-
output_file.close()
187+
raise
188+
finally:
189+
self.upload_file.close()
181190
os.rename(self.tmp_filepath, self.filepath)
182191
self.clear = True
183192

@@ -285,24 +294,14 @@ def upload(self, id, max_size=10):
285294
if e.errno != 17:
286295
raise
287296
tmp_filepath = filepath + '~'
288-
output_file = open(tmp_filepath, 'wb+')
289-
self.upload_file.seek(0)
290-
current_size = 0
291-
while True:
292-
current_size = current_size + 1
293-
# MB chunks
294-
data = self.upload_file.read(2 ** 20)
295-
296-
if not data:
297-
break
298-
output_file.write(data)
299-
if current_size > max_size:
297+
with open(tmp_filepath, 'wb+') as output_file:
298+
try:
299+
_copy_file(self.upload_file, output_file, max_size)
300+
except logic.ValidationError:
300301
os.remove(tmp_filepath)
301-
raise logic.ValidationError(
302-
{'upload': ['File upload too large']}
303-
)
304-
305-
output_file.close()
302+
raise
303+
finally:
304+
self.upload_file.close()
306305
os.rename(tmp_filepath, filepath)
307306
return
308307

0 commit comments

Comments
 (0)