(Django) SMTP-Verbindung offen halten?

Sockets, TCP/IP, (XML-)RPC und ähnliche Themen gehören in dieses Forum
Antworten
metty
User
Beiträge: 99
Registriert: Samstag 13. Dezember 2008, 19:30

Hallo zusammen,

ich programmiere mit Django eine relativ große Webapplikation und es klappt alles soweit wunderbar. Ich bin aber auf ein kleines "Schönheitsproblem" gestoßen.

Ich versende über "EmailMultiAlternatives" (django.core.mail) html- bzw. text-eMails, das funktioniert auch wunderbar.

Das Problem ist jedoch, dass der eMail-Server nicht lokal läuft und so erst eine Verbindung zum Mailserver aufgebaut werden muss und das dauert immer etwas. Für den User hat das den Anschein, als ob der Befehl die eMail zu senden nicht angekommen ist, da die Programm-Abarbeitung hier "steht", bis die email korrekt versendet wurde. So kommt der Benutzer vielleicht auf die Idee nochmal zu klicken. Bei "EmailMultiAlternatives" kann jedoch ein Connection-Objekt angegeben werden. Sprich eine bestehende SMTP-Verbindung.

Wo bzw. wie kann eine solche SMTP-Verbindung schon vor dem versenden öffnen (z.B. beim Aufruf des Django-apps), dass das Versenden der Mail schneller von statten geht?
Kann ich das in sowas wie einem Konstruktor des jeweiligen App's angeben?


Vielen Dank.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Da für jeden Request ja Django ja quasi neu gestartet wird, funktioniert es prinzipiell nicht, dass etwas permanent läuft oder verbunden bleibt.

Dein Problem lässt sich tpyischerweise durch einen Queue-Service lösen, in den der Auftrag eine Mail zu verschicken eingestellt wird (was in der Regel sehr schnell geht) und dann später abgearbeitet wird, wenn Django schon wieder schläft und auf den nächsten Request wartet.

Im Prinzip kann man sich so was auch selbst bauen, in dem du in einem Tabelle einen passenden Eintrag schreibst und dann in einem zweiten Prozess (oder mehreren) regelmäßig in der Datenbank nachschaust, ob etwas zu tun ist und das dann tust.

Da dieser zweite Prozess vielleicht auch den ORM von Django benutzen will, ist es am einfachsten, ihn als custom commands zu implementieren, da diese aus der richtigen Umgebung direkt auf die Modelle zugreifen dürfen.

Statt einer Datenbank bietet sich auch häufig ein gemeinsamer Cache (z.B. memcached) an, da dies wesentlich effizienter ist. Und nicht jeder braucht ACID auf Queues, wie es z.B. JMS im Java-Umfeld bieten würde.

Schau dir mal http://code.google.com/p/django-queue-service/ an. Andere Stichworte sind Starling oder ActiveMQ, die haben aber nichts direkt mit Django zu tun. Vielleicht taugt http://code.google.com/p/peafowl/ etwas..?

Stefan
metty
User
Beiträge: 99
Registriert: Samstag 13. Dezember 2008, 19:30

sma hat geschrieben:Da für jeden Request ja Django ja quasi neu gestartet wird, funktioniert es prinzipiell nicht, dass etwas permanent läuft oder verbunden bleibt.

Dein Problem lässt sich tpyischerweise durch einen Queue-Service lösen, in den der Auftrag eine Mail zu verschicken eingestellt wird (was in der Regel sehr schnell geht) und dann später abgearbeitet wird, wenn Django schon wieder schläft und auf den nächsten Request wartet.

Im Prinzip kann man sich so was auch selbst bauen, in dem du in einem Tabelle einen passenden Eintrag schreibst und dann in einem zweiten Prozess (oder mehreren) regelmäßig in der Datenbank nachschaust, ob etwas zu tun ist und das dann tust.

Da dieser zweite Prozess vielleicht auch den ORM von Django benutzen will, ist es am einfachsten, ihn als custom commands zu implementieren, da diese aus der richtigen Umgebung direkt auf die Modelle zugreifen dürfen.

Statt einer Datenbank bietet sich auch häufig ein gemeinsamer Cache (z.B. memcached) an, da dies wesentlich effizienter ist. Und nicht jeder braucht ACID auf Queues, wie es z.B. JMS im Java-Umfeld bieten würde.

Schau dir mal http://code.google.com/p/django-queue-service/ an. Andere Stichworte sind Starling oder ActiveMQ, die haben aber nichts direkt mit Django zu tun. Vielleicht taugt http://code.google.com/p/peafowl/ etwas..?

Stefan
Die Idee mit dem Datenbankeintrag hatte ich auch schon, habe sie aber wieder verworfen, da ich sie nicht wirklich effizient gefunden habe. Aber ich gucke mir mal deine anderen Vorschläge an.

Eine andere Alternative wäre ein lokaler SMTP-(Queue)-Server, der nur von localhost Mails entgegennimmt in eine Warteschlange stellt und nacheinander an einen Smarthost verschickt. Das kann/macht eigentlich jeder Mailserver...

Werde vermtl. nicht um einen solchen, lokalen "Mini-Mailserver" herumkommen.

Vielen Dank dafür.
apollo13
User
Beiträge: 827
Registriert: Samstag 5. Februar 2005, 17:53

sma hat geschrieben:Da für jeden Request ja Django ja quasi neu gestartet wird, funktioniert es prinzipiell nicht, dass etwas permanent läuft oder verbunden bleibt.
Hu, wie meinst du das? Es sollte ohne Probleme möglich sein eine Connection offen zu halten, kann mich nicht erinnern, dass da irgendwas neu gestartet wird...
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

metty hat geschrieben:Eine andere Alternative wäre ein lokaler SMTP-(Queue)-Server, der nur von localhost Mails entgegennimmt in eine Warteschlange stellt und nacheinander an einen Smarthost verschickt. Das kann/macht eigentlich jeder Mailserver...
Ich finde auch dass ein Relay-Only SMTP-Server wie ESMTP oder MSMTP die für ihren Aufwand beste und simpelste Lösung ist.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
metty
User
Beiträge: 99
Registriert: Samstag 13. Dezember 2008, 19:30

Leonidas hat geschrieben:
metty hat geschrieben:Eine andere Alternative wäre ein lokaler SMTP-(Queue)-Server, der nur von localhost Mails entgegennimmt in eine Warteschlange stellt und nacheinander an einen Smarthost verschickt. Das kann/macht eigentlich jeder Mailserver...
Ich finde auch dass ein Relay-Only SMTP-Server wie ESMTP oder MSMTP die für ihren Aufwand beste und simpelste Lösung ist.
So kleine SMTP-Server sind genau das richtige für mich, denn ich wollte die Installation so schlank (und sicher) halten wie möglich...

ESMTP scheint hier genau das zu sein, was ich will 8)
Ansonsten werde ich mal freshmeat.net konsultieren
metty
User
Beiträge: 99
Registriert: Samstag 13. Dezember 2008, 19:30

apollo13 hat geschrieben:
sma hat geschrieben:Da für jeden Request ja Django ja quasi neu gestartet wird, funktioniert es prinzipiell nicht, dass etwas permanent läuft oder verbunden bleibt.
Hu, wie meinst du das? Es sollte ohne Probleme möglich sein eine Connection offen zu halten, kann mich nicht erinnern, dass da irgendwas neu gestartet wird...

Naja, django ist kein Server-Dienst. Ruft man z.B. .../meineseite/ auf, wird die jeweils zugeordnete Funktion "abgearbeitet", an deren Ende kann z.B. ein Template mit Daten versorgt werden, dann jedoch ist die Ausführung zu Ende.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

metty hat geschrieben:Naja, django ist kein Server-Dienst. Ruft man z.B. .../meineseite/ auf, wird die jeweils zugeordnete Funktion "abgearbeitet", an deren Ende kann z.B. ein Template mit Daten versorgt werden, dann jedoch ist die Ausführung zu Ende.
Jein. Es ist nicht definiert was passiert. Bei CGI-Applikationen ist die Ausführung an dieser Stelle zuende, aber bei FastCGI oder mod_wsgi läuft die Applikation auch nach dem Request noch weiter und kann in dieser Zeit was auch immer machen.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
metty
User
Beiträge: 99
Registriert: Samstag 13. Dezember 2008, 19:30

Leonidas hat geschrieben:
metty hat geschrieben:Naja, django ist kein Server-Dienst. Ruft man z.B. .../meineseite/ auf, wird die jeweils zugeordnete Funktion "abgearbeitet", an deren Ende kann z.B. ein Template mit Daten versorgt werden, dann jedoch ist die Ausführung zu Ende.
Jein. Es ist nicht definiert was passiert. Bei CGI-Applikationen ist die Ausführung an dieser Stelle zuende, aber bei FastCGI oder mod_wsgi läuft die Applikation auch nach dem Request noch weiter und kann in dieser Zeit was auch immer machen.
Stimmt, das hatte ich vergessen...
Jedoch habe ich bis jetzt von keiner Möglichkeit gehört eine Art "Dienst", wie eben eine SMTP-Verbindung offen zu halten, laufen zu lassen.

Aber wie gesagt, es gibt ein paar Relay-only SMTP Server bzw. sog. store and forward SMTP Server, die sollten hier genau das richtige sein.

http://emailrelay.sourceforge.net/ ist hier besonders einfach zu konfigurieren.
Einfach "emailrelay --as-server --poll 3600 --forward-to smarthost:smtp" und schon werden gespoolte Mails jede Stunde an einen Smarthost ausgeliefert.
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

Leonidas hat geschrieben:
metty hat geschrieben:Naja, django ist kein Server-Dienst. Ruft man z.B. .../meineseite/ auf, wird die jeweils zugeordnete Funktion "abgearbeitet", an deren Ende kann z.B. ein Template mit Daten versorgt werden, dann jedoch ist die Ausführung zu Ende.
Jein. Es ist nicht definiert was passiert. Bei CGI-Applikationen ist die Ausführung an dieser Stelle zuende, aber bei FastCGI oder mod_wsgi läuft die Applikation auch nach dem Request noch weiter und kann in dieser Zeit was auch immer machen.
Das ist aber ein Implementierungsdetail und das Konzept ist und bleibt, dass die Anwendung für einen Aufruf lebt und keinen Zustand bis zum nächsten Aufruf überlebt.

Stefan
apollo13
User
Beiträge: 827
Registriert: Samstag 5. Februar 2005, 17:53

Ist es imho nicht, im Prinzip kannst du dich bei Django schon darauf verlassen, dass es den request überlebt, weil cgi einfach zu lahm ist, wenn du mal nen haufen libs lädst...
sma
User
Beiträge: 3018
Registriert: Montag 19. November 2007, 19:57
Wohnort: Kiel

IMHO nicht, aber so kommen wir nicht weiter ;) Ich kann keinen Zustand in den nächsten Request rüberretten, denn möglicherweise haben ich 10 voneinander komplett unabhängige Betriebssystem-Prozesse, die beliebig sterben und neu geboren werden und wo auch nicht klar ist, welches Prozess als nächstes dran kommt. Ein geteilter globaler Zustand existiert einfach nicht. Die Anfrage ist unwiderruflich beendet, da ich sie nicht mehr von irgendwo referenzieren kann. Die Nicht-Erreichbarkeit von einem globalen Zustand aus ist aber gerade als tot definiert. Damit stirbt jeder Anfrage nach Erfüllung ihrer Lebensaufgabe, eine HTTP-Response zu generieren.

Die üblichen Rahmenwerke für Python oder Ruby funktionieren alle genau wie PHP nach dem "share nothing"-Prinzip. Wer's anders haben will, braucht einen dedizierten Anwendungsserver a la Zope oder JavaEE.

Stefan
Antworten