You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 14:09:51