Як працює завантаження файлів HTTP?


527

Коли я надсилаю просту форму на зразок цієї, додається файл:

<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 при надсиланні файлу.


Я не використовував sniffer деякий час, але якщо ви хочете побачити, що надсилається у вашому запиті (оскільки саме на сервер це запит), обнюхайте його. Це питання занадто широке. Так більше стосується конкретних питань програмування.
папараццо

... як нюхають, фіддлер - моя зброя вибору. Ви навіть можете створити власні тестові запити, щоб побачити, як вони публікуються.
Філ Купер

Для тих , хто зацікавлений, а також побачити « MAX_FILE_SIZEв PHP - то , що точка» на stackoverflow.com/q/1381364/632951
Pacerier

Я вважаю MAX_FILE_SIZE дивним. тому що я можу змінити свій html у chrome до 100000000 перед публікацією, щоб він вийшов кращим. 1. Будьте у файлі cookie із захищеним хешем через сіль, тому файли cookie, якщо їх змінено, сервер може перевірити і викинути винятки (як веб-фрагменти або п’єси), або якусь перевірку форми, що все не змінилося. @ 0xSina
Дін Гіллер

Відповіді:


320

Давайте подивимось, що відбувається, коли ви виберете файл і подаєте форму (я скоротив заголовки для стислості):

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

ПРИМІТКА: кожен граничний рядок повинен бути встановлений додатковою --, як і в кінці останнього граничного рядка. Наведений вище приклад уже включає це, але його можна легко пропустити. Дивіться коментар @Andreas нижче.

Замість URL-адреси, що кодує параметри форми, параметри форми (включаючи дані файлу) надсилаються у вигляді розділів у багаточастинному документі в тілі запиту.

У наведеному вище прикладі ви можете побачити вхід MAX_FILE_SIZEіз значенням, встановленим у формі, а також розділ, що містить дані про файл. Назва файлу - частина Content-Dispositionзаголовка.

Повна інформація тут .


7
@ source.rar: Ні. Веб-сервери (майже?) завжди є потоковими, щоб вони могли працювати з одночасними з'єднаннями. По суті, демон-процес, який прослуховує порт 80, негайно передає завдання подачі іншому потоку / процесу, щоб він міг повернутися до прослуховування для іншого з'єднання; навіть якщо два вхідні з'єднання надійдуть точно в той самий момент, вони просто сидять у мережевому буфері, поки демон не буде готовий їх прочитати.
eggyal

10
Пояснення нитки є трохи невірним, оскільки існують високопродуктивні сервери, які розроблені як однопотокові та використовують державну машину для швидкого почергового завантаження пакетів даних із з'єднань. Скоріше, у TCP / IP порт 80 - це порт прослуховування, а не порт, на який передаються дані.
slebetman

9
Коли IP-прослуховувальний сокет (порт 80) приймає з'єднання, інший сокет створюється на іншому порту, як правило, з випадковим числом вище 1000. Потім цей сокет підключається до віддаленого сокета, залишаючи порт 80 вільним для прослуховування нових з'єднань.
slebetman

11
@slebetman Перш за все, це про HTTP. Активний режим FTP тут не застосовується. По-друге, розетка прослуховування не блокується при кожному з'єднанні. Ви можете мати стільки з’єднань з одним портом, скільки інші сторони мають порти, щоб прив’язати їх власний кінець.
Слотос

33
Зауважте, що граничний рядок, який передається як частина поля заголовка Content-Type, на 2 символи коротший, ніж граничні рядки для окремих частин нижче. Я щойно витратив годину, намагаючись з’ясувати, чому мій завантажувач не працює, тому що досить важко помітити, що в першій граничній рядку насправді є лише 4 тире, а в інших граничних рядках. Іншими словами: При використанні граничного рядка для розділення даних про окремі форми він повинен бути префіксований двома тиреми: - Це описано в RFC1867, звичайно, але я думаю, що це слід вказати і тут
Andreas

279

Як він надсилає файл внутрішньо?

Формат називається 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, ncBSD 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=---------------------------735323031399963166993862150встановлює тип вмісту multipart/form-dataта каже, що поля розділені заданим boundaryрядком.

    Але зауважте, що:

    boundary=---------------------------735323031399963166993862150
    

    має два менші тири, --ніж фактичний бар'єр

    -----------------------------735323031399963166993862150
    

    Це тому, що стандарт вимагає, щоб межа починалася з двох тире --. Інші тире виглядають лише як Firefox вирішив реалізувати довільну межу. RFC 7578 чітко зазначає, що --потрібні ці два провідні тире :

    4.1. Параметр "Межі" багаточастинкових / форм-даних

    Як і у інших типів з кількома частинами, деталі розмежовані граничним роздільником, побудованим за допомогою CRLF, "-" та значення параметра "межа".

  • кожне поле отримує деякі підзаголовки перед своїми даними:, Content-Disposition: form-data;поле name, the filename, за якими йдуть дані.

    Сервер зчитує дані до наступного граничного рядка. Веб-переглядач повинен вибрати межу, яка не відображатиметься в жодному з полів, тому межа може залежати від запитів.

    Оскільки у нас є унікальна межа, кодування даних не потрібно: двійкові дані надсилаються як є.

    TODO: який оптимальний розмір межі ( log(N)я ставлю ставку) та ім'я / час виконання алгоритму, який його знаходить? На запитання: /cs/39687/find-the-shortest-sequence-that-is-not-a-sub-sequence-of-a-set-of-sequences

  • Content-Type автоматично визначається браузером.

    Як саме це визначено, запитували на: Як визначається браузер браузером типу mime завантаженого файлу?

додаток / x-www-form-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, ми б не хотіли цього, бо це настільки неефективно.

Але для друкованих символів, знайдених у текстових полях, це не має значення і створює менше накладних витрат, тому ми просто використовуємо це.


1
Як би ви додали бінарний додаток? (Тобто невелике зображення) - я можу бачити зміни значення для Content-Dispositionі Content-Typeатрибутів , але як впоратися з «змістом»?
blurfus

3
@ianbeks Браузер робить це автоматично, перш ніж надсилати запит. Я не знаю, яку евристику вона використовує, але, швидше за все, серед них є розширення файлу. Це може відповісти на питання: stackoverflow.com/questions/1201945 / ...
Чіро Сантіллі郝海东冠状病六四事件法轮功

3
@CiroSantilli 六四 事件 法轮功 纳米比亚 威 视 Я думаю, що ця відповідь набагато краща за обрану. Але видаліть невідповідний вміст зі свого профілю. Це проти духу SO.
smwikipedia

2
@smwikipedia дякую за цитату rfc та за вподобання у цій відповіді! Про ім'я користувача: для мене дух SO полягає в тому, що кожен повинен мати найкращу інформацію в усі часи. ~~ Давайте продовжувати цю дискусію щебетати чи мета. Мир.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

1
@KumarHarsh недостатньо деталей, щоб відповісти, я думаю. Будь ласка, відкрийте нові надзвичайно детальні запитання.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

62

Надіслати файл у вигляді двійкового вмісту (завантажити без форми або FormData)

У наведених відповідях / прикладах файл (швидше за все) завантажується у формі HTML або за допомогою API FormData . Файл - це лише частина даних, що надсилаються в запиті, звідси і 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);

Якщо ви не хочете використовувати форми, і вам цікаво лише завантаження одного файлу, це найпростіший спосіб включити свій файл у запит.


Як налаштувати серверну службу для цього за допомогою Asp.Net 4.0? Чи буде вона також обробляти декілька вхідних параметрів, таких як userId, path, captionText тощо?
Asle G

1
@AsleG Ні, це лише для надсилання одного файлу як вмісту вашого запиту. Я не є експертом Asp.Net, але вам слід просто витягнути вміст (крапку) із запиту та зберегти його у файл за допомогою Content-Typeзаголовка.
Загинув

@AsleG Можливо, це посилання може допомогти
Wilt

@wilt Якщо я не використовую форму, але я хочу використовувати API формаданих даних, чи можу я це зробити?
сердитий ківі

1
@AnkitKhettry Здається, що він завантажений у форму чи за допомогою API форми. Ці "дивні рядки", на які ви посилаєтесь, - це межі форми, які зазвичай використовуються для поділу даних форми на частини на сервері.
Загинув

9

У мене є цей зразок коду Java:

import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;

public class TestClass {
    public static void main(String[] args) throws IOException {
        ServerSocket socket = new ServerSocket(8081);
        Socket accept = socket.accept();
        InputStream inputStream = accept.getInputStream();

        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

Повідомлення HTTP може мати частину даних, що надсилаються після рядків заголовка. У відповідь це запитуваний ресурс повертається клієнтові (найчастіше використовується тіло повідомлення) або, можливо, пояснювальний текст, якщо є помилка. У запиті саме тут вводяться користувачеві дані або завантажені файли на сервер.

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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.