HTTP POST Problem mit Chrome, Opera, Safari
-
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!
-
KuhTee schrieb:
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.
Ja.... bloss warum sagst du uns das?
readyRead verwendet er doch schon längst.
Was irgendwie ein dezenter Hinweis darauf ist dass er es vielleicht kennen könnte.
Nur mal so.
-
Habs hinbekommen. Ich schaue wieviel Daten kommen müssten und wenn diese da sind mach ich weiter. Geht super! Danke für eure Tipps!
Jetzt hab ich nur noch das Problem, dass ich nicht weiß an welcher Stelle ich wieder Speicher freigebe. Kann ich einfach im Destruktor delete this; aufrufen oder ist es aufwendiger zu lösen?
Grüße
sowsio
-
Du willst im Destruktor "delete this;" machen? Öh. Überleg mal
Und wo du den Speicher freigibst, das hängt wohl davon ab wem er gehört.
Wobei ich einfach nen std::vector<char> irgendwo als Member reinpacken würde, dann musst du dir gar keinen Kopf machen wer was wann wo freigibt, weil es automatisch passiert.
-
Wie funktioniert denn das mit dem std::vector<char>
Grüße
sowiso
-
int main() { std::vector<char> buf(50000); // .resize() und .size() helfen socket.recv(&buf[0], buf.size()); // Die Elemente im Vektor liegen alle hintereinander im Speicher. } // Der Destruktor des Vektors gibt alles automatisch frei.
Aber.. so etwas wusstest du noch nicht?