2017-05-25 5 views
7

Ich kann zwar Text erstellen und streamen, aber eine komprimierte Datei nicht im laufenden Betrieb erstellen und streamen.Komprimierte Datei mit Flask generieren und streamen

from flask import Flask, request, Response,stream_with_context 
import zlib 
import gzip 

app = Flask(__name__) 

def generate_text(): 
    for x in xrange(10000): 
     yield "this is my line: {}\n".format(x) 

@app.route('/stream_text') 
def stream_text(): 
    response = Response(stream_with_context(generate_text())) 
    return response 

def generate_zip(): 
    for x in xrange(10000): 
     yield zlib.compress("this is my line: {}\n".format(x)) 

@app.route('/stream_zip') 
def stream_zip(): 
    response = Response(stream_with_context(generate_zip()), mimetype='application/zip') 
    response.headers['Content-Disposition'] = 'attachment; filename=data.gz' 
    return response 

if __name__ == '__main__': 
    app.run(host='0.0.0.0', port=8000, debug=True) 

als mit curl und gunzip:

curl http://127.0.0.1:8000/stream_zip > data.gz 

gunzip data.gz 
gunzip: data.gz: not in gzip format 

ist mir egal, ob es zip, gzip, oder jede andere Art von Kompression ist.

generate_text in meinem realen Code generiert über 4 GB Daten, so würde ich gerne im laufenden Betrieb komprimieren.

Speichern von Text in Datei, zippen, ZIP-Datei zurückgeben, und dann löschen ist nicht die Lösung, nach der ich bin.

Ich muss in einer Schleife sein, einige Text generieren -> diesen Text komprimieren -> komprimierte Daten streamen, bis ich fertig bin.

zip/gzip ... alles ist in Ordnung, solange es funktioniert.

+0

Ich denke, dass, da zlib.compress eine Zeichenfolge zurückgibt, Sie nicht einen tatsächlichen gzip Bytecode senden. –

+0

@DrorHilman: Dies ist Python 2, also sind sowohl gzip-Daten als auch rohe zlib-komprimierte Daten Strings. Aber ja, du kannst gzip nicht benutzen, um rohe zlib-Daten direkt zu dekomprimieren, zumindest nicht ohne Hacker (siehe [Wie entpacke ich zlib-Daten in UNIX?] (// unix.stackexchange.com/q/22834)). –

Antwort

-1

Ich denke, dass Sie derzeit senden nur den Generator anstelle der Daten! Sie können so etwas wie dies tun wollen (ich habe es nicht getestet, so können eine gewisse Änderung benötigen):

def generate_zip(): 
    import io 
    with gzip.GzipFile(fileobj=io.BytesIO(), mode='w') as gfile: 
     for x in xrange(10000): 
      gfile.write("this is my line: {}\n".format(x)) 
    return gfile.read() 
+0

Das ist der * ganze Punkt * des Generators; es wird an anderer Stelle iteriert. Das Problem besteht darin, 'zlib.compress()' zu verwenden. Ihre Lösung entfernt die Fähigkeit, insgesamt zu streamen. –

-1

Arbeiten generate_zip() mit geringen Speicherverbrauch :):

def generate_zip(): 
    buff = io.BytesIO() 
    gz = gzip.GzipFile(mode='w', fileobj=buff) 
    for x in xrange(10000): 
     gz.write("this is my line: {}\n".format(x)) 
     yield buff.read() 
     buff.truncate() 
    gz.close() 
    yield buff.getvalue() 
+0

Dies gibt tatsächlich keine Daten bis zum letzten Ertrag zurück. – mattjvincent

+0

Dies ergibt 10k leere Zeichenfolgen, dann das gesamte Dokument. Das ist auch Overkill. Es ist nicht notwendig, separate Dateiobjekte im Speicher zu behalten, sondern die Komprimierung direkt zu streamen. Wenn Sie ein 'GzipFile()' -Objekt verwenden wollen, warum fangen Sie nicht einfach '' write() '- Aufrufe ein, anstatt ein' BytesIO() '-Objekt zu verwenden? –

+0

Ihr Ansatz konnte nur behoben werden, wenn Sie 'buff.getvalue()' in der Schleife (nicht 'buff.read()') verwendet haben 'buff.truncate (0)' (Sie schneiden an der aktuellen Position ab, nicht zum Start). Um zu vermeiden, leere Blöcke zu erzeugen, möchten Sie vielleicht testen, ob 'buff.getvalue()' tatsächlich etwas produziert hat, und nur dann nachgeben und abschneiden, wenn dies der Fall ist. –

9

Sie nachgebend sind eine Reihe von komprimierten Dokumenten, kein einziger komprimierter Stream. Verwenden Sie nicht zlib.compress(), es enthält den Header und bildet ein einzelnes Dokument.

Sie benötigen ein zlib.compressobj() object stattdessen zu schaffen und die Compress.compress() method für das Objekt verwenden, um einen Strom von Daten (von einem letzten Aufruf zu Compress.flush() gefolgt) zu erzeugen:

def generate_zip(): 
    compressor = zlib.compressobj() 
    for x in xrange(10000): 
     chunk = compressor.compress("this is my line: {}\n".format(x)) 
     if chunk: 
      yield chunk 
    yield compressor.flush() 

Der Kompressor leere Blöcke erzeugen kann, wenn es Es sind noch nicht genug Daten vorhanden, um einen vollständigen komprimierten Datenblock zu erzeugen, das obige ergibt nur, wenn tatsächlich etwas zu senden ist. Da Ihre Eingabedaten so hochgradig repetitiv sind und somit die Daten effizient komprimiert werden können, ergibt dies nur 3-mal (einmal mit 2-Byte-Header, einmal mit ungefähr 21 KB komprimierter Daten über die ersten 8288 Iterationen über xrange() und schließlich mit den verbleibenden 4kb für den Rest der Schleife).

Zusammen ergeben dies die gleichen Daten wie ein einzelner Aufruf zlib.compress() mit allen verketteten Eingängen. Der korrekte MIME-Typ für dieses Datenformat ist application/zlib, nichtapplication/zip.

Dieses Format kann nicht einfach mit gzip dekomprimiert werden, jedoch nicht without some trickery. Das ist, weil das oben genannte noch keine GZIP Datei erzeugt, es produziert nur einen rohen zlib-komprimierten Stream.Um es GZIP kompatibel zu machen, müssen Sie configure the compression correctly, einen Header senden erste und fügen Sie ein CRC checksum und Datenlängenwert am Ende:

import zlib 
import struct 
import time 

def generate_gzip(): 
    # Yield a gzip file header first. 
    yield (
     '\037\213\010\000' + # Gzip file, deflate, no filename 
     struct.pack('<L', long(time.time())) + # compression start time 
     '\002\377' # maximum compression, no OS specified 
    ) 

    # bookkeeping: the compression state, running CRC and total length 
    compressor = zlib.compressobj(
     9, zlib.DEFLATED, -zlib.MAX_WBITS, zlib.DEF_MEM_LEVEL, 0) 
    crc = zlib.crc32("") 
    length = 0 

    for x in xrange(10000): 
     data = "this is my line: {}\n".format(x) 
     chunk = compressor.compress(data) 
     if chunk: 
      yield chunk 
     crc = zlib.crc32(data, crc) & 0xffffffffL 
     length += len(data) 

    # Finishing off, send remainder of the compressed data, and CRC and length 
    yield compressor.flush() 
    yield struct.pack("<2L", crc, length & 0xffffffffL) 

dieser Serve als application/gzip:

@app.route('/stream_gzip') 
def stream_gzip(): 
    response = Response(stream_with_context(generate_gzip()), mimetype='application/gzip') 
    response.headers['Content-Disposition'] = 'attachment; filename=data.gz' 
    return response 

und das Ergebnis kann im Handumdrehen dekomprimiert werden:

curl http://127.0.0.1:8000/stream_gzip | gunzip -c | less 
+0

Martin - in diesem Sinne ... wäre eine andere Kompression als zlib einfacher? Ich konvertiere gerade zu Python3. Vielen Dank! – mattjvincent