HTTP Streams
Skrifað af bjori þann 18. mars 2009 - 12:46
1 athugasemd skrifuð - toppur / botn
Fjöldi lestra: 545
PHP built-in HTTP stream wrapperinn hendir villuskilaboðum og returnar false á alla status kóða en 200 OK - og syður ekki HTTP/1.1 chunked transfer encoding

Þessi classi er drop-in replacement fyrir built-in HTTP/HTTPS wrapperinn.

Usage:

<?php
include "restwrapper.class.php";
stream_wrapper_unregister("http");
stream_wrapper_register("http", "restWrapper");
?>
  1 |
  2 |
  3 |
  4 |
  5 |
  6 |
  7 |
  8 |
  9 |
 10 |
 11 |
 12 |
 13 |
 14 |
 15 |
 16 |
 17 |
 18 |
 19 |
 20 |
 21 |
 22 |
 23 |
 24 |
 25 |
 26 |
 27 |
 28 |
 29 |
 30 |
 31 |
 32 |
 33 |
 34 |
 35 |
 36 |
 37 |
 38 |
 39 |
 40 |
 41 |
 42 |
 43 |
 44 |
 45 |
 46 |
 47 |
 48 |
 49 |
 50 |
 51 |
 52 |
 53 |
 54 |
 55 |
 56 |
 57 |
 58 |
 59 |
 60 |
 61 |
 62 |
 63 |
 64 |
 65 |
 66 |
 67 |
 68 |
 69 |
 70 |
 71 |
 72 |
 73 |
 74 |
 75 |
 76 |
 77 |
 78 |
 79 |
 80 |
 81 |
 82 |
 83 |
 84 |
 85 |
 86 |
 87 |
 88 |
 89 |
 90 |
 91 |
 92 |
 93 |
 94 |
 95 |
 96 |
 97 |
 98 |
 99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
<?php

/**
 * restWrapper - HTTP stream wrapper, "drop in replacement"
 *
 * All methods prefixed with stream_ are PHP magic stream methods.
 *
 * Returns the response content no matter the HTTP response status.
 * Supports HTTP/1.1 chunked transfer encodings.
 *
 * Planned features:
 *  - HTTP 30x Redirects
 *  - Write support (i.e. POST/PUT files)
 * 
 * @author  Hannes Magnusson <bjori@redpill-linpro.com>
 */
class restWrapper {

  
// Identical to $http_response_header
  
public static $lastHeaders = array();

  
// Must be public so PHP can set it..
  
public $context;

  private 
$default_options   = array();
  private 
$eof               false;
  private 
$contentlen        0;

  
// The stream resource
  
private $fp                null;

  
// HTTP/1.1 chunked
  
private $chunked           false;
  private 
$buffer            "";
  private 
$buflen            0;

  public function 
__construct()
  {
    
$this->default_options = array(
      
"method"           => "GET",
      
"header"           => ""// TODO: Use ini_get();
      
"user_agent"       => "RPL/Wrapper"// TODO: Use ini_get();
      
"content"          => "",
      
"proxy"            => "",
      
"request_fulluri"  => false,
      
"max_redirects"    => 20,
      
"protocol_version" => "1.1",
      
"timeout"          => 5,
      
"ignore_errors"    => false,
    );
  }
  private function 
resetProps()
  {
    
// Prevent cross stream contamination
    
$this->eof        false;
    
$this->contentlen 0;

    
$this->fp         null;

    
$this->chunked    false;
    
$this->buffer     "";
    
$this->bufflen    0;
  }

  public function 
parseHeaders(array $headers)
  {
    unset(
$headers[0]);
    foreach(
$headers as $line)
    {
      list(
$key$value) = explode(":"$line2);
      
$value trim($value);

      switch(
strtolower($key))
      {
        case 
"content-length":
          
$this->contentlen $value;
          break;

        case 
"transfer-encoding":
          
$this->chunked $value == "chunked";
          break;
      }
    }
  }
  protected function 
write($buf)
  {
    
$buf .= "\r\n";
    
//echo $buf;
    
$len fwrite($this->fp$buf);
    
$buflen strlen($buf);
    if (
$len != $buflen)
    {
      throw new 
UnexpectedValueException("Cannot write buffer, $len vs $buflen");
    }

    return 
true;
  }

  public function 
stream_open($path$mode$options$opened_path)
  {
    
$this->resetProps();

    
$parsed parse_url($path);
    if (!isset(
$parsed["path"]))
    {
      
$parsed["path"] = "/";
    }
    if (isset(
$parsed["query"]))
    {
      
$parsed["query"] = "?" $parsed["query"];
    }
    else
    {
      
$parsed["query"] = "";
    }
    if (!isset(
$parsed["port"]))
    {
      
$parsed["port"] = $parsed["scheme"] == "https" 443 80;
    }

    if (
strpbrk($mode"awx+"))
    {
      throw new 
UnexpectedValueException("I don't support writing at the moment");
    }

    
$options stream_context_get_options($this->context);

    if (isset(
$options["http"]))
    {
      
$options array_merge($this->default_options$options["http"]);
    }
    else
    {
      
$options $this->default_options;
    }

    
$socket sprintf("%s://%s:%d"$parsed["scheme"] == "https" "ssl" "tcp"$parsed["host"], $parsed["port"]);
    
$this->fp $fp stream_socket_client($socket$errno$errstr$options["timeout"], STREAM_CLIENT_CONNECT$this->context);

    if (!
is_resource($fp))
    {
      throw new 
UnexpectedValueException("Connection failed");
    }
    
$this->sendHeaders($options$parsed);
    
$headers $this->streamReadHeaders();
    
$this->parseHeaders($headers);
    return 
true;

  }
  protected function 
sendHeaders($ctx$url)
  {
    
$buf  sprintf("%s %s%s HTTP/%.1f\r\n"$ctx["method"], $url["path"], $url["query"], $ctx["protocol_version"]);
    
$buf .= sprintf("Host: %s\r\n"$url["host"]);

    
// Some webservers require Content-Lenght to be sent before
    // Content-Type - which could be part of the user specified header..
    // see PHP bug#44603
    
if ($ctx["content"])
    {
      if (
stripos($ctx["header"], "content-length") === false)
      {
        
$buf .= sprintf("Content-Length: %d\r\n"strlen($ctx["content"]));
      }
    }

    if (
$ctx["header"])
    {
      
$buf .= rtrim($ctx["header"]) . "\r\n";
    }

    
// If we have content we _MUST_ send Content-Type headers.. but they 
    // _SHOULD_ be sent by the user via the "header" key
    
if ($ctx["content"])
    {
      if (
stripos($buf"content-type") === false)
      {
        
$buf .= "Content-Type: application/x-www-form-urlencoded\r\n";
        
trigger_error("Content-type not specified assuming application/x-www-form-urlencoded"E_USER_NOTICE);
      }
    }

    if (isset(
$url["user"], $url["pass"]))
    {
      
$base base64_encode($url["user"] . ":" $url["pass"]);
      
$buf .= "Authorization: Basic $base\r\n";
    }

    
// TODO: Allow "persistent" connections
    
$buf .= sprintf("Connection: close\r\n");

    if (
$ctx["content"])
    {
      
$buf .= "\r\n" $ctx["content"];
    }

    return 
$this->write($buf);
  }
  public function 
stream_read($max)
  {
    
// For performance reasons we check right away
    
if ($this->chunked)
    {
      
//var_dump("Reading chunked");
      
return $this->streamReadChunked($max);
    }
    if (
$this->contentlen)
    {
      
//var_dump("Reading {$this->contentlen}, but only $max");
      
static $left null;

      if (
$left === null)
      {
        
$left $this->contentlen;
      }
      if (
$left === 0)
      {
        
// We've read all the content of this response. Reset $left for 
        // the next request so we get the new contentlen
        
$left null;
        return 
null;
      }

      
// If we are asked to read more then the actual content length we 
      // have to do minor magic
      
if ($max >= $left)
      {
        
$content fread($this->fp$left);
        
$tmplen strlen($content);
        
// If we managed to read everything left of the request
        
if ($tmplen >= $left)
        {
          
// We tell PHP that. NOTE: We cannot prepare for the next 
          // request right away as PHP will call us again to make sure 
          // we have no more data
          
$left 0;
          
$this->eof true;
        }
        else
        {
          
// If however the data on the stream was less then we 
          // requested then we'll have to go again later
          
$left -= $tmplen;
        }
        return 
$content;
      }

      
// We cannot be sure we actually read $max bytes, we have to check 
      // it explicitly
      
$content fread($this->fp$max);
      
$left -= strlen($content);
      return 
$content;
    }

    
// We have no idea how much data is on the stream, we'll just have to 
    // read and read until the stream closes, or PHP doesn't want more
    
$content "";
    do {
      
$content .= $line fread($this->fp$max);
      
$max -= strlen($line);

      if (
feof($this->fp))
      {
        
$this->eof true;
        break;
      }
    } while(
$max 1);

    return 
$content;
  }
  public function 
streamReadChunked($max)
  {
    
//echo "\n";
    //var_dump($max);
    
$content $this->buffer;
    
$contentlen $this->buflen;

    
// If we have enough data in the buffer already we should return it rather 
    // then keep increasing it
    
if ($contentlen $max)
    {
      
$this->buffer substr($content$max);
      
$this->buflen $contentlen $max;
      
$retval substr($content0$max);
      
//var_dump("Returning " . strlen($retval) . " I have left: " . strlen($this->buffer));
      
return $retval;
    }
    
$this->buffer "";
    
$this->buflen 0;

    do {
      
$chunksize fgets($this->fp);
      
$chunksize rtrim($chunksize);
      if (!
ctype_xdigit($chunksize))
      {
        return 
$content;
      }

      
$bufsize $chunksize hexdec($chunksize);
      
//var_dump($bufsize);
      
if ($bufsize === 0)
      {
        
$this->eof true;
        return 
$content;
      }

      do {
        
$content    .= $line fread($this->fp$chunksize);
        
$linelen     strlen($line);

        
$chunksize  -= $linelen;
        
$contentlen += $linelen;

        
$this->eof   $eof  feof($this->fp);
      } while(!
$eof && $chunksize 0);

      
// Read the newline marker
      
if (!$eof)
      {
        
fgets($this->fp);
      }
    } while(!
$eof && $contentlen $max);


    if (
$contentlen $max)
    {
      
$this->buffer substr($content$max);
      
$this->buflen $contentlen $max;
      
$retval substr($content0$max);
      
//var_dump("Returning " . strlen($retval) . " I have left: " . strlen($this->buffer));
      
return $retval;
    }
    return 
$content;
  }
  public function 
streamReadHeaders()
  {
    global 
$http_response_header// PHP built-in variable
    
$http_response_header = array();

    do {
      
$line fgets($this->fp);
      if (
$line === "\r\n")
      {
        break;
      }

      
// The new line marker is not supposed to be in the response array
      
$http_response_header[] = rtrim($line);
    } while(
true);

    
// Just so we don't have to do scope magic for $http_response_header
    
self::$lastHeaders $http_response_header;

    return 
$http_response_header;
  }
  public function 
stream_eof()
  {
    return !
$this->buffer && ($this->eof || feof($this->fp));
  }

  public function 
stream_flush()
  {
    
// TODO
    // Only relevant when writing, not reading
  
}
  public function 
stream_stat()
  {
    
// This will never be implemented as none of these values are relevant
    
return array();
  }
  public function 
stream_close()
  {
    return 
fclose($this->fp);
  }
}
?>



16. apríl 2010 - 18:23
#1 - bjori
http://bjori.blogspot.com
Þess má geta að frá og með PHP5.3 er stream context option til að "ignora errors" (sjá http://no.php.net/manual/en/context.http.php#context.http.ignore-errors).

Einnig styður PHP5.3 chunked encoding þegar protocol version context optioninn er settur sem 1.1 (sjá http://no.php.net/manual/en/context.http.php#context.http.protocol-version)




Nafn:


Netfang:


Veffang:


Hvað er tólf plús einn?



Skilaboð: