Post

Gerçek Zamanlı ZIP: Sunucular Klasörleri Anında Nasıl Zipleyip İndirilebilir Hale Getiriyor?

🇹🇷 GitHub gibi platformların anlık zip oluşturma sırrını keşfedin. Python (Flask) ve Node.js (Express) ile disk kullanmadan gerçek zamanlı zip sıkıştırma ve streaming'i öğrenin.

Gerçek Zamanlı ZIP: Sunucular Klasörleri Anında Nasıl Zipleyip İndirilebilir Hale Getiriyor?

Bir GitHub reposunu veya Google Drive’dan bir klasörü indirirken hiç düşündünüz mü: Tıkladığım anda inmeye başlayan bu .zip dosyası sunucuda hazır mı bekliyordu? Cevap genellikle hayır. Bu dosyalar, siz indirme butonuna bastığınız anda, gerçek zamanlı olarak (on-the-fly) oluşturulur ve doğrudan size akıtılır (stream edilir). Bu sayede sunucuda milyonlarca olası dosya kombinasyonu için disk alanı harcanmaz ve kullanıcı her zaman en güncel veriyi alır.

Bu yazıda, bu güçlü tekniğin arkasındaki mantığı inceleyecek ve Python (Flask) ile basit bir uygulamasını yapacağız.

🧐 Neden “On-the-Fly” Zip?

Dosyaları istek anında sıkıştırıp göndermenin birçok avantajı vardır:

  • 💾 Disk Alanı Tasarrufu: Sunucuda her olası klasör veya dosya seçimi için önceden bir .zip arşivi oluşturup saklamak, özellikle sürekli değişen verilerle çalışırken, devasa bir disk israfına yol açar. Gerçek zamanlı sıkıştırma ile hiçbir arşiv dosyası diske kaydedilmez.
  • 🔄 Her Zaman Güncel Veri: Kullanıcı bir arşivi indirdiğinde, dosyaların o anki en son sürümünü aldığından emin olur. Önceden oluşturulmuş arşivler hızla eskiyebilir.
  • 🔧 Esneklik ve Özelleştirme: Kullanıcının seçtiği belirli dosyaları veya bir veritabanı sorgusunun sonucunda oluşan raporları anında sıkıştırarak özel arşivler sunabilirsiniz.
  • ⚡️ Düşük Bellek Kullanımı ve Hız: Streaming (akıtma) mantığı sayesinde, oluşturulacak olan zip dosyasının tamamı sunucunun belleğine (RAM) yüklenmez. Dosyalar parça parça okunur, sıkıştırılır ve anında kullanıcıya gönderilir. Bu, çok büyük arşivler oluştururken bile sunucunun yorulmasını engeller ve indirme işleminin daha hızlı başlamasını sağlar.

🌊 Akış Diyagramı

Sürecin temel mantığı aşağıdaki gibidir. İstemciden (tarayıcı) gelen istek, sunucuda bir streaming sürecini tetikler.

flowchart TD
    A["Kullanıcı 'İndir' butonuna tıklar"] --> B{"Sunucuya /download-zip isteği gelir"}
    B --> C["Sunucu HTTP başlıklarını ayarlar<br />Content-Type: application/zip<br />Content-Disposition: attachment"]
    C --> D["Zip stream'i başlatılır"]
    D --> E{"Dosyalar sırayla okunur"}
    E --> F["Dosya içeriği zip stream'ine eklenir"]
    F --> G["Sıkıştırılmış veri parçası (chunk) kullanıcıya gönderilir"]
    E -- "Tüm dosyalar bitince" --> H["Zip stream'i sonlandırılır"]
    H --> I["HTTP bağlantısı kapatılır"]
    G -- "Daha fazla dosya varsa" --> E

🐍 Python (Flask) ile Basit Zip Örneği

Bu ilk örnekte, dosyaları sunucu belleğinde (RAM) sıkıştırıp göndermenin en temel yolunu göreceğiz. Bu yöntem, küçük ve orta boyutlu dosyalar için harikadır ve anlaşılması çok kolaydır.

Kurulum ve Kod

Öncelikle projemiz için gerekli olan Flask kütüphanesini kuralım:

1
pip install Flask

Şimdi, app.py dosyamızı oluşturalım:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# app.py
from flask import Flask, Response
import zipfile
import io

app = Flask(__name__)

@app.route('/download-zip')
def download_zip():
    # 1. Bellekte geçici bir dosya gibi davranan bir buffer oluştur.
    memory_file = io.BytesIO()

    # 2. Bu buffer'ı kullanarak bir zip dosyası oluştur.
    with zipfile.ZipFile(memory_file, 'w', zipfile.ZIP_DEFLATED) as zf:
        # Arşive eklenecek verileri tanımla.
        file1_content = b"fr0st"
        file2_content = b"b1rd"

        # Verileri arşive dosya olarak ekle.
        zf.writestr('fr0st.txt', file1_content)
        zf.writestr('b1rd.txt', file2_content)
    
    # 3. Buffer'ın imlecini başa al ki içeriği baştan okuyabilelim.
    memory_file.seek(0)

    # 4. Flask Response objesi ile veriyi ve HTTP başlıklarını ayarla.
    return Response(
        memory_file,
        mimetype='application/zip',
        headers={'Content-Disposition': 'attachment; filename="ornek_arsiv.zip"'}

    )

if __name__ == '__main__':
    app.run(debug=True)

Çalıştırma Adımları

  1. app.py adında bir dosya oluşturup yukarıdaki kodu içine yapıştır.
  2. Uygulamayı çalıştır: python app.py
  3. Test et: Tarayıcınızda http://127.0.0.1:5000/download-zip adresine gidin. ornek_arsiv.zip dosyası inmeye başlayacaktır.

Bu yöntem çok basittir ancak bir dezavantajı vardır: Oluşturulan zip dosyası tamamen sunucunun belleğinde tutulur. Eğer 1 GB’lık bir arşiv oluşturursanız, sunucunuzun RAM’inde 1 GB’lık bir yer kaplar. Çok büyük dosyalar için bu verimli değildir. İşte bu noktada “streaming” devreye girer.

⚙️ Arkasındaki Mühendislik: Streaming (Akıtma) Nasıl Çalışır?

Streaming, büyük bir veriyi tek seferde işlemek yerine, küçük parçalara (chunk) bölerek işlemeyi ve iletmeyi sağlayan güçlü bir tekniktir. Gerçek zamanlı zip oluşturmada bu, bellek verimliliği için hayati önem taşır.

Temel Kavramlar

  1. HTTP Chunked Transfer Encoding: Normalde bir sunucu, bir dosyayı gönderirken tarayıcıya önce dosyanın toplam boyutunu (Content-Length başlığı ile) bildirir. Ancak streaming yaparken toplam boyut en başta belli değildir. İşte burada “Chunked Transfer Encoding” devreye girer. Sunucu, tarayıcıya “Sana veriyi parçalar halinde göndereceğim, toplam boyutu bilmiyorum ama her parçanın boyutunu sana söyleyeceğim.” der. Veri akışı bittiğinde ise “0 boyutlu” son bir parça göndererek işlemi tamamlar. Tarayıcı bu parçaları birleştirerek orijinal dosyayı oluşturur.

  2. Python’da Generator’lar (yield): Bir fonksiyonda return yerine yield kullandığınızda, o fonksiyon bir “generator”a dönüşür. Generator’lar, değerleri tek tek üretir ve her yield ifadesinde duraklar. Bir sonraki değer istendiğinde kaldığı yerden devam eder. Bu, tüm veriyi bellekte bir liste olarak tutmak yerine, ihtiyaç anında üretmeyi sağlar. Zip streaming’de, her bir sıkıştırılmış veri parçası yield ile üretilip anında istemciye gönderilir.

  3. Verimli Kütüphaneler (zipstream-ng): zipstream-ng gibi kütüphaneler, bu generator mantığını bizim için otomatikleştirir. Dosyaları alır, küçük parçalar halinde okur, sıkıştırır ve bu sıkıştırılmış parçaları yield ederek bir veri akışı oluşturur. Flask’in stream_with_context fonksiyonu da bu akışı alıp HTTP yanıtına dönüştürür.

🚀 zipstream-ng ile Streaming Zip Oluşturma

zipstream-ng, büyük dosyaları ve klasörleri stream etmek için modern, verimli ve esnek bir API sunar. Web sunucuları ve büyük veri işleme için en iyi seçenektir.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# pip install zipstream-ng
from zipstream import ZipStream, ZIP_DEFLATED

# 1. Tüm bir klasörü anında zipleyip dosyaya yazmak:
zs = ZipStream.from_path("/path/to/files/")
with open("files.zip", "wb") as f:
    f.writelines(zs)

# 2. Gelişmiş API: Yorum, sıkıştırma seviyesi, farklı kaynaklar
zs = ZipStream(compress_type=ZIP_DEFLATED, compress_level=9)
zs.comment = b"Contains compressed important files"
zs.add_path("/path/to/files/") # Klasör ekle
zs.add_path("/path/to/file.txt", "data.txt") # Dosyayı farklı isimle ekle

# Generator'dan veri ekle
def random_data():
    import random
    for _ in range(10):
        yield random.randbytes(1024)
zs.add(random_data(), "random.bin")

# Metin verisi ekle
zs.add(b"This is some text", "README.txt")

with open("files.zip", "wb") as f:
    f.writelines(zs)

# 3. Flask ile streaming entegrasyonu:
import os.path
from flask import Flask, Response
from zipstream import ZipStream

app = Flask(__name__)

@app.route("/", defaults={"path": "."})
@app.route("/<path:path>")
def stream_zip(path):
    name = os.path.basename(os.path.abspath(path))
    zs = ZipStream.from_path(path)
    return Response(
        zs,
        mimetype="application/zip",
        headers={
            "Content-Disposition": f"attachment; filename={name}.zip",
            "Content-Length": str(len(zs)),
            "Last-Modified": zs.last_modified,
        }
    )

# 4. Manifest ekleme (arşiv içeriği listesi):
import json

def gen_zipfile_with_manifest():
    zs = ZipStream.from_path("/path/to/files")
    yield from zs.all_files() # Önce dosyaları yield et
    
    # Sonra manifest'i oluştur ve ekle
    manifest = json.dumps(zs.info_list(), indent=2).encode()
    zs.add(manifest, "manifest.json")
    
    yield from zs.finalize() # Arşivi sonlandır

Avantajları:

  • 🚀 Yüksek Performans: Bellek kullanımı çok düşüktür ve büyük dosyalarda hızlıdır.
  • 🧩 Esnek API: Dosya, klasör, generator veya metin gibi farklı kaynaklardan veri eklemeyi destekler.
  • 🌐 Web Dostu: Content-Length ve Last-Modified gibi HTTP başlıklarını otomatik hesaplayarak Flask/Django gibi framework’lerle entegrasyonu kolaylaştırır.
  • 🛠️ Zengin Özellikler: ZIP64 desteği (4GB+ arşivler için), yorum ekleme, sıkıştırma seviyesi belirleme gibi birçok özellik sunar.

🧰 Standart Kütüphane (stdlib) ile Streaming Zip

Python’un dahili zipfile modülü ile de streaming yapmak mümkündür, ancak bu, zipstream-ng‘ye kıyasla çok daha fazla manuel kodlama gerektirir ve daha karmaşıktır.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# Sadece standart kütüphane kullanılır
import os
import io
from zipfile import ZipFile, ZipInfo

# Geçici bir buffer görevi görecek özel bir stream sınıfı
class Stream(io.RawIOBase):
    def __init__(self):
        self._buffer = bytearray()
        self._closed = False
    def close(self):
        self._closed = True
    def write(self, b):
        if self._closed:
            raise ValueError("Kapalı stream'e yazılamaz")
        self._buffer += b
        return len(b)
    def readall(self):
        chunk = bytes(self._buffer)
        self._buffer.clear()
        return chunk

# Dosyaları ve klasörleri yinelemeli olarak gezen bir generator
def iter_files(path):
    for dirpath, _, files in os.walk(path, followlinks=True):
        if not files:
            yield dirpath
        for f in files:
            yield os.path.join(dirpath, f)

# Bir dosyayı parçalar halinde okuyan bir generator
def read_file_chunks(path):
    with open(path, "rb") as fp:
        while True:
            buf = fp.read(1024 * 64)
            if not buf:
                break
            yield buf

# Tüm parçaları birleştiren ana zip streaming generator'ı
def generate_zipstream(path):
    stream = Stream()
    with ZipFile(stream, mode="w") as zf:
        toplevel = os.path.basename(os.path.normpath(path))
        for f in iter_files(path):
            arcname = os.path.join(toplevel, os.path.relpath(f, path))
            zinfo = ZipInfo.from_file(f, arcname)
            with zf.open(zinfo, mode="w") as fp:
                if zinfo.is_dir():
                    continue
                for chunk in read_file_chunks(f):
                    fp.write(chunk)
                    yield stream.readall() # Her yazma sonrası buffer'ı boşalt ve gönder
    yield stream.readall() # Son kalan veriyi gönder

# Kullanım örneği (bir send_stream fonksiyonu varsayılarak):
send_stream(generate_zipstream("/path/to/files/"))

Dezavantajları:

  • 🤯 Karmaşık Kod: Görüldüğü gibi, süreci yönetmek için birden fazla yardımcı fonksiyon ve sınıf yazmak gerekir.
  • 🔧 Düşük Seviye Yönetim: Veri parçalarını, buffer’ları ve generator’ları manuel olarak yönetmek gerekir, bu da hata yapma olasılığını artırır.
  • ❌ Eksik Özellikler: Content-Length gibi önemli HTTP başlıklarını önceden hesaplamak neredeyse imkansızdır. Bu, indirme sürelerinin tarayıcıda düzgün görüntülenmesini engeller.

Bu karşılaştırma, zipstream-ng‘nin modern ve ölçeklenebilir uygulamalar için neden standart kütüphaneye göre çok daha üstün bir çözüm olduğunu açıkça göstermektedir.

Sonuç

Gerçek zamanlı sıkıştırma ve streaming, modern web uygulamaları için vazgeçilmez bir tekniktir. Basit senaryolar için bellek içi sıkıştırma yeterli olabilirken, ölçeklenebilir ve verimli uygulamalar için streaming mantığını ve zipstream-ng gibi kütüphaneleri kullanmak en doğru yaklaşımdır.

Başka bir yazıda görüşmek üzere, esen kalın.

This post is licensed under CC BY 4.0 by the author.