Chrome浏览器GET请求不支持错误排查(TrustProxies相关)
Chrome浏览器专属音频保存异常排查方案
异常概述
- 仅Chrome浏览器中保存录制音频时,抛出错误:
The GET method is not supported for route crea/attività/professionale. Supported methods: PATCH,其他浏览器功能正常。 - 错误定位在
trustproxies.php文件的return $next($request)代码行。
相关代码片段
路由配置
Route::middleware('auth', 'admin')->group(function () { Route::patch('/salva/attivita/specifica', [CreaAttivitàController::class, 'store'])->middleware(['auth', 'verified'])->name('salva.attività'); Route::patch('/crea/attività/professionale', [CreaAttivitàController::class, 'indexProfessionale'])->middleware(['auth', 'verified'])->name('update.manutenzione.professionale'); Route::post('/salva/attività/professionale', [CreaAttivitàController::class, 'storeProfessionale'])->middleware(['auth', 'verified'])->name('salva.attività.professionale'); Route::patch('/modifica/attività/professionale', [CreaAttivitàController::class, 'modificaProfessionale'])->middleware(['auth', 'verified'])->name('modifica.attività.professionale'); });
表单代码
<form action="{{ route('salva.attività.professionale') }}" method="POST" enctype="multipart/form-data"> @csrf @method('POST') <input type="button" id="recordButton" /> <input type="file" id="hiddenAudioInput" name="audios[]" multiple hidden /> </form>
控制器处理方法
public function storeProfessionale(Request $request) { $area = $request->area; $qr_linea = $request->qr_linea; $linea = Linee::where('qr_code', $qr_linea)->value('nome_linea'); $qr_blocco = $request->qr_blocco; $blocco = BloccoLinee::where('qr_code', $qr_blocco)->value('nome_blocco'); $qr_macchina = $request->qr_macchina; $macchina = $request->macchina; $frequenza = "personalizzata"; $ciclo = $request->ciclo; $start = $request->start; $durata = $request->durata; $stato = "Da fare"; $step_id_value = $request->step_id; $step = $request->step; $permessi_1 = $request->checked; $permessi = array($permessi_1); $attività_id = Attivita::create([ 'area' => $area, 'linea' => $linea, 'blocco_linea' => $blocco, 'macchina' => $macchina, 'frequenza' => $frequenza, 'ciclo' => $ciclo, 'start' => $start, 'stato' => $stato, 'qr_code' => $qr_macchina, 'qr_macchina' => $qr_macchina, 'qr_blocco' => $qr_blocco, 'qr_linea' => $qr_linea, 'durata' => $durata ])->id; Attivita::where('id', $attività_id)->update(['permessi' => $permessi]); $step_id = Step::create([ 'attivita_id' => $attività_id, 'step_id' => $step_id_value, 'step' => $step ])->id; $request->validate([ 'audios.*' => 'nullable|file|mimes:ogg,mp3,m4a,mp4,mpeg,mpga,wav,aac', ]); if ($request->hasFile('audios')) { foreach ($request->file('audios') as $audio) { $audioName = $audio->hashName(); $audio->storeAs('audio', $audioName, 'private'); Audio::create([ 'attivita_id' => $attività_id, 'step_id' => $step_id, 'audio' => htmlspecialchars($audioName, ENT_QUOTES, 'UTF-8') ]); } } $notifiche = Aree::sum('attività'); $item_step = Attivita::with(['step']) ->where('id', $attività_id) ->first(); $step_count = Step::where('attivita_id', $attività_id)->get()->last()->step_id + 1; $macchina_item = Macchine::where('qr_code', $qr_macchina)->get(); return $this->indexProfessionale($request)->with(['notifiche' => $notifiche, 'macchina' => $macchina_item, 'data_frequenza' => $start, 'ciclo' => $ciclo, 'step' => $step_count, 'item_step' => $item_step]) ->withErrors(['msgSuccess' => "Step creato correttamente"]); }
前端音频处理JS
let mediaRecorder; let audioChunks = []; const recordButton = document.getElementById('recordButton'); const audioGallery = document.getElementById('audioGallery'); recordButton.addEventListener('click', async () => { if (!mediaRecorder) { try { const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); mediaRecorder = new MediaRecorder(stream); mediaRecorder.ondataavailable = event => { audioChunks.push(event.data); }; mediaRecorder.onstop = () => { const audioBlob = new Blob(audioChunks, { type: 'audio/webm' }); const audioUrl = URL.createObjectURL(audioBlob); audioChunks = []; addAudioToGallery(audioUrl); }; mediaRecorder.start(); recordButton.classList.add('recording'); recordButton.textContent = 'Ferma Registrazione'; } catch (error) { console.error('Errore durante la registrazione:', error); alert('Error: ' + error.message); } } else { mediaRecorder.stop(); recordButton.classList.remove('recording'); recordButton.textContent = 'Avvia Registrazione'; mediaRecorder = null; } }); function addAudioToGallery(audioUrl) { const audioItem = document.createElement('div'); audioItem.classList.add('audio-item'); const audioElement = document.createElement('audio'); audioElement.controls = true; audioElement.src = audioUrl; audioElement.name = "audios[]"; const removeBtn = document.createElement('button'); removeBtn.classList.add('remove-btn-record'); const imgDelete = document.createElement('img'); imgDelete.src = "/css/external/Icone/Delete.svg"; imgDelete.id = "delete"; imgDelete.alt = "Rimuovi"; removeBtn.appendChild(imgDelete); removeBtn.onclick = () => { audioGallery.removeChild(audioItem); removeAudioFromInput(audioUrl); }; audioItem.appendChild(audioElement); audioItem.appendChild(removeBtn); audioGallery.appendChild(audioItem); addAudioToInput(audioUrl); } function addAudioToInput(audioUrl) { const hiddenInput = document.getElementById('hiddenAudioInput'); fetch(audioUrl) .then(response => { if (!response.ok) { throw new Error('Network response was not ok: ' + response.statusText); } return response.blob(); }) .then(blob => { const file = new File([blob], 'audio_' + Date.now() + '.wav', { type: 'audio/wav' }); const dataTransfer = new DataTransfer(); for (const file of hiddenInput.files) { dataTransfer.items.add(file); } dataTransfer.items.add(file); hiddenInput.files = dataTransfer.files; }) .catch(error => { console.error('Error fetch dell\'audio:', error); }); } function removeAudioFromInput(audioUrl) { const hiddenInput = document.getElementById('hiddenAudioInput'); const dataTransfer = new DataTransfer(); for (const file of hiddenInput.files) { if (!file.name.includes(audioUrl)) { dataTransfer.items.add(file); } } hiddenInput.files = dataTransfer.files; }
TrustProxies中间件
/** * The trusted proxies for this application. * * @var array<int, string>|string|null */ protected $proxies; /** * The headers that should be used to detect proxies. * * @var int */ protected $headers = Request::HEADER_X_FORWARDED_FOR | Request::HEADER_X_FORWARDED_HOST | Request::HEADER_X_FORWARDED_PORT | Request::HEADER_X_FORWARDED_PROTO | Request::HEADER_X_FORWARDED_AWS_ELB; /** * Add Content Security Policy headers to accept everything already present in your Laravel app. * * @param \Illuminate\Http\Request $request * @param \Closure $next * @return mixed */ public function handle($request, Closure $next) { $response = $next($request); $response->headers->set('Content-Security-Policy', "default-src\'self\'"); $response->headers->add([ 'Content-Security-Policy' => "default-src\ 'self'; object-src 'self'; base-uri 'self'; report-uri 'self';", ]); $response->headers->set('Permissions-Policy', "geolocation=()"); $response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains'); $response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin'); return $response; }
排查与修复方案
1. 修复CSP头部重复冲突
TrustProxies中间件中重复设置Content-Security-Policy会导致Chrome浏览器解析异常,合并规则并移除重复设置:
public function handle($request, Closure $next) { $response = $next($request); // 合并CSP规则,避免重复设置引发冲突 $response->headers->set('Content-Security-Policy', "default-src 'self'; object-src 'self'; base-uri 'self'; report-uri 'self';"); $response->headers->set('Permissions-Policy', "geolocation=()"); $response->headers->set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains'); $response->headers->set('Referrer-Policy', 'strict-origin-when-cross-origin'); return $response; }
2. 移除表单冗余方法声明
表单已指定method="POST",无需额外添加@method('POST'),避免Chrome解析异常:
<form action="{{ route('salva.attività.professionale') }}" method="POST" enctype="multipart/form-data"> @csrf <input type="button" id="recordButton" /> <input type="file" id="hiddenAudioInput" name="audios[]" multiple hidden /> </form>
3. 优化音频文件匹配逻辑
removeAudioFromInput中通过文件名匹配不可靠,改为自定义属性标记文件:
function addAudioToInput(audioUrl) { const hiddenInput = document.getElementById('hiddenAudioInput'); fetch(audioUrl) .then(response => { if (!response.ok) throw new Error('Network response was not ok: ' + response.statusText); return response.blob(); }) .then(blob => { const fileId = Date.now(); const file = new File([blob], `audio_${fileId}.wav`, { type: 'audio/wav' }); // 添加自定义属性用于后续匹配 file.audioId = fileId; const dataTransfer = new DataTransfer(); Array.from(hiddenInput.files).forEach(f => dataTransfer.items.add(f)); dataTransfer.items.add(file); hiddenInput.files = dataTransfer.files; // 存储audioUrl与fileId的映射 hiddenInput.audioMap = hiddenInput.audioMap || {}; hiddenInput.audioMap[audioUrl] = fileId; }) .catch(error => console.error('Error fetch dell\'audio:', error)); } function removeAudioFromInput(audioUrl) { const hiddenInput = document.getElementById('hiddenAudioInput'); if (!hiddenInput.audioMap || !hiddenInput.audioMap[audioUrl]) return; const targetId = hiddenInput.audioMap[audioUrl]; const dataTransfer = new DataTransfer(); Array.from(hiddenInput.files).forEach(file => { if (file.audioId !== targetId) { dataTransfer.items.add(file); } }); hiddenInput.files = dataTransfer.files; delete hiddenInput.audioMap[audioUrl]; }
4. 验证表单提交逻辑
检查是否存在JS代码意外修改表单提交方法的情况,确保表单始终以POST方式提交。
内容的提问来源于stack exchange,提问作者Laboratoriodigitaleicr
相关产品推荐
相关产品推荐

