Как работает загрузка файлов HTTP?

когда я отправляю такую простую форму с прикрепленным файлом:

<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 при отправке файла.

5 ответов


давайте посмотрим, что происходит, когда вы выбираете файл и отправляете свою форму (я усек заголовки для краткости):

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 со значением, установленным в форме, а также раздел, содержащий данные файла. Имя файла является частью the Content-Disposition заголовок.

полная информация здесь.


как он отправляет файл внутри?

формат называется multipart/form-data, как просил на: что означает enctype='multipart/form-data'?

я:

  • добавить еще несколько ссылок HTML5
  • объяснить почему он прав с формой представить пример

в HTML5 ссылки

здесь три возможности на enctype:

  • x-www-urlencoded
  • multipart/form-data (spec указывает на RFC2388)
  • text-plain. Это "не надежно интерпретируется компьютером", поэтому его никогда не следует использовать в производстве, и мы не будем углубляться в него.

как генерировать примеры

как только вы видите пример каждого метода, становится очевидным, как они работают, и когда вы должны использовать каждый из них.

вы можете привести примеры, используя:

сохранить форму до минимума :

<!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&#x03C9;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&#x03C9;b, что означает aωb, потому что ω is 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.

multipart/form-data

в 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, the filename, а затем данные.

    сервер считывает данные до следующей граничной строки. Браузер должен выбрать границу, которая не будет отображаться ни в одном из полей, поэтому граница может варьироваться между запросами.

    поскольку у нас есть уникальная граница, кодирование данных не требуется: двоичные данные отправляются как есть.

    TODO: каков оптимальный размер границы (log(N) спорим), и имя / время работы алгоритма, который его находит? Спросил: https://cs.stackexchange.com/questions/39687/find-the-shortest-sequence-that-is-not-a-sub-sequence-of-a-set-of-sequences

  • Content-Type автоматически определяется браузером.

    как именно определяется был задан вопрос:как тип MIME файла определяется браузер?

application / x-www-form-urlencoded

изменить enctype to 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 и до 3 байт каждая: %CF%89!

сравнение

загрузка файлов часто содержит много непечатаемых символов (например, изображений), в то время как текстовые формы почти никогда не делают.

из примеров мы видели, что:

  • multipart/form-data: добавляет несколько байтов граничных накладных расходов к сообщению и должен потратить некоторое время на его вычисление, но отправляет каждый байт в одном байт.

  • application/x-www-form-urlencoded: имеет границу одного байта на поле (&), но добавляет линейный коэффициент накладных расходов 3x для каждого непечатаемого символа.

поэтому, даже если бы мы могли отправлять файлы с application/x-www-form-urlencoded, мы бы не хотели, потому что это так неэффективно.

но для печатаемых символов для текстовых полей, это не имеет значения и генерирует меньше накладных расходов, поэтому мы просто используем он.


отправить файл в виде двоичного содержимого (загрузить без формы или 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);

если вы не (хотите) использовать формы, и вас интересует только загрузка одного файла, это самый простой способ включить ваш файл в запрос.


у меня есть этот пример кода 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);
    }
}

и у меня есть этот тест.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>

и, наконец, файл я буду использовать для тестирования, с именем а.дат имеет следующее содержание:

0x39 0x69 0x65

если вы интерпретируете байты выше как символы ASCII или UTF-8, они фактически будут представлять:

9ie

Итак, давайте запустим наш Java-код, откройте


HTTP-сообщение может иметь тело данных, отправленных после строк заголовка. В ответе это место, где запрашиваемый ресурс возвращается клиенту (наиболее распространенное использование тела сообщения) или, возможно, пояснительный текст, если есть ошибка. В запросе на сервер отправляются введенные пользователем данные или загруженные файлы.

http://www.tutorialspoint.com/http/http_messages.htm