実現したいこと
HTMLで記述した画面のアップロードボタン押下後にサーバの指定のディレクトリにPDFをアップロードする処理を行いたい。
・エンドポイント : /api/upload
・アップロード先のディレクトリ : /var/uploads
※確認済みの事項
・エンドポイントのパスは正しく設定されている
・ファイルのバリデーションは正常に動作
・フォームデータは正しく構築されている
発生している問題・分からないこと
アップロードボタン押下時に404エラーが発生
エラーメッセージ
error
1Failed to load resource: the server responded with a status of 404 (Not Found) 2Response status: 404 3Error response: Not Found 4Response is not JSON: SyntaxError: Unexpected token 'N', "Not Found" is not valid JSON
該当のソースコード
Javascript
1document.addEventListener('DOMContentLoaded', function() { 2 // 要素の取得 3 const fileInput = document.getElementById('file-input'); 4 const previewContainer = document.getElementById('preview-container'); 5 const uploadButton = document.getElementById('upload-btn'); 6 const cancelButton = document.getElementById('cancel-btn'); 7 const radioButtons = document.querySelectorAll('input[name="file-type"]'); 8 9 let selectedFile = null; 10 11 // ファイル選択時の処理 12 fileInput.addEventListener('change', () => { 13 const file = fileInput.files[0]; 14 if (!file) { 15 clearUploadForm(); 16 return; 17 } 18 19 // 基本バリデーション 20 if (file.size > 5 * 1024 * 1024) { 21 alert('ファイルサイズが5MBを超えています'); 22 clearUploadForm(); 23 return; 24 } 25 26 if (file.type !== 'application/pdf') { 27 alert('PDFファイルのみアップロード可能です'); 28 clearUploadForm(); 29 return; 30 } 31 32 // プレビュー表示 33 const fileURL = URL.createObjectURL(file); 34 previewContainer.innerHTML = `<object data="${fileURL}" type="application/pdf" width="100%" height="600px"></object>`; 35 selectedFile = file; 36 }); 37 38 // アップロード処理 39 uploadButton.addEventListener('click', async (e) => { 40 try { 41 e.preventDefault(); 42 43 if (!selectedFile) { 44 alert('ファイルが選択されていません'); 45 return; 46 } 47 48 let selectedType = ''; 49 for (const radio of radioButtons) { 50 if (radio.checked) { 51 selectedType = radio.value; 52 break; 53 } 54 } 55 56 if (!selectedType) { 57 alert('ファイルタイプを選択してください'); 58 return; 59 } 60 61 const formData = new FormData(); 62 formData.append('file', selectedFile); 63 formData.append('file_type', selectedType); 64 65 const response = await fetch('/api/upload', { 66 method: 'POST', 67 body: formData 68 }); 69 70 if (!response.ok) { 71 throw new Error(`HTTP error! status: ${response.status}`); 72 } 73 74 const data = await response.json(); 75 alert(data.message || 'アップロード完了'); 76 clearUploadForm(); 77 } catch (error) { 78 alert(`アップロードエラー: ${error.message}`); 79 } 80 }); 81 82 // フォームクリア 83 function clearUploadForm() { 84 fileInput.value = ''; 85 previewContainer.innerHTML = ''; 86 selectedFile = null; 87 radioButtons.forEach(radio => radio.checked = false); 88 } 89 90 // キャンセル処理 91 cancelButton.addEventListener('click', (e) => { 92 e.preventDefault(); 93 clearUploadForm(); 94 }); 95});
Python
1import os 2import json 3import cgi 4import logging 5import datetime 6import configparser 7from sqlalchemy import create_engine, text, MetaData, Table 8from sqlalchemy.exc import SQLAlchemyError 9 10# 基本設定 11logging.basicConfig(filename='/tmp/app.log', level=logging.DEBUG) 12 13def load_config(): 14 try: 15 config = configparser.ConfigParser() 16 config.read('config.env', encoding="utf-8_sig") 17 return config 18 except Exception as e: 19 logging.error(f"Config error: {str(e)}") 20 raise 21 22# アップロード設定 23UPLOAD_FOLDER = '/var/uploads' 24ALLOWED_EXTENSIONS = {'pdf'} 25 26def allowed_file(filename): 27 return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS 28 29def validate_file_type(file_type): 30 return file_type in ['type1', 'type2', 'type3'] 31 32def save_file(file_item, file_type): 33 try: 34 if not file_item.filename: 35 raise ValueError('ファイルが選択されていません') 36 37 filename = os.path.basename(file_item.filename) 38 39 # 基本バリデーション 40 if not all([ 41 validate_file_type(file_type), 42 allowed_file(filename), 43 len(file_item.file.read()) <= 5 * 1024 * 1024 44 ]): 45 raise ValueError('ファイルが要件を満たしていません') 46 47 # ファイル保存 48 file_path = os.path.join(UPLOAD_FOLDER, filename) 49 with open(file_path, 'wb') as f: 50 f.write(file_item.file.read()) 51 52 return filename 53 except Exception as e: 54 logging.error(f"Save error: {str(e)}") 55 raise 56 57def application(environ, start_response): 58 try: 59 headers = [ 60 ('Content-Type', 'application/json'), 61 ('Access-Control-Allow-Origin', '*'), 62 ('Access-Control-Allow-Methods', 'POST, OPTIONS'), 63 ('Access-Control-Allow-Headers', 'Content-Type') 64 ] 65 66 # パスチェック 67 if environ.get('PATH_INFO', '').rstrip('/') != '/api/upload': 68 start_response('404 Not Found', headers) 69 return [json.dumps({'error': 'Not Found'}).encode('utf-8')] 70 71 # OPTIONSリクエスト処理 72 if environ['REQUEST_METHOD'] == 'OPTIONS': 73 start_response('200 OK', headers) 74 return [b''] 75 76 # POSTリクエスト処理 77 elif environ['REQUEST_METHOD'] == 'POST': 78 try: 79 form = cgi.FieldStorage( 80 fp=environ['wsgi.input'], 81 environ=environ, 82 keep_blank_values=True 83 ) 84 85 if 'file' not in form: 86 raise ValueError('ファイルがありません') 87 88 saved_filename = save_file( 89 form['file'], 90 form.getvalue('file_type') 91 ) 92 93 start_response('200 OK', headers) 94 return [json.dumps({ 95 'status': 'success', 96 'message': f'アップロード成功: {saved_filename}' 97 }).encode('utf-8')] 98 99 except ValueError as e: 100 start_response('400 Bad Request', headers) 101 return [json.dumps({'error': str(e)}).encode('utf-8')] 102 103 # その他のメソッド 104 else: 105 start_response('405 Method Not Allowed', headers) 106 return [json.dumps({'error': 'Method Not Allowed'}).encode('utf-8')] 107 108 except Exception as e: 109 start_response('500 Internal Server Error', headers) 110 return [json.dumps({'error': 'Internal Server Error'}).encode('utf-8')]
試したこと・調べたこと
- teratailやGoogle等で検索した
- ソースコードを自分なりに変更した
- 知人に聞いた
- その他
上記の詳細・結果
ログを細かく出力し、原因を探りましたが解決しませんでした。
補足
リクエストからレスポンスの流れは次のようになってます。
① html ⇒ ② nginx ⇒ ③ unit ⇒ ④ python ⇒ ③ unit ⇒ ② nginx ⇒ ① html