Bagaimana cara kerja unggah file HTTP?

528

Ketika saya mengirimkan formulir sederhana seperti ini dengan file terlampir:

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

Bagaimana cara mengirim file secara internal? Apakah file dikirim sebagai bagian dari badan HTTP sebagai data? Di tajuk permintaan ini, saya tidak melihat apa pun yang terkait dengan nama file.

Saya hanya ingin mengetahui cara kerja internal HTTP saat mengirim file.

0xSina
sumber
Saya belum pernah menggunakan sniffer untuk sementara waktu tetapi jika Anda ingin melihat apa yang sedang dikirim dalam permintaan Anda (karena itu ke server, itu adalah permintaan) mengendusnya. Pertanyaan ini terlalu luas. SO lebih untuk pertanyaan pemrograman tertentu.
paparazzo
... saat penghirup pergi, pemain biola adalah senjata pilihan saya. Anda bahkan dapat membangun permintaan pengujian Anda sendiri untuk melihat bagaimana mereka memposting.
Phil Cooper
Bagi yang berminat, lihat juga " MAX_FILE_SIZEdi PHP - apa gunanya" di stackoverflow.com/q/1381364/632951
Pacerier
Saya menemukan MAX_FILE_SIZE aneh. karena saya dapat memodifikasi html saya di chrome ke 100000000 sebelum mempostingnya sehingga memposting nilai yang lebih baik. Entah 1. memilikinya dalam cookie dengan hash yang aman melalui garam sehingga cookie jika dimodifikasi, server dapat memvalidasi dan melempar pengecualian (seperti yang dilakukan webpieces atau playframework) atau semacam validasi formulir yang tidak berubah. @ 0xSina
Dean Hiller

Jawaban:

320

Mari kita lihat apa yang terjadi ketika Anda memilih file dan mengirimkan formulir Anda (saya telah memotong header untuk singkatnya):

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

CATATAN: setiap string batas harus diawali dengan tambahan --, sama seperti pada akhir string batas terakhir. Contoh di atas sudah termasuk ini, tetapi bisa mudah dilewatkan. Lihat komentar olehAndreas di bawah ini.

Alih-alih URL yang menyandikan parameter formulir, parameter formulir (termasuk data file) dikirim sebagai bagian dalam dokumen multi bagian di badan permintaan.

Dalam contoh di atas, Anda dapat melihat input MAX_FILE_SIZEdengan nilai yang ditetapkan dalam formulir, serta bagian yang berisi data file. Nama file adalah bagian dari Content-Dispositiontajuk.

Detail lengkapnya ada di sini .

toddsundsted
sumber
7
@ source.rar: Tidak. Webservers (hampir?) selalu di-threaded sehingga mereka dapat menangani koneksi bersamaan. Pada dasarnya, proses daemon yang mendengarkan pada port 80 segera melepaskan tugas untuk melayani utas / proses lain agar dapat kembali ke mendengarkan untuk koneksi lain; bahkan jika dua koneksi masuk tiba pada saat yang sama, mereka hanya akan duduk di buffer jaringan sampai daemon siap membacanya.
eggyal
10
Penjelasan threading agak salah karena ada server berkinerja tinggi yang dirancang sebagai single threaded dan menggunakan mesin negara untuk dengan cepat bergantian mengunduh paket data dari koneksi. Sebaliknya, dalam TCP / IP, port 80 adalah port mendengarkan, bukan port data yang ditransfer.
Slebetman
9
Ketika soket mendengarkan IP (port 80) menerima koneksi, soket lain dibuat pada port lain, biasanya dengan nomor acak di atas 1000. Soket ini kemudian dihubungkan ke soket jarak jauh sehingga port 80 bebas untuk mendengarkan koneksi baru.
Slebetman
11
@slebetman Pertama-tama, ini tentang HTTP. Mode aktif FTP tidak berlaku di sini. Kedua, soket pendengaran tidak terhalang pada setiap koneksi. Anda dapat memiliki banyak koneksi ke satu port, karena pihak lain memiliki port untuk mengikat ujungnya sendiri.
Foto
33
Perhatikan bahwa string batas yang dilewatkan sebagai bagian dari bidang header Tipe-Konten adalah 2 karakter lebih pendek dari string batas untuk masing-masing bagian di bawah ini. Saya baru saja menghabiskan satu jam mencoba mencari tahu mengapa pengunggah saya tidak berfungsi karena cukup sulit untuk memperhatikan bahwa sebenarnya hanya ada 4 tanda hubung dalam string batas pertama tetapi 6 tanda hubung di string batas lainnya. Dengan kata lain: Ketika menggunakan string batas untuk memisahkan data formulir individual, itu harus diawali oleh dua garis: - Ini dijelaskan dalam RFC1867 tentu saja tapi saya pikir itu harus ditunjukkan di sini juga
Andreas
279

Bagaimana cara mengirim file secara internal?

Format ini disebut multipart/form-data, seperti yang ditanyakan pada: Apa arti enctype = 'multipart / form-data'?

Saya akan:

  • tambahkan beberapa referensi HTML5 lainnya
  • jelaskan mengapa dia benar dengan formulir serahkan contoh

Referensi HTML5

Ada tiga kemungkinan untuk enctype:

Cara menghasilkan contoh

Setelah Anda melihat contoh masing-masing metode, menjadi jelas bagaimana cara kerjanya, dan kapan Anda harus menggunakannya.

Anda dapat menghasilkan contoh menggunakan:

Simpan formulir ke .htmlfile minimal :

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

Kami menetapkan nilai teks default menjadi a&#x03C9;b, yang berarti aωbkarena ωis U+03C9, yang merupakan byte61 CF 89 62 dalam UTF-8.

Buat file untuk diunggah:

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

Jalankan server gema kecil kami:

while true; do printf '' | nc -l 8000 localhost; done

Buka HTML di browser Anda, pilih file dan klik kirim dan periksa terminal.

nc mencetak permintaan yang diterima.

Diuji pada: Ubuntu 14.04.3, nc BSD 1.105, Firefox 40.

multipart / formulir-data

Firefox mengirim:

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

Untuk file biner dan bidang teks, byte 61 CF 89 62( aωbdalam UTF-8) dikirim secara harfiah. Anda dapat memverifikasi itu dengan nc -l localhost 8000 | hd, yang mengatakan bahwa byte:

61 CF 89 62

dikirim ( 61== 'a' dan 62== 'b').

Karena itu jelas bahwa:

  • Content-Type: multipart/form-data; boundary=---------------------------735323031399963166993862150 setel jenis konten menjadi multipart/form-data dan mengatakan bahwa bidang dipisahkan oleh boundarystring yang diberikan .

    Tetapi perhatikan bahwa:

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

    memiliki dua ayah kurang --dari penghalang yang sebenarnya

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

    Ini karena standar mengharuskan batas untuk memulai dengan dua tanda hubung --. Tanda hubung lainnya tampaknya hanya bagaimana Firefox memilih untuk menerapkan batas arbitrer. RFC 7578 dengan jelas menyebutkan bahwa kedua tanda hubung utama --diperlukan:

    4.1. "Batas" Parameter multipart / formulir-data

    Seperti jenis multi bagian lainnya, bagian-bagian dibatasi dengan pembatas batas, dibangun menggunakan CRLF, "-", dan nilai parameter "batas".

  • setiap bidang mendapatkan beberapa subjudul sebelum datanya Content-Disposition: form-data;:, bidang name,filename , diikuti oleh data.

    Server membaca data hingga string batas berikutnya. Peramban harus memilih batas yang tidak akan muncul di bidang mana pun, jadi inilah sebabnya batas mungkin berbeda di antara permintaan.

    Karena kami memiliki batas unik, tidak diperlukan penyandian data: data biner dikirim apa adanya.

    TODO: berapakah ukuran batas optimal ( log(N)saya yakin), dan nama / waktu berjalan dari algoritma yang menemukannya? Ditanya di: /cs/39687/find-the-shortest- berikutnyaence-that-is-not-a-sub- berikutnyaence- of-a- set- of- afterences

  • Content-Type ditentukan secara otomatis oleh browser.

    Bagaimana ditentukan dengan tepat ditanyakan di: Bagaimana jenis mime dari file yang diunggah ditentukan oleh browser?

application / x-www-form-urlencoded

Sekarang ubah enctypeke application/x-www-form-urlencoded, muat ulang peramban, dan kirim kembali.

Firefox mengirim:

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

Jelas data file tidak dikirim, hanya nama dasarnya. Jadi ini tidak dapat digunakan untuk file.

Sedangkan untuk bidang teks, kita melihat bahwa karakter yang biasa dicetak suka adan bdikirim dalam satu byte, sementara yang tidak dapat dicetak suka 0xCFdan masing-masing 0x89mengambil 3 byte :%CF%89 !

Perbandingan

Unggahan file sering mengandung banyak karakter yang tidak dapat dicetak (mis. Gambar), sedangkan bentuk teks hampir tidak pernah dilakukan.

Dari contoh-contoh yang telah kita lihat bahwa:

  • multipart/form-data: menambahkan beberapa byte batas overhead ke pesan, dan harus meluangkan waktu untuk menghitungnya, tetapi mengirimkan setiap byte dalam satu byte.

  • application/x-www-form-urlencoded: memiliki batas byte tunggal per bidang ( &), tetapi menambahkan faktor overhead linier 3x untuk setiap karakter yang tidak dapat dicetak.

Karena itu, bahkan jika kita dapat mengirim file application/x-www-form-urlencoded , kami tidak akan mau, karena sangat tidak efisien.

Tetapi untuk karakter yang dapat dicetak ditemukan di bidang teks, itu tidak masalah dan menghasilkan lebih sedikit overhead, jadi kami hanya menggunakannya.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
sumber
1
Bagaimana Anda menambahkan lampiran biner? (Yaitu gambar kecil) - Saya bisa melihat mengubah nilai untuk Content-Dispositiondan Content-Typeatribut tetapi bagaimana menangani 'konten'?
blurfus
3
@ianbeks Browser melakukannya secara otomatis sebelum mengirim permintaan. Saya tidak tahu heuristik apa yang digunakannya, tetapi kemungkinan besar ekstensi file ada di antara mereka. Ini dapat menjawab pertanyaan: stackoverflow.com/questions/1201945/…
Ciro Santilli 郝海东 冠状 病 六四 六四 事件 法轮功
3
@CiroSantilli 六四 事件 法轮功 纳米比亚 威 视 Saya pikir jawaban ini jauh lebih baik daripada yang dipilih. Tapi tolong hapus konten yang tidak relevan dari profil Anda. Itu bertentangan dengan semangat SO.
smwikipedia
2
@smwikipedia terima kasih atas kutipan rfc dan menyukai jawaban ini! Tentang nama pengguna: bagi saya, semangat SO adalah setiap orang harus memiliki informasi terbaik setiap saat. ~~ Mari kita bahas diskusi ini di twitter atau meta. Perdamaian.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
@KumarHarsh tidak cukup detail untuk menjawab saya pikir. Silakan buka pertanyaan super detail baru.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
62

Kirim file sebagai konten biner (unggah tanpa formulir atau FormData)

Dalam jawaban / contoh yang diberikan, file tersebut (kemungkinan besar) diunggah dengan formulir HTML atau menggunakan API FormData . File hanya merupakan bagian dari data yang dikirim dalam permintaan, karenanyamultipart/form-data Content-Type header.

Jika Anda ingin mengirim file sebagai satu-satunya konten maka Anda dapat langsung menambahkannya sebagai badan permintaan dan Anda mengatur Content-Typeheader ke tipe MIME dari file yang Anda kirim. Nama file dapat ditambahkan di Content-Dispositionheader. Anda dapat mengunggah seperti ini:

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

Jika Anda tidak (ingin) menggunakan formulir dan Anda hanya tertarik untuk mengunggah satu file saja, ini adalah cara termudah untuk memasukkan file Anda dalam permintaan.

Melayu
sumber
Bagaimana Anda mengonfigurasi layanan sisi server untuk ini dengan Asp.Net 4.0? Apakah ini akan menangani banyak parameter input juga, seperti userId, path, captionText dll?
Asle G
1
@ AsleG Tidak, itu hanya untuk mengirim satu file sebagai konten permintaan Anda. Saya bukan ahli Asp.Net, tetapi Anda hanya perlu menarik konten (gumpalan) dari permintaan dan menyimpannya ke file menggunakan Content-Typedari header.
Layu
@ AsleG Mungkin tautan ini bisa membantu
Wilt
@ layu Jika saya tidak menggunakan formulir, tetapi saya ingin menggunakan formdata API, dapatkah saya melakukannya dengan cara itu?
Kiwi
1
@AnkitKhettry Kedengarannya seperti itu diunggah dengan formulir atau dengan menggunakan formulir API. 'String aneh' yang Anda rujuk ini adalah batas formulir yang biasanya digunakan untuk memisahkan data formulir menjadi bagian-bagian di server.
Layu
9

Saya punya contoh Kode Java ini:

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);
    }
}

dan saya punya file test.html ini:

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

dan akhirnya file yang akan saya gunakan untuk tujuan pengujian, bernama a.dat memiliki konten berikut:

0x39 0x69 0x65

jika Anda menafsirkan byte di atas sebagai karakter ASCII atau UTF-8, mereka akan benar-benar mewakili:

9ie

Jadi mari kita jalankan Kode Java kita, buka test.html di browser favorit kita, unggah a.datdan kirim formulir dan lihat apa yang server kami terima:

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

Ya saya tidak terkejut melihat karakternya 9ie karena kami memberitahu Java untuk mencetaknya dengan memperlakukan mereka sebagai karakter UTF-8. Anda juga dapat memilih untuk membacanya sebagai byte mentah ..

Cookie: JSESSIONID=27D0A0637A0449CF65B3CB20F40048AF 

sebenarnya adalah HTTP Header terakhir di sini. Setelah itu muncul HTTP Body, tempat meta dan konten file yang kita unggah sebenarnya bisa dilihat.

Koray Tugay
sumber
6

Pesan HTTP mungkin memiliki badan data yang dikirim setelah baris tajuk. Dalam respons, ini adalah tempat sumber daya yang diminta dikembalikan ke klien (penggunaan yang paling umum dari badan pesan), atau mungkin teks penjelasan jika ada kesalahan. Dalam permintaan, ini adalah tempat data yang dimasukkan pengguna atau file yang diunggah dikirim ke server.

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

flagg19
sumber