initial commit
[m6w6/m6w6.github.io] / _posts / 2005-12-06-peclhttp-nasty-bug-and-new-example.md
1 ---
2 title: PECL::HTTP - A nasty bug and a new example
3 author: m6w6
4 tags:
5 - PHP
6 ---
7
8 I just fixed a nasty bug which caused GZIP encoded files (speak tgz etc.) to
9 be decoded. While it'll just eat **some** memory on a 200 response (besides
10 that the body is not what one would expect), it'll eat all memory on 206
11 (partial content) responses because the part is fed through zlib. I'll just
12 need to revisit the HTTP RFC to check if checking for the "Vary" response
13 header is the best bet before I drop the new release.
14
15 Ok, that's it about the bad news -- I also added a new example to the tutorial
16 which shows how one could efficiently download big files. Using the example
17 code would look like:
18
19 ```php
20 $bigget = BigGet::url('http://example.com/big_file.bin');
21 $bigGet->saveTo('big_file.bin');
22
23 class BigGetRequest extends HttpRequest
24 {
25 public $id;
26 }
27
28 class BigGet extends HttpRequestPool
29 {
30 const SIZE = 1048576;
31
32 private $url;
33 private $size;
34 private $count = 0;
35 private $files = array();
36
37 static function url($url)
38 {
39 $head = new HttpRequest($url, HttpRequest::METH_HEAD);
40 $headers = $head->send()->getHeaders();
41 $head = null;
42
43 if (!isset($headers['Accept-Ranges'])) {
44 throw new HttpExcpetion("Did not receive an ".
45 "Accept-Ranges header from HEAD $url");
46 }
47 if (!isset($headers['Content-Length'])) {
48 throw new HttpException("Did not receive a ".
49 "Content-Length header from HEAD $url");
50 }
51
52 $bigget = new Bigget;
53 $bigget->url = $url;
54 $bigget->size = $headers['Content-Length'];
55 return $bigget;
56 }
57
58 function saveTo($file)
59 {
60 $this->send();
61 if ($w = fopen($file, 'wb')) {
62 echo "nCopying temp files to $file ...n";
63 foreach (glob("bigget_????.tmp") as $tmp) {
64 echo "t$tmpn";
65 if ($r = fopen($tmp, 'rb')) {
66 stream_copy_to_stream($r, $w);
67 fclose($r);
68 }
69 unlink($tmp);
70 }
71 fclose($w);
72 echo "nDone.n";
73 }
74 }
75
76 function send()
77 {
78 // use max 3 simultanous requests with a req size of 1MiB
79 while ($this->count < 3 &&
80 -1 != $offset = $this->getRangeOffset()) {
81 $this->attachNew($offset);
82 }
83
84 while ($this->socketPerform()) {
85 if (!$this->socketSelect()) {
86 throw new HttpSocketException;
87 }
88 }
89 }
90
91 private function attachNew($offset)
92 {
93 $stop = min($this->count * self::SIZE + self::SIZE,
94 $this->size) - 1;
95
96 echo "Attaching new request to get range: $offset-$stopn";
97
98 $req = new BigGetRequest(
99 $this->url,
100 HttpRequest::METH_GET,
101 array(
102 'headers' => array(
103 'Range' => "bytes=$offset-$stop"
104 )
105 )
106 );
107 $this->attach($req);
108 $req->id = $this->count++;
109 }
110
111 private function getRangeOffset()
112 {
113 return ($this->size >=
114 $start = $this->count * self::SIZE) ? $start : -1;
115 }
116
117 protected function socketPerform()
118 {
119 try {
120 $rc = parent::socketPerform();
121 } catch (HttpRequestPoolException $x) {
122 foreach ($x->exceptionStack as $e) {
123 echo $e->getMessage(), "n";
124 }
125 }
126
127 foreach ($this->getFinishedRequests() as $r) {
128 $this->detach($r);
129
130 if (206 != $rc = $r->getResponseCode()) {
131 throw new HttpException(
132 "Unexpected response code: $rc");
133 }
134
135 file_put_contents(
136 sprintf("bigget_%04d.tmp", $r->id),
137 $r->getResponseBody());
138
139 if (-1 != $offset = $this->getRangeOffset()) {
140 $this->attachNew($offset);
141 }
142 }
143
144 return $rc;
145 }
146 }
147 ```