Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
169 views
in Technique[技术] by (71.8m points)

Downloading a file with Powershell from IIS Express Server with PHP randomly stops

Situation:

We have a small application where you can choose something like "I want Version X.X.X, Nightly, Branch: XYZ" and the Application will fetch the latest package according to your specifications from the build server.

Problem:

The download of the package works in 95 out of 100 cases, but in 5% of all cases the download just stops after a random amount of data is downloaded. When you retry the same thing, it might work or sometimes it might stop at a different amount downloaded. As you might have guessed, this is not a very acceptable rate of success.

The powershell script randomly stops at $response_stream.Read($buffer, 0, $buffer.length) (see script below) after a random amount of data was read. The download speed does not get smaller and smaller and then stops. It just makes a full stop as if the server is not sending anything anymore, and after a few minutes we get a timout error ("Exception when calling "Read" with 3 arguments: The timeout for the process has been exceeded").

I have tried a lot of things, but I'm out of ideas (basically tried to up the timeout on the IIS, tried different approaches wih the powershell download, googled the problem, but all without success). This also happens at random times, at night (very few users) but also during the day (lots of users). Does anybody have an idea how or what could be tried to make the download more robust? My guess is that somethings wrong with the PHP Script/IIS, but sadly my knowledge with both ist very limited...

Additional Information:

The download on the client side is done via Powershell. I wrote a neat little function which takes care of that:

function Get-FileFromURL
{
    param(
        [Parameter(Mandatory, Position = 0)] [System.Uri]$URL,
        [Parameter(Mandatory, Position = 1)] [string]$OutputFile,
        [Parameter(Mandatory, Position = 2)] [System.Management.Automation.PsCredential]$Credentials,
        [Parameter(Position = 3)]            [string]$Filename
    )

    try
    {
        $request = [System.Net.HttpWebRequest]::Create($URL)
        $request.Credentials = $Credentials;
        $request.set_Timeout(5000) # 5 second timeout
        $response = $request.GetResponse()
        $total_bytes = $response.ContentLength
        $response_stream = $response.GetResponseStream()
        $header = $response.Headers.Get("content-disposition");
        
        if($Filename -eq "")
        {
            $Filename = ($header -split "=")[1].Trim("`"");
            $OutputFile = $OutputFile + "" + $Filename; 
        }

        try 
        {
            # 256KB works better on my machine for 1GB and 10GB files
            # See https://www.microsoft.com/en-us/research/wp-content/uploads/2004/12/tr-2004-136.pdf
            # Cf. https://stackoverflow.com/a/3034155/10504393
            $buffer = New-Object -TypeName byte[] -ArgumentList 256KB
            $target_stream = [System.IO.File]::Create($OutputFile)
            $stopwatch =  [system.diagnostics.stopwatch]::StartNew();
            $refreshTimerInMilliseconds = 1000;
            $refreshTimer = $refreshTimerInMilliseconds / 1000;

            do
            {
                $count = $response_stream.Read($buffer, 0, $buffer.length)
                $target_stream.Write($buffer, 0, $count)
                $downloaded_bytes = $downloaded_bytes + $count

                if ($stopwatch.ElapsedMilliseconds -ge $refreshTimerInMilliseconds)
                {
                    $percent = $downloaded_bytes / $total_bytes;
                    $speed = ($downloaded_bytes - $prev_downloaded_bytes) / 1KB / $refreshTimer;
                    $status = @{
                        completed  = "{0,6:p2} Completed" -f $percent
                        downloaded = "{0:n0} MB of {1:n0} MB" -f ($downloaded_bytes / 1MB), ($total_bytes / 1MB)
                        speed      = "{0,7:n0} KB/s" -f ($speed)
                        eta        = "Time Remaining: {0:hh:mm:ss}" -f (New-TimeSpan -Seconds (($total_bytes - $downloaded_bytes) / 1KB / $speed))
                    }
                    $progress_args = @{
                        Activity        = "Downloading '$Filename'"
                        Status          = "$($status.completed) ($($status.downloaded)), $($status.speed), $($status.eta)"
                        PercentComplete = $percent * 100
                    }
                    
                    Write-Progress @progress_args

                    $prev_downloaded_bytes = $downloaded_bytes
                    $stopwatch.Restart();
                }
            } while ($count -gt 0)

            $stopwatch.Stop();
        }
        finally 
        {
            if ($target_stream) { $target_stream.Dispose() }
            # If file exists and $count is not zero or $null, then script was interrupted by user
            if ((Test-Path $Filename) -and $count) { Remove-Item -Path $Filename }
        }
    }
    finally 
    {
        if ($response)        { $response.Dispose() }
        if ($response_stream) { $response_stream.Dispose() }
    }
}

On the server side we have an IIS Express with a PHP Script that is feeding the file.

if (is_file($file_path))
{
    $file_size  = filesize($file_path);
    $file = @fopen($file_path,"rb");
    
    if ($file)
    {
        // set the headers, prevent caching
        header("Pragma: public");
        header("Expires: -1");
        header("Cache-Control: public, must-revalidate, post-check=0, pre-check=0");
        header("Content-Disposition: attachment; filename="$file_name"");
        
        // set appropriate headers for attachment or streamed file
        $is_attachment = isset($_REQUEST['stream']) ? false : true;
        if ($is_attachment)
            header("Content-Disposition: attachment; filename="$file_name"");
        else
            header('Content-Disposition: inline;');
        
        // set the mime type based on extension, add yours if needed.
        $ctype_default = "application/octet-stream";
        $content_types = array(
            "exe" => "application/octet-stream",
            "zip" => "application/zip",
            "mp3" => "audio/mpeg",
            "mpg" => "video/mpeg",
            "avi" => "video/x-msvideo",
        );
        $ctype = isset($content_types[$file_ext]) ? $content_types[$file_ext] : $ctype_default;
        header("Content-Type: " . $ctype);
        
        //check if http_range is sent by browser (or download manager)
        if(isset($_SERVER['HTTP_RANGE']))
        {
            list($size_unit, $range_orig) = explode('=', $_SERVER['HTTP_RANGE'], 2);
            if ($size_unit == 'bytes')
            {
                //multiple ranges could be specified at the same time, but for simplicity only serve the first range
                //http://tools.ietf.org/id/draft-ietf-http-range-retrieval-00.txt
                list($range, $extra_ranges) = explode(',', $range_orig, 2);
            }
            else
            {
                $range = '';
                header('HTTP/1.1 416 Requested Range Not Satisfiable');
                exit;
            }
        }
        else
        {
            $range = '';
        }
        
        //figure out download piece from range (if set)
        list($seek_start, $seek_end) = explode('-', $range, 2);
        
        //set start and end based on range (if set), else set defaults
        //also check for invalid ranges.
        $seek_end   = (empty($seek_end)) ? ($file_size - 1) : min(abs(intval($seek_end)),($file_size - 1));
        $seek_start = (empty($seek_start) || $seek_end < abs(intval($seek_start))) ? 0 : max(abs(intval($seek_start)),0);
        
        //Only send partial content header if downloading a piece of the file (IE workaround)
        if ($seek_start > 0 || $seek_end < ($file_size - 1))
        {
            header('HTTP/1.1 206 Partial Content');
            header('Content-Range: bytes '.$seek_start.'-'.$seek_end.'/'.$file_size);
            header('Content-Length: '.($seek_end - $seek_start + 1));
        }
        else
            header("Content-Length: $file_size");
        
        header('Accept-Ranges: bytes');
        
        set_time_limit(0);
        fseek($file, $seek_start);
        
        while(!feof($file))
        {
            print(@fread($file, 1024*8));
            ob_flush();
            flush();
            if (connection_status()!=0)
            {
                @fclose($file);
                exit;
            }
        }
    
        // file save was a success
        @fclose($file);
        exit;
    }
    else
    {
        // file couldn't be opened
        header("HTTP/1.0 500 Internal Server Error");
        exit;
    }
}
else
{
    // file does not exist
    header("HTTP/1.0 404 File not found");
    exit;
}
question from:https://stackoverflow.com/questions/66051233/downloading-a-file-with-powershell-from-iis-express-server-with-php-randomly-sto

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)
Waitting for answers

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...