PHPGangsta - Der praktische PHP Blog

PHP Blog von PHPGangsta


POST-Request mit mehreren gleichen Parametern verschicken

with 26 comments

Ich hatte ein komisches Problem, bei dem ich auch erstmal nachdenken und nachlesen musste wie ich das gelöst bekomme. Folgende Ausgangssituation:

Ich musste einen POST-Request machen zu einem Webserver. Soweit kein Problem. Doch bei diesem Request war das besondere dass es mehrere POST-Parameter gab mit dem selben Namen. Beispiel:

attr=blue
attr=yellow
attr=green
amount=14

Normalerweise mache ich solche POST-Requests immer folgendermaßen (vereinfacht, dazu kommen evtl. noch Dinge wie CURLOPT_SSL_VERIFYPEER, CURLOPT_CAPATH, CURLOPT_SSL_VERIFYHOST, CURLOPT_CONNECTTIMEOUT, CURLOPT_TIMEOUT, CURLOPT_HTTPAUTH …):

$url = 'https://server.tld/api/endpoint';
$postData = array(
    'attr' => 'blue',
    'attr' => 'yellow',
    'attr' => 'green',
    'amount' => 14,
);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);

$data = curl_exec($ch);
curl_close($ch);

Hieran sieht man schon wo das Problem liegt: Im $postData Array gibt es Einträge mit dem selben Key, PHP würde in diesem Fall ein Array erzeugen mit den letzten beiden Einträgen (attr=green und amount=14). Der erste und zweite attr-Wert sind verschwunden und werden nicht an die API geschickt.

Auch der folgende Versuch, die attr-Werte zu verschachteln, schlug fehl:

$postData = array(
    'attr' => array(
        'blue',
        'yellow',
        'green'
    ),
    'amount' => 14,
);

Doch man kann auch als CURLOPT_POSTFIELDS Wert einen String übergeben, der so aussiehen muss wie ein GET-Query: attr=blue&attr=yellow&attr=green&amount=14

Den Query muss man natürlich selbst bauen, ist etwas unkomfortabler als die Array-Variante. Ein kurzer Gedanke an die schöne Funktion http_build_query(), die einen solchen String einfach zusammenbasteln kann, mußte ich leider schnell verwerfen da auch diese Funktion ein Array erwartet, und das können wir ja aus bekannten Gründen nicht erstellen.

Also blieb mir nur eine Lösung: Den String selbst zusammenzubasteln mittels Stringkonkatenation. Das folgende Script funktioniert dann wie gewünscht, ich habe den String hardcoded da das Zusammenbasteln hier unwichtig ist:

$url = 'https://server.tld/api/endpoint';
$postData = 'attr=blue&attr=yellow&attr=green&amount=14';

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $postData);

$data = curl_exec($ch);
curl_close($ch);

Die erste Frage ist natürlich warum jemand so eine API mit gleichen Parametern überhaupt erstellt, schöner wäre es ja evtl. gewesen hätte man einen attr-Parameter mit einer kommaseparierten Liste übergeben müssen. Aber manchmal muss man damit leben und sich damit abfinden.

Habt ihr andere/bessere Lösungen, die ich übersehen habe?

Written by Michael Kliewe

September 5th, 2012 at 10:34 am

Posted in PHP

Tagged with , , , ,

26 Responses to 'POST-Request mit mehreren gleichen Parametern verschicken'

Subscribe to comments with RSS or TrackBack to 'POST-Request mit mehreren gleichen Parametern verschicken'.

  1. Francis

    5 Sep 12 at 11:01

  2. Die HTML input-Felder kann man via name=“attr[]“ als Array übergeben, geht das nicht mit CURL?

    $postData = array(
    ‚attr[]‘ => ‚blue‘,
    ‚attr[]‘ => ‚yellow‘,
    ‚attr[]‘ => ‚green‘
    );

    Holgersen

    5 Sep 12 at 11:35

  3. @Holgeren
    Das funktioniert aber auch nur, wenn der Request an einen Server geht, der die Parameter aufbereitet.
    Weil PHP dir die Parameter in _GET und _POST als Array aufbereitet (oder auch andere Sprachen und Frameworks aufbereitet zu Verfügung stellt).
    Der Webserver zerlegt die Parameter nicht und das kann man mit einem CGI Programm welches nur den QueryString oder den RawPostBody zu Verfügung hat oder auch in PHP leicht nachvollziehen.
    Aufruf auf test.php?test=12&test=23


    print_r($_GET); // von PHP
    echo $_SERVER["QUERY_STRING"]; // vom Webserver

    Problematisch kann auch die Reihenfolge der Übergabeparameter sein, da diese auch unterschiedlich zerlegt als erwartet werden könnte.

    pce

    5 Sep 12 at 12:09

  4. @Holgersen

    1. Dein Array würde in PHP trotzdem wieder nur ein Key/Value-Paar enthalten, da 3x der gleiche Key verwendet wird.

    2. Der Webserver, der angesprochen werden soll, erwartet die Parameter nunmal so, wie es beschrieben worden ist.

    Mir fällt da auf Anhieb auch keine echte Alternative ein.

    Quetschi

    5 Sep 12 at 12:14

  5. Ansonsten wenn du einen solchen PostString bequem erzeugen willst -> eigene Klasse schreiben, die du dann in etwa so verwenden kannst:

    $post = new postData;

    $post->addVal(„attr“, „green“)
    ->addVal(„attr“, „blue“)
    ->addVal(„attr“, „yellow“)
    ->addVal(„amount“, „14“);

    echo $post->toString();

    Quetschi

    5 Sep 12 at 12:23

  6. Mittels attr[] als array verschicken, auf dem zielserver dann als array auseinander nehmen.
    Warum am Standard rütteln, es funktioniert doch alles. Was für radio-boxes gut ist, kann dir nur recht sein…

    Sascha Presnac

    5 Sep 12 at 12:50

  7. @Francis: Genau das ist ein Beispiel wie es nicht funktioniert. Dort wird ein Array (siehe InterfaceJsonRpcRequest::getPostData()) genutzt, und da gibt es die Probleme mit den doppelten Keys.

    @Holgersen Leider nein, in deinem Beispiel sind die Keys auch gleich, und es bleibt nur der dritte Eintrag übrig im Array. Array-Keys müssen eindeutig sein, sonst überschreiben sie sich. Das mit den [] stimmt, in PHP erhält man dann in $_POST ein Array das so aussieht:
    array(2) {
    'attr' =>
    array(1) {
    [0] =>
    string(5) "green"
    }
    'amount' =>
    string(2) "14"
    }

    Das mit dem verschachtelten Array hatte ich im Blogartikel oben bereits ausprobiert, geht leider nicht.

    @Quetschi Sollen wir da mal fix auf GitHub einen ersten Versuch starten?

    Michael Kliewe

    5 Sep 12 at 12:52

  8. Mir würden aktuell auch nur String-Conkats einfallen und mein Vorschlag wäre sowas wie:

    function getPOSTData(/* polymorph */)
    {
    return array_reduce(
    func_get_args(),
    function($mPrev, $aKeyValue) {
    return $mPrev . (!$mPrev ? “ : ‚&‘) . http_build_query($aKeyValue);
    } // if
    );
    } // test

    echo $sPOSTString = getPOSTData(array(‚attr‘ => ‚blau‘), array(‚attr‘ => ‚rot‘));

    WBL_BjoernLange

    5 Sep 12 at 13:00

  9. @WBL_BjoernLange

    Der Name getPOSTData() passt in meinen Augen nicht so ganz. Suggeriert mir, dass ich hier an das Script gesandte POST-Daten bekomme und nicht, dass ich hier einen POST-Request erstelle.

    Denke, dass sich das am schönsten mit einem Fluent-Interface am schönsten nutzen lassen würde. Damit man weiß, was man grad macht und später ohne großartige Kommentare den Code versteht, könnte es schlicht so aussehen:

    $PostString = POSTDATA::create()
    ->addValue(„bla“, „blub“)
    ->toString();

    @Michael
    Tu was du nicht lassen kannst 😀

    Quetschi

    5 Sep 12 at 13:21

  10. Achja,

    weil auch noch die Frage im Raum stand, warum jemand überhaupt eine API erstellt, in der gleichnamige Parameter mehrfach übergeben werden sollen:

    Weil es vollkommen legitim ist. Mir wäre zumindest nicht bekannt, dass in einem Query-String oder POST-Data-String Parameternamen nicht mehrfach vorkommen dürften.
    Was der angesprochene Dienst damit macht steht auf einen anderen Papier und die entsprechenden Spezifikation dürfte es auch nicht sonderlich interessieren, wer seine Parameter wie erstellt und welche Einschränkungen da ggf. eine verwendete Programmiersprache hat.

    Quetschi

    5 Sep 12 at 13:34

  11. @Quetschi
    Genau, warum? Bei solchen API’s kann dann auch die Stabilität und Sicherheit in Frage gestellt werden.
    Unter dem Stichwort „HTTP Parameter Pollution Attacks“ findet man auch Auflistungen wie unterschiedlich gleiche Parameter aufgeschlüsselt werden, und was man damit anstellen kann. Bspw. wird in Python/Zope ein Array aus den gleichen Parameter, wohingegen in PHP das über angehängte eckige Klammern geschieht oder der letzte Parameter zählt und in JSP, sowie Python/WSGI nur der erste Parameter zählt.

    pce

    5 Sep 12 at 13:51

  12. @pce

    Das Argument kann ich nicht nachvollziehen. Ob der Service die übergebenen Parameter sicher/stabild verarbeitet liegt in der Hand des Service bzw. desjenigen, der den Service erstellt und welche Programmiersprache usw. er dabei verwendet hat.

    Quetschi

    5 Sep 12 at 14:01

  13. @Quetschi
    Es mag ja legitim sein gleichnamige Parameter zu verwenden, aber irgendwie wird mir der Sinn nicht klar. Ein Parameter ist doch dazu da, einer Anwendung Werte zu übergeben. Damit die Anwendung versch. Werte entsprechend behandeln kann, benutzt man Namen. Warum sollte man dann Namen mehrfach verwenden? Macht es Sinn Parameter wie „&save=1&save=0“ zu übergeben?
    Wenn ich so ein API verwenden müsst, würde mich das nur verwirren.

    Vielleicht hast du mal ein Beispiel wo es Sinn machen würde?
    (Ich lass jetzt mal Check- oder Selectboxen im HTML außen vor, da nutzt man dann ja auch name[])

    Alex

    5 Sep 12 at 15:12

  14. @Alex
    Natürlich kann es Sinn haben mehrere Werte mit gleichem Parameternamen zu übergeben. Angenommen sei einfach eine API die, ähnlich wie im Beispiel von Michael, z.B. ein bestimmtes Produkt speichert und dieses Produkt könnte in verschiedenen Farben lieferbar sein. Also will ich hier auf irgendeine Art und Weise die verfügbaren Farben übergeben können.

    Und das man für Checkboxen usw. in HTML z.B. name[] nutzt stimmt nicht. Nicht HTML gibt vor wie das wie das zu geschehen hat, sondern dahinterstehende System bestimmt, wie mehrere Parameter mit gleichem Namen übergeben werden können. Bei PHP schafft man das eben mit den Array-Klammern – andere Sprachen brauchen diese aber nicht. PHP kann man das auch beibringen indem man z.B. $HTTP_RAW_POST_DATA entsprechend verarbeitet.

    Quetschi

    5 Sep 12 at 15:31

  15. Noch eine kleine Korrektur:

    $HTTP_RAW_POST_DATA ist nicht per Default verfügbar. Stattdessen den Inputstream auslesen:
    http://php.net/manual/de/wrappers.php.php

    Quetschi

    5 Sep 12 at 16:17

  16. @Quetschi
    Ok mit dem Beispiel (bzw. so erklärt) wird es mir klarer.
    Ich würds dann aber trotzdem nicht so kompliziert machen. Entweder ein Trennzeichen (bei Farben sollten sich die möglichen Zeichen ja in Grenzen halten) oder sowas wie json. Einfach ein Array erstellen, json_encode und json_decode und fertig.

    Alex

    5 Sep 12 at 19:19

  17. @Alex

    grade der Weg mit den Trennzeichen wär mir eher unsympathisch, weil man hier irgendwo beginnt seine eigenen Brötchen zu backen anstatt bei Standards zu bleiben. Es sind dann ja nicht nur Farben, die man mal an einen Service übergeben will sondern auch Werte ganz anderer Art.

    Beim erwähnten JSON sieht es natürlich etwas anders aus, da wir hier schon von einem Quasi-Standard der ziemlich weitgehend verbreitet sein dürfte.

    Komplizierter als solche Geschichten ist die Übergabe von mehreren gleichnamigen Parametern aber IMHO auch nicht und auch kein Verstoß gegen einen Standard – da es sich ja beinah so anhört als ob es das wäre.

    Quetschi

    5 Sep 12 at 20:32

  18. $postData = array(
    ‚attr[0]‘ => ‚blue‘,
    ‚attr[1]‘ => ‚yellow‘,
    ‚attr[2]‘ => ‚green‘,
    ‚amount‘ => 14
    );

    Wäre das nicht eine einfache Möglichkeit?

    So kommt das ganze auf dem Server auch als array an.

    Daniel

    5 Sep 12 at 21:04

  19. @Daniel

    Kommt drauf an, ob die Gegenstelle das so verwursten kann.

    Man muss sich an dieser Stelle eigentlich von dem Gedanken verabschieden, man würde Arrays übergeben – das tut man nicht. Man übergibt nur eine Anzahl an Key/Value-Paaren. Die serverseitige Interpretation dieser Daten hängt vom verwendeten System bzw. der verwendeten Programmiersprache ab und die kann bekanntlich anders aussehen als die von PHP.

    Quetschi

    5 Sep 12 at 21:53

  20. Zu bedenken ist, dass die curl_setopt bei POSTFIELDS die Handhabung ändert, wenn statt Array String kommt und anderen header sendet (was manchmal auch wichtig sein kann).

    Meines Wissens gibt es in PHP keine andere Methode bei solch unsinnigen APIs.

    BTW: Habe ich schon erzählt, dass es bei Fraunhofer auch so Spezialisten gibt (oder jedenfalls gab), die in URLs eine bestimmte Reihenfolge erwartet haben, weil sie die Parameter einer GET Anfrage nacheinander verarbeitet haben? Menschen gibt’s…

    @Quetschi: Nicht alles, was legitim und vom Manual nicht verboten ist, ergibt Sinn. Z.B. mit dem neu gekauften Ferrari bewusst gegen den Baum fahren.

    UmbertoKo

    6 Sep 12 at 00:14

  21. Hallo,

    ich hatte das Problem ebenfalls.

    cURL unterstützt keine multidimensionale Arrays bei der Post Option.

    Die einfachste Methode ist htttp_build_query zu nutzten und den generierten String an cURL weiter zu geben.

    Gruß,
    Jan

    Jan

    6 Sep 12 at 09:08

  22. @UmbertoKo

    Ich mach auch gerne diese Autovergleiche – aber dieser hinkt schon mehr als gewaltig 😉

    In der Diskussion hier fehlen mir immer noch sachliche Argumente gegen diese Form der Parameterübergabe sondern nur Aussagen wie ‚unsinnige API‘ die auf persönlichen Präferenzen beruhen.

    Nicht alles, womit PHP nicht outOfBox fertig wird ist gleich unsinnig. PHP mag weit verbreitet sein aber ist längst nicht allein auf der Welt.

    Quetschi

    6 Sep 12 at 09:59

  23. Wenn man mit JSP/Servlets arbeitet, dann ist es vollkommen normal, dass die Parameter so übergeben werden.

    Teilweise lassen sich die Server nicht einfach an einen „Standard“ anpassen, weil sie einem vielleicht nicht gehören oder einfach schon zuviele diese API nutzen.

    Es schadet auch nicht, die Gedanken mal vom Standard abzulenken. 😉

    Daniel

    6 Sep 12 at 14:19

  24. Webservices außerhalb der PHP-Welt erwarten oft Listen als gleichnamige Parameter, da z. B. in Java mit diversen Frameworks diese in Listen umgewandelt werden.

    Marc

    14 Sep 12 at 11:18

  25. Ich würds mit dem verschachtelten Array und http_build_query machen und anschließend das Ergebnis noch mit preg_replace anpassen.

    Also in Etwa so:

    array(
    ‚blue‘,
    ‚yellow‘,
    ‚green‘
    ),
    ‚amount‘ => 14,
    );

    echo preg_replace(‚/attr%5B[0-9]+%5D/i‘, ‚attr‘, http_build_query($postData));

    ?>

    Gruß
    Rotzbengel

    Rotzbengel

    23 Nov 12 at 11:03

  26. Leider ist der Anfang meines Beitrags durch die „Reply“ Funktion abgeschnitten worden.

    Das Array muss natürlich wie in Deinem Eingangsposting, also so aussehen:

    $postData = array(
    ‚attr‘ => array(
    ‚blue‘,
    ‚yellow‘,
    ‚green‘
    ),
    ‚amount‘ => 14,
    );

    Gruß

    Rotzbengel

    23 Nov 12 at 11:10

Leave a Reply

You can add images to your comment by clicking here.