İçeriğe geç
academia.sh

Ders 24 / 25

Dosya Yükleme

Dosya alanının ürettiği değer, çok parçalı gönderim kodlamasının gövdedeki karşılığı, kabul türü bildirimi ve boyut sınırının nerede uygulandığı.

İçindekiler

Doğrulama, metin ve sayı alanları üzerinde kuruldu. Dosya alanı bu düzenlere uymaz. Değeri bir dizgi değildir; ikilik içerik taşır, kendi adı ve kendi içerik türü vardır.

Form Yapısı dersinde tanımlanan varsayılan kodlama —ad=deger çiftlerinin & ile birleştirilmesi— bu içeriği taşıyamaz. Dosya alanı olan bir formda gönderim kodlaması değiştirilir.

Üç Öznitelik

<form action="/olcum/yukle" method="post" enctype="multipart/form-data">
  <p><label for="kayit">Ham kayıt dosyası</label>
     <input id="kayit" name="kayit" type="file" accept=".csv,text/csv" required></p>
  <p><button type="submit">Yükle</button></p>
</form>

enctype="multipart/form-data" gönderim kodlamasını değiştirir. Yazılmadığında dosya alanı yalnızca dosya adını gönderir; içerik gitmez. Bu, sessizce yanlış çalışan bir formun en sık nedenidir: form gönderilir, sunucu bir değer alır, ama aldığı şey bir dizgidir.

accept özniteliği, dosya seçicide hangi türlerin öne çıkarılacağını bildirir. Değeri uzantı listesi, içerik türü listesi ya da ikisinin karışımı olabilir.

multiple özniteliği birden çok dosya seçilmesine izin verir; her dosya giriş listesine aynı adla ayrı bir giriş olarak katılır.

Gövdenin Biçimi

Aşağıdaki betik bir sunucu açar, aynı iki alanı iki farklı kodlamayla gönderir ve sunucunun aldığı ham gövdeyi yazdırır. Satır sonları görünür olsun diye \r\n dizisi açıkça yazdırılmıştır.

// dosya.mjs — iki gonderim kodlamasinin govdedeki karsiligi
import { createServer } from "node:http";

// Sinir dizgisi her gonderimde rastgele uretilir; ciktinin yinelenebilir olmasi icin
// yazdirmadan once sabit bir yer tutucuyla degistirilir.
const sunucu = createServer((istek, yanit) => {
  const parcalar = [];
  istek.on("data", (parca) => parcalar.push(parca));
  istek.on("end", () => {
    const govde = Buffer.concat(parcalar);
    const tur = istek.headers["content-type"];
    const sinir = tur.match(/boundary=(.+)$/)?.[1];
    const maskele = (d) => (sinir ? d.split(sinir).join("SINIR") : d);
    console.log("content-type  :", maskele(tur));
    console.log("govde uzunlugu:", govde.length, "bayt");
    console.log("--- ham govde ---");
    console.log(maskele(govde.toString()).replace(/\r\n/g, "\\r\\n\n"));
    console.log("===");
    yanit.writeHead(204).end();
  });
});

sunucu.listen(0, "127.0.0.1", async () => {
  const adres = "http://127.0.0.1:" + sunucu.address().port + "/yukle";

  // enctype="multipart/form-data"
  const veri = new FormData();
  veri.append("istasyon", "kuzey-yamac");
  veri.append("kayit", new Blob(["saat,sicaklik\n07:00,-4.2\n"], { type: "text/csv" }), "olcum.csv");
  await fetch(adres, { method: "POST", body: veri });

  // enctype="application/x-www-form-urlencoded" — ayni iki alan, dosya icerigi olmadan
  await fetch(adres, {
    method: "POST",
    headers: { "content-type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams([["istasyon", "kuzey-yamac"], ["kayit", "olcum.csv"]]).toString(),
  });

  sunucu.close();
});
content-type  : multipart/form-data; boundary=SINIR
govde uzunlugu: 295 bayt
--- ham govde ---
--SINIR\r\n
Content-Disposition: form-data; name="istasyon"\r\n
\r\n
kuzey-yamac\r\n
--SINIR\r\n
Content-Disposition: form-data; name="kayit"; filename="olcum.csv"\r\n
Content-Type: text/csv\r\n
\r\n
saat,sicaklik
07:00,-4.2
\r\n
--SINIR--\r\n

===
content-type  : application/x-www-form-urlencoded
govde uzunlugu: 36 bayt
--- ham govde ---
istasyon=kuzey-yamac&kayit=olcum.csv
===

Çıktıdaki SINIR bir yer tutucudur: gerçek sınır dizgisi her gönderimde rastgele üretilir ve betik onu yazdırmadan önce sabit bir metinle değiştirir. Sınırın metni gönderen tarafça seçilir; tek koşulu, gövdenin hiçbir yerinde geçmemesidir. Gövde uzunluğu bu betikte değişmez, çünkü üretilen sınırın uzunluğu sabittir.

Gövde yapısı şöyledir. Content-Type başlığında bir sınır (boundary) dizgisi bildirilir. Gövde bu sınırla parçalara ayrılır; her parça kendi başlıklarını, bir boş satırı ve içeriğini taşır. Content-Disposition başlığı alanın adını verir; dosya alanlarında ayrıca filename parametresi bulunur. Dosya parçasında bir de Content-Type başlığı vardır ve dosyanın türünü bildirir. Gövde, sınırın sonuna iki tire eklenmiş biçimiyle biter.

İki temel fark görünür.

İçerik kaçırılmaz. Çok parçalı kodlamada dosya içeriği olduğu gibi taşınır; yüzde kodlaması uygulanmaz. Ayırma işini sınır dizgisi yapar. İkilik bir dosya, kodlanmadan gönderilebilir.

Yük oranı yüksektir. Aynı iki alan için 36 bayta karşılık 295 bayt kullanılmıştır. Her parça kendi başlıklarını taşır ve sınır dizgisi her parçada yinelenir. Bu maliyet dosya büyüdükçe oransal olarak küçülür; ancak dosya alanı olmayan bir formu çok parçalı kodlamayla göndermek gereksiz bir yüktür.

Çıktının ikinci bölümü, yanlış kodlamanın sonucunu gösterir: kayit alanı yalnızca dosya adını taşır. Sunucu bir değer alır ve içerik yoktur.

Kabul Türü Bir Kısıt Değildir

accept özniteliği dosya seçici penceresini süzer. Süzgeç kullanıcı tarafından kaldırılabilir ve gönderim belge kullanılmadan da yapılabilir; Yerleşik Doğrulama dersindeki sonuç burada da geçerlidir.

Dosyanın gerçekten bildirdiği türde olup olmadığı, gövdedeki Content-Type başlığından anlaşılmaz: o başlığı da gönderen taraf yazar. Sunucu türü ancak dosyanın içeriğine bakarak doğrulayabilir — dosya biçimlerinin baş baytlarındaki tanımlayıcı diziler bu iş içindir.

Uzantıya güvenmek de yeterli değildir. Yüklenen dosya adı sunucuda doğrudan kullanılmaz: kullanıcıdan gelen ad yol ayıracı içerebilir, var olan bir dosyanın üzerine yazabilir ya da sunucunun çalıştırdığı bir uzantı taşıyabilir. Güvenli davranış, sunucunun kendi ürettiği bir ad kullanması ve özgün adı yalnızca üstveri olarak saklamasıdır.

Alanın Değeri Belgeden Yazılamaz

Dosya alanı, diğer alanlardan bir yönüyle daha ayrılır: değeri belgeden verilemez. value özniteliği yazılsa da yok sayılır, çünkü aksi hâlde bir belge, kullanıcının diskindeki herhangi bir dosyayı sessizce gönderebilirdi. Alanın değeri yalnızca kullanıcının seçimiyle oluşur.

Aynı gerekçe dosyanın yolunu da gizler. Belgeye açılan bilgi dosyanın adı, boyutu ve türüdür; bulunduğu dizin değildir. Kullanıcının dizin yapısı, o kullanıcı hakkında bilgi taşır ve belgeye verilmez.

Bunun bir sonucu, bir formun önceki gönderimden kalan dosyayı yeniden göstermesinin olanaksız olmasıdır. Doğrulama hatası nedeniyle geri dönülen bir formda diğer alanların değerleri belgede yeniden yazılabilir; dosya alanı boş kalır ve kullanıcının dosyayı yeniden seçmesi gerekir. Çok adımlı formlarda bu, dosyanın erken bir adımda yüklenip sunucuda geçici olarak saklanmasını ve sonraki adımlarda bir kimlikle anılmasını gerektirir.

Boyut Sınırı Belgede Bildirilemez

Dosya boyutunu sınırlayan bir öznitelik yoktur. Bunun bir eksiklik değil, tutarlı bir tasarım olduğu söylenebilir: belgede bildirilen bir sınır zaten uygulanamazdı.

Sınır iki yerde uygulanır. Sunucu, gövdenin tamamını okumadan Content-Length başlığına bakarak isteği reddedebilir ve okurken de bir üst sınır uygulayabilir; bu, belleğin bir istekle doldurulmasını önler. Sunucu yazılımının kendi yapılandırması da genellikle bir istek gövdesi üst sınırı taşır.

Kullanıcıya erken geri bildirim vermek isteniyorsa dosya boyutu gönderim öncesinde davranış katmanında okunabilir; bu bir kolaylıktır, kısıt değildir. Beklenen sınır belgede metin olarak yazılır — kullanıcı, seçtiği dosyanın reddedileceğini yükleme bittikten sonra öğrenmemelidir.

İstasyon Belgesinde

<fieldset>
  <legend>Ham kayıt</legend>
  <p><label for="kayit">Ölçüm dosyası</label>
     <input id="kayit" name="kayit" type="file"
            accept=".csv,text/csv" aria-describedby="kayit-yardim">
     <span id="kayit-yardim">Virgülle ayrılmış değer dosyası, en çok 5 MB.</span></p>
</fieldset>

Formun enctype özniteliği bu alan eklendiği anda değiştirilmelidir; unutulduğunda form çalışıyor görünür ve dosya içeriği hiç ulaşmaz.

Özet

  • Dosya alanı olan formda enctype="multipart/form-data" yazılır; yazılmadığında yalnızca dosya adı gönderilir ve içerik kaybolur.
  • Çok parçalı gövde bir sınır dizgisiyle parçalara ayrılır; her parça kendi başlıklarını taşır ve içerik kaçırılmadan aktarılır.
  • Bu kodlamanın yük oranı yüksektir; dosya alanı olmayan formlarda kullanılmaz.
  • accept yalnızca dosya seçiciyi süzer; tür denetimi sunucuda, dosyanın içeriğine bakılarak yapılır ve yüklenen ad doğrudan kullanılmaz.
  • Boyut sınırı belgede bildirilemez; sunucuda uygulanır ve kullanıcıya metinle bildirilir.

Sonraki Adım

Form bu noktada veri topluyor, doğruluyor ve dosya alabiliyor. Geriye bir soru kalıyor: gönderim reddedildiğinde kullanıcı ne görüyor? Tarayıcının kendi iletisi tek alanı gösteriyor, sunucudan dönen hata ise yeni bir belgeyle geliyor. Son ders hata bildirimini ve odak yönetimini ele alır ve kursu, bu formun erişilebilir sürümünü kurarak kapatır.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat