HTTP dosya yüklemesi nasıl çalışır?


528

Ekli bir dosya ile böyle basit bir form gönderdiğinizde:

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

Dosyayı dahili olarak nasıl gönderir? Dosya, HTTP gövdesinin bir parçası olarak veri olarak gönderiliyor mu? Bu isteğin başlıklarında dosyanın adıyla ilgili hiçbir şey göremiyorum.

Sadece bir dosya gönderirken HTTP'nin dahili işleyişini bilmek istiyorum.


Bir süre sniffer kullanmadım ama isteğinizde ne gönderildiğini görmek istiyorsanız (sunucuya olduğu için bu bir istektir) koklayın. Bu soru çok geniş. SO spesifik programlama soruları için daha fazladır.
paparazzo

... koklayıcılar gittikçe, kemancı benim seçim silahım. Nasıl yayınlandıklarını görmek için kendi test isteklerinizi bile oluşturabilirsiniz.
Phil Cooper

Bu ilgi, aynı zamanda "bakınız MAX_FILE_SIZEPHP'de - nokta nedir" konulu stackoverflow.com/q/1381364/632951
Pacerier

MAX_FILE_SIZE tuhaf buluyorum. daha iyi bir değer yayınlar böylece göndermeden önce krom benim html 100000000 için değiştirebilirsiniz. Ya 1. modifiye edilmişse, tuz yoluyla güvenli bir karma içeren bir çereze sahip olun, sunucu doğrulayabilir ve istisna atabilir (web parçaları veya oyun çerçeveleri gibi) veya bir şeylerin değişmediği bir form doğrulama. @ 0xSina
Dean Hiller

Yanıtlar:


320

Bir dosya seçtiğinizde ve formunuzu gönderdiğinizde neler olduğuna bir göz atalım (başlıkları kısaca kısalttım):

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

NOT: Her sınır dizesine --, tıpkı son sınır dizesinin sonunda olduğu gibi, bir ek ile önek eklenmelidir . Yukarıdaki örnek zaten bunu içerir, ancak kaçırılması kolay olabilir. Aşağıdaki @Andreas'ın yorumuna bakın.

Form parametrelerini kodlayan URL yerine, form parametreleri (dosya verileri dahil), isteğin gövdesindeki çok parçalı bir belgede bölümler olarak gönderilir.

Yukarıdaki örnekte MAX_FILE_SIZE, formda ayarlanan değere sahip girdinin yanı sıra dosya verilerini içeren bir bölümü görebilirsiniz. Dosya adı Content-Dispositionbaşlığın bir parçasıdır .

Tüm detaylar burada .


7
@ source.rar: Hayır. Web sunucuları (neredeyse?) daima eşzamanlı bağlantıları işleyebilecek şekilde işlenirler. Esasen, bağlantı noktası 80'de dinleyen daemon işlemi, başka bir bağlantıyı dinlemeye geri dönebilmek için derhal başka bir iş parçacığına / işleme hizmet etme görevini yerine getirir; iki gelen bağlantı tam olarak aynı anda gelse bile, arka plan programı onları okumaya hazır olana kadar ağ ara belleğine otururlar.
eggyal

10
Tek iş parçacıklı olarak tasarlanan ve bağlantılardan veri paketlerini hızlı bir şekilde sırayla indirmek için bir durum makinesi kullanan yüksek performanslı sunucular olduğundan, iş parçacığı açıklaması biraz yanlıştır. Aksine, TCP / IP'de bağlantı noktası 80, verilerin aktarıldığı bağlantı noktası değil, bir dinleme bağlantı noktasıdır.
slebetman

9
Bir IP dinleme soketi (bağlantı noktası 80) bir bağlantı aldığında, başka bir bağlantı noktasında, genellikle 1000'in üzerinde rasgele bir sayı ile başka bir yuva oluşturulur. Bu soket, uzak bağlantı noktasına bağlanır ve bağlantı noktasını 80 yeni bağlantıları dinlemek için serbest bırakır.
slebetman

11
@slebetman Her şeyden önce bu HTTP ile ilgili. FTP etkin modu burada geçerli değil. İkincisi, dinleme soketi her bağlantıda engellenmez. Bir bağlantı noktasına, diğer tarafların kendi uçlarını bağlayacak bağlantı noktalarına sahip olduğu kadar çok bağlantınız olabilir.
Slotos

33
Content-Type üstbilgi alanının bir parçası olarak iletilen sınır dizesinin, aşağıdaki tek tek bölümlerin sınır dizelerinden 2 karakter daha kısa olduğunu unutmayın. Yükleyicimin neden çalışmadığını anlamaya çalışmak için bir saat geçirdim, çünkü ilk sınır dizesinde sadece 4 çizgi ancak diğer sınır dizelerinde 6 çizgi olduğunu fark etmek oldukça zor. Başka bir deyişle: Tek tek form verilerini ayırmak için sınır dizesini kullanırken, buna iki tire eklenmelidir: - Tabii ki RFC1867'de açıklanmıştır, ancak burada da belirtilmesi gerektiğini düşünüyorum
Andreas

279

Dosyayı dahili olarak nasıl gönderir?

Format şu multipart/form-dataşekilde sorulur: enctype = 'multipart / form-data' ne anlama geliyor?

Şuna gidiyorum:

  • biraz daha HTML5 referansı ekle
  • bir form göndererek neden haklı olduğunu açıklayın

HTML5 referansları

Orada üç olasılık için enctype:

Örnekler nasıl oluşturulur?

Her yöntemin bir örneğini gördüğünüzde, nasıl çalıştıklarını ve her birini ne zaman kullanmanız gerektiği açık hale gelir.

Aşağıdakileri kullanarak örnekler üretebilirsiniz:

Formu minimal bir .htmldosyaya kaydedin :

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

Biz varsayılan metin değeri a&#x03C9;b, araçlar aωbnedeniyle ωolduğunu U+03C9bayt olan, 61 CF 89 62UTF-8.

Yüklenecek dosyalar oluşturun:

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

Küçük yankı sunucumuzu çalıştırın:

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

Tarayıcınızda HTML'yi açın, dosyaları seçin ve gönder'i tıklayın ve terminali kontrol edin.

nc alınan isteği yazdırır.

Test tarihi: Ubuntu 14.04.3, ncBSD 1.105, Firefox 40.

Çok parçalı / form-

Firefox gönderdi:

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

İkili dosya ve metin alanı için baytlar 61 CF 89 62( aωbUTF-8'de) kelimenin tam anlamıyla gönderilir. Sen ile doğrulamak olabilir nc -l localhost 8000 | hdbayt olduğunu öne sürdüğü:

61 CF 89 62

gönderildi ( 61== 'a' ve 62== 'b').

Bu nedenle açıktır:

  • Content-Type: multipart/form-data; boundary=---------------------------735323031399963166993862150içerik türünü ayarlar ve multipart/form-dataalanların verilen boundarydize ile ayrıldığını belirtir .

    Ancak şunu unutmayın:

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

    --gerçek engelden daha az iki baba var

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

    Bunun nedeni, standardın sınırın iki tire ile başlamasını gerektirmesidir --. Diğer çizgiler, Firefox'un keyfi sınırı nasıl uyguladığını gösteriyor. RFC 7578, bu iki önde gelen tirenin --gerekli olduğunu açıkça belirtmektedir :

    4.1. "Sınır" Çok parçalı / form verisi parametresi

    Diğer çok parçalı tiplerde olduğu gibi, parçalar da CRLF, "-" ve "sınır" parametresinin değeri kullanılarak oluşturulan bir sınır sınırlayıcı ile sınırlandırılır.

  • Her alan kendi verilerine önce bazı alt başlıklarını alır: Content-Disposition: form-data;, alan name, filenameverilerin ardından.

    Sunucu, verileri bir sonraki sınır dizesine kadar okur. Tarayıcı, alanların hiçbirinde görünmeyecek bir sınır seçmelidir, bu nedenle sınır istekler arasında değişebilir.

    Benzersiz sınırımız olduğu için, verilerin kodlanması gerekmez: ikili veriler olduğu gibi gönderilir.

    YAPILACAKLAR: İdeal sınır boyutu ( log(N)bahse girerim) ve onu bulan algoritmanın adı / çalışma süresi nedir? Sorulduğu yer: /cs/39687/find-the-shortest-sequence-that-is-not-a-sub-sequence-of-a-set-of-sequences

  • Content-Type tarayıcı tarafından otomatik olarak belirlenir.

    Tam olarak nasıl belirlendi: Şu şekilde yüklenen bir dosyanın mime türü tarayıcı tarafından nasıl belirlenir?

Uygulama / x-www-form-urlencoded

Şimdi değiştirmek enctypeiçin application/x-www-form-urlencoded, tarayıcı ve yeniden gönderin yükleyin.

Firefox gönderdi:

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çıkça, dosya verileri gönderilmedi, sadece temel isimler. Yani bu dosyalar için kullanılamaz.

Metin alanına gelince, biz gibi o zamanki yazdırılabilir karakterleri görmek ave bbenzeri olmayan basılabilir olanlar ise, tek bayt gönderildi 0xCFve 0x89aldı 3 bayt her: %CF%89!

karşılaştırma

Dosya yüklemeleri genellikle yazdırılamayan çok sayıda karakter (örn. Resimler) içerirken, metin formları neredeyse hiç yapmaz.

Örneklerden şunu gördük:

  • multipart/form-data: mesaja birkaç bayt sınır ek yükü ekler ve bunu hesaplamak için biraz zaman harcamalıdır, ancak her baytı bir baytta gönderir.

  • application/x-www-form-urlencoded: alan başına tek bir bayt sınırına ( &) sahiptir, ancak yazdırılamayan her karakter için 3x doğrusal bir ek yük faktörü ekler .

Bu nedenle, ile dosya gönderebilsek bile application/x-www-form-urlencoded, istemezdik, çünkü bu çok verimsiz.

Ancak metin alanlarında bulunan yazdırılabilir karakterler için önemli değildir ve daha az ek yük oluşturur, bu yüzden sadece kullanırız.


1
İkili eki nasıl eklersiniz? (yani küçük bir resim) - Content-Dispositionve Content-Typeözniteliklerinin değerlerini değiştirmeyi görebiliyorum, ancak 'içerik' nasıl işlenir?
blurfus

3
@ianbeks Tarayıcı isteği göndermeden önce otomatik olarak yapar. Hangi sezgisel tarama kullandığını bilmiyorum, ancak büyük olasılıkla dosya uzantısı bunlar arasında. Bu soruya cevap verebilir: stackoverflow.com/questions/1201945/…
Ciro Santilli: 冠状 病 六四 事件 法轮功

3
@CiroSantilli answer 事件 法轮功 纳米比亚 威 视 Bence bu cevap seçilen cevaptan çok daha iyi. Ancak lütfen alakasız içeriği profilinizden kaldırın. SO ruhuna aykırıdır.
smwikipedia

2
@ smwikipedia rfc teklifi ve bu cevabı beğendiğiniz için teşekkürler! Kullanıcı adı hakkında: bana göre, SO'nun ruhu herkesin her zaman en iyi bilgiye sahip olması gerektiğidir. ~~ Bu tartışmayı twitter veya meta olarak tutalım. Barış.
Ciro Santilli 法轮功 15 病 六四 事件 法轮功

1
@KumarHarsh cevaplamak için yeterli detay olmadığını düşünüyorum. Lütfen yeni bir süper ayrıntılı soru açın.
Ciro Santilli 法轮功 12: 病 六四 事件 法轮功

62

Dosyayı ikili içerik olarak gönder (form veya FormData olmadan yükle)

Verilen cevaplarda / örneklerde dosya (büyük olasılıkla) bir HTML formuyla veya FormData API'sı kullanılarak yüklenir . Dosya, istekte gönderilen verinin yalnızca bir bölümüdür, dolayısıyla multipart/form-data Content-Typebaşlık.

Dosyayı tek içerik olarak göndermek istiyorsanız, doğrudan istek gövdesi olarak ekleyebilirsiniz ve Content-Typebaşlığı, gönderdiğiniz dosyanın MIME türüne ayarlayabilirsiniz . Dosya adı Content-Dispositionbaşlığa eklenebilir . Aşağıdaki gibi yükleyebilirsiniz:

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

Formları kullanmıyorsanız (kullanmak istemiyorsanız) ve yalnızca tek bir dosya yüklemek istiyorsanız, dosyanızı isteğe eklemenin en kolay yolu budur.


Asp.Net 4.0 ile bunun için bir sunucu tarafı hizmetini nasıl yapılandırabilirsiniz? UserId, path, captionText gibi çoklu giriş parametrelerini de işleyecek mi?
Asle G

1
@ AsleG Hayır, yalnızca isteğinizin içeriği olarak tek bir dosya göndermek içindir. Ben bir Asp.Net uzmanı değilim, ama sadece içeriği (bir damla) istek dışına çekmek Content-Typeve başlıktan kullanarak bir dosyaya kaydetmelisiniz .
Wilt

@AsleG Belki bu bağlantıyı yardımcı olabilir
Wilt

@wilt Form kullanmazsam, ancak formdata API'sini kullanmak istiyorsam, bu şekilde yapabilir miyim?
kızgın kivi

1
@AnkitKhettry Bir formla veya form API'sı kullanılarak yüklenmiş gibi görünür. Bu 'garip dizeler', form verilerini sunucudaki parçalara ayırmak için normalde kullanılan form sınırlarıdır.
Wilt

9

Bu örnek Java kodu var:

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

ve bu test.html dosyası var:

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

ve son olarak a.dat adında test amacıyla kullanacağım dosya aşağıdaki içeriğe sahip:

0x39 0x69 0x65

yukarıdaki baytları ASCII veya UTF-8 karakterleri olarak yorumlarsanız, bunlar aslında şunları temsil eder:

9ie

Şimdi Java Kodumuzu çalıştıralım, test.html dosyasını favori tarayıcımızda açalım a.dat, formu yükleyip gönderelim ve sunucumuzun ne aldığını görelim:

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 karakterlerini gördüğüme şaşırmadım çünkü Java'ya onlara UTF-8 karakterleri olarak davranmalarını yazdırmasını söyledik. Bunları ham bayt olarak okumayı da seçebilirsiniz.

Cookie: JSESSIONID=27D0A0637A0449CF65B3CB20F40048AF 

aslında buradaki son HTTP Başlığıdır. Bundan sonra, yüklediğimiz dosyanın meta ve içeriklerinin gerçekten görülebildiği HTTP Gövdesi geliyor.


6

Bir HTTP iletisinin başlık satırlarından sonra gönderilen bir veri gövdesi olabilir. Yanıt olarak, istenen kaynak istemciye (ileti gövdesinin en yaygın kullanımı) veya bir hata varsa açıklayıcı metne geri döndürülür. Bir istekte, kullanıcı tarafından girilen veriler veya yüklenen dosyalar sunucuya gönderilir.

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

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.