Как работает загрузка HTTP-файла? - PullRequest
454 голосов
/ 28 декабря 2011

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

<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 ]

268 голосов
/ 29 декабря 2011

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

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.

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

226 голосов

Как отправить файл изнутри?

Формат называется multipart/form-data, как указано по адресу: Что означает enctype = 'multipart / form-data'?

Я собираюсь:

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

HTML5 ссылки

Существует три варианта для enctype:

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

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

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

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

Сохраните форму в минимальном .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&#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, потому что ω равно 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) бьюсь об заклад) и имя / время выполнения алгоритма, который его находит? На вопрос: https://cs.stackexchange.com/questions/39687/find-the-shortest-sequence-that-is-not-a-sub-sequence-of-a-set-of-sequences

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

    Как именно это определяется, было задано по адресу: Как браузер определяет тип mime загруженного файла?

* * +1136 применение / х-WWW-форм-urlencoded * * 1 137

Теперь измените 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, мы бы этого не хотели, потому что это так неэффективно.

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

49 голосов
/ 28 января 2015

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

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

8 голосов
/ 29 января 2016

У меня есть этот пример кода 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:

<!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 в нашем любимом браузере, загрузите 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 Body, где на самом деле видны мета и содержимое загруженного нами файла.

6 голосов
/ 28 декабря 2011

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...