File Download via API using Guzzle

39
May 12, 2022, at 03:50 AM

I'm still on the learning slopes with Guzzle, but am stumped with this issue...

I'm trying to retrieve an audio file from a 3rd party API using Guzzle. I've written a class with a number of functions and they all work apart from the very last stage of the 'download' function - the browser does not prompt to save the audio file

Call to get audio file, using AJAX

$callRows.on("click", ".download", function() {
    const $this = $(this);
    $this.parent().hide().next().show();
    $.ajax({
        type : "POST",
        url  : "submgmt",
        data : {
            task   : "getMedia",
            target : $this.data("id")
        },
        success  : function(data) {
            $this.parent().show().next().hide();
        },
        error : function (XMLHttpRequest, textStatus, errorThrown) {
            $dialog.attr("title", textStatus);
            $dialog.html("<br>" + errorThrown + "<br><br><h4>Click OK to continue</h4>");
            $dialog.dialog({
                buttons  : {
                    "OK" : function () {
                        $dialog.dialog("destroy");
                    }
                },
                dialogClass : "no-close",
                modal       : true,
                resizable   : false
            });
            $this.parent().show().next().hide();
        }
    });
});

FIRST TRY Function to call download function

$ucCall = new ucCalls($ucAuth);
try {
    $response = $ucCall->getMedia($target);
    if ($response['code']===404) {
        $result['ERROR'] = "1";
        $result['DETAILS'] = "File is no longer available";
        echo json_encode($result);
        exit();
    } else {
        header("Date: " . $response['headers']['Date'][0]);
        header("Cache-Control: " . $response['headers']['Cache-Control'][0]);
        header("Pragma: " . $response['headers']['Pragma'][0]);
        header("Expires: " . $response['headers']['Expires'][0]);
        header("Content-Type: " . $response['headers']['Content-Type'][0]);
        header('Content-Disposition: attachment; filename="' . basename($response['filename']) . '"');
        header("Content-Length: " . filesize($response['filename']));
        ob_clean();
        flush();
        readfile($response['filename']);
        exit;
    }
} catch (Exception $e) {
    $result['ERROR']   = "1";
    $result['DETAILS'] = $e->getCode() . " - " . $e->getMessage();
    echo json_encode($result);
    exit();
}

Class to retrieve audio files from 3rd party API

class ucCalls {
private Client $client;
public function __construct($authString)
{
    $this->client = new Client([
        'base_uri' => 'https://url-of-host',
        'timeout'  => 5,
        'headers' => [
            'Authorization' => 'Basic ' . $authString,
            'Accept' => 'application/json'
        ],
    ]);
}
/**
 * Retrieves the unencrypted MP3 media for a (completed) recording
 * @param string $id
 * @return array
 * @throws Exception
 */
public function getMedia(string $id): array
{
    try {
        $temp_file = sys_get_temp_dir() . "\uc" . make_id(6) . ".mp3";
        $resource = Utils::tryFopen($temp_file, 'w');
        $response = $this->client->request('GET','fw/path/to/audio/file/store/' . $id . '/Media', ['sink' => $resource]);
        return [
            'code'     => $response->getStatusCode(),
            'headers'  => $response->getHeaders(),
            'filename' => $temp_file
        ];
    } catch (ConnectException $e) {
        error_log(print_r("## EXCEPTION ## ucCalls (getMedia) - ConnectException -> " . $e->getMessage(),true));
        throw new Exception ("Connection Error",500);
    } catch (ServerException|ClientException $e) {
        error_log(print_r("## EXCEPTION ## ucCalls (getMedia) - ServerException|ClientException -> " . $e->getMessage(),true));
        throw new Exception ($e->getResponse()->getReasonPhrase(),$e->getResponse()->getStatusCode());
    } catch (GuzzleException $e) {
        error_log(print_r("## EXCEPTION ## ucCalls (getMedia) - GuzzleException -> " . $e->getMessage(),true));
        throw new Exception ("Unhandled Exception",400);
    }
}

}

SECOND TRY was to try using the Guzzle Stream

Function to call download function

$ucCall = new ucCalls($ucAuth);
try {
    
    $ucCall->getMedia($target);
} catch (Exception $e) {
    $result['ERROR']   = "1";
    $result['DETAILS'] = $e->getCode() . " - " . $e->getMessage();
    echo json_encode($result);
    exit();
}

Class

public function getMedia(string $id)
{
    try {
        $temp_file = sys_get_temp_dir() . "\uc" . make_id(6) . ".mp3";
        $resource = Utils::tryFopen($temp_file, 'w+');
        $stream = Utils::streamFor($resource);
        $this->client->request('GET','fw/path/to/audio/file/store/' . $id . '/Media', ['sink' => $stream]);
        header("Cache-Control: no-cache");
        header("Content-Type: audio/mp3");
        header('Content-Disposition: attachment; filename="' . basename($stream->getMetadata('uri')) . '"');
        header('Content-Length: ' . filesize($stream->getMetadata('uri')));
        echo $stream;
    } catch (ConnectException $e) {
        error_log(print_r("## EXCEPTION ## ucCalls (getMedia) - ConnectException -> " . $e->getMessage(),true));
        throw new Exception ("Connection Error",500);
    } catch (ServerException|ClientException $e) {
        error_log(print_r("## EXCEPTION ## ucCalls (getMedia) - ServerException|ClientException -> " . $e->getMessage(),true));
        throw new Exception ($e->getResponse()->getReasonPhrase(),$e->getResponse()->getStatusCode());
    } catch (GuzzleException $e) {
        error_log(print_r("## EXCEPTION ## ucCalls (getMedia) - GuzzleException -> " . $e->getMessage(),true));
        throw new Exception ("Unhandled Exception",400);
    }
}

Both these functions create the audio file in a temp store on my local web server, so the audio file retrieval from the 3rd party works. This is where I get stumped...

The file appears to transfer to the client, the header attributes are recognised and the correct amount of data is transfer (filesize) - but the client browser (tested in Edge and Firefox) is not prompting to save the file. Just getting an encoded string, as per the screenshots...

Any thoughts or guidance would be appreciated...

Answer 1

Once you get the encoded response you can check it whether it is base64 encoded or not or use base64_encode($content) provided by php.

$encoded_response = 'your encoded string';
$base64 = base64_encode($encoded_response);
$mime = "audio/mpeg";
$img = ('data:' . $mime . ';base64,' . $base64); // would be like data:audio/mpeg;base64,YxAAEaAIEeUAQAgBgNg...
<audio autoplay controls src="data:audio/mpeg;base64,YxAAEaAIEeUAQAgBgNg...">
  The “audio” tag is not supported by your browser.
</audio>
READ ALSO
How would i curl a hls using php

How would i curl a hls using php

I want to curl a HLS that returns forbidden in the browser with Php so in another words i want to return the contents of say

74
Complex query between 4 tables in Laravel

Complex query between 4 tables in Laravel

I have 4 tables : users , user_store_info , user_store_options , product_supplies

70
how to add data to json file flutter?

how to add data to json file flutter?

i have a datajson file like this in which i am getting a list of data i want to add another parameter{p_quantity} to this json file

54
Android MenuItem not always initialized

Android MenuItem not always initialized

I have an activity with three MenuItem'sI have three variables at the top of the activity class for each MenuItem (e

70