Een POST request maken zonder CURL
Soms moet je vanuit een PHP script communiceren met een ander site. Denk aan een SMS gateway of een payment gateway. Bij dergelijke webservices is het meestal de bedoeling dat je via HTTP POST een stukje XML die kant op stuurt.
Wanneer je vanuit je PHP script een HTTP POST request moet maken wordt er al snel geroepen dat je de CURL library moet gebruiken. Nu is er niets mis met die library, maar sommige hosters hebben die nu eenmaal niet meegenomen in hun configuratie. In dit artikeltje zie je hoe je zonder CURL of andere externe libraries een HTTP POST request kunt maken.
Let op: Zoals altijd op Scriptorama laten we de benodigde fout afhandeling achterwege zodat we het concept dat we proberen uit te leggen met zo min mogelijk code kunnen demonstreren.
Hoe ziet een HTTP POST request eruit?
Het loont om eerst eens even te kijken hoe zo'n POST request nu opgebouwd moet worden. Het request ziet er als volgt uit:
-
POST /doel.php HTTP/1.1
-
Host: www.example.org
-
Content-Type: application/x-www-form-urlencoded
-
Content-Length: 7
-
-
a=b&c=d
In de eerste regel vertellen we de webserver dat we graag een POST request willen uitvoeren op /doel.php. In de 3e regel vertellen we in wat voor formaat we gegevens gaan mee sturen, de 4e regel zegt hoelang die gegevens zijn en de laatste regel zijn de daadwerkelijke gegevens, in hetzelfde formaat zoals je bij GET requests zou hebben.
Let's get that stream rolling
Sinds PHP 4.3.0 heeft PHP een uitgebreid streams systeem. Streams biedt verschillende features. Zo kun filters definieren, stukjes code die boven op een transport werken maar je kunt ook de specifieke transports, dat zijn de modules die toegang tot HTTP, HTTPS en bijv. FTP mogelijk maken, configureren zodat je er meer mee kunt dan alleen een simpel GET requestje versturen. Wij zullen ons in dit artikeltje richten op het laatste.
Om een transport te configureren creëer je een zogenaamde context en dat kun je doen met de functie stream_context_create. Deze functie verwacht een array van opties per transport. De mogelijke opties voor een bepaalde transport kun je vinden in de handleiding.
Wij zullen 3 opties gaan gebruiken voor het HTTP transport: method, content en header. Zo kunnen we alle benodigde gegevens meegeven:
- method: geeft aan wat voor soort HTTP request we willen uitvoeren
- header: geeft ons de mogelijkheid om de headers Content-Type en Content-Length mee te geven
- content: gebruiken we om de POST variabelen in te plaatsen
Laten we stellen dat we de volgende variabelen willen mee sturen, die we voor de handigheid alvast even in een array hebben gehangen:
-
'site' => 'http://www.scriptorama.nl',
-
'desc' => 'Webdevelopment weblog'
-
);
Het content type stelt bepaalde eisen aan ons, we zullen deze data moeten encoderen in een bepaald formaat. PHP biedt hier gelukkig een handige functie voor: rawurlencode() welke vrijwel alle niet alfanumerieke waarden omzet naar een hex-waarde voorgegaan door een %-teken.
We kunnen meteen even gebruik maken van de kennis die in het artikeltje Callbacks in PHP wordt beschreven om deze variabelen om te zetten in de juiste encoding en in een bruikbaar formaat:
-
function rawurlencode_callback($value, $key)
-
{
-
}
-
-
'site' => 'http://www.scriptorama.nl/',
-
'desc' => 'Webdevelopment weblog'
-
);
-
-
'rawurlencode_callback',
-
$variables,
-
)
-
);
Als we deze code uitvoeren zien we dat we van array_map() een geindexeerde array terug krijgen met daarin de geencodeerde waarden:
-
array(2) {
-
[0]=>
-
string(39) "site=http%3A%2F%2Fwww.scriptorama.nl%2F"
-
[1]=>
-
string(28) "desc=Webdevelopment%20weblog"
-
}
Het mooie van deze array is dat we nu met een simpele aanroep naar Join() met '&' als delimiter deze array kunnen omzetten naar de benodigde string. Hierna is het bepalen van de benodigde waarde voor de Content-Length variabele ook erg simpel geworden. We halen de var_dump() aanroep weg en voegen wat definities toe:
In principe hebben we nu alle benodigde waarden en kunnen we dus een Stream Context gaan maken:
-
// HTTP Headers moeten gescheiden worden door \r\n, we gebruiken een constante voor betere leesbaarheid.
-
-
array (
-
'method' => 'POST', // dit MOET in hoofdletters
-
'content' => $postContent,
-
'header' => "Content-Type: application/x-www-form-urlencoded" . CRLF . "Content-Length: $postContentLen" . CRLF
-
)
-
)
-
);
Deze context kunnen we vervolgens aan de verschillende file-functies die PHP rijk is geven om zo een aangepaste aanroep naar een website te doen. In PHP5 kun je file_get_contents() gebruiken om dat pas in die versie context-support werd toegevoegd, maar aangezien veel mensen nog met PHP4 werken zullen we het nog even met fopen() moeten doen. Aan fopen() kun je als 4e argument de context meegeven. Het derde argument specificeert of fopen() ook in het include_path moet kijken. Dat is hier niet van toepassing en dus geven we FALSE mee:
Het bestand doel.php kan bijvoorbeeld voor test doeleinden het volgende bevatten:
Dus, als we deze code uitvoeren tegen doel.php:
-
$ php bron.php
-
array(2) {
-
["site"]=>
-
string(25) "http://www.scriptorama.nl"
-
["desc"]=>
-
string(21) "Webdevelopment weblog"
-
}
En zo hebben we zonder curl en andere externe libraries een HTTP POST request kunnen uitvoeren!
De gehele code
Hier is nog de gehele code uit dit artikel in 1 blok:
-
function rawurlencode_callback($value, $key)
-
{
-
}
-
-
'site' => 'http://www.scriptorama.nl',
-
'desc' => 'Webdevelopment weblog'
-
);
-
-
-
-
-
array (
-
'method' => 'POST',
-
'content' => $postContent,
-
'header' => "Content-Type: application/x-www-form-urlencoded" . CRLF . "Content-Length: $postContentLen" . CRLF
-
)
-
)
-
);
-
-
$data = '';
-
-
{
-
}
-
-
-
echo $data;
Conclusie
PHP biedt met streams een enorm flexibele API om gebruik te maken van filters en transports, bijvoorbeeld om HTTP POST requests mee uit te voeren naar een SMS of payment gateway!
Volg Scriptorama via RSS!
Reageer ook!
Dit is een mooi voorbeeld voor het posten van een form naar een andere server met callback, maar wat als de server deze instelling bevatt:
allow_url_fopen = Off
Deze instelling wordt vaak gebruikt op shared webhostings, het gebruik van cURL is in mijn ogen een eenvoudigere manier...
Door Olaf
op 09.11.06 @ 9:15 am | Permalink
[...] In "POST request maken zonder CURL" beschreef ik hoe je vanuit een PHP script een formulier kon posten zonder dat je daarvoor de CURL extensie voor nodig had. Daarbij had ik een ding nog niet besproken en dat is het feit dat zo'n website ook down of slecht bereikbaar kan zijn. [...]
Door Scriptorama.nl » Timeouts en PHP streams op 10.07.07 @ 11:06 am | Permalink
waarom eigenlijk bij array_map een eigen functie gebruiken?
waarom niet gelijk
$encodedVariables = array_map('rawurlencode', $variables);
?
Door maghiel
op 11.13.07 @ 3:07 pm | Permalink
Omdat ik in die eigen functie de key en value samenvoeg naar key=value, dat is niet mogelijk met array_map('rawurlencode', $variables).
Door Mathieu Kooiman
op 11.13.07 @ 4:17 pm | Permalink
sorry, te snel eroverheen gelezen :D
Door maghiel
op 11.15.07 @ 1:15 pm | Permalink
Zeer duidelijke 'tutorial', ik wou dat alle voorbeelden zo duidelijk waren. Ik kijk ernaar uit nog meer voorbeeldscripts van je te lezen...
Klasse!!
Richard
Door Richard
op 01.07.08 @ 1:01 pm | Permalink
Een iets geoptimaliseerdere versie, bevat enkele kleine wijzigingen.
'http://www.scriptorama.nl',
'desc' => 'Webdevelopment weblog'
);
foreach ($variables as $key => $value){
$new_variables[] = $key.'='.rawurlencode($value);
}
$postContent = join('&', $new_variables);
$postContentLen = strlen($postContent);
$streamCtx = stream_context_create(
array (
'http' => array (
'method' => 'POST', // dit MOET in hoofdletters
'content' => $postContent,
'header' => "Content-Type: application/x-www-form-urlencoded" . PHP_EOL . "Content-Length: $postContentLen" . PHP_EOL
)
)
);
$fp = fopen('http://localhost/doel.php', 'r', FALSE, $streamCtx);
$data = '';
while (!feof($fp)){
$data .= fread($fp, 1024);
}
fclose($fp);
echo $data;
?>
Door Arjan
op 01.19.08 @ 5:51 pm | Permalink
In plaats van mijn array_map(), dat meer een referentie was naar een eerder artikel dan echt nuttig, maar ook in plaats van jouw foreach kun je ook de functie http_build_query() gebruiken.
Door Mathieu Kooiman
op 01.20.08 @ 1:25 pm | Permalink
Ja leuk,
nu nog even met plaatje uploaden, cookie authorization en een secure (ssl) verbinding.
Want welk formulier wil je hier nu eigenlijk vullen?
Met een beetje gateway als paypal of blog als wordpress kom je er met dit scriptje niet.
Door joeri
op 08.22.08 @ 9:10 pm | Permalink
Hoi Joeri,
Het doel van dit artikel is om jou, de lezer, uit te leggen dat naast de CURL extensie, PHP zélf alle benodigdheden bevat om een HTTP POST request naar een server te sturen. Uiteraard laat ik daarbij ook zien hoe je dat dan kunt bereiken.
Wanneer mensen vragen hoe ze een POST request moeten maken verwijzen mensen direct naar CURL. CURL is oke (maar check de HTTP module in PECL ook eens!), áls je het hebt. Echter, niet alle mensen hebben de luxe om een module als CURL toe te laten voegen aan hun PHP configuratie en hosters zijn -enigzins terecht- niet erg happig op upgrades van hun virtual hosts waar 500 klanten op draaien.
Uit dit artikel blijkt nog niet helemaal de echte kracht van de stream wrappers. In dit geval gebruik ik een voorgedefinieërde stream wrapper, maar je kunt ook je eigen stream wrappers definieren. In dat geval wordt het gebruik van PHP's eigen functie tov het gebruik van CURL toch interessant(er), want het is flexibeler.
Om even verder in te gaan op jouw comment, cookie authorization en SSL gebruiken is echt geen probleem met die 'scriptje'. Een cookie toevoegen is een kwestie van een regel toevoegen aan de headers en voor SSL gebruik je simpelweg de https wrapper ipv de http wrapper. Of het uploaden van een bestand via stream wrappers ook mogelijk is durf ik niet met 100% zekerheid te zeggen nog, maar ik verwacht dat het wel te doen is. Een kwestie van de juiste headers en content payload samenstellen. Toegegeven, niet heel erg gebruikersvriendelijk ;).
Met het juiste gebruik van SSL en met het versturen van de juiste gegevens zal ook PayPal het request gewoon accepteren. Uiteindelijk doet het PHP script gewoon een HTTP request, eventueel over SSL, en verstuurt de headers (bijv. cookies) die je wilt hebben.
Door Mathieu Kooiman
op 08.23.08 @ 7:53 am | Permalink
Line and paragraph breaks automatic, e-mail address never displayed, HTML allowed:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>