Hochladen großer Dateien mit Httplib, Urllib(2)

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
WillemJansen
User
Beiträge: 2
Registriert: Samstag 31. Dezember 2011, 09:26

Hallo zusammen,

ich arbeite momentan an einer Erweiterung von Duplicity, einem Backupprogramm. Die Erweiterung erlaubt es Backups bei Rapidshare zu machen.

Zum Upload der Dateien nutze ich momentan httlib, was mit Dateien bis 512 MB auch noch funktioniert. Bei Dateien mit 1Gb hängt sich das Script leider mit einem Memory-Error weg. Das ist auch absolut verständlich, da ja nur 2.2 Gb / Prozess allokiert werden können und wg einer Kopie des 1Gb-Strings und einer Übergabe in ein Unterprogramm diese Grenzen verletzt werden.

Meine Frage:
- Gibt es eine Möglichkeit das elegant per httplib / urllib / urllib2 zu lösen ohne sich jedes Mal den Arbeitsspeicher vollzuknallen?

Code:

Code: Alles auswählen

...
fields = ('sub', 'upload'), ('login', self.parsed_url.username), ('password', self.get_password()), ('folder', self.__getFolderID())
file = 'filecontent', remote_filename, str(source_file.read()) # Source_file ist sehr groß! 
content_type, body = self.__encode_multipart_formdata(fields, file)
connection = httplib.HTTPSConnection('rs'+rs_uploadserverID+'.rapidshare.com', 443)
connection.request('POST', "/cgi-bin/rsapi.cgi", body, headers)
response = connection.getresponse()
...
def __encode_multipart_formdata(self, fields, files):
		"""
		fields is a sequence of (name, value) elements for regular form fields.
		files is a sequence of (name, filename, value) elements for data to be uploaded as files
		Return (content_type, body) ready for httplib.HTTP instance
		"""
		BOUNDARY = '----------632865735RS4EVER5675865'
		CRLF = '\r\n'
		L = []
		for (key, value) in fields:
			L.append('--' + BOUNDARY)
			L.append('Content-Disposition: form-data; name="%s"' % key)
			L.append('')
			L.append(value)
		L.append('--' + BOUNDARY)
		L.append('Content-Disposition: form-data; name="%s"; filename="%s"' % (files[0], files[1]))
		L.append('Content-Type: %s' % self.__get_content_type(files[1]))
		L.append('')
		L.append(files[2])
		L.append('--' + BOUNDARY + '--')
		L.append('')
		########
		# Save some memory!
		del files
		files = ''
		gc.collect()
		########
		body = CRLF.join(L)
		content_type = 'multipart/form-data; boundary=%s' % BOUNDARY
		return content_type, body
Zuletzt geändert von Anonymous am Samstag 31. Dezember 2011, 14:45, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Code-Tags gesetzt.
nomnom
User
Beiträge: 487
Registriert: Mittwoch 19. Mai 2010, 16:25

Es gibt übrigens -Tags, mit denen der Code auch hervorgehoben wird, außerdem ist es möglich, als body ein `file`-Objekt zu übergeben. Allerdings ist das möglicherweise nicht ganz praktikabel …

Auf jeden Fall könntest du es einfach lassen, 1 GB-Strings an Funktionen zu übergeben, da diese vermutlich eine Kopie davon erhalten …
BlackJack

@WillemJansen: Wie nomnom schon sagte, könntest Du Dir ein dateiähnliches Objekt basteln, das den `body` erst auf Anfrage und in der bei `read()` angeforderten Blockgrösse generiert.

Ich sehe da so viele doppelte führende Unterstriche die per Konvention eigentlich nur ein führender Unterstrich sein sollten.

Der `str()`-Aufruf nach dem `read()` ist überflüssig.

Die Dokumentation von _encode_multipart_formdata stimmt nicht mit dem Inhalt der Methode überein — `files` wird anders beschrieben als es tatsächlich aussehen muss damit der Code funktioniert. Ausserdem sieht mir das nach einer Funktion aus und nicht nach einer Methode.

``del files`` mit dem Kommentar darüber ist irreführend. Man kann diese Zeile weglassen, ohne das sich für das Programm effektiv etwas ändert. Aber auch die beiden anderen Zeilen erscheinen mir überflüssig, denn sie sparen nicht wirklich Speicher ein. Der fetteste Brocken ist das dritte Element im `files`-Tupel, aber das bleibt auch im Speicher wenn das Tupel aus dem Speicher gelöscht würde, denn das Element ist in `L` noch einmal referenziert. Das Tupel wird aber gar nicht aus dem Speicher entfernt, denn es ist in der aufrufenden Funktion ebenfalls an einen Namen gebunden. Das dort Speicher gespart wird, ist also in der jetzigen Form reines Wunschdenken.

@nomnom: Die Funktion erhält keine Kopie. Python kopiert keine Objekte ohne dass man das explizit sagt. Beim ``CRLF.join(L)`` entsteht allerdings eine „Kopie“, danach ist der komplette Inhalt von der Zeichenkette in dem `fields`-Tupel und er steckt noch einmal in `body`.
WillemJansen
User
Beiträge: 2
Registriert: Samstag 31. Dezember 2011, 09:26

Full ACK, der Code ist noch ein "Rohdiamant", insbesondere die "Speichersparversuche" sind eher unzureichend :)

Ich denke, ich versuche es mal mit dem "dateiähnlichen Objekt", mmap soll da ggf. helfen (so habe ich gelesen). Am liebsten würde ich komplett auf große Strings verzichten, habe bislang aber keine elegante "Musterlösung" gefunden.

Falls Ihr da was in petto habt, nehme ich es gerne!

Willem
nomnom
User
Beiträge: 487
Registriert: Mittwoch 19. Mai 2010, 16:25

BlackJack hat geschrieben:@nomnom: Die Funktion erhält keine Kopie. Python kopiert keine Objekte ohne dass man das explizit sagt.
Ah, gut zu wissen!

Viele Grüße
Antworten