Seite 1 von 1

cgigz.py; CGI Seite komprimiert mit gzip versenden

Verfasst: Donnerstag 3. Februar 2005, 14:15
von XT@ngel
Das ist die erste Version meine stdout -> gzip klasse.
Auf tempfile hab ich verzichtet, da es unter Windows nicht richtig arbeitet.
Ziel war eigentlich eine benutzung wie bei PHP, was ich bis jetzt noch nicht hinbekommen habe.
Deshalb auch die verwendung eines Destruktors, wo ich es Python überlassen das Object am Ende des scripts zu löschen.
Wems nicht gefällt kann es sich ja umschreiben :mrgreen:

Code: Alles auswählen

__version__ = '0.1'

import sys, gzip, time, os

class NotSupportedEncoding(Exception):
    pass

class enable:
    def __init__(self):
        Encodings = os.environ['HTTP_ACCEPT_ENCODING'].split(',')
        if 'gzip' in Encodings:
            self.TMP_File = "%s.cgigz" % str(time.time())
            self.GZIP  = gzip.GzipFile(filename=self.TMP_File, mode='wb')
            sys.stdout = self.GZIP
        else:
            raise NotSupportedEncoding

    def __del__(self):
        sys.stdout = sys.__stdout__
        self.GZIP.close() 
        send = open(self.TMP_File, 'rb')
        print send.read()
        send.close()
        os.remove(self.TMP_File)
Beispiel

Code: Alles auswählen

import cgigz

try:
    print "Content-Type: text/html"
    print "Content-Encoding: gzip"
    print
    garbage_collection_dummy = cgigz.enable()
except cgigz.NotSupportedEncoding:
    print "Content-Type: text/html"
    print

print "hallo"
print "TETFSDHSDGHGHSD"

Verfasst: Donnerstag 3. Februar 2005, 16:12
von jens
cgigz hab ich garnicht ?!?! Oder ist damit dein erster codebrocken gemeint???

Hier mal meine Variante... Die aber nicht funktioniert :( Warum???

Code: Alles auswählen

#!/usr/bin/python
# -*- coding: ISO-8859-1 -*-

import os,zlib

def putHTML( HTMLdata ):
    if "gzip" in os.environ['HTTP_ACCEPT_ENCODING'].split(','):
        print "Content-Type: text/html"
        print "Content-Encoding: gzip"
        print
        print zlib.compress( HTMLdata )
    else:
        #~ print "Content-Encoding: deflate"
        print "Content-Type: text/html\n"
        print HTMLdata



if __name__=="__main__":
    os.environ['HTTP_ACCEPT_ENCODING'] = "gzip,deflate"
    #~ os.environ['HTTP_ACCEPT_ENCODING'] = "deflate"
    putHTML("<html><body><h1>TEST</h1></body></html>")
Ich sehe nicht genau, warum es nicht geht... Auch wenn man die Zeile mit print "Content-Encoding: deflate" auskommentiert, zeigt der Browser nix mehr an :(

Mit deiner Variante bleibt allerdings die Seite auch leer...

Ob zlib nicht ganz das richtige liefert???

Verfasst: Donnerstag 3. Februar 2005, 18:06
von XT@ngel
cgigz hab ich garnicht ?!?! Oder ist damit dein erster codebrocken gemeint???
Ja, sorry für die schlechte Erklärung.
Mit deiner Variante bleibt allerdings die Seite auch leer...
Bei mir funktionierts einwandfrei. (Win 2000 prof. & Apache 2.0 , Python2.4 als CGI)
Vielleicht liegts an einem Copy/Paste fehler...
Wenn die Seite leer bleibt ist es meistens ein fehlerhaft gesendeter Header.
evtl. hilft dir auch die error log deines servers.

zlib werd ich mir mal genauer anschaun...

Verfasst: Freitag 4. Februar 2005, 03:01
von BlackJack
Wenn ich die erste Methode richtig verstanden habe, dann basiert die darauf, dass das senden der Daten in der __del__() Methode stattfindet?

Das ist IMHO nicht besonders empfehlenswert. Lies Dir mal durch, was Python für Garantien gibt wann __del__() ausgeführt wird, bzw. was alles nicht als gegeben angesehen werden kann.

Wenn das Skript zuende ist, dann werden nach und nach die Objekte beseitigt und dabei eventuell vorhandene __del__() Methoden aufgerufen. Ich weiss zum Beispiel nicht, ob das Objekt sys.__stdout__ noch existieren muss, wenn Deine Methode ausgeführt wird!? Von __del__() sollte man eigentlich die Finger lassen wenn es nicht zwingend notwendig ist.

Die Funktion os.tmpfile() würde übrigens schon funktionieren. Was nicht geht, ist das erneute öffnen, da die Datei keinen Namen besitzt. Aber man kann vor dem Auslesen mit seek() wieder an den Anfang der Datei gehen.

Verfasst: Freitag 4. Februar 2005, 04:19
von XT@ngel
Hi BlackJack,
danke für deinen Hinweis auf os.tmpfile.
Ich hatte die ganze Zeit die Funktionen aus dem Modul tempfile verwendet
und da konnt ich mit seek() rumspielen wie ich wollte, ohne Ergebnis.
Dagegen ist os.tmpfile fast ein Wunder.
Wäre dankbar für ein Beispiel (tempfile) unter Windows2000 :)
Lies Dir mal durch, was Python für Garantien gibt..
Ich habs mir sogar extra nochmal durchgelesen und da ich wusste, dass es Kritik geben wird, schrieb ich...
XT@ngel hat geschrieben:Wems nicht gefällt kann es sich ja umschreiben :mrgreen:
Vielleicht hätte ich schreiben sollen, wieso es einem nicht gefallen sollte. :roll:

Code: Alles auswählen

__author__ = 'python-forum.de'
__version__ = '0.3'

import sys, gzip, os

class NotSupportedEncoding(Exception):
    pass

class ContentAlreadySend(Exception):
    pass

class enable:
    def __init__(self):
        if 'gzip' in os.environ['HTTP_ACCEPT_ENCODING'].split(','):
            self.Send = False
            self.TMP_File = os.tmpfile()
            self.GZIP  = gzip.GzipFile(fileobj=self.TMP_File)
            sys.stdout = self.GZIP
        else:
            raise NotSupportedEncoding

    def send(self):
        if self.Send:
            raise ContentAlreadySend
        else:
            sys.stdout = sys.__stdout__
            self.GZIP.close() 
            self.TMP_File.seek(0)
            print self.TMP_File.read()
            self.TMP_File.close()
            self.Send = True
       

Verfasst: Samstag 5. Februar 2005, 00:11
von BlackJack
Mir gefällt's immer noch nicht, darum hab' ich was ähnliches geschrieben. :-)

Ohne eine extra Klasse und ohne temporäre Datei, weil man ganz einfach `sys.stdout` in ein `GzipFile` "einwickeln" kann.

Code: Alles auswählen

import os, sys
from gzip import GzipFile

def is_supported():
    """Checks if *gzip* compression is supported by client side."""
    try:
        return 'gzip' in os.environ['HTTP_ACCEPT_ENCODING'].split(',')
    except KeyError:
        return False

def enable():
    """Enables gzip compression of `sys.stdout` if client side
    supports it.
    
    :attention: Must be called *after* all HTTP header lines are
        written as `enable()` sends the separator line between headers
        and content!
    """
    if is_supported():
        print 'Content-Encoding: gzip'
        print
        sys.stdout = GzipFile(mode='wb', fileobj=sys.stdout)
    else:
        print


#
# Test code.
#
print 'Content-Type: text/html'
enable()

print ('<html><body><h1>Compression test</h1>'
       '<p>Compression support: %s</p>'
       '</body></html>' % is_supported())

Re: cgigz.py; CGI Seite komprimiert mit gzip versenden

Verfasst: Samstag 5. Februar 2005, 00:17
von BlackJack
Aus dem allerersten Beitrag:
XT@ngel hat geschrieben:

Code: Alles auswählen

try:
    print "Content-Type: text/html"
    print "Content-Encoding: gzip"
    print
    garbage_collection_dummy = cgigz.enable()
except cgigz.NotSupportedEncoding:
    print "Content-Type: text/html"
    print
Falls die Ausnahme ausgelöst wird, weil die Komprimierung nicht unterstützt wird, ist schon das "Content-Encoding: gzip" geschrieben worden. :?

Verfasst: Samstag 5. Februar 2005, 00:23
von BlackJack
jens hat geschrieben:Ob zlib nicht ganz das richtige liefert???
Du hast das "falsche Etikett draufgeklebt". Du sendest das Ergebnis von zlib.compress(), sagst dem Browser aber vorher, da kommen `gzip` Daten. Du müsstest `deflate` ankündigen.

Verfasst: Montag 7. Februar 2005, 18:45
von jens
Jep, mit "deflate" klappt es... Jetzt bin ich allerdings ein wenig verwirrt... Worin besteht der Unterschied zwischen gzip und deflate ??? Welches ist besser?

Hab das gefunden:
gzip basiert auf dem deflate-Algorithmus, der eine Kombination aus LZ77 und Huffman-Kodierung ist. deflate wurde als Reaktion auf die Patente entwickelt, die auf LZW und andere Kompressionsalgorithmen bestanden.

Um die Entwicklung von Software zu vereinfachen, die Datenkompression nutzt, wurde die zlib-Bibliothek geschrieben. Sie unterstützt das gzip-Dateiformat und die deflate-Kompression. Die Bibliothek ist weit verbreitet, da sie klein, effizient und vielseitig ist.
http://de.wikipedia.org/wiki/Gzip

Dennoch weiß ich nicht, welches besser ist. Ich gehe einfach mal von aus, das gzip dem deflate vorzuziehen ist...

Und vor allem, kann ich GzipFile() so benutzen, wie ich es mit
print zlib.compress( HTMLdata ) gemacht hab???

Meine bisherige Variante:

Code: Alles auswählen

import os, sys

def putHTML( HTMLdata ):
    print "Content-Type: text/html"
    modes = os.environ['HTTP_ACCEPT_ENCODING'].split(',')

    if "gzip" in modes:
        from gzip import GzipFile
        
        print 'Content-Encoding: gzip\n'
        oldstdout = sys.stdout
        sys.stdout = GzipFile(mode='wb', fileobj=sys.stdout)
        print HTMLdata
        sys.stdout = oldstdout
        return
    elif "deflate" in modes:
        from zlib import compress
        
        print "Content-Encoding: deflate\n"
        print compress( HTMLdata )
        return
    else:
        print "\n", HTMLdata

Verfasst: Dienstag 8. Februar 2005, 00:18
von BlackJack
jens hat geschrieben:Jep, mit "deflate" klappt es... Jetzt bin ich allerdings ein wenig verwirrt... Worin besteht der Unterschied zwischen gzip und deflate ??? Welches ist besser?

Hab das gefunden:
gzip basiert auf dem deflate-Algorithmus, der eine Kombination aus LZ77 und Huffman-Kodierung ist. deflate wurde als Reaktion auf die Patente entwickelt, die auf LZW und andere Kompressionsalgorithmen bestanden.

Um die Entwicklung von Software zu vereinfachen, die Datenkompression nutzt, wurde die zlib-Bibliothek geschrieben. Sie unterstützt das gzip-Dateiformat und die deflate-Kompression. Die Bibliothek ist weit verbreitet, da sie klein, effizient und vielseitig ist.
Da steht der Unterschied eigentlich. Deflate ist ein Kompressionsalgorithmus und gzip ist ein Dateiformat. Bei der zlib Variante kannst Du Daten als String nehmen, sie komprimieren und bekommst die reinen komprimierten Daten zurück. Bei GzipFile wird in die Datei erst ein Header geschrieben, in dem der Dateiname drinsteht und dann folgen deflate-komprimierte Datenblöcke. Ich glaube eine Prüfsumme wird auch über jeden Block gebildet.

Der Vorteil von GzipFile() beim benutzen in diesem Szenario ist meiner Meinung nach, dass man davon im restlichen Programm nichts merken muss. Man "wrapped" einfach sys.stdout und kann dann ganz normal mit print oder sys.stdout.write() Daten schreiben. Die Daten werden automatisch in Blöcke aufgeteilt, komprimiert und verschickt. Es wird nie mehr Speicher verbraucht, als für diese relativ kleinen Blöcke benötigt wird. Bei zlib.compress() dagegen muss man erst die kompletten Daten lesen und komprimieren.

Verfasst: Dienstag 8. Februar 2005, 07:03
von jens
Muß man dann nicht auch am Ende ein close() machen???

Ich meine, was passiert mit dem letzten, vielleicht gerade erst angefangenen Block? Der wird doch ohne close() garnicht gesendet, oder?

Unabhändig von der Speicherauslastung, die IMHO nur bei super, super langen HTML-Seiten überhaupt erst zum tragen kommt... Ist deflate nicht generell besser, weil kleiner, weil kein Header vorhanden ist??? Eine CRC o.ä. sollte doch überhaupt nicht nötig sein, da bei TCP/IP die Daten doch eigentlich schon Fehlergeschützt sind...

Verfasst: Dienstag 8. Februar 2005, 14:02
von BlackJack
Das close() wird am Skriptende implizit gemacht würde ich sagen. Es klappt jedenfalls so. Der Overhead ist nicht gross, bloss ein paar Byte.

Verfasst: Dienstag 8. Februar 2005, 14:03
von Leonidas
BlackJack hat geschrieben:Das close() wird am Skriptende implizit gemacht würde ich sagen.
Trotzdem sollte man es schon vorher zumachen, das ist guter Stil.

Verfasst: Mittwoch 9. Februar 2005, 21:56
von BlackJack
Okay, dann kann man das Modul atexit importieren und nach der Zeile, die sys.stdout durch das GzipFile ersetzt, noch folgendes einfügen:

Code: Alles auswählen

atexit.register(sys.stdout.close)

Verfasst: Donnerstag 3. März 2005, 18:08
von jens
Langsam finde ich gefallen daran stdout zu verbiegen... Nun frage ich mich, ob es auch bei "deflate" mit zlib geht... Ich nehme mal an, dazu muß ich zlib.compressobj() verwenden, oder?

Verfasst: Donnerstag 3. März 2005, 21:13
von jens
Also ich hab mir hier ein gebastelt:

Code: Alles auswählen

#!/usr/bin/python
# -*- coding: ISO-8859-1 -*-

import os, sys

class ZLIBout:
    def __init__( self ):
        from zlib import compress
        self.compress = compress

    def write( self, txt ):
        sys.stdout.write( self.compress(txt) )


class Out:
    def __init__( self ):
        print "Content-Type: text/html"
        self.MyStdOut = sys.stdout
        self.mode = self.detect_mode()
        print 'Content-Encoding: %s\n' % self.mode
        self.set_mode()

    def write( self, txt ):
        self.MyStdOut.write( txt )

    def detect_mode( self ):
        "Ermittle den möglichen Modus"
        if os.environ.has_key('HTTP_ACCEPT_ENCODING'):
            modes = os.environ['HTTP_ACCEPT_ENCODING'].split(',')
            if "gzip" in modes:
                return "gzip"
            elif "deflate" in modes:
                return "deflate"
        return "flat"

    def set_mode( self ):
        if self.mode == "gzip":
            from gzip import GzipFile
            sys.stdout = GzipFile(mode='wb', fileobj=sys.stdout)
        elif self.mode == "deflate":
            MyZLIBout = ZLIBout()
            sys.stdout = MyZLIBout

    def get_mode( self ):
        return self.mode




if __name__ == "__main__":
    os.environ['HTTP_ACCEPT_ENCODING'] = "deflate"
    MyOut = Out()
    print "Das ist ein toller Test Text!"
    print MyOut.get_mode
"gzip" und "flat" funktioniert einwandfrei...
Leider bietet zlib kein fileobj... Zwar gibt es da dieses compressobj() aber das scheind mit was anderes zu sein... Deshalb hab ich dafür auch noch mal eine Klasse geschrieben, die eigentlich das selbe machen sollte wie es gzip.GzipFile() kann...
Aber es gibt ein endlos Loop beim schreiben in ZLIBout.write():

Code: Alles auswählen

Traceback (most recent call last):
  File "CompressedOut.py", line 84, in ?
    print "Das ist ein toller Test Text!"
  File "CompressedOut.py", line 13, in write
    print self.compress(txt)
  File "CompressedOut.py", line 13, in write
    print self.compress(txt)
[...]
Ein seperater Test funktioniert aber:

Code: Alles auswählen

    MyZLIBout = ZLIBout()
    MyZLIBout.write("Noch ein Test...")

Verfasst: Freitag 4. März 2005, 02:11
von BlackJack
jens hat geschrieben:Aber es gibt ein endlos Loop beim schreiben in ZLIBout.write():

Code: Alles auswählen

Traceback (most recent call last):
  File "CompressedOut.py", line 84, in ?
    print "Das ist ein toller Test Text!"
  File "CompressedOut.py", line 13, in write
    print self.compress(txt)
  File "CompressedOut.py", line 13, in write
    print self.compress(txt)
[...]
Irgendwie glaube ich Du hast nicht den Quelltext gepostet, der diese Ausgabe produziert. Ich sehe in Zeile 13 nämlich kein ``print self.compress(txt)``.

Aber auch das `sys.stdout.write()` wird nicht funktionieren. Du hast sys.stdout doch ersetzt. Alles was dort hingeschrieben wird, wird komprimiert. Also auch das Ergebnis von `self.compress()`. Damit wird wieder Dein `ZLIBout.write()` aufgerufen. Und das schreibt wieder auf `sys.stdout` und ... naja immer so weiter.

Verfasst: Montag 7. März 2005, 16:11
von jens
So, jetzt hab ich's aber! Ich habe nun die ZlibFile so gebastelt, das sie genauso wie gzip.GzipFile() funktioniert:

Code: Alles auswählen

#!/usr/bin/python
# -*- coding: ISO-8859-1 -*-

import os, sys

class ZlibFile:
    def __init__( self, fileobj ):
        self.fileobj = fileobj
        from zlib import compress
        self.compress = compress

    def write( self, txt ):
        self.fileobj.write( self.compress(txt) )

    def flush( self ):
        self.fileobj.flush()

class AutoCompressedOut:
    def __init__( self ):
        print "Content-Type: text/html"
        self.mode = self.detect_mode()
        print 'Content-Encoding: %s\n' % self.mode
        self.set_mode()

    def detect_mode( self ):
        "Ermittle den möglichen Modus"
        if os.environ.has_key('HTTP_ACCEPT_ENCODING'):
            modes = os.environ['HTTP_ACCEPT_ENCODING'].split(',')
            if "gzip" in modes:
                return "gzip"
            elif "deflate" in modes:
                return "deflate"
        return "flat"

    def set_mode( self ):
        if self.mode == "gzip":
            from gzip import GzipFile
            sys.stdout = GzipFile( mode='wb', fileobj=sys.stdout )
        elif self.mode == "deflate":
            sys.stdout = ZlibFile( fileobj=sys.stdout )

    def get_mode( self ):
        return self.mode

if __name__ == "__main__":
    #~ os.environ['HTTP_ACCEPT_ENCODING'] = "deflate"
    os.environ['HTTP_ACCEPT_ENCODING'] = "gzip"
    MyOut = AutoCompressedOut()

    print "Das ist ein toller Test Text!"
    print "Und noch eine Zeile..."
    print "...und noch was..."
    print "Verwendeter Modus: '%s'" % MyOut.get_mode()

Verfasst: Donnerstag 17. März 2005, 21:14
von jens
Leider muß ich nun feststellen, die komprimierte Übertragung hat einen Nachteil bei langen Seiten! Denn der Browser zeigt mit immer erst die Seite an, wenn sie komplett geladen ist und nicht schon die ersten Teile einer Tabelle...

Code: Alles auswählen

if __name__ == "__main__":
    #~ os.environ['HTTP_ACCEPT_ENCODING'] = "deflate"
    os.environ['HTTP_ACCEPT_ENCODING'] = "gzip"
    MyOut = AutoCompressedOut()

    import time
    for i in xrange(10):
        print i
        time.sleep(0.1)
Mit diesem Test kann man sehen, das gzip auch wirklich erst die Daten raushaut, wenn die Schleife fertig ist. (ein sys.stdout.flush() ändert da auch nix).
deflate kann es allerdings! Dennoch beginnt der Browser erst mit der Dekompression, wenn die Seite ganz da ist :twisted:

Verfasst: Donnerstag 17. März 2005, 23:12
von BlackJack
Die Testschleife ist ein bischen zu klein. Da werden sicher ein paar KB gepuffert. Ansonsten schreibt GzipFile die komprimierten Daten sofort auf das darunterliegende Dateiobjekt.

Und deflate muss doch erst die Daten komplett haben um sie komprimieren zu können. Wie kann da vorher schon etwas geschickt werden?

Ob die Seite während des Empfangs schon entpackt und angezeigt wird, ist ein Problem des Browsers. Technisch möglich ist es zumindest bei gzip jedenfalls.