I'm building a Flask web application where users can record and submit 30 seconds of audio (WebM format) through a form. However, when I try to submit a 30-second recording (~650 KB), I get a 413 Request Entity Too Large error.
I've tried the following:
Set MAX_CONTENT_LENGTH in Flask:
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024 # 100MB limit
Running Flask with Waitress:
from waitress import serveserve
(app, host="127.0.0.1", port=5000, max_request_body_size=100 * 1024 * 1024)
Testing Gunicorn with gunicorn_config.py:
limit_request_field_size = 0
limit_request_line = 0
timeout = 300
worker_connections = 1000
Despite these changes, submitting larger requests (30 seconds of audio) still triggers the 413 error.
Here’s what I’ve ruled out:
Code Snippets
Frontend (JavaScript) – Recording and Submitting Audio
function processRecording() {
let audioBlob = new Blob(audioChunks, { type: 'audio/webm' });
let reader = new FileReader();
reader.readAsDataURL(audioBlob);
reader.onloadend = function () {
audioDataInput.value = reader.result;
document.querySelector('form').submit();
};
}
Backend (Flask Route for Submitting Audio)
@app.route('/speaking_task_submit', methods=['POST'])
def speaking_task_submit():
print(f"Flask MAX_CONTENT_LENGTH: {app.config.get('MAX_CONTENT_LENGTH')} bytes")
print(f"Received Content-Length: {request.content_length} bytes")
if request.content_length and request.content_length > app.config.get('MAX_CONTENT_LENGTH'):
return jsonify({"error": "Request size exceeded!"}), 413
audio_data = request.form['audio_data']
if not audio_data or "," not in audio_data:
return jsonify({"error": "Invalid or missing audio data"}), 400
# Save audio as .webm
audio_content = audio_data.split(",")[1]
audio_file_path = f"uploads/candidate_audio.webm"
with open(audio_file_path, "wb") as audio_file:
audio_file.write(base64.b64decode(audio_content))
return jsonify({"message": "Audio received successfully"})
Environment Details:
What I’ve Tried:
Question:
Any help is greatly appreciated!
The error message results from the way you upload the data. I use the following code to upload the blob directly in a form as a file and have no problems.
The method used is more efficient for larger audio files, as FormData objects have no size restrictions and no decoding is required. The data is sent in binary format and is therefore smaller than base64 encoded data.
from flask import (
Flask,
abort,
current_app,
render_template,
redirect,
request,
stream_with_context,
url_for
)
from collections import namedtuple
from glob import glob
from mimetypes import add_type, guess_extension, guess_type
from werkzeug.utils import secure_filename
import os
add_type('audio/aac', '.m4a', strict=True)
Record = namedtuple('Record', ('filename', 'created_at'))
app = Flask(__name__)
app.config.from_mapping(
MAX_CONTENT_LENGTH=100*1024*1024,
UPLOAD_FOLDER='uploads',
)
os.makedirs(
os.path.join(
app.instance_path,
app.config.get('UPLOAD_FOLDER', 'uploads')
),
exist_ok=True
)
@app.route('/')
def audio_index():
patterns = [
'*.m4a',
'*.wav',
'*.weba'
]
path = os.path.join(
current_app.instance_path,
current_app.config.get('UPLOAD_FOLDER', 'uploads')
)
records = [
Record(fn[len(path)+1:], os.path.getctime(fn)) \
for ptrn in patterns for fn in glob(os.path.join(path, ptrn))
]
return render_template('index.html', records=records)
@app.post('/audio-upload')
def audio_upload():
if 'audio_file' in request.files:
file = request.files['audio_file']
extname = guess_extension(file.mimetype)
if not extname:
abort(400)
# Check for allowed file extensions.
i = 1
while True:
dst = os.path.join(
current_app.instance_path,
current_app.config.get('UPLOAD_FOLDER', 'uploads'),
secure_filename(f'audio_record_{i}{extname}'))
if not os.path.exists(dst): break
i += 1
file.save(dst)
return redirect(url_for('audio_index'))
@app.route('/audio/stream/<path:filename>')
def audio_download(filename):
@stream_with_context
def generator(src):
CHUNK_SIZE = 8*1024
with open(src, 'rb') as fp:
while True:
data = fp.read(CHUNK_SIZE)
if not data: break
yield data
src = os.path.join(
current_app.instance_path,
current_app.config.get('UPLOAD_FOLDER', 'uploads'),
filename
)
if not os.path.exists(src):
return abort(404)
mime,_ = guess_type(src)
return app.response_class(generator(src), mimetype=mime)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Audio Index</title>
</head>
<body>
<div class="rec-container">
<div class="rec-column rec-column-1">
<button class="rec-btn" id="toggle-rec-btn">Record</button>
</div>
<div class="rec-column rec-column-2">
{% for record in records|sort(attribute='created_at', reverse=True) -%}
<div class="rec-item">
<div class="content">
<span class="rec-title">{{ record.filename }}</span>
<audio
controls
src="{{ url_for('audio_download', filename=record.filename) }}"
></audio>
</div>
</div>
{% endfor -%}
</div>
</div>
<script type="text/javascript">
(function(uploadURL) {
const startButton = document.getElementById('toggle-rec-btn');
startButton.addEventListener('click', function() {
if (!navigator.mediaDevices) {
console.error('MediaDevices not supported.')
return;
}
const mimeType = 'audio/webm;codecs=opus';
if (!MediaRecorder.isTypeSupported(mimeType)) {
console.error('Unsupported media type.');
return;
}
const constraints = { audio: true };
navigator.mediaDevices.getUserMedia(constraints)
.then(stream => {
let chunks = []
const options = { mimeType: mimeType };
const recorder = new MediaRecorder(stream, options);
recorder.ondataavailable = event => {
chunks.push(event.data);
};
recorder.onstop = event => {
console.log('Recording stopped.');
const blob = new Blob(chunks, { type: recorder.mimeType });
chunks = [];
startButton.disabled = false;
const formData = new FormData();
formData.append('audio_file', blob);
fetch(uploadURL, {
method: 'POST',
cache: 'no-cache',
body: formData
}).then(resp => {
if (!resp.ok) {
throw new Error('Something went wrong.');
}
window.location.reload(true);
}).catch(err => {
console.error(err);
});
};
recorder.onstart = event => {
console.log('Recording started.');
startButton.disabled = true;
setTimeout(function() { recorder.stop(); }, 30000);
};
recorder.start();
})
.catch(err => console.error(err));
});
})({{ url_for('audio_upload') | tojson }});
</script>
</body>
</html>