Tutorials

Sockets in PHP

communicatie tussen servers en modules

Pagina 1

De functies

fsockopen
fsockopen() is waarschijnlijk de belangrijkste functie in deze tutorial :)
Het opent een verbinding naar een bepaalde poort op een bepaalde server (een IP of adres). Zoals je ziet op de PHP.NET website, neemt de functie tot 5 parameters. De laatste 3 zijn niet nodig, geef wel het poortnummer mee.
Ik ga het over HTTP requests hebben, dus port 80.
De return value van de functie (als het goed gaat) is een resource met daarin de connectie.

fwrite
Waarschijnlijk ken je de functie fwrite() wel. Het schrijft data naar een connectie. Een connectie als die we gemaakt hebben met fsockopen().

fread
Ook deze zal je wel kennen. Deze functie leest uit een connectie :) Raad maar wat voor connectie... Idd, een connectie zoals uit fsockopen.

Misschien vraag je je af wat er met fopen() gebeurd is. Die functie hebben we niet nodig. We gaan namelijk niet zomaar een bestand openen, we maken er een socket van.

Op de volgende pagina hoe HTTP requests werken en dan een voorbeeldje...
Pagina 2

HTTP Requests! Nieuw?

HTTP requests is waar je ongelooflijk veel van gebruikt, maar waarschijnlijk niets van merkt en niets van weet. Waarom niet? Omdat je browser ze afhandelt.

HTTP Headers
Je browser stuurt een aantal HTTP Headers naar een website en krijgt dan een aantal HTTP Headers terug, met een beetje data (of niet). Die data heeft een Content Type.
Het standaard Content Type die een website terug stuurt, is text/html. De browser ontvangt data met text/html als Content Type en weet dan hoe ze dat moet implementeren (als HTML, dus tags worden vervangen door opmaak).
Je kan het content type dat een website 'verstuurt', veranderen met de PHP functie header(). Logisch toch!? :)
De functie header() neemt 1 parameter die de header bepaalt, maar die bestaat altijd uit twee onderdelen. Een naam en een waarde.
Een voorbeeld van een HTTP Header is dus Content Type:
<?php

header("Content-Type: text/plain");

?>

Het is interessant hoeveel macht een HTTP Header heeft en hoe makkelijk een header te versturen is :) Als je de bovenstaande header verstuurt op een website en die website opent in je browser, zal de output - of er nou HTML, XML of wat dan ook in staat - plain text zijn. Je ziet elk mogelijk karakter dus als gewoon karakter. De "source" van de pagina zal precies hetzelfde zijn als wat je in je venster ziet.
Een paar content-types:
  • [li]text/html[/li]
    [li]text/plain[/li]
    [li]text/xml[/li]
    [li]image/gif[/li]
    [li]image/png[/li]
    [li]audio/mpeg-object[/li]


Je hebt het misschien niet door, maar nu snap je HTTP Headers.
Een "standaard" HTTP Header 'blok' (response, niet request):
HTTP/1.1 200 OK
Date: Mon, 23 May 2005 22:38:34 GMT
Server: Apache/1.3.27 (Unix)  (Red-Hat/Linux)
Content-Length: 438
Connection: close
Content-Type: text/html; charset=UTF-8


De eerste regel is wat voor iets er terug komt uit de server, naar aanleiding van de request. De STATUS regel. 200 is status "OK".
"HTTP/1.1 200 OK" is goed :)

Ik had gezegd een voorbeeldje, maar dat moet nog heel eventjes wachten :)
Eerst wat er voorgaat aan de response: de request...
Pagina 3

GET en POST requests

Een browser (of jij, met php of telnet, o.i.d.) kan twee soorten HTTP requests maken: GET en POST.
Even voor de duidelijkheid: ELKE pagina die je opent is het resultaat van een GET of POST request. We kennen allemaal wat een POST request doet. Of toch niet!? Een form heeft twee "methods": GET of POST :) We gebruiken method POST om 'grote' hoeveelheden data met een form te versturen. Wat we eigenlijk doen dan, is een POST request maken, met een aantal parameters.
Wanneer we een GET request maken, zonder parameters, openen we gewoon een pagina. Als je naar www.google.nl surft maak je een GET request zonder parameters.

een GET request
De eerste regel van een HTTP request is de methode, de http versie en de locatie. Een voorbeeld:

GET / HTTP/1.1


Ziet er makkelijk uit he :) Maar wat gebeurt er!? Duidelijk... een GET request naar /. Dat is vaak index.php, maar dat is nog niet interessant.
Een request moet, net zoals een response, bestaan uit een status regel en een aantal headers. Een voorbeeld :)
POST /external_socket.php HTTP/1.1
Host: www.jouwmoeder.nl
Content-Type: application/x-www-form-urlencoded
Content-Length: 3
Connection: Close

Zoals je ziet is er een header Content-Length. Die zegt hoeveel data er wordt verstuurd. Als je een GET request maakt is dit niet nodig.
Omdat er 3 bytes verstuurd worden volgens ons, moeten we iets verzinnen ;)
Een voorveeld "data blok" zou kunnen zijn:
c=6


Dit ontvang je in external_socket.php als $_POST['c']. Zo makkelijk is het.
Als meerdere variabelen wil sturen moet je die scheiden met een ampersand (&). Denk er aan om altijd je waarden te urlencoden! Maar alleen je waarden! Niet de var namen. Bijvoorbeeld:
<?php

$szHTTPData = "naam=" . urlencode("rudie") . "&wachtwoord=" . urlencode("met spatie+s/< en meer");

?>


Nu gaan we nog even snel een request sturen en data ontvangen :)
Ik gebruik als server "www.jouwmoeder.nl" en als script "/external_socket.php"
<?php

$server = "www.jouwmoeder.nl";
$script = "/external_socket.php";

// Maak socket - ik ga er van uit dat het lukt, dus geen error afhandeling :)
$sock = fsockopen($server, 80);
if ( $sock )
{
    $szParams = "user=rudie&pass=" . urlencode("mijn wachtwoord");

    $headers = "POST ".$script." HTTP/1.1\r\n";
    $headers.= "Content-Type: application/x-www-form-urlencoded\r\n";
    $headers.= "Content-Length: ".strlen($szParams)."\r\n";
    $headers.= "Connection: Close";
    $headers.= "\r\n"; // met een extra 'open regel' maken we http duidelijk dat de headers afgelopen zijn

    // En nu http headers naar de server schrijven
    fwrite($sock, $headers);
    fwrite($sock, $szParams);

    // En minstens zo belangrijk, headers (en meer) ophalen en opslaan
    $output = "";
    while ( !feof($sock) )
    {
        $output .= fread($sock, 4096);
    }

    // $output bestaat nu uit twee onderdelen: Headers en Data
    // Om uit elkaar te houden: headers is TOT de eerste open regel, dus \r\n\r\n
    list($headers, $body) = explode("\r\n\r\n", $output, 2);

    // Maar we printen even alles voor de duidelijkheid
    echo $output;
}

?>


Als ik dat probeer zal mijn venster dit printen:

HTTP/1.1 200 OK
Date: Mon, 16 Oct 2006 20:11:39 GMT
Server: Apache/2.0.54 (Debian GNU/Linux) FrontPage/5.0.2.2635 mod_python/3.1.3 Python/2.3.5 PHP/4.4.1-0.dotdeb.3 mod_ssl/2.0.54 OpenSSL/0.9.7e mod_perl/1.999.21 Perl/v5.8.4
X-Powered-By: PHP/4.4.1-0.dotdeb.3
secret1: geheime shit
secret2: Array();\n!scalar
secret1: meer geheimes
Content-Length: 96
Connection: close
Content-Type: text/plain

METHOD: POST

GET:
Array
(
)

POST:
Array
(
[user] => rudie
[pass] => mijn wachtwoord
)
Pagina 4

Duidelijkheid en verwerking

HTTP en ook PHP heeft aan ontzettend veel gedacht. Zo kan de header functie veel meer dan ik wist, voordat ik sockets een http requests ontdekte.

Huh wat bedoel ik!?
Dit: wat je net zag was onduidelijk. Lange zinnen, afgebroken, of niet, ligt aan je browser, lettertypes, zag ik ergens een html tag? Wow wat een hoop info!?!?!
Je bent aan t developen dus je wil duidelijkheid! Een hele makkelijke manier om data te printen (vooral data die je net opgehaald hebt uit een socket) is als plain text :)
Klinkt bekend? Should!
Zet het volgende net boven waar je je output print:

Header("content-type: text/plain");

Dan zal je browser de output niet meer als HTML beschouwen, maar als een plain tekst bestand. Dus geen afgebroken regels en gekke lettertypen. Wel duidelijkheid!

Het script op www.jouwmoeder.nl/external_socket.php is om te testen. Het print GET en POST variabelen. Zoals je in de headers kan zien die we net ontvangen hebben, is het Content-Type: text/plain. Dat zie je als je naar http://www.jouwmoeder.nl/external_socket.php surft. Geen html in de source (zoals <pre> o.i.d.) en toch nette duidelijke code die makkelijk te lezen is.


Verwerking
Hoe het leest is natuurlijk geweldig interessant, maar vooral niet boeiend :)
Als je een script schrijft, zoals we net gedaan hebben, dat headers en body leest, is de Content-Type eigenlijk helemaal niet interessant. Je bent namelijk geen browser die er iets mee gaat doen. Voor ons is het gewoon text en text is text. Maar ook images zijn text, en zelfs flash en films. Alles is text ;) Zelfs binary is text. Het maakt allemaal niet meer uit. Je kan een bestand downloaden (de output van een extern programma) met behulp van sockets. Met behulp van HTTP Requests eigenlijk. Voor de simpelste GET request (get op /) is er natuurlijk file_get_contents(), maar als er paar vars mee moeten als POST data... Tsja, daar zit je dan met je file_get_contents(). Sockets dus!

Veiligheid
Ik bedoel niet HTTPS of SFTP o.i.d. Ik wil het alleen over http hebben.
Het is dan ook niet officieel iets veiligs, maar je kan ongelooflijk veel met http headers (geloof je me al?).
Zoals je net in de response headers zag, stonden er drie (meer?) "vreemde" headers in. En alleen om aan te tonen wat je kan met informatie die niemand niemand niemand ziet!
Ik heb drie secrets meegestuurd die een browser nooit zou opmerken. Omdat je browser er niks mee kan, worden ze genegeerd. Jouw programma kan er echter waardevolle data uithalen! Of niet!? Misschien niet, maar de mogelijkheid is er wel...
En andersom ook! Ik kan headers meesturen die je normaal nooit mee kan sturen. Niet m.b.v. een formulier (POST) en ook niet als gewoon GET.
Ik doe een simpele GET request naar mijn server om bijvoorbeeld in te loggen. Ik had het als POST request kunnen doen met user en pass als data. Ik kan het ook doen met user en pass in de http headers :)
Hoe?
Zo...
Pagina 5

secrets in http headers

secrets versturen - request / command

Om te sturen:
GET /external_socket.php HTTP/1.1
Host: www.jouwmoeder.nl
Connection: Close
my_username: rudie
my_password: wachtwoord ofzo


Dan ontvangt de server (ook jij) een stel http headers. Opgeslagen in $_SERVER.
Maar niet zo makkelijk als letterlijk.
De header "my_username" die we meesturen wordt opgeslagen in _SERVER als HTTP_MY_USERNAME. Met "HTTP_" ervoor en in hoofdletters dus. Maar dat weerhoudt ons niet :)
Je zou dus kunnen controleren of $_SERVER['HTTP_MY_USERNAME'] en $_SERVER['HTTP_MY_PASSWORD'] bestaan. Als ze beiden bestaan ga je ze controleren in je database.
Merk op: absouut geen data verstuurd. Alleen http headers.
En dan? Dan geeft de database resultaat terug. Een hoop PHP onderwater natuurlijk, maar uiteindelijk weten we of je request valid is of niet. Als de headers niet beiden zijn geset weten we voldoende: niet goed!
Hoe we dat laten merken?
Niet zo:
<?php

echo "Het spijt me maar je gebruikersnaam en / of wachtwoord zijn niet correct ontvangen.";

?>

Dat is leuk als je een persoon bent, maar niet als je een machine bent. Een machine wil een code of een woord. Een goede manier om dit wel te doen (jawel, zonder data te versturen) is met een header. Whaaat!?
Jep, gewoon een header teruggooien!
En wel zo een:
<?php

header("HTTP/1.1 405 Method Not Allowed");

?>


Ik kies nu voor "Method not allowed", maar je kan ook gaan voor eh... "Forbidden", of "Bad request" of "Not acceptable" of "Unauthorized". Allemaal mogelijkheden :)

Als het inloggen gelukt is sturen we netjes een "OK", maar we kunnen ook gaan voor een "Accepted" of een "Continue". Aan jou de keus, leef je uit!

Een nette lijst HTTP Status Codes: http://en.wikipedia.org/wiki/List_of_HTTP_status_codes

Maar er is meer...
Pagina 6

Afsluiting

Meer...
Het is ongelofeloos, maar je kan nog veel meer. De mogelijkheden zijn bijna eindeloos. En het mooie: het is gratis, makkelijk en snel!
Maak een pagina op je server (hoeft niet eens extern of online) die print wat je wilt printen. Stuur headers, print data, ontvang data, ontvang headers. Bewerk, wijzig, muteer. Nogmaals: leef je helemaal uit!

Ik had ooit een login systeem gemaakt voor Administratie van een website. Het was een Permissions systeem, dus je kon mooi instellen wie wat kon.
Ik vond het een beetje overduidelijk om te zeggen dat je wachtwoord fout was, bijvoorbeeld. Er was nl. een hele makkelijke loginbox met een formulier. Dat formulier linkte (action="pagina.php") naar een andere pagina.
Die pagina was headers aan het spugen :) Altijd 404, omdat de pagina niet bestaat... Ik had custom error paginas (dus ook een 404 Not Found). Die pagina zou ik dan printen. Maar dat is voor persoontjes. Een header erbij van 404 en ook de machientjes denken dat de pagina niet bestaat.
Je bepaalt helemaal zelf wat je ermee doet... Je browser interpreteert HTTP en headers, maar je kan ze zelf maken. Niet alleen de browser zelf.

Ga eens met header() spelen en gooi zowel status codes als header vars in het rond. Zoals je zag kan je custom headers sturen (secret1, secret2, etc) en ook HTTP statussen (200 voor OK, 404 voor Not Found en 401 voor Unauthorized.

Internet (vooral HTTP in dit geval) is veel meer dan jij en ik kunnen zien. We kunnen echter wel tooltjes bouwen die voor ons kunnen kijken. En zelfs wat die tooltjes zien hoeven we niet te printen, als we er maar iets mee doen.
Uiteindelijk wil je natuurlijk een berichtje krijgen dat iets al dan niet gelukt is, maar er kunnen ondertussen 8 requests zijn geweest, over en weer, en een paar fake status codes en een paar secret headers. En dan is er misschien wel een nette sessie opgezet. Een soort van inloggen voor browsers :) Niet met SESSIONS of COOKIES, maar met secrets in http headers.

Laatste keer:
<?php

$mogelijkheden = "bijna oneindig";
leef_je_uit();

?>


Als je niet steeds wil typen in PHP en op F5 duwen, kan je een progje downloaden (eerst zoeken) waarmee je zelf voor browser kan spelen. Typ headers in en ontvang headers. Als je twee keer op enter drukt worden de headers gestuurd. Na je paramaters (als je die hebt) nog een keer, en alles wordt verstuurd.

Dat is geweldig spelen, en makkelijk. Je kan echter niet de ontvangende kant van het spel spelen. Maak dus een socket target, zoals external_socket.php

<?php
// external_socket.php | response, show everything

header("content-type: text/plain");


// supersecretheaders //
header("secret1: geheime shit");
header("secret2: Array();".'\n'."!scalar");
header("secret1: meer geheimes",false); // remember = no overwrite, so FALSE


echo print_r( $_SERVER, true ) . "\n";

echo "METHOD: ".$_SERVER['REQUEST_METHOD']."\n\n";

echo "GET:\n";
print_r( $_GET );
echo "\nPOST:\n";
print_r( $_POST );
exit;

?>



Veel plezier :)
Hulp is er hier, op php.net en op wikipedia. HTTP is interessant! Veel interessanter dan output lezen als html!


Een tip om sockets te openen:
"http://POST@www.jouwmoeder.nl/external_socket.php";
laat php lezen als
_PORT_ :// _METHOD_ @ _SERVER_ _SCRIPT_
m.b.v. parse_url()
Pagina 7

request

GET /script.php HTTP/1.1
Niks: weinig
Connection: Close
Pagina 8

request

POST / HTTP/1.1
Host: www.example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 30
Connection: Close
Meer: Ja, meer :)

dit=zijn_meer_dan_30_bytes
Pagina 9

response

header("super-secret1: bla");
header("super-secret2: geen overwrite toch?");
header("super-secret2: welke wordt onthouden?", true);
Pagina 10

response

header("HTTP/1.1 404 Not Found");

echo "Ik ben not found maar ik praat lekker toch!?";

Reacties

0
Nog geen reacties.