CHAPTER 9 – FILES AND STREAMS – URL Streams
The last category of streams is URL streams. URL streams have a path that resemble a URL, such as http://example.com/index.php or ftp://user:pass- word@ftp.example.com. In fact, all special wrappers use a URL-like path, such as compress.zlib://file.gz. However, only schemes that resemble a remote resource, such as a file on an FTP server or a document on a gopher server, fall into the category URL streams. The basic URL streams that PHP supports are http://. For files located on an HTTP server https://. For files located on an SSL enhanced HTTP server ftp://. For files on an FTP server ftps://. For files on an FTP server with SSL support SSL support for HTTP and FTP is only available if you added OpenSSL by specifying --with-openssl when you configured PHP. For authentication to HTTP or FTP servers, you can prefix the hostname in the URL with user- name:password@, as in the following: $fp = fopen ('ftp://derick:secret@ftp.php.net', 'wb'); The HTTP handler only supports the reading of files, so you need to spec- ify the mode rb. (Strictly, the b is only needed on Windows, but it doesn't hurt to add it.) The FTP handler supports opening a stream only in either read or write mode, but not in both simultaneously. Also, if you try to open an existing file for writing, the connection fails, unless you set the 'overwrite' context option (see Figure 9.2): <?php $context = stream_context_create( array('ftp' => array('overwrite' => true)); $fp = fopen('ftp://secret@ftp.php.net', 'wb', false, $context); ?>
Fig. 9.2 phpsuck in action. The following example demonstrates reading a file from an HTTP server and saving it into a compressed file. This example also introduces a fourth parameter to the fopen() call that specifies a context for the stream. By using the context parameter, you can set special options for a stream. For example, you can set a notifier. This notifier callback will be called on different events during the transaction: #!/usr/local/bin/php <?php /* Check for arguments */ if ($argc < 2) { echo "Usage:nphpsuck.php url [max kb/sec]nn"; exit(-1); } /* Url to fetch */ $url = $argv[1]; /* Bandwidth limiting */ if ($argc == 3) { $max_kb_sec = $argv[2]; } else { $max_kb_sec = 1000; } /* Cursor to column 1 for xterms */ $term_sol = "x1b[1G"; $severity_map = array ( 0 => 'info ', 1 => 'warning', 2 => 'error ' ); /* Callback function for stream events */ function notifier($code, $severity, $msg, $xcode, $sofar, $max) { global $term_sol, $severity_map, $max_kb_sec, $size; /* Do not print status message prefix when the PROGRESS * event is received. */ if ($code != STREAM_NOTIFY_PROGRESS) { echo $severity_map[$severity]. ": "; } switch ($code) { case STREAM_NOTIFY_CONNECT: printf("Connectedn"); /* Set begin time for kb/sec calculation */ $GLOBALS['begin_time'] = time() - 0.001; break; case STREAM_NOTIFY_AUTH_REQUIRED: printf("Authentication required: %sn", trim($msg)); break; case STREAM_NOTIFY_AUTH_RESULT: printf("Logged in: %sn", trim($msg)); break; case STREAM_NOTIFY_MIME_TYPE_IS: printf("Mime type: %sn", $msg); break; case STREAM_NOTIFY_FILE_SIZE_IS: printf("Downloading %d kbn", $max / 1024); /* Set the global size variable */ $size = $max; break; case STREAM_NOTIFY_REDIRECTED: printf("Redirecting to %s...n", $msg); break; case STREAM_NOTIFY_PROGRESS: /* Calculate the number of stars and stripes */ if ($size) { $stars = str_repeat ('*', $c = $sofar * 50 / $size); } else { $stars = ''; } $stripe = str_repeat ('-', 50 - strlen($stars)); /* Calculate download speed in kb/sec */ $kb_sec = ($sofar / (time() - $GLOBALS['begin_time'])) / 1024; /* Pause the script if we are above the maximum suck * speed */ while ($kb_sec > $max_kb_sec) { usleep(1); $kb_sec = ($sofar / (time() - $GLOBALS['begin_time'])) / 1024; } /* Display the progress bar */ printf("{$term_sol}[%s] %d kb %.1f kb/sec", $stars.$stripe, $sofar / 1024, $kb_sec); break; case STREAM_NOTIFY_FAILURE: printf("Failure: %sn", $msg); break; } } /* Determine filename to save too */ $url_data = parse_url($argv[1]); $file = basename($url_data['path']); if (empty($file)) { $file = "index.html"; } printf ("Saving to $file.gzn"); $fil = "compress.zlib://$file.gz"; /* Create context and set the notifier callback */ $context = stream_context_create(); stream_context_set_params($context, array ("notification" => "notifier")); /* Open the target URL */ $fp = fopen($url, "rb", false, $context); if (is_resource($fp)) { /* Open the local file */ $fs = fopen($fil, "wb9", false, $context); if (is_resource($fs)) { /* Read data from URL in blocks of 1024 bytes */ while (!feof($fp)) { $data = fgets($fp, 1024); fwrite($fs, $data); } /* Close local file */ fclose($fs); } /* Close remote file */ fclose($fp); /* Display download information */ printf("{$term_sol}[%s] Download time: %dsn", str_repeat('*', 50), time() - $GLOBALS['begin_time']); } ?> Some events can be handled in the notify callback function. Although most are only useful for debug purposes (NOTIFY_CONNECT, NOTIFY_AUTH_REQUIRED, NOTIFY_AUTH_REQUEST), others can be used to perform some neat tricks, like the bandwidth limiting we do in the previous example. The following is a full list of all the different events. STREAM_NOTIFY_CONNECT This event is fired when a connection with the resource has been established-- for example, when the script connected to a HTTP server. STREAM_NOTIFY_AUTH_REQUIRED When a request for authorization is complete, this event is triggered by the stream's API. STREAM_NOTIFY_AUTH_RESULT As soon as the authentication has finished, this event is triggered to tell you if there was a successful authentication or a failure. STREAM_NOTIFY_MIME_TYPE_IS The HTTP stream wrapper (http:// and https://) fires this event when the Content-Type header is available in the response to the HTTP request. STREAM_NOTIFY_FILE_SIZE_IS This event is triggered when the FTP wrapper figures out the size of the file, or when an HTTP wrapper sees the Content-Length header. STREAM_NOTIFY_REDIRECTED This event is triggered by the HTTP wrapper when it encounters a redi- rect request (Location: header). STREAM_NOTIFY_PROGRESS This is one of the fancier events; it is used extensively in our example. It's sent as soon as a packet of data has arrived. In our example, we used this event to perform bandwidth limiting and display the progress bar. STREAM_NOTIFY_FAILURE When a failure occurs, such as the login credentials were wrong, the wrapper triggers this event.