Nov 6th

1

Resume HTTP downloads in PHP using cURL or fsockopen

4 years 8 months ago comment 1 Comment

This post describes a method to download a file using a PHP script and resume the download if the previous download was interrupted. The code here could be used while downloading a new version of a PHP application from the application author’s website. Two implementation examples are provided – one using cURL and another using fsockopen.

Range header of the HTTP protocol

First, lets start by seeing how the HTTP protocol allows downloads to be resumed. If you don’t already have PuTTY, get it from here. We will use PuTTY to directly connect to a HTTP server, send it some HTTP headers and see the response.

Create a text file with some text in it (at least 20 characters long to make it easier for us to test). Upload it so it is accessible at http://localhost/file.txt . I have used the URL http://localhost/file.txt . Change the settings below according to your URL. Open PuTTY and use the following settings:

Host Name: localhost
Port: 80
Connection Type: Raw
Close window on exit: Never

Click on Open. In the window that shows up, type the following:

GET /file.txt HTTP/1.1
Host: localhost
Range: bytes=5-
Connection: Close

Make sure you hit enter twice after typing the text above. You should see something like the text below if you had used something like “abcdefghijklmnopqrstuvwxyz” as the contents of file.txt:

HTTP/1.1 206 Partial Content
Date: Fri, 06 Nov 2009 16:25:07 GMT
Server: Apache/2.2.12 (Win32) mod_ssl/2.2.12 OpenSSL/0.9.8k
Last-Modified: Fri, 06 Nov 2009 15:56:25 GMT
ETag: "4000000044a82-1a-477b5e2d8093c"
Accept-Ranges: bytes
Content-Length: 21
Content-Range: bytes 5-25/26
Connection: close
Content-Type: text/plain

fghijklmnopqrstuvwxyz

You can see that it has returned a few headers followed by the contents of the file starting from the 6th byte. Try it a few more times modifying the Range header (for example, 5-15 to get bytes 6 to 16).

Using cURL to resume downloads

Resuming a download using cURL is quite straight forward. We use the CURLOPT_RANGE option to set the range if the file we are trying to download already exists and use the “a” option to fopen to append the contents to the file. The code is below:

$url="http://localhost/file.txt";
$fileName = "file.txt";

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);

if (file_exists($fileName)) {
	$from = filesize($fileName);
	curl_setopt($ch, CURLOPT_RANGE, $from . "-");
}

$fp = fopen($fileName, "a");
if (!$fp) {
	exit;
}
curl_setopt($ch, CURLOPT_FILE, $fp);
$result = curl_exec($ch);
curl_close($ch);

fclose($fp);

Using fsockopen to resume downloads

Resuming a download with fsockopen is more involved as it requires us to send the request headers and then parse the response headers:

$url="http://localhost/file.txt";
$fileName = "file.txt";

$partialContent = false;
$finalFileSize = 0;

$urlParts = parse_url($url);
$socketHandler = fsockopen($urlParts["host"], 80, $errno, $errstr, 30);
if (!$socketHandler) {
	exit;
} else {
	$from = 0;

	if (file_exists($fileName)) {
		$from = filesize($fileName);
	}

	$out = "GET " . $urlParts["path"] . " HTTP/1.1rn";
	$out .= "Host: " . $urlParts["host"] . "rn";
	$out .= "Range: bytes=" . $from . "-rn";
	$out .= "Connection: Closernrn";

	$headerFound = false;

	if (!$fileHandler = fopen($fileName, "a")) {
		exit;
	}

	fwrite($socketHandler, $out);
	while (!feof($socketHandler)) {
		if ($headerFound) {
			if ($partialContent) {
				$result = fread($socketHandler, 8192);

				if (fwrite($fileHandler, $result) === false) {
					exit;
				}
			} else {
				fclose($fileHandler);
				fclose($socketHandler);
				exit;
			}
		} else {
			$result = fgets($socketHandler, 8192);
			$result = trim($result);
			if ($result === "") {
				$headerFound = true;
			}

			if (strstr($result, "206 Partial Content")) {
				$partialContent = true;
			}

			if (preg_match("/^Content-Range: bytes (d+)-(d+)/(d+)$/", $result, $matches)) {
				$finalFileSize = intval($matches[3]);
			}
		}
	}
	fclose($fileHandler);
	fclose($socketHandler);

	clearstatcache();

	if (filesize($fileName) == $finalFileSize) {
		// success
	} else {
		exit;
	}
}

Related posts:

  1. cURL wrapper class with executable and PHP extension support
  2. Domain name WHOIS query using PHP
  3. Javascript and CSS compression using YUI compressor, PHP and gzip

One Response to “Resume HTTP downloads in PHP using cURL or fsockopen”

  1. Alex says:

    Hi!
    You have typo here (and some other lines actually too):
    $out = “GET ” . $urlParts["path"] . ” HTTP/1.1rn”;

    There should be \r\n at the end:
    $out = “GET ” . $urlParts["path"] . ” HTTP/1.1\r\n”;

    Thanks for article, it was helpful for me.

Leave a Reply