Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Ответ
multipart/form-data — это формат передачи данных в HTTP, используемый главным образом для загрузки файлов вместе с другими полями формы. Это один из трёх основных способов отправки данных через HTML формы.
Три типа кодирования данных формы
1. application/x-www-form-urlencoded (по умолчанию):
name=John&age=30&city=Moscow
Прост, но не подходит для файлов.
2. multipart/form-data (для файлов):
------WebKitFormBoundary123
Content-Disposition: form-data; name="name"
John
------WebKitFormBoundary123
Content-Disposition: form-data; name="file"; filename="photo.jpg"
Content-Type: image/jpeg
[бинарные данные файла]
------WebKitFormBoundary123--
3. text/plain (редко используется):
key1=value1
key2=value2
Структура multipart/form-data
Типичный запрос выглядит так:
POST /upload HTTP/1.1
Host: api.example.com
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary123
Content-Length: 1234
------WebKitFormBoundary123
Content-Disposition: form-data; name="username"
john_doe
------WebKitFormBoundary123
Content-Disposition: form-data; name="email"
john@example.com
------WebKitFormBoundary123
Content-Disposition: form-data; name="avatar"; filename="avatar.png"
Content-Type: image/png
Content-Transfer-Encoding: binary
[PNG image data]
------WebKitFormBoundary123
Content-Disposition: form-data; name="documents"; filename="resume.pdf"
Content-Type: application/pdf
Content-Transfer-Encoding: binary
[PDF document data]
------WebKitFormBoundary123--
Ключевые элементы
Boundary — строка-разделитель:
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary123
^
Это уникальный разделитель для этого запроса
Headers для каждого поля:
Content-Disposition: form-data; name="fieldName"
Content-Type: image/jpeg (только для файлов)
Использование в Flutter с Dio
import 'package:dio/dio.dart';
class FileUploadService {
final Dio _dio = Dio();
Future<String> uploadFile(
String filePath,
String username,
String email,
) async {
try {
// Создать FormData
FormData formData = FormData.fromMap({
'username': username,
'email': email,
'avatar': await MultipartFile.fromFile(
filePath,
filename: 'avatar.jpg',
contentType: DioMediaType.parse('image/jpeg'),
),
});
// Отправить
Response response = await _dio.post(
'https://api.example.com/upload',
data: formData,
);
return response.data['success'];
} catch (e) {
print('Upload failed: $e');
rethrow;
}
}
// Загрузить несколько файлов
Future<void> uploadMultipleFiles(
List<String> filePaths,
Map<String, String> formFields,
) async {
try {
Map<String, dynamic> map = Map.from(formFields);
for (int i = 0; i < filePaths.length; i++) {
map['files'] = <MultipartFile>[
for (String path in filePaths)
await MultipartFile.fromFile(path),
];
}
FormData formData = FormData.fromMap(map);
Response response = await _dio.post(
'https://api.example.com/upload-multiple',
data: formData,
);
print('Upload successful: ${response.data}');
} catch (e) {
print('Multi-file upload failed: $e');
rethrow;
}
}
}
Использование в Flutter с http пакетом
import 'package:http/http.dart' as http;
class FileUploadHttp {
Future<void> uploadFile(String filePath, String username) async {
try {
var request = http.MultipartRequest(
'POST',
Uri.parse('https://api.example.com/upload'),
);
// Добавить текстовые поля
request.fields['username'] = username;
request.fields['email'] = 'user@example.com';
// Добавить файл
request.files.add(
await http.MultipartFile.fromPath(
'avatar',
filePath,
contentType: MediaType('image', 'jpeg'),
),
);
// Отправить
var response = await request.send();
if (response.statusCode == 200) {
print('Upload successful');
} else {
print('Upload failed: ${response.statusCode}');
}
} catch (e) {
print('Error: $e');
}
}
}
Обработка на backend (пример с Express.js)
const express = require('express');
const multer = require('multer');
const app = express();
const upload = multer({ dest: 'uploads/' });
// Обработать multipart/form-data
app.post('/upload', upload.single('avatar'), (req, res) => {
// req.file содержит информацию о файле
// req.body содержит текстовые поля
console.log('Username:', req.body.username);
console.log('File:', req.file.filename);
res.json({
success: true,
filename: req.file.filename,
});
});
Обработка на backend (пример с FastAPI/Python)
from fastapi import FastAPI, UploadFile, File, Form
from fastapi.responses import JSONResponse
app = FastAPI()
@app.post("/upload")
async def upload_file(
username: str = Form(...),
email: str = Form(...),
avatar: UploadFile = File(...),
):
# Сохранить файл
contents = await avatar.read()
with open(f"uploads/{avatar.filename}", "wb") as f:
f.write(contents)
return {
"success": True,
"username": username,
"filename": avatar.filename,
}
Отслеживание прогресса загрузки
import 'package:dio/dio.dart';
class ProgressiveUpload {
final Dio _dio = Dio();
Future<void> uploadWithProgress(
String filePath,
Function(int, int) onProgress,
) async {
try {
FormData formData = FormData.fromMap({
'file': await MultipartFile.fromFile(filePath),
});
await _dio.post(
'https://api.example.com/upload',
data: formData,
onSendProgress: (int sent, int total) {
onProgress(sent, total);
print('Progress: ${(sent / total * 100).toStringAsFixed(0)}%');
},
);
} catch (e) {
print('Upload failed: $e');
}
}
}
Использование в UI
class FileUploadPage extends StatefulWidget {
@override
State<FileUploadPage> createState() => _FileUploadPageState();
}
class _FileUploadPageState extends State<FileUploadPage> {
final _uploadService = FileUploadService();
int _uploadProgress = 0;
bool _isUploading = false;
void _selectAndUpload() async {
final result = await FilePicker.platform.pickFiles(type: FileType.image);
if (result != null) {
setState(() => _isUploading = true);
try {
await _uploadService.uploadWithProgress(
result.files.single.path!,
(sent, total) {
setState(() => _uploadProgress = (sent / total * 100).toInt());
},
);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Upload successful!')),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Upload failed: $e')),
);
} finally {
setState(() => _isUploading = false);
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('File Upload')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (_isUploading)
Column(
children: [
LinearProgressIndicator(value: _uploadProgress / 100),
Text('$_uploadProgress%'),
],
)
else
ElevatedButton(
onPressed: _selectAndUpload,
child: Text('Select and Upload File'),
),
],
),
),
);
}
}
Важные моменты
- Boundary генерируется автоматически и должен быть уникален
- Content-Type для файлов определяется автоматически (image/jpeg, application/pdf и т.д.)
- Content-Length вычисляется HTTP клиентом
- Порядок полей не имеет значения
- Можно загружать несколько файлов в одном запросе
- multipart/form-data неэффективен для большого количества маленьких полей — используйте JSON
multipart/form-data — это стандартный и надёжный способ для загрузки файлов в веб-приложениях.