HTTP POST Problem mit Chrome, Opera, Safari
-
sowiso schrieb:
Es kommt nur folgender Text an wenn ich das Programm Debuge! Ich schließe danach die Verbindung da nicht mehr kommt!
Wie lange wartest du denn? Am besten wäre natürlich, wenn du die Codestelle an der empfangen wird posten könntest.
-
Ich programmiere mit QT. Dort nutze ich die Funktion readall(). Das sieht so aus:
QString incoming(QString::fromUtf8(socket->readAll()));
Danach wird die Variable incoming ausgewertet. Ich hab es auch bereits mit readLine() und atEnd()-Funktion probiert. Aber da ändert sich nichts. Der HTTP-Header vom Firefox ist länger als der vom Chrome. Deshalb denke ich kaum dass ich nicht alles lese.
Ich vermute eher dass ich nicht alles geschickt bekomme. Warum auch immer.
-
Na, die QT Sockets kenne ich nicht, ich dachte du arbeitest mit Systemsockets. Die Frage ist halt wie readAll() funktioniert. Liest das blockierend bis die Verbindung geschlossen wird, oder gibt das einfach nur alles zurück was gerade im Puffer steht? Die Doku scheint das jedenfalls nicht zu klären.
Hast du mal probiert nach readAll() waitForReadyRead() aufzurufen und dann noch mal readAll()? Wenn da dann plötzlich die Daten zum Vorschein kommen, weiß man immerhin woran es liegt. Wenn waitForReadyRead() ewig blockt geht wohl irgendetwas anderes schief.
-
Hey!
Ich habs mal probiert aber das macht ja wenig sind mit waitForReadyRead(time msec) da dies ja solange blockiert bis ein neues readyRead() SIGNAL ausgelöst wird bzw. wenn die Zeit abläuft.
Ich hatte ja wie gesagt schon das ausprobiert:QByteArray test; while(!socket->atEnd()) test.append(socket->readLine());
Dadurch bin ich auch ziemlich sicher, dass das Ende erreicht wird da er ja sonst in der while-Schleife hängen bleiben würde oder?
-
Prüf mal mit Wireshark, was die Browser tatsächlich senden. Vielleicht klärt sich so das Problem schon.
-
Na ja, atEnd() testet ja auch nur ob im Moment des Aufrufs Daten vorhanden sind. Muss nicht heißen, dass nicht noch welche kommen können. Von daher einfach mal ausprobieren mit dem waitForReadyRead().
Ansonsten: Kannst du mal eine vollständige Beispielkommunikation posten? (Also das HTTP-Zeugs) Dann kann ich das schnell nachbauen und mal testen ob ich das gleiche Problem kriege.
Edit: Wireshark ist auch gar keine schlechte Idee. Gibt sogar eine portable Version.
-
Ich hab auch schon an WireShark gedacht. Das Problem ist nur das die Verbindung verschlüsselt ist. Da sieht man leider nichts.
-
Lies Zeile für Zeile, bis zum doppelten "\n".
Danach musst du den Header parsen. In der "Content-Length" Zeile steht dann wie viel Daten noch kommen.
Dann liest du einfach so viele Bytes, wie bei "Content-Length" angegeben wurde.
Wenn du nicht willst dass das blockiert, dann musst du halt ein asynchrones IO Modell verwenden.Du kannst dein System ja nicht dazu zwingen dir die Daten sofort zu geben. Wenn sie noch nicht da sind, sind sie halt noch nicht da.
EDIT: OK, ich sehe du machst eh schon mit asynchronem IO. Naja... readData wird aufgerufen wenn EIN STÜCK Daten angekommen ist. Und wenn das nächste Stück Daten da ist, wird readData nochmal aufgerufen. Und so weiter.
D.h. deine Socket Klasse muss irgendwo die empfangenen Headers puffer, sich merken in welchem "Modus" sie ist (Headers empfangen, Daten empfangen, Antwort senden) usw.
Anders gesagt: das Ding muss eine State-Machine werden.
/EDIT
-
@hustbaer Also meinst du es würden noch kommen wenn ich warte? Und dann nochmal etwas annehme?
-
Guck mal in Zeile 4 deines Beispiel-Requests, da steht
\nContent-Length: 33
.
Wenn der Browser nicht total kaputt ist, dann kommt da ganz sicher noch was.Der Unterschied wird vermutlich einfach sein, dass Firefox den gesamten Request inklusive Daten mit einem einzigen send() wegschickt, manche andere Browser aber Header und Daten mit getrennten send() Aufrufen wegschicken.
Nicht dass jetzt die send() Aufrufe des Clients genau damit korrelieren müssen wie das beim Server ankommt: Daten können grundsätzlich in beliebigen Stücken zu beliebiger Zeit eintrudeln!
Es macht aber oft genug einen Unterschied wie der Client send() aufruft. Wenn man sich ansieht wie TCP/IP so tut, dann sieht man auch wieso, aber das würde etwas zu weit führen
-
Ja das hat mich auch irritiert mit der ContentLength, aber deine Lösung klingt sinnvoll. Vielen Dank!
So hab ich wenigstens wieder einen Ansatz um das Problem zu lösen auch wenn ich erstmal einiges an der Serverstruktur ändern muss.
Werd mich dann frühstens morgen daran machen. Meld mich dann um zu sagen obs so ging.EDIT
Hat vielleicht noch jemand eine Idee wie ich identfizieren kann das der Content von richtigen Client kommt und nicht von einem anderen? Weil es ist ja nicht ganz klar in welcher Reihemfolge die Abarbeitung abläuft.Grüße
sowiso
-
ps:
Jedes vernünftige Netzwerkprotokoll gibt dir eine Möglichkeit alleine anhand der Daten rauszubekommen wann ein Request "komplett" ist.
OK, manche Protokolle sehen auch vor dass man einseitig den Socket zu macht, wenn der Request fertig ist, und das dann das "komplett" Signal darstellt.
HTTP ist z.B. eines dieser Protokolle. Da ist es nämlich erlaubt dass man keine "Content-Length" mitschickt - dann ist eben das einseitige schliessen der Verbindung das "komplett" Signal.
(Wobei ich nicht sicher bin ob das bei POST Requests erlaubt ist - bei Antworten ist es auf jeden Fall erlaubt).Auf jeden Fall musst man sich nie darauf verlassen, dass irgendwas "am Stück" ankommt. Und man kann sich eben auch nicht drauf verlassen, da TCP/IP diesbezüglich keinerlei Garantien liefert.
Langer Reder kurzer Unsinn: du musst immer nach der im Protokoll vorgesehenen Möglichkeit gehen das Request-Ende rauszubekommen (bzw. genau so Response-Ende). Nie danach was "am Stück" ankommt und in einem Rutsch ohne Warten gelesen werden kann.
Und nochwas: dummerweise bedeutet das auch, dass du irgendeinen Timeout-Mechanismus basteln musst, wenn du nicht willst, dass dein Server eine Connection lange lange Zeit offen lässt, falls der Client einfach nix mehr schickt. Und das kann auch passieren, wenn man den Client einfach abdreht.
Wenn man einfach nur den Client-Prozess killt, dannn schickt normalerweise das SO ne Benachrichtigung dass die Verbindung getrennt wurde. Das bekommt der Server dann mit, und alles ist gut. Wenn man aber einfach den Strom ausknpist, dann kommt gar nix.
Und genau so wenn der Client ein ganz böser ist, der deinen Server mit lauter "halben" Requests bombardiert um die Resourcen des Servers wegzulutschen (DOS attack aka. Denial-of-service attack).
-
Mach dir am besten einfach eine Client-Klasse, der Server hält dann eine std::list<Client>. Dann wartest du auf alle Verbindungen gleichzeitig. Hat es am Server geklopft, baue die neue Verbindung auf (sofern noch Platz ist), ansonsten lass die Client Klasse das verarbeiten. Die hat dann einen eigenen Puffer und kümmert sich selbst darum, dass dieser nicht zu Groß wird und wird auch erst aktiv, wenn ein (HTTP) Paket vollständig angekommen ist und verarbeitet werden kann, oder die Verbindung getrennt wurde, oder ein Timeout abgelaufen ist.
-
sowiso schrieb:
Hat vielleicht noch jemand eine Idee wie ich identfizieren kann das der Content von richtigen Client kommt und nicht von einem anderen? Weil es ist ja nicht ganz klar in welcher Reihemfolge die Abarbeitung abläuft.
Hm, ich versteh nicht ganz wo das Problem ist.
Jede eingehende Connection (jeder Client) bekommt ja sein eigenes SSLServerConnection Objekt. Wenn Daten ankommen, dann wird über die SIGNAL/SLOT Connection SSLServerConnection::readData() aufgerufen. Und zwar mit dem SSLServerConnection Objekt (this), dem der entsprechende Socket gehört.
D.h. du speicherst die ganzen Headers und Daten in SSLServerConnection ab, dann gibt es auch keine Verwirrung was wohin gehört.
Nochwas ganz anderes: sieht SSLServerConnection::deleteLater vielleicht ca. so aus?
void SSLServerConnection::deleteLater() { delete socket; delete this; }
?
Wenn ja, dann würde ich das ändern, auf nur "delete this;", und den restlichen Cleanup-Code in den Destruktor reinschreiben.
Auch wenn das der einzige Mechanismus ist, wie SSLServerConnection Objekte gelöscht werden... kommt mir einfach nicht richtig vor einen Destruktor zu haben der das Objekt nicht vollständig wegräumen kann.
-
@cooky451
Er hat doch schon eine Client-Klasse, die heisst "SSLServerConnection".
Und er muss - wenn ich das richtig verstehe - auch nicht auf irgendwelche Daten warten, da Qt das für ihn übernimmt.
Und einfach, wenn ein neues Datenstück angekommen ist, das "readyRead" SIGNAL des entsprechenden Sockets feuert.
Da dran hängt seine SSLServerConnection::readData Handler-Funktion, und da drin liest er dann die Daten.D.h. er braucht in der Server-Klasse auch nicht unbedingt eine Liste aller Clients/Connections.
Oder hab' ich dich falsch verstanden?
-
Nein stimmt, ich sehe gerade auch dass Qt das größtenteils übernimmt. Wobei ich da nichts für ein Timeout sehe, aber das ist hier wohl auch Qt Zeug.
Also muss wohl nur noch die Client Klasse einen Puffer bekommen, an den jedes mal bei readData() angehängt wird. Dann wird gecheckt ob eine Nachricht vollständig ist, und entsprechend reagiert. (Testen ob der Puffer zu groß wird und dann die Verbindung kappen sollte man natürlich auch.)
-
Ja, Timeout müsste man noch irgendwie machen.
Dabei kann es leicht sein, dass die "schonendste" Methode die ist, alle paar zig Sekunden mal eine Liste mit allen Connections durchzuackern, und zu gucken welche man killen möchte.
Dann braucht man die Liste natürlich.Für Server die nicht mit tausenden gleichzeitigen Connections klarkommen müssen, wäre es aber sich auch vertretbar, wenn jede Connection ihren eigenen Timer hat.
Oder der Server könnte nen "Heartbeat" Timer haben, und diesen der Connection im Konstruktor mitgeben. Die Connection hängt sich dann in das "Tick" Signal (oder wie auch immer das heisst) dieses einen zentralen Timers. Die Liste verwaltet der Timer dann implizit (bzw. das Signal).
-
@sowiso:
Nochwas anderes...
Es ist allgemein viel einfacher, wenn man für jede Connection einen eigenen Thread macht. Weil man dann nämlich mit blocking IO arbeiten kann, und nicht für jede Kleinigkeit ne State-Machine basteln muss.Irgendwann skaliert das zwar nimmer gut, aber bis etliche hundert gleichzeitige Connections ist es kein Problem (Erfahrungswert).
Wie das mit Qt geht, müsstest vermutlich diesem Beispiel entnehmen können:
http://www.trinitydesktop.org/docs/qt4/network-threadedfortuneserver.htmlUnd noch ein Vorteil von "1 Thread pro Connection": man kann andere blockierende Aufrufe verwenden, ohne dass man dadurch gleich den gesamten Server ausbremst. Oft will man diverse Funktionen diverser anderer Libraries verwenden, wo es einfach keine asynchrone Alternative gibt. Mit einem "1 Thread für alle" Server bremst man sich damit den ganzen Server aus, wenn diese Funktionen mal blockieren. z.B. weil sie auch Disk-IO warten o.ä. Oder man muss diese Aufgaben in einen Thread-Pool auslagern, was zwar gut funktioniert (und dann auch nicht mehr bremst), dafür aber einiges an Aufwand darstellt.
Der Nachteil: man kann nicht mehr einfach so auf gemeinsam genutzten State zugreifen, da ja nun mehrere Threads parallel laufen. Man muss sich dann also selbst darum kümmern, die Zugriffe entsprechend zu synchronisieren.
Und vergiss meine Frage bezüglich deleteLater - hab mittlerweile gesehen dass das ne Qt Framework Funktion ist. Dafür andere Frage: wer löscht jetzt den QSslSocket? Hab ich das in deinem Code übersehen, oder hast du es vergessen? (Oder kümmert sich irgend ein automagischer Mechanismus des Qt Frameworks darum?)
Denn du rufst ja nur deleteLater() für die SSLServerConnection selbst auf, und in ~SSLServerConnection() löscht du den Socket ja nicht...
-
Die klasse QAbstractSocket erbt von QIODevice, und dort gibt es signals wie readyRead(), welches immer dann emited wird, wenn neue daten verfügbar sind. Sollte dann wohl ein leichtes sein, sich die zu holen.
-
Bleib mal auf dem Teppicb, bzw. im Keller!