Commit 895173ba authored by Leigh B Stoller's avatar Leigh B Stoller

Add support the HTTP_RANGE header so that wget/curl can restart

an interrupted download instead of starting from the beginning.
parent b7a2e967
#!/usr/bin/perl -wT #!/usr/bin/perl -w
# #
# Copyright (c) 2000-2016, 2019 University of Utah and the Flux Group. # Copyright (c) 2000-2016, 2019 University of Utah and the Flux Group.
# #
...@@ -38,11 +38,13 @@ sub usage() ...@@ -38,11 +38,13 @@ sub usage()
"Spew an image file to a (widearea) node.\n"; "Spew an image file to a (widearea) node.\n";
exit(-1); exit(-1);
} }
my $optlist = "t:k:hsde"; my $optlist = "t:k:hsder:";
my $debug = 0; my $debug = 0;
my $headonly = 0; my $headonly = 0;
my $sigfile = 0; my $sigfile = 0;
my $delta = 0; my $delta = 0;
my $rangestart;
my $rangeend;
my $access_key; my $access_key;
my $timestamp; # GM Time. my $timestamp; # GM Time.
...@@ -76,6 +78,7 @@ use OSImage; ...@@ -76,6 +78,7 @@ use OSImage;
# Locals # Locals
my $bytelen = 0; my $bytelen = 0;
my $foffset = 0;
# Protos # Protos
sub SpewImage(); sub SpewImage();
...@@ -129,6 +132,20 @@ if (defined($options{"t"})) { ...@@ -129,6 +132,20 @@ if (defined($options{"t"})) {
die("*** Bad data in timestamp: $timestamp\n"); die("*** Bad data in timestamp: $timestamp\n");
} }
} }
if (defined($options{"r"})) {
my $range = $options{"r"};
if ($range =~ /^(\d+)(\-)?$/) {
$rangestart = $1;
}
elsif ($range =~ /^(\d+)\-(\d+)$/) {
$rangestart = $1;
$rangeend = $2;
}
else {
die("*** Bad data in range: $range\n");
}
}
if (@ARGV != 1 || !defined($access_key)) { if (@ARGV != 1 || !defined($access_key)) {
usage(); usage();
} }
...@@ -163,7 +180,6 @@ sub SpewImage() ...@@ -163,7 +180,6 @@ sub SpewImage()
# #
# Deal with NFS read failures # Deal with NFS read failures
# #
my $foffset = 0;
my $retries = 5; my $retries = 5;
my $buf; my $buf;
...@@ -248,16 +264,9 @@ sub VerifyImage() ...@@ -248,16 +264,9 @@ sub VerifyImage()
# #
# Stat the file to get the bytelen for spewing. # Stat the file to get the bytelen for spewing.
# #
my (undef,undef,undef,undef,undef,undef,undef,$length, my (undef,undef,undef,undef,undef,undef,undef,$filelength,
undef,$mtime) = stat($file); undef,$mtime) = stat($file);
if ($headonly) {
print "Content-Length: $length\n";
print "Last-Modified: " .
strftime("%a, %d %b %Y %H:%M:%S GMT", gmtime($mtime)) . "\n";
exit(0);
}
# #
# Check timestamp if supplied. Remember, we get GM timestamps, so # Check timestamp if supplied. Remember, we get GM timestamps, so
# must convert the local stamp. # must convert the local stamp.
...@@ -267,7 +276,35 @@ sub VerifyImage() ...@@ -267,7 +276,35 @@ sub VerifyImage()
return 2 return 2
if ($timestamp >= $mtime); if ($timestamp >= $mtime);
} }
my $length = $filelength;
if (defined($rangestart)) {
if ($rangeend) {
$length = $rangeend - $rangestart;
$length += 1;
}
else {
$length = $filelength - $rangestart;
$rangeend = $filelength - 1;
}
print "Content-Range: bytes ${rangestart}-${rangeend}/$filelength\n";
}
else {
print "Accept-Ranges: bytes\n";
}
$bytelen = $length; $bytelen = $length;
$foffset = ($rangestart ? $rangestart : 0);
print "Content-Length: $length\n";
print "Last-Modified: " .
strftime("%a, %d %b %Y %H:%M:%S GMT", gmtime($mtime)) . "\n";
exit(0)
if ($headonly);
# End of headers.
print "\n";
return 0; return 0;
} }
......
...@@ -91,6 +91,7 @@ $pid = $group->pid(); ...@@ -91,6 +91,7 @@ $pid = $group->pid();
$unix_gid = $group->unix_gid(); $unix_gid = $group->unix_gid();
$project = $image->Project(); $project = $image->Project();
$unix_pid = $project->unix_gid(); $unix_pid = $project->unix_gid();
$rangearg = "";
if ($image->noexport()) { if ($image->noexport()) {
SPITERROR(403, "This image is marked as export restricted"); SPITERROR(403, "This image is marked as export restricted");
...@@ -100,7 +101,25 @@ if (!$image->isglobal()) { ...@@ -100,7 +101,25 @@ if (!$image->isglobal()) {
} }
# #
# We want to support HEAD requests to avoid send the file. # Check for RANGE header. We support a singlw range, there is no
# reason for the client to ask for multiple ranges for an image.
#
if (isset($_SERVER['HTTP_RANGE'])) {
// Delimiters are case insensitive
if (preg_match('/bytes=(\d*)\-$/i', $_SERVER['HTTP_RANGE'], $matches) ||
preg_match('/bytes=(\d*)\-(\d*)$/i', $_SERVER['HTTP_RANGE'], $matches)){
$rangearg = "-r " . $matches[1] . "-";
if (count($matches) == 3) {
$rangearg .= $matches[2];
}
}
else {
SPITERROR(416, "Client requested invalid Range.");
}
}
#
# We want to support HEAD requests to avoid sending the file.
# #
$ishead = 0; $ishead = 0;
$headarg = ""; $headarg = "";
...@@ -110,7 +129,8 @@ if ($_SERVER['REQUEST_METHOD'] == "HEAD") { ...@@ -110,7 +129,8 @@ if ($_SERVER['REQUEST_METHOD'] == "HEAD") {
} }
if ($fp = popen("$TBSUEXEC_PATH nobody $unix_pid,$unix_gid ". if ($fp = popen("$TBSUEXEC_PATH nobody $unix_pid,$unix_gid ".
"webspewimage $arg $headarg -k $access_key $versid", "r")) { "webspewimage $arg $headarg $rangearg -k $access_key $versid",
"r")) {
header("Content-Type: application/octet-stream"); header("Content-Type: application/octet-stream");
header("Cache-Control: no-cache, must-revalidate"); header("Cache-Control: no-cache, must-revalidate");
header("Pragma: no-cache"); header("Pragma: no-cache");
...@@ -135,13 +155,29 @@ if ($fp = popen("$TBSUEXEC_PATH nobody $unix_pid,$unix_gid ". ...@@ -135,13 +155,29 @@ if ($fp = popen("$TBSUEXEC_PATH nobody $unix_pid,$unix_gid ".
# The first read will come back with no output, which means nothing is # The first read will come back with no output, which means nothing is
# going to be sent except the headers. # going to be sent except the headers.
# #
$string = fread($fp, 1024); $string = fgets($fp);
if ($string) { if ($string) {
print($string); # We know the first line is a header.
while (!feof($fp) && connection_status() == 0) { $string = rtrim($string);
print(fread($fp, 1024*32)); header($string);
flush();
} # Look for end of headers.
$found_headers = false;
while (!$found_headers) {
$string = fgets($fp);
if ($string == "\n") {
$found_headers = true;
}
else {
$string = rtrim($string);
header($string);
}
}
while (!feof($fp) && connection_status() == 0) {
print(fread($fp, 1024*32));
flush();
}
} }
} }
$retval = pclose($fp); $retval = pclose($fp);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment