Skip to content

Commit de66c61

Browse files
Improve file upload handling with robust error handling and extensive logging
1 parent 07fb5c5 commit de66c61

1 file changed

Lines changed: 97 additions & 27 deletions

File tree

mcc/web/server.py

Lines changed: 97 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -251,43 +251,78 @@ def check():
251251

252252
# Extract file without loading into RAM
253253
try:
254-
app.logger.info(f"Processing upload request with response type: {resp_type}")
255-
app.logger.info(f"Available files in request: {list(request.files.keys())}")
254+
# Print all request information for debugging
255+
app.logger.info(f"Request method: {request.method}")
256+
app.logger.info(f"Request content type: {request.content_type}")
257+
app.logger.info(f"Response type: {resp_type}")
258+
app.logger.info(f"Form data: {list(request.form.keys())}")
259+
app.logger.info(f"Files in request: {list(request.files.keys())}")
256260

257-
uploaded_file = request.files.get('file-upload')
261+
# Check all possible file field names
262+
possible_file_fields = ['file-upload', 'file', 'upload', 'fileUpload']
263+
uploaded_file = None
264+
265+
for field in possible_file_fields:
266+
if field in request.files:
267+
uploaded_file = request.files[field]
268+
app.logger.info(f"Found file in field: {field}")
269+
break
270+
258271
if not uploaded_file:
259-
app.logger.error("No file-upload in request.files")
260-
return abort(400, 'No file uploaded.')
272+
app.logger.error("No file found in any expected field")
273+
return abort(400, 'No file uploaded. Please select a file to check.')
261274

262275
app.logger.info(f"File received: {uploaded_file.filename if uploaded_file else 'None'}")
263276

264277
filename = uploaded_file.filename
265278
if not filename or filename == '':
266279
app.logger.error("Empty filename received")
267-
return abort(400, 'Invalid filename.')
280+
return abort(400, 'Invalid filename. Please select a file with a valid name.')
268281

269-
# Sanitize filename to prevent path traversal
270-
original_filename = filename
271-
filename = os.path.basename(filename)
272-
app.logger.info(f"Sanitized filename: {original_filename} -> {filename}")
282+
# Use a very simple filename sanitization
283+
safe_filename = os.path.basename(filename)
284+
app.logger.info(f"Using filename: {safe_filename}")
273285

274-
# Calculate size using stream pointer without reading content
275-
# This is more memory efficient than loading the file to check its size
286+
# Get file size
276287
try:
277-
uploaded_file.seek(0, os.SEEK_END)
278-
file_size = uploaded_file.tell()
279-
uploaded_file.seek(0) # Reset pointer to beginning of file
288+
# Save to a temporary file first to avoid stream issues
289+
temp_file = os.path.join('/tmp', f"mcc_upload_{int(time.time())}_{safe_filename}")
290+
app.logger.info(f"Saving to temporary file: {temp_file}")
280291

281-
app.logger.info(f"File size: {file_size} bytes ({format_byte_size(file_size)})")
292+
# Ensure /tmp exists and is writable
293+
if not os.path.exists('/tmp'):
294+
app.logger.error("/tmp directory does not exist")
295+
os.makedirs('/tmp', exist_ok=True)
296+
297+
if not os.access('/tmp', os.W_OK):
298+
app.logger.error("/tmp directory is not writable")
299+
return abort(500, 'Server configuration error: temporary directory is not writable')
300+
301+
# Save the file
302+
uploaded_file.save(temp_file)
303+
304+
# Check if file exists and get size
305+
if not os.path.exists(temp_file):
306+
app.logger.error(f"Failed to save file to {temp_file}")
307+
return abort(500, 'Failed to save uploaded file')
308+
309+
file_size = os.path.getsize(temp_file)
310+
app.logger.info(f"File saved successfully, size: {file_size} bytes ({format_byte_size(file_size)})")
282311

283312
if file_size == 0:
313+
os.remove(temp_file)
284314
app.logger.error("Empty file uploaded (zero bytes)")
285-
return abort(400, 'Empty file uploaded.')
315+
return abort(400, 'Empty file uploaded. Please select a valid file.')
316+
317+
# Replace the uploaded_file with the saved file path for further processing
318+
info = {'file': temp_file}
319+
286320
except Exception as e:
287-
app.logger.error(f"Error determining file size: {str(e)}")
288-
return abort(400, 'Could not process uploaded file.')
321+
app.logger.error(f"Error processing uploaded file: {str(e)}")
322+
return abort(400, f'Could not process uploaded file: {str(e)}')
323+
289324
except Exception as e:
290-
app.logger.error(f"Unexpected error during file upload processing: {str(e)}")
325+
app.logger.error(f"Unexpected error during file upload: {str(e)}")
291326
return render_template(
292327
'error.html',
293328
error='Unable to process file',
@@ -306,6 +341,8 @@ def check():
306341
selected_checkers['CF-version'] = request_dict.get('CF-version') or CF.DEFAULT_VERSION
307342
if request_dict.get('GDS2') == 'on':
308343
selected_checkers['GDS2-parameter'] = request_dict.get('GDS2-parameter') or GDS2.DEFAULT_VERSION
344+
345+
app.logger.info(f"Selected checkers: {selected_checkers}")
309346

310347
# ASYNC PATH: For files larger than the threshold (default: 1GB)
311348
if file_size > app.config['LARGE_FILE_THRESHOLD']:
@@ -380,13 +417,46 @@ def check():
380417
)
381418

382419
# SYNC PATH: For smaller files that can be processed immediately
383-
info = parse_post_arguments(request.form, request.files, CHECKERS)
384-
ds_container = get_dataset_from_file(info['file']) # Memory-efficient dataset loading
385-
386-
# Run selected checkers against the dataset
387-
results = []
388-
for checker in info['checkers']:
389-
results.append(checker.run(ds_container['dataset']))
420+
try:
421+
app.logger.info(f"Processing file synchronously: {temp_file}")
422+
# We already have the file saved to temp_file
423+
ds_container = get_dataset_from_file(temp_file) # Memory-efficient dataset loading
424+
app.logger.info(f"Dataset loaded successfully from {temp_file}")
425+
426+
# Initialize checkers based on selected_checkers
427+
checker_instances = []
428+
if 'ACDD-version' in selected_checkers:
429+
app.logger.info(f"Adding ACDD checker with version {selected_checkers['ACDD-version']}")
430+
checker_instances.append(ACDD(selected_checkers['ACDD-version']))
431+
if 'CF-version' in selected_checkers:
432+
app.logger.info(f"Adding CF checker with version {selected_checkers['CF-version']}")
433+
checker_instances.append(CF(selected_checkers['CF-version']))
434+
if 'GDS2-parameter' in selected_checkers:
435+
app.logger.info(f"Adding GDS2 checker with parameter {selected_checkers['GDS2-parameter']}")
436+
checker_instances.append(GDS2(selected_checkers['GDS2-parameter']))
437+
438+
# If no checkers were selected, use all available checkers with default versions
439+
if not checker_instances:
440+
app.logger.info("No checkers selected, using all with default versions")
441+
checker_instances = [ACDD(), CF(), GDS2()]
442+
443+
# Run selected checkers against the dataset
444+
results = []
445+
for checker in checker_instances:
446+
app.logger.info(f"Running checker: {checker.__class__.__name__}")
447+
results.append(checker.run(ds_container['dataset']))
448+
449+
except Exception as e:
450+
app.logger.error(f"Error processing dataset: {str(e)}")
451+
return render_template(
452+
'error.html',
453+
error='Unable to read file',
454+
text="Failed to process the uploaded file.",
455+
description=str(e),
456+
homepage_url=app.config['HomepageURL'],
457+
venue=app.config['Venue'],
458+
mcc_version=mcc_version
459+
), 500
390460

391461
# Get data model and close dataset to free resources
392462
ds_data_model = ds_container['dataset'].data_model

0 commit comments

Comments
 (0)