1994f9e7ab850509265846e48f81a6478a685262
[m6w6/ext-http] / docs / examples / tutorial.txt
1
2 A Beginners Tutorial
3 --------------------
4 $Revision$
5
6
7 - GET Queries
8
9 The HttpRequest class can be used to execute any HTTP request method.
10 The following example shows a simple GET request where a few query
11 parameters are supplied. Additionally potential cookies will be
12 read from and written to a file.
13
14 <?php
15 $r = new HttpRequest('http://www.google.com/search');
16
17 // store Googles cookies in a dedicated file
18 touch('google.txt');
19 $r->setOptions(
20 array( 'cookiestore' => 'google.txt',
21 )
22 );
23
24 $r->setQueryData(
25 array( 'q' => '+"pecl_http" -msg -cvs -list',
26 'hl' => 'de'
27 )
28 );
29
30 // HttpRequest::send() returns an HttpMessage object
31 // of type HttpMessage::TYPE_RESPONSE or throws an exception
32 try {
33 print $r->send()->getBody();
34 } catch (HttpException $e) {
35 print $e;
36 }
37 ?>
38
39 - Multipart Posts
40
41 The following example shows an multipart POST request, with two form
42 fields and an image that's supposed to be uploaded to the server.
43 It's a bad habit as well as common practice to issue a redirect after
44 an received POST request, so we'll allow a redirect by enabling the
45 redirect option.
46
47 <?php
48 $r = new HttpRequest('http://dev.iworks.at/.print_request.php', HTTP_METH_POST);
49
50 // if redirects is set to true, a single redirect is allowed;
51 // one can set any reasonable count of allowed redirects
52 $r->setOptions(
53 array( 'cookies' => array('MyCookie' => 'has a value'),
54 'redirect' => true,
55 )
56 );
57
58 // common form data
59 $r->setPostFields(
60 array( 'name' => 'Mike',
61 'mail' => 'mike@php.net',
62 )
63 );
64 // add the file to post (form name, file name, file type)
65 touch('profile.jpg');
66 $r->addPostFile('image', 'profile.jpg', 'image/jpeg');
67
68 try {
69 print $r->send()->getBody();
70 } catch (HttpException $e) {
71 print $e;
72 }
73 ?>
74
75 - Parallel Requests
76
77 It's possible to execute several HttpRequests in parallel with the
78 HttpRequestPool class. HttpRequests to send, do not need to perform
79 the same request method, but can only be attached to one HttpRequestPool
80 at the same time.
81
82 <?php
83 try {
84 $p = new HttpRequestPool;
85 // if you want to set _any_ options of the HttpRequest object,
86 // you need to do so *prior attaching* to the request pool!
87 $p->attach(new HttpRequest('http://pear.php.net', HTTP_METH_HEAD));
88 $p->attach(new HttpRequest('http://pecl.php.net', HTTP_METH_HEAD));
89 } catch (HttpException $e) {
90 print $e;
91 exit;
92 }
93
94 try {
95 $p->send();
96 // HttpRequestPool implements an iterator over attached HttpRequest objects
97 foreach ($p as $r) {
98 echo "Checking ", $r->getUrl(), " reported ", $r->getResponseCode(), "\n";
99 }
100 } catch (HttpException $e) {
101 print $e;
102 }
103 ?>
104
105 - Parallel Requests?
106
107 You can use a more advanced approach by using the protected interface of
108 the HttpRequestPool class. This allows you to perform some other tasks
109 while the requests are executed.
110
111 <?php
112 class Pool extends HttpRequestPool
113 {
114 public function __construct()
115 {
116 parent::__construct(
117 new HttpRequest('http://pear.php.net', HTTP_METH_HEAD),
118 new HttpRequest('http://pecl.php.net', HTTP_METH_HEAD)
119 );
120
121 // HttpRequestPool methods socketPerform() and socketSelect() are
122 // protected; one could use this approach to do something else
123 // while the requests are being executed
124 print "Executing requests";
125 for ($i = 0; $this->socketPerform(); $i++) {
126 $i % 10 or print ".";
127 if (!$this->socketSelect()) {
128 throw new HttpException("Socket error!");
129 }
130 }
131 print "\nDone!\n";
132 }
133 }
134
135 try {
136 foreach (new Pool as $r) {
137 echo "Checking ", $r->getUrl(), " reported ", $r->getResponseCode(), "\n";
138 }
139 } catch (HttpException $ex) {
140 print $e;
141 }
142 ?>
143
144 - Cached Responses
145
146 One of the main key features of HttpResponse is HTTP caching. HttpResponse
147 will calculate an ETag based on the http.etag_mode INI setting as well as
148 it will determine the last modification time of the sent entity. It uses
149 those two indicators to decide if the cache entry on the client side is
150 still valid and will emit an "304 Not Modified" response if applicable.
151
152 <?php
153 HttpResponse::setCacheControl('public');
154 HttpResponse::setCache(true);
155 HttpResponse::capture();
156
157 print "This will be cached until content changes!\n";
158 print "Note that this approach will only save the clients download time.\n";
159 ?>
160
161 - Bandwidth Throttling
162
163 HttpResponse supports a basic throttling mechanism, which is enabled by
164 setting a throttle delay and a buffer size. PHP will sleep the specified
165 amount of seconds after each sent chunk of specified bytes.
166
167 <?php
168 // send 5000 bytes every 0.2 seconds, i.e. max ~25kByte/s
169 HttpResponse::setThrottleDelay(0.2);
170 HttpResponse::setBufferSize(5000);
171 HttpResponse::setCache(true);
172 HttpResponse::setContentType('application/x-zip');
173 HttpResponse::setFile('../archive.zip');
174 HttpResponse::send();
175 ?>
176
177 Exemplar Use Cases
178 ------------------
179
180 - KISS XMLRPC Client
181
182 <?php
183 class XmlRpcClient
184 {
185 public $namespace;
186 protected $request;
187
188 public function __construct($url, $namespace = '')
189 {
190 $this->namespace = $namespace;
191 $this->request = new HttpRequest($url, HTTP_METH_POST);
192 $this->request->setContentType('text/xml');
193 }
194
195 public function setOptions($options = array())
196 {
197 return $this->request->setOptions($options);
198 }
199
200 public function addOptions($options)
201 {
202 return $this->request->addOptions($options);
203 }
204
205 public function __call($method, $params)
206 {
207 if ($this->namespace) {
208 $method = $this->namespace .'.'. $method;
209 }
210 $this->request->setRawPostData(xmlrpc_encode_request($method, $params));
211 $response = $this->request->send();
212 if ($response->getResponseCode() != 200) {
213 throw new Exception($response->getBody(), $response->getResponseCode());
214 }
215 return xmlrpc_decode($response->getBody(), 'utf-8');
216 }
217
218 public function getHistory()
219 {
220 return $this->request->getHistory();
221 }
222 }
223
224 ?>
225
226 - Simple Feed Aggregator
227
228 <?php
229 class FeedAggregator
230 {
231 public $directory;
232 protected $feeds = array();
233
234 public function __construct($directory = 'feeds')
235 {
236 $this->setDirectory($directory);
237 }
238
239 public function setDirectory($directory)
240 {
241 $this->directory = $directory;
242 foreach (glob($this->directory .'/*.xml') as $feed) {
243 $this->feeds[basename($feed, '.xml')] = filemtime($feed);
244 }
245 }
246
247 public function url2name($url)
248 {
249 return preg_replace('/[^\w\.-]+/', '_', $url);
250 }
251
252 public function hasFeed($url)
253 {
254 return isset($this->feeds[$this->url2name($url)]);
255 }
256
257 public function addFeed($url)
258 {
259 $r = $this->setupRequest($url);
260 $r->send();
261 $this->handleResponse($r);
262 }
263
264 public function addFeeds($urls)
265 {
266 $pool = new HttpRequestPool;
267 foreach ($urls as $url) {
268 $pool->attach($r = $this->setupRequest($url));
269 }
270 $pool->send();
271
272 foreach ($pool as $request) {
273 $this->handleResponse($request);
274 }
275 }
276
277 public function getFeed($url)
278 {
279 $this->addFeed($url);
280 return $this->loadFeed($this->url2name($url));
281 }
282
283 public function getFeeds($urls)
284 {
285 $feeds = array();
286 $this->addFeeds($urls);
287 foreach ($urls as $url) {
288 $feeds[] = $this->loadFeed($this->url2name($url));
289 }
290 return $feeds;
291 }
292
293 protected function saveFeed($file, $contents)
294 {
295 if (file_put_contents($this->directory .'/'. $file .'.xml', $contents)) {
296 $this->feeds[$file] = time();
297 } else {
298 throw new Exception("Could not save feed contents to $file.xml");
299 }
300 }
301
302 protected function loadFeed($file)
303 {
304 if (isset($this->feeds[$file])) {
305 if ($data = file_get_contents($this->directory .'/'. $file .'.xml')) {
306 return $data;
307 } else {
308 throw new Exception("Could not load feed contents from $file.xml");
309 }
310 } else {
311 throw new Exception("Unknown feed/file $file.xml");
312 }
313 }
314
315 protected function setupRequest($url)
316 {
317 $r = new HttpRequest($url);
318 $r->setOptions(array('redirect' => true));
319
320 $file = $this->url2name($url);
321
322 if (isset($this->feeds[$file])) {
323 $r->setOptions(array('lastmodified' => $this->feeds[$file]));
324 }
325
326 return $r;
327 }
328
329 protected function handleResponse(HttpRequest $r)
330 {
331 if ($r->getResponseCode() != 304) {
332 if ($r->getResponseCode() != 200) {
333 throw new Exception("Unexpected response code ". $r->getResponseCode());
334 }
335 if (!strlen($body = $r->getResponseBody())) {
336 throw new Exception("Received empty feed from ". $r->getUrl());
337 }
338 $this->saveFeed($this->url2name($r->getUrl()), $body);
339 }
340 }
341 }
342 ?>
343
344 - Download a big file
345
346 <?php
347
348 /*
349 $bigGet = BigGet::url('http://www.example.com/big_file.bin');
350 $bigGet->saveTo('file.bin');
351 */
352
353 class BigGetRequest extends HttpRequest
354 {
355 public $id;
356 }
357
358 class BigGet extends HttpRequestPool
359 {
360 const SIZE = 1048576;
361
362 private $url;
363 private $size;
364 private $count = 0;
365 private $files = array();
366
367 static function url($url)
368 {
369 $head = new HttpRequest($url, HttpRequest::METH_HEAD);
370 $headers = $head->send()->getHeaders();
371 $head = null;
372
373 if (!isset($headers['Accept-Ranges'])) {
374 throw new HttpException("Did not receive an Accept-Ranges header from HEAD $url");
375 }
376 if (!isset($headers['Content-Length'])) {
377 throw new HttpException("Did not receive a Content-Length header from HEAD $url");
378 }
379
380 $bigget = new BigGet;
381 $bigget->url = $url;
382 $bigget->size = $headers['Content-Length'];
383 return $bigget;
384 }
385
386 function saveTo($file)
387 {
388 $this->send();
389 if ($w = fopen($file, 'wb')) {
390 echo "\nCopying temp files to $file ...\n";
391 foreach (glob("bigget_????.tmp") as $tmp) {
392 echo "\t$tmp\n";
393 if ($r = fopen($tmp, 'rb')) {
394 stream_copy_to_stream($r, $w);
395 fclose($r);
396 }
397 unlink($tmp);
398 }
399 fclose($w);
400 echo "\nDone.\n";
401 }
402 }
403
404 function send()
405 {
406 // use max 3 simultanous requests with a req size of 1MiB
407 while ($this->count < 3 && -1 != $offset = $this->getRangeOffset()) {
408 $this->attachNew($offset);
409 }
410
411 while ($this->socketPerform()) {
412 if (!$this->socketSelect()) {
413 throw new HttpSocketException;
414 }
415 }
416 }
417
418 private function attachNew($offset)
419 {
420 $stop = min($this->count * self::SIZE + self::SIZE, $this->size) - 1;
421
422 echo "Attaching new request to get range: $offset-$stop\n";
423
424 $req = new BigGetRequest(
425 $this->url,
426 HttpRequest::METH_GET,
427 array(
428 'headers' => array(
429 'Range' => "bytes=$offset-$stop"
430 )
431 )
432 );
433 $this->attach($req);
434 $req->id = $this->count++;
435 }
436
437 private function getRangeOffset()
438 {
439 return ($this->size >= $start = $this->count * self::SIZE) ? $start : -1;
440 }
441
442 protected function socketPerform()
443 {
444 try {
445 $rc = parent::socketPerform();
446 } catch (HttpRequestPoolException $x) {
447 foreach ($x->exceptionStack as $e) {
448 echo $e->getMessage(), "\n";
449 }
450 }
451
452 foreach ($this->getFinishedRequests() as $r) {
453 $this->detach($r);
454
455 if (206 != $rc = $r->getResponseCode()) {
456 throw new HttpException("Unexpected response code: $rc");
457 }
458
459 file_put_contents(sprintf("bigget_%04d.tmp", $r->id), $r->getResponseBody());
460
461 if (-1 != $offset = $this->getRangeOffset()) {
462 $this->attachNew($offset);
463 }
464 }
465
466 return $rc;
467 }
468 }
469 ?>
470