HTTP POST Problem mit Chrome, Opera, Safari



  • Hallo!

    Ich programmiere gerade einen kleinen Server mit HTTPs. Für die Anmeldung verwende ich eine POST-Anfrage über den HTTP Header. Mit Firefox ist das auch kein Problem. Es wird alles so gesendet wie hier beschrieben:
    http://de.wikipedia.org/wiki/Hypertext_Transfer_Protocol#HTTP_POST

    Allerdings wird bei den anderen Browsern (Chrome, Safari, Opera, Safari auf iPod) der Inhalt der nach den zwei Leerzeilen kommen sollte nicht mitgeschickt! Hat jemand eine Ahnung wo der sein kann? Liegt es vielleicht daran, dass mein Zertifikat nicht zertifiziert?

    Danke schonmal für Tipps!

    Grüße
    sowiso



  • Was heißt denn nicht mitgeschickt? Schließen die die Verbindung einfach? Oder empfängst du vielleicht nur ein Mal?



  • Es kommt nur folgender Text an wenn ich das Programm Debuge! Ich schließe danach die Verbindung da nicht mehr kommt!

    POST / HTTP/1.1
    \nHost: 192.168.1.102:8080
    \nConnection: keep-alive
    \nContent-Length: 33
    \nCache-Control: max-age=0
    \nOrigin: null
    \nUser-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.2 (KHTML, like Gecko) Chrome/15.0.874.121 Safari/535.2
    \nContent-Type: application/x-www-form-urlencoded
    \nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    \nAccept-Encoding: gzip,deflate,sdch
    \nAccept-Language: de-DE,de;q=0.8,en-US;q=0.6,en;q=0.4
    \nAccept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
    \n
    \n
    

    Beim Firefox sieht es allerdings so aus (so wie es sein sollte):

    POST / HTTP/1.1
    \nHost: 192.168.1.102:8080
    \nUser-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:8.0) Gecko/20100101 Firefox/8.0
    \nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
    \nAccept-Language: de-de,de;q=0.8,en-us;q=0.5,en;q=0.3
    \nAccept-Encoding: gzip, deflate
    \nAccept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
    \nConnection: keep-alive
    \nReferer: https://192.168.1.102:8080/
    \nCookie: session=3416a75f4cea9109507cacd8e2f2aefc
    \nContent-Type: application/x-www-form-urlencoded
    \nContent-Length: 37
    \n
    \nuser=sowiso&password=xxx&action=login
    


  • Dieser Thread wurde von Moderator/in SeppJ aus dem Forum C++ (auch C++0x, bzw. C++11) in das Forum Rund um die Programmierung verschoben.

    Im Zweifelsfall bitte auch folgende Hinweise beachten:
    C/C++ Forum :: FAQ - Sonstiges :: Wohin mit meiner Frage?

    Dieses Posting wurde automatisch erzeugt.



  • 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.)


Anmelden zum Antworten