Когда я отправляю простую форму, например, с прикрепленным файлом:
<form enctype="multipart/form-data" action="http://localhost:3000/upload?upload_progress_id=12344" method="POST">
<input type="hidden" name="MAX_FILE_SIZE" value="100000" />
Choose a file to upload: <input name="uploadedfile" type="file" /><br />
<input type="submit" value="Upload File" />
</form>
Как он отправляет файл внутри? Является ли файл отправленным как часть тела HTTP в качестве данных? В заголовках этого запроса я не вижу ничего, связанного с именем файла.
Мне просто хотелось бы знать внутреннюю работу HTTP при отправке файла.
Ответ 1
Посмотрим, что произойдет, когда вы выберете файл и отправьте форму (я сократил заголовки для краткости):
POST /upload?upload_progress_id=12344 HTTP/1.1
Host: localhost:3000
Content-Length: 1325
Origin: http://localhost:3000
... other headers ...
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryePkpFF7tjBAqx29L
------WebKitFormBoundaryePkpFF7tjBAqx29L
Content-Disposition: form-data; name="MAX_FILE_SIZE"
100000
------WebKitFormBoundaryePkpFF7tjBAqx29L
Content-Disposition: form-data; name="uploadedfile"; filename="hello.o"
Content-Type: application/x-object
... contents of file goes here ...
------WebKitFormBoundaryePkpFF7tjBAqx29L--
Вместо кодировки URL-адресов параметров формы параметры формы (включая данные файла) отправляются как разделы в многостраничном документе в теле запроса.
В приведенном выше примере вы можете увидеть вход MAX_FILE_SIZE
со значением, установленным в форме, а также секцией, содержащей данные файла. Имя файла является частью заголовка Content-Disposition
.
Подробнее... здесь.
Ответ 2
Как он отправляет файл внутри?
Формат называется multipart/form-data
, как задано по адресу: Что делает enctype = 'multipart/form-data 'означает?
Я собираюсь:
- добавить еще несколько ссылок HTML5
- объяснить почему он прав с примером формы
Ссылки HTML5
Есть три возможности для enctype
:
Как сгенерировать примеры
Как только вы увидите пример каждого метода, становится очевидным, как они работают, и когда вы должны использовать каждый из них.
Вы можете создавать примеры, используя:
-
nc -l
или сервер ECHO
- пользовательский агент, например браузер или cURL
Сохраните форму в минимальном файле .html
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<title>upload</title>
</head>
<body>
<form action="http://localhost:8000" method="post" enctype="multipart/form-data">
<p><input type="text" name="text1" value="text default">
<p><input type="text" name="text2" value="aωb">
<p><input type="file" name="file1">
<p><input type="file" name="file2">
<p><input type="file" name="file3">
<p><button type="submit">Submit</button>
</form>
</body>
</html>
Мы устанавливаем текстовое значение по умолчанию aωb
, что означает aωb
, потому что ω
есть U+03C9
, которые являются байтами 61 CF 89 62
в UTF-8.
Создать файлы для загрузки:
echo 'Content of a.txt.' > a.txt
echo '<!DOCTYPE html><title>Content of a.html.</title>' > a.html
# Binary file containing 4 bytes: 'a', 1, 2 and 'b'.
printf 'a\xCF\x89b' > binary
Запустите наш маленький сервер эха:
while true; do printf '' | nc -l 8000 localhost; done
Откройте HTML-код в своем браузере, выберите файлы и нажмите "Отправить" и проверьте терминал.
nc
печатает полученный запрос.
Протестировано: Ubuntu 14.04.3, nc
BSD 1.105, Firefox 40.
многочастная/форма-данные
Отправлено Firefox:
POST / HTTP/1.1
[[ Less interesting headers ... ]]
Content-Type: multipart/form-data; boundary=---------------------------735323031399963166993862150
Content-Length: 834
-----------------------------735323031399963166993862150
Content-Disposition: form-data; name="text1"
text default
-----------------------------735323031399963166993862150
Content-Disposition: form-data; name="text2"
aωb
-----------------------------735323031399963166993862150
Content-Disposition: form-data; name="file1"; filename="a.txt"
Content-Type: text/plain
Content of a.txt.
-----------------------------735323031399963166993862150
Content-Disposition: form-data; name="file2"; filename="a.html"
Content-Type: text/html
<!DOCTYPE html><title>Content of a.html.</title>
-----------------------------735323031399963166993862150
Content-Disposition: form-data; name="file3"; filename="binary"
Content-Type: application/octet-stream
aωb
-----------------------------735323031399963166993862150--
Для двоичного файла и текстового поля байты 61 CF 89 62
(aωb
в UTF-8) отправляются буквально. Вы можете проверить, что с помощью nc -l localhost 8000 | hd
, в котором говорится, что байты:
61 CF 89 62
были отправлены (61
== 'a' и 62
== 'b').
Поэтому ясно, что:
-
Content-Type: multipart/form-data; boundary=---------------------------9051914041544843365972754266
устанавливает тип содержимого multipart/form-data
и говорит, что поля разделены данной строкой boundary
.
-
каждое поле получает некоторые сводные заголовки перед его данными: Content-Disposition: form-data;
, поле name
, filename
, за которым следуют данные.
Сервер считывает данные до следующей граничной строки. Браузер должен выбрать границу, которая не будет отображаться ни в одном из полей, поэтому поэтому граница может меняться между запросами.
Поскольку у нас есть уникальная граница, не требуется кодирование данных: двоичные данные отправляются как есть.
TODO: какой оптимальный размер границы (log(N)
я bet), а также имя/время выполнения алгоритма, который его находит? На вопрос: https://cs.stackexchange.com/questions/39687/find-the-shortest-sequence-that-is-not-a-sub-sequence-of-a-set-of-sequences
-
Content-Type
автоматически определяется браузером.
Как точно определяется вопрос: Как mime-тип загруженного файла определяется браузером?
применение/х-WWW-форм-urlencoded
Теперь измените enctype
на application/x-www-form-urlencoded
, перезагрузите браузер и повторно отправьте.
Отправлено Firefox:
POST / HTTP/1.1
[[ Less interesting headers ... ]]
Content-Type: application/x-www-form-urlencoded
Content-Length: 51
text1=text+default&text2=a%CF%89b&file1=a.txt&file2=a.html&file3=binary
Очевидно, что данные файла не отправлены, а только базовые имена. Поэтому это невозможно использовать для файлов.
Что касается текстового поля, мы видим, что обычные печатные символы, такие как a
и b
, были отправлены в один байт, тогда как непечатаемые, такие как 0xCF
и 0x89
, заняли 3 байта каждый: %CF%89
!
Сравнение
Загрузка файлов часто содержит много непечатаемых символов (например, изображений), в то время как текстовые формы почти никогда не выполняются.
Из приведенных примеров видно, что:
-
multipart/form-data
: добавляет несколько байтов пограничных служебных сообщений в сообщение и должно потратить некоторое время на его вычисление, но отправляет каждый байт в один байт.
-
application/x-www-form-urlencoded
: имеет одну байтовую границу для поля (&
), но добавляет линейный накладной коэффициент 3x для каждого непечатаемого символа.
Поэтому, даже если мы могли бы отправлять файлы с application/x-www-form-urlencoded
, мы бы этого не хотели, потому что это так неэффективно.
Но для печатаемых символов, найденных в текстовых полях, это не имеет значения и создает меньше накладных расходов, поэтому мы просто используем его.
Ответ 3
Отправить файл как двоичный контент (загрузить без формы или FormData)
В приведенных ответах/примерах файл (скорее всего) загружен с помощью HTML-формы или с помощью FormData API. Файл является только частью данных, отправленных в запросе, поэтому заголовок multipart/form-data
Content-Type
.
Если вы хотите отправить файл как единственный контент, вы можете напрямую добавить его в качестве тела запроса, и вы установите заголовок Content-Type
в тип MIME отправляемого файла. Имя файла можно добавить в заголовок Content-Disposition
. Вы можете загрузить так:
var xmlHttpRequest = new XMLHttpRequest();
var file = ...file handle...
var fileName = ...file name...
var target = ...target...
var mimeType = ...mime type...
xmlHttpRequest.open('POST', target, true);
xmlHttpRequest.setRequestHeader('Content-Type', mimeType);
xmlHttpRequest.setRequestHeader('Content-Disposition', 'attachment; filename="' + fileName + '"');
xmlHttpRequest.send(file);
Если вы не используете (хотите) использовать формы, и вас интересует только загрузка одного файла, это самый простой способ включить ваш файл в запрос.
Ответ 4
HTTP-сообщение может содержать тело данных, отправленных после строк заголовка. В ответ на это запрашиваемый ресурс возвращается клиенту (наиболее часто используемое тело сообщения) или, возможно, пояснительный текст, если есть ошибка. В запросе это где пользовательские данные или загруженные файлы отправляются на сервер.
http://www.tutorialspoint.com/http/http_messages.htm
Ответ 5
У меня есть этот пример кода Java:
<!-- language: java -->
import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
public class TestClass {
public static void main(String[] args) throws IOException {
final ServerSocket socket = new ServerSocket(8081);
final Socket accept = socket.accept();
final InputStream inputStream = accept.getInputStream();
final InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
char readChar;
while ((readChar = (char) inputStreamReader.read()) != -1) {
System.out.print(readChar);
}
inputStream.close();
accept.close();
System.exit(1);
}
}
и у меня есть этот файл test.html:
<!-- language: html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>File Upload!</title>
</head>
<body>
<form method="post" action="http://localhost:8081" enctype="multipart/form-data">
<input type="file" name="file" id="file">
<input type="submit">
</form>
</body>
</html>
и, наконец, файл, который я буду использовать для тестирования, с именем a.dat имеет следующий контент:
0x39 0x69 0x65
если вы интерпретируете байты выше как символы ASCII или UTF-8, они будут фактически представлять:
9ie
Итак, давайте запустим наш Java-код, откройте test.html inChrome, загрузите файл a.dat и отправьте форму и посмотрите, что получает наш сервер:
POST / HTTP/1.1
Host: localhost:8081
Connection: keep-alive
Content-Length: 196
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Origin: null
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.97 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary06f6g54NVbSieT6y
DNT: 1
Accept-Encoding: gzip, deflate
Accept-Language: en,en-US;q=0.8,tr;q=0.6
Cookie: JSESSIONID=27D0A0637A0449CF65B3CB20F40048AF
------WebKitFormBoundary06f6g54NVbSieT6y
Content-Disposition: form-data; name="file"; filename="a.dat"
Content-Type: application/octet-stream
9ie
------WebKitFormBoundary06f6g54NVbSieT6y--
Ну, я не удивлен, увидев символы 9ie, потому что мы сказали Java, чтобы они обрабатывали их как символы UTF-8. Вы можете также читать их как сырые байты.
Cookie: JSESSIONID=27D0A0637A0449CF65B3CB20F40048AF
на самом деле является последним заголовком HTTP. После этого появляется тело HTTP, где мета и содержимое загружаемого файла на самом деле можно увидеть.