Tutorials
Webbrowser tutorial
Alles wat je moet weten over HTTP om de meest gebruikelijke dingen op het web te kunnen.
Pagina 1
Inleiding
Aanschouw, mijn volgende HTTP gerelateerde tutorial is hier! Deze keer
ga ik het meer over HTTP houden, zoals het in de oorsprong bedoeld is.
Dus: hoe maak je met PHP een simpele webbrowser.
Misschien is de naam webbased proxy echter beter gekozen in dit
verband, aangezien elke webbrowser die je met (Server-side) PHP maakt
natuurlijk ook een echte browser nodig heeft. Maar ook webbased
proxies kunnen handig zijn, en uiteindelijk gaat het natuurlijk om de
echte HTTP code achter dit alles. Je zou dus je nieuwe HTTP kennis
moeten kunnen gebruiken om iets totaal anders te maken, zolang het
maar met HTTP werkt.
ga ik het meer over HTTP houden, zoals het in de oorsprong bedoeld is.
Dus: hoe maak je met PHP een simpele webbrowser.
Misschien is de naam webbased proxy echter beter gekozen in dit
verband, aangezien elke webbrowser die je met (Server-side) PHP maakt
natuurlijk ook een echte browser nodig heeft. Maar ook webbased
proxies kunnen handig zijn, en uiteindelijk gaat het natuurlijk om de
echte HTTP code achter dit alles. Je zou dus je nieuwe HTTP kennis
moeten kunnen gebruiken om iets totaal anders te maken, zolang het
maar met HTTP werkt.
Pagina 2
Basiskennis HTTP
Aangezien dit geheel over HTTP gaat is het toch wel handig om te weten
hoe een HTTP request en response zijn opgebouwd. Dit is vrij simpel,
beide bestaan uit: eerst een beginlijn, dan de headers, vervolgens een
lege regel en daarna de inhoud.
Voor een request is de eerste lijn de regel waar op staat welke
methode je gebruikt, welke resource je wil en welke HTTP versie. De
HTTP versie is in deze tutorial 1.0, ookal is de meest gebruikte
tegenwoordig 1.1. Dit is omdat, ondanks dat ik dit alles best voor 1.1
zou kunnen schrijven, het vanuit het oogpunt dit een simpele tutorial
te houden het beter is om bij 1.0 te blijven.
Voor een response is de eerste lijn de statuslijn. Deze zul je vast al
kennen van de bekende 404 errors. Normaal verwacht je hier echter een
200, wat betekent "OK".
Natuurlijk is dit alles veel duidelijker met een zeer simpel
voorbeeldje, dus, zie hier:
Request:
Response:
hoe een HTTP request en response zijn opgebouwd. Dit is vrij simpel,
beide bestaan uit: eerst een beginlijn, dan de headers, vervolgens een
lege regel en daarna de inhoud.
Voor een request is de eerste lijn de regel waar op staat welke
methode je gebruikt, welke resource je wil en welke HTTP versie. De
HTTP versie is in deze tutorial 1.0, ookal is de meest gebruikte
tegenwoordig 1.1. Dit is omdat, ondanks dat ik dit alles best voor 1.1
zou kunnen schrijven, het vanuit het oogpunt dit een simpele tutorial
te houden het beter is om bij 1.0 te blijven.
Voor een response is de eerste lijn de statuslijn. Deze zul je vast al
kennen van de bekende 404 errors. Normaal verwacht je hier echter een
200, wat betekent "OK".
Natuurlijk is dit alles veel duidelijker met een zeer simpel
voorbeeldje, dus, zie hier:
Request:
GET /example.php HTTP/1.0
Host: www.example.org
User-Agent: LegolasWeb WebBrowserDemo 1.0
Response:
HTTP/1.0 200 OK
Content-Type: text/html
Server: Apache/1.3.33 (Win32) PHP/4.4.3-dev
<pagina>
Pagina 3
GET: Een pagina opvragen
GET kennen we allemaal wel, gewoon een pagina opvragen. Als je een
browser hebt die GET ondersteunt kom je al een heel eind, je kan alle
pagina's opvragen. Je kan alleen nog niet dingens als formulieren
verzenden.
In princiepe hoef je alleen het body gedeelte uit de pagina te knippen
en voor de rest moet je natuurlijk een goede request maken en je
sockets goed opbouwen. Met die basis krijg je iets als deze basis
browser:
browser hebt die GET ondersteunt kom je al een heel eind, je kan alle
pagina's opvragen. Je kan alleen nog niet dingens als formulieren
verzenden.
In princiepe hoef je alleen het body gedeelte uit de pagina te knippen
en voor de rest moet je natuurlijk een goede request maken en je
sockets goed opbouwen. Met die basis krijg je iets als deze basis
browser:
<?php
// Nodige informatie
define('SERVER', 'www.example.org');
define('PORT', 80);
define('PATH', '/example.php');
// Versie
define('VERSION', '1.0');
// End of line
define('_EOL', "\r\n");
// Maak een socket
$sock = socket_create(AF_INET, SOCK_STREAM, 0);
if (!$sock) {
echo 'Fatal error: Couldn\'t create socket';
exit;
}
// Maak verbinding
if (!socket_connect($sock, SERVER, PORT)) {
echo 'Fatal error: Couldn\'t connect';
exit;
}
// Maak het request
$request = null;
$request .= 'GET ' . PATH . ' HTTP/1.0' . _EOL;
$request .= 'Host: ' . SERVER . _EOL;
$request .= 'User-Agent: LegolasWeb WebBrowserDemo ' . VERSION . _EOL;
$request .= _EOL;
// En verstuur het
if (!socket_send($sock, $request, strlen($request), 0)) {
echo 'Fatal error: Couldn\'t send request';
exit;
}
// Haal de response op
$output = null;
$buffer = null;
while (socket_recv($sock, $buffer, 1024, 0) != 0) {
$output .= $buffer;
}
// Isoleer het body gedeelte en toon het
$output = explode(_EOL . _EOL, $output);
$output = $output[1];
echo $output;
?>
Pagina 4
POST I: Een formulier verzenden
Normale formulieren gebruiken voor het body gedeelte van een request
het data type application/x-www-form-urlencoded. Dit betekent simpel
gezegd dat in de data er staat als een url geëncodeerde query string.
Dat is dus:
In een POST request komen er dus sowieso twee headers bij,
Content-Length en Content-Type. De eerste is de lengte van het body
gedeelte en de tweede het data type daarvan,
application/x-www-form-urlencoded dus. Als je dus een variabele iets
met daarin hoi en een variabele niets met doei wilt versturen krijg je
dus:
Als we dit toepassen in het GET script dat we eerder hebben gezien, en
natuurlijk vooral niet vergeten de variabelen te encoderen, krijgen we
zoiets:
Met dit alles heb je al helemaal zeker een goede basis voor HTTP, maar
er is altijd nog wel meer, dus gaan we snel verder met het 2e POST
gedeelte, multipart/form-data.
het data type application/x-www-form-urlencoded. Dit betekent simpel
gezegd dat in de data er staat als een url geëncodeerde query string.
Dat is dus:
var=inhoud&anderevar=andereinhoud
In een POST request komen er dus sowieso twee headers bij,
Content-Length en Content-Type. De eerste is de lengte van het body
gedeelte en de tweede het data type daarvan,
application/x-www-form-urlencoded dus. Als je dus een variabele iets
met daarin hoi en een variabele niets met doei wilt versturen krijg je
dus:
POST /example.php HTTP/1.1
Host: www.example.org
Connection: close
User-Agent: LegolasWeb Socket Browser 1.0.2
Content-Type: application/x-www-form-urlencoded
Content-Length: 19
iets=hoi&niets=doei
Als we dit toepassen in het GET script dat we eerder hebben gezien, en
natuurlijk vooral niet vergeten de variabelen te encoderen, krijgen we
zoiets:
<?php
// Nodige informatie
define('SERVER', 'www.example.org');
define('PORT', 80);
define('PATH', '/example.php');
// Versie
define('VERSION', '1.0');
// End of line
define('_EOL', "\r\n");
// Data
$data = array('test' => 'wiej!', 'fiets' => 'pomp', '35@&%' => '?&=');
// Maak een socket
$sock = socket_create(AF_INET, SOCK_STREAM, 0);
if (!$sock) {
echo 'Fatal error: Couldn\'t create socket';
exit;
}
// Maak verbinding
if (!socket_connect($sock, SERVER, PORT)) {
echo 'Fatal error: Couldn\'t connect';
exit;
}
// Maak het body stuk
$body = null;
foreach ($data as $var => $content) {
if ($body != null) {
$body .= '&';
}
$body .= rawurlencode($var) . '=' . rawurlencode($content);
}
// Maak het request
$request = null;
$request .= 'POST ' . PATH . ' HTTP/1.0' . _EOL;
$request .= 'Host: ' . SERVER . _EOL;
$request .= 'User-Agent: LegolasWeb WebBrowserDemo ' . VERSION . _EOL;
$request .= 'Content-Type: application/x-www-form-urlencoded' . _EOL;
$request .= 'Content-Length: ' . strlen($body) . _EOL;
$request .= _EOL;
$request .= $body;
// En verstuur het
if (!socket_send($sock, $request, strlen($request), 0)) {
echo 'Fatal error: Couldn\'t send request';
exit;
}
// Haal de response op
$output = null;
$buffer = null;
while (socket_recv($sock, $buffer, 1024, 0) != 0) {
$output .= $buffer;
}
// Isoleer het body gedeelte en toon het
$output = explode(_EOL . _EOL, $output);
$output = $output[1];
echo $output;
?>
Met dit alles heb je al helemaal zeker een goede basis voor HTTP, maar
er is altijd nog wel meer, dus gaan we snel verder met het 2e POST
gedeelte, multipart/form-data.
Pagina 5
POST II: Bestanden uploaden
Het is tijd om nog een stapje verder te gaan, we gaan verder met het
2e bekende POST data type, namelijk multipart/form-data. Deze is
natuurlijk bekend van de file uploads, en dat is dus ook wat we gaan
doen, bestanden uploaden.
Dit is weer net iets moeilijker, aangezien een request nu uit meerdere
delen gaat bestaan, die afgescheiden zijn door zogenaamde boundaries.
Een boundary is gewoon een willekeurig gekozen hoeveelheid tekens die
niet verder in de data voor komt. Het body gedeelte bestaat dan
uiteindelijk uit stukjes die beginnen met een regel met eerst twee
streepjes ("--") vervolgens de boundary, daarna op aparte regels de
header, gevolgd door een witregel en daarna de data zelf. Het laatste
stukje wordt afgesloten door de boundary met aan beide kanten twee
streepjes ("--"). De headers van een stukje bevatten sowieso een
Content-Disposition header die de naam van het stukje data aangeeft en
aangeeft dat het een stukje data is.
Een voorbeeld request volgt hieronder:
Dit is niets meer dan het formulier van hierboven met een tekst
bestandje erbij. Het illustreert ook meteen goed dat je
multipart/form-data alleen moet gebruiken als het ook echt nodig is,
anders wordt je request onnodig groot.
Dit alles weer in het socket script verwerkt geeft deze creatie:
Hiermee gaat dan dus de wereld van het uploaden van bestanden voor je
open, maar dan vooral de wereld er achter. Je zou nu dus al kunnen
gaan werken aan een script dat automatisch back-ups voor je maakt of
iets in die richting.
2e bekende POST data type, namelijk multipart/form-data. Deze is
natuurlijk bekend van de file uploads, en dat is dus ook wat we gaan
doen, bestanden uploaden.
Dit is weer net iets moeilijker, aangezien een request nu uit meerdere
delen gaat bestaan, die afgescheiden zijn door zogenaamde boundaries.
Een boundary is gewoon een willekeurig gekozen hoeveelheid tekens die
niet verder in de data voor komt. Het body gedeelte bestaat dan
uiteindelijk uit stukjes die beginnen met een regel met eerst twee
streepjes ("--") vervolgens de boundary, daarna op aparte regels de
header, gevolgd door een witregel en daarna de data zelf. Het laatste
stukje wordt afgesloten door de boundary met aan beide kanten twee
streepjes ("--"). De headers van een stukje bevatten sowieso een
Content-Disposition header die de naam van het stukje data aangeeft en
aangeeft dat het een stukje data is.
Een voorbeeld request volgt hieronder:
POST /example.php HTTP/1.0
Host: www.example.org
Connection: close
User-Agent: LegolasWeb Socket Browser 1.0.2
Content-Type: multipart/form-data; boundary=boundsX
Content-Length: 250
--boundsX
Content-Disposition: form-data; name="iets"
hoi
--boundsX
Content-Disposition: form-data; name="niets"
doei
--boundsX
Content-Disposition: form-data; name="test"; filename="test.txt"
Content-Type: text/plain
test
--boundsX--
Dit is niets meer dan het formulier van hierboven met een tekst
bestandje erbij. Het illustreert ook meteen goed dat je
multipart/form-data alleen moet gebruiken als het ook echt nodig is,
anders wordt je request onnodig groot.
Dit alles weer in het socket script verwerkt geeft deze creatie:
<?php
// Nodige informatie
define('SERVER', 'www.example.org');
define('PORT', 80);
define('PATH', '/example.php');
// Versie
define('VERSION', '1.0');
// End of line
define('_EOL', "\r\n");
// Data
$data = array('test' => array(null, null, 'wiej!'), 'fiets' =>
array(null, null, 'pomp'), '35@&%' => array(null, null, '?&='), 'file'
=> array('text/plain', 'test.txt', 'test'));
// Maak een socket
$sock = socket_create(AF_INET, SOCK_STREAM, 0);
if (!$sock) {
echo 'Fatal error: Couldn\'t create socket';
exit;
}
// Maak verbinding
if (!socket_connect($sock, SERVER, PORT)) {
echo 'Fatal error: Couldn\'t connect';
exit;
}
// Maak een boundary
$bound = null;
for ($i = 0; $i < 15; $i++) {
$bound .= chr(rand(33, 126));
}
// Maak het body stuk
$body = null;
foreach ($data as $var => $content) {
$body .= '--' . $bound . _EOL;
if ($content[0] != null) {
$body .= 'Content-Disposition: form-data; name="' . $var . '";
filename="' . $content[1] . '"' . _EOL;
$body .= 'Content-Type: ' . $content[0] . _EOL;
}
else {
$body .= 'Content-Disposition: form-data; name="' .
$var . '"' . _EOL;
}
$body .= _EOL;
$body .= $content[2] . _EOL;
}
$body .= '--' . $bound . '--';
// Maak het request
$request = null;
$request .= 'POST ' . PATH . ' HTTP/1.0' . _EOL;
$request .= 'Host: ' . SERVER . _EOL;
$request .= 'User-Agent: LegolasWeb WebBrowserDemo ' . VERSION . _EOL;
$request .= 'Content-Type: multipart/form-data; boundary=' . $bound . _EOL;
$request .= 'Content-Length: ' . strlen($body) . _EOL;
$request .= _EOL;
$request .= $body;
// En verstuur het
if (!socket_send($sock, $request, strlen($request), 0)) {
echo 'Fatal error: Couldn\'t send request';
exit;
}
// Haal de response op
$output = null;
$buffer = null;
while (socket_recv($sock, $buffer, 1024, 0) != 0) {
$output .= $buffer;
}
// Isoleer het body gedeelte en toon het
$output = explode(_EOL . _EOL, $output);
$output = $output[1];
echo $output;
?>
Hiermee gaat dan dus de wereld van het uploaden van bestanden voor je
open, maar dan vooral de wereld er achter. Je zou nu dus al kunnen
gaan werken aan een script dat automatisch back-ups voor je maakt of
iets in die richting.
Pagina 6
De response headers en status
De server stuurt natuurlijk ook wat terug, maar naast de opgevraagde
resource zelf zitten hier ook headers bij en ook een status code.
Om te beginnen kun je uit de status code het een en ander afleiden.
Als deze, het 3 nummerige cijfer, met een 2 begint is alles goed. Bij
een 3 wordt je door gestuurd, bij een 4 is er een fout in de request
en bij een 5 is er een probleem met de server. Hoewel het net is om de
code's in je webbrowser te onderzoeken is dit technisch gezien niet
echt nodig. Het is echter vrij simpel om uit de statuslijn even de
code en de beschrijving te halen.
Deze functie haalt ook meteen de headers uit de response, en sommige
van die headers zijn wel belangrijk om ook in je webbrowser te
verwerken. Een goed voorbeeld is de Set-Cookie header. Er kunnen
meerdere cookies verzonden worden per response dus daarom is de output
van deze functie ook zo gemaakt dat hij er meerdere aan kan. De
headers array bevat immers voor elke header een array met daarin elk
voorkomen. Het hoofd gedeelte van een Set-Cookie header is de naam en
de data, dit keert ook weer terug in de volgende request in de Cookie
header, waarin bij meerdere cookies deze gescheiden zijn door een
puntkomma en een spatie. Een voorbeeldje volgt hieronder:
De subvalues van de cookie header bevatten een datum waarop de cookie
ongeldig wordt ('expires') en de path en het domein ('domain') waarop
het cookie geldt. Als het domein .example.org is betekent dit dat deze
geld voor alles dat eindigt op example.org, inclusief example.org.
Je kunt nu inmiddels met je HTTP kennis op een site inloggen, en
ingelogd blijven, en wat posten. Hier eindigt dan ook deze tutorial
bijna, op een dingetje na, proxies.
resource zelf zitten hier ook headers bij en ook een status code.
Om te beginnen kun je uit de status code het een en ander afleiden.
Als deze, het 3 nummerige cijfer, met een 2 begint is alles goed. Bij
een 3 wordt je door gestuurd, bij een 4 is er een fout in de request
en bij een 5 is er een probleem met de server. Hoewel het net is om de
code's in je webbrowser te onderzoeken is dit technisch gezien niet
echt nodig. Het is echter vrij simpel om uit de statuslijn even de
code en de beschrijving te halen.
<?php
// Parset een response
// De parameter $response moet hier een response bevatten
function parseResponse($response) {
$output = array();
// Split body en head
$response = explode(_EOL . _EOL, $response);
$output['body'] = $response[1];
// Split de verschillende head lijnen
$head = explode(_EOL, $response[0]);
// Haal de informatie uit de status lijn
$status = explode(' ', array_shift($head));
$output['protocol'] = array_shift($status);
$output['status'] = array_shift($status);
$output['status_exp'] = implode(' ', $status);
// Bouw de headers om naar een array
$headers = array();
foreach ($head as $line) {
$exp = explode(': ', $line);
$name = array_shift($exp);
$exp = explode('; ', implode(': ', $exp));
$val = array_shift($exp);
// Bereid de subheaders voor
$subvals = array();
foreach ($exp as $sub) {
$subparts = explode('=', $sub);
$subvals[array_shift($subparts)] = implode('=', $subparts);
}
// Zet de headers in een array
$headers[$name][] = array('value' => $val, 'subvalues' => $subvals);
}
$output['headers'] = $headers;
return $output;
}
?>
Deze functie haalt ook meteen de headers uit de response, en sommige
van die headers zijn wel belangrijk om ook in je webbrowser te
verwerken. Een goed voorbeeld is de Set-Cookie header. Er kunnen
meerdere cookies verzonden worden per response dus daarom is de output
van deze functie ook zo gemaakt dat hij er meerdere aan kan. De
headers array bevat immers voor elke header een array met daarin elk
voorkomen. Het hoofd gedeelte van een Set-Cookie header is de naam en
de data, dit keert ook weer terug in de volgende request in de Cookie
header, waarin bij meerdere cookies deze gescheiden zijn door een
puntkomma en een spatie. Een voorbeeldje volgt hieronder:
Cookie: NAME=VALUE; NAME2=VALUE
De subvalues van de cookie header bevatten een datum waarop de cookie
ongeldig wordt ('expires') en de path en het domein ('domain') waarop
het cookie geldt. Als het domein .example.org is betekent dit dat deze
geld voor alles dat eindigt op example.org, inclusief example.org.
Je kunt nu inmiddels met je HTTP kennis op een site inloggen, en
ingelogd blijven, en wat posten. Hier eindigt dan ook deze tutorial
bijna, op een dingetje na, proxies.
Pagina 7
Proxies
Eigenlijk is dit een zo makkelijk iets in HTTP dat dit niet eens een
eigen pagina nodig heeft. Een HTTP proxy slikt namelijk gewone HTTP
requests, alleen is het PATH hier vervangen door een volledige URI
('Unified Resource Identifier') en de host die van de proxy:
eigen pagina nodig heeft. Een HTTP proxy slikt namelijk gewone HTTP
requests, alleen is het PATH hier vervangen door een volledige URI
('Unified Resource Identifier') en de host die van de proxy:
GET http://www.example.org/example.php HTTP/1.0
Host: www.proxy.org
User-Agent: LegolasWeb WebBrowserDemo 1.0
Pagina 8
En nu?
Nu kun je een behoorlijke webbrowser maken in PHP, of eigenlijk, zoals
ik al eerder zei, een webbased proxy. Je kan namelijk nog steeds geen
(X)HTML parsen en laten zien zoals firefox dat doet, en je hebt dus
daarvoor nog steeds een webbrowser nodig. Je kan echter wel bijna alle
gewone dingen met HTTP. Dingen als backups maken met HTTP fileuploads
zijn leuk om toch eens te proberen, of een formulier invullen op een
zoeksite en de zoekgegevens vervolgens uit de opgevraagde pagina
halen. De mogelijkheden zijn weer eens groot, dus, vermaak je!
ik al eerder zei, een webbased proxy. Je kan namelijk nog steeds geen
(X)HTML parsen en laten zien zoals firefox dat doet, en je hebt dus
daarvoor nog steeds een webbrowser nodig. Je kan echter wel bijna alle
gewone dingen met HTTP. Dingen als backups maken met HTTP fileuploads
zijn leuk om toch eens te proberen, of een formulier invullen op een
zoeksite en de zoekgegevens vervolgens uit de opgevraagde pagina
halen. De mogelijkheden zijn weer eens groot, dus, vermaak je!
Reacties
0