hi, i try to implement a function to download a file in my micro api with Cors.
this is my controller function of downloading action get .../download/filename/version
public function downloadAction($name, $version, $mode = 'attachment'){
$errors = [];
$data = [];
if ($name != null) {
if ((!is_string($name)) && (!is_null($name)))
$errors['name'] = 'tag must be a string';
$data['name'] = $name;
}
if ($version != null) {
if (!ctype_digit($version) || ($version < 0))
$errors['version'] = 'The version must be a positive Integer';
$data['version'] = $version;
}
if ($errors) {
$exception = new Http400Exception(_('Input parameters validation error'), self::ERROR_INVALID_REQUEST);
throw $exception->addErrorDetails($errors);
}
try {
$downloadFile = $this->filesService->downloadFiles($data, $mode);
} catch (ServiceException $e) {
switch ($e->getCode()) {
case FilesService::ERROR_UNABLE_TO_FIND_DOWNLOAD:
case FilesService::ERROR_UNABLE_TO_OPEN_FILE:
throw new Http422Exception($e->getMessage(), $e->getCode(), $e);
default:
throw new Http500Exception(_('Internal Server Error'), $e->getCode(), $e);
}
}
}
i found this here at the board for the downloadFiles function and change it litte bit:
public function downloadFiles(array $data, $mode){
try {
$search = [];
$single = explode(".", $data['name']);
$search['name'] = $single[0];
$search['extension'] = $single[1];
$search['version'] = $data['version'];
$userId = $this->session->get("userId");
$dir = FILES_PATH.$userId.'/';
$files = Files::findFirst([
'conditions' => 'name=:name: AND extension=:extension: AND version=:version:',
'bind' => [
'name' => $search['name'],
'extension' => $search['extension'],
'version' => $search['version'],
]
]);
$file_name = $files->name;
$file_ext = $files->extension;
echo $files->id;
$file_path = $dir.$files->id.'_'.$file_name.'.'.$file_ext;
if (is_file($file_path)){
$file_size = filesize($file_path);
$file = @fopen($file_path, 'rb');
if($file) {
//set headers, prevent chaching
header('Pragma: public');
header('Expires: -1');
header('Cache-Control: public, must-revalidate, post-check=0, pre-check=0');
//set appropriate headers for attachment or streamed file
if ($mode == 'attachment') {
header("Content-Disposotion: attachment; filename=\"{$file_name}.{$file_ext}\"");
}else
header("Content-Disposotion: inline; filename=\"{$file_name}\"");
// set the mime type based on extension
$ctype_default = 'text/plain';
$content_types = array(
'txt' => 'text/plain',
'html' => 'text/html',
'json' => 'application/json',
'xml' => 'application/xml',
);
$ctype = isset($content_types[$file_ext]) ? $content_types[$file_ext] : $ctype_default;
echo $ctype;
header("Content-Type: {$ctype}");
//check if http_range is sent by brwoser (or download manger)
if (isset($_SERVER['HTTP_RANGE'])) {
list($size_unit, $range_orig) = explode('=', $_SERVER['HTTP_RANGE'], 2);
if ($size_unit == 'bytes') {
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 of range (if set), else set default
//also check for invalid ranges
ob_clean();
$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 Not Found");
exit;
// return $this->response;
} catch (\PDOException $e) {
if ($e->getCode() == 12004) {
throw new ServiceException('Can not find file', self::ERROR_UNABLE_TO_FIND_FILES);
} else {
throw new ServiceException($e->getMessage(). $e->getCode(), $e); }
}catch (\Exception $e){
$this->response->setStatusCode(500, 'Internal Server Error');
}
}
after that i have a middleware (set as after) for response:
public function call(Micro $app)
{
try {
$return = $app->getReturnedValue();
if (is_array($return)) {
// Transforming arrays to JSON
$app->response->setContent(json_encode($return));
} elseif (!strlen($return)) {
// Successful response without any content
$app->response->setStatusCode('204', 'No Content');
} else {
// Unexpected response
throw new Exception('Bad Response');
}
//Sending response to the client
$app->response->send();
return true;
} catch (AbstractHttpException $e) {
$response = $app->response;
$response->setStatusCode($e->getCode(), $e->getMessage());
$response->setJsonContent($e->getAppError());
$response->send();
} catch (\Phalcon\Http\Request\Execption $e) {
$app->response->setStatusCode(400, 'Bad request')
->setJsonContent([
AbstractHttpException::KEY_CODE => 400,
AbstractHttpException::KEY_MESSAGE => 'Bad request'
])
->send();
} catch (\Exception $e) {
if ($e->getCode() === null) {
$app->response->setStatusCode(500, 'Internal Server Error');
} else {
// $app->response->setStatusCode($e->getCode());
}
}
}
with postmen it seems to work. Now i have a Angular client which calls the download endpoint and tries to save the file:
download() {
const filename = 'test.txt';
const version = '4';
const options = {
headers: new HttpHeaders().append('Authorization', this.oAuthService.getAccessToken()),
params: new HttpParams().append('responseType', 'text')
}
return this.http.get(this.fileServerUrl + 'file/download/' + filename + '/' + version options)
.subscribe(response => this.saveToFileSystem(response));
// .then();
// .catch( error => { console.error(error); });
}
private saveToFileSystem(response) {
const contentDispositionHeader: string = response.headers.get('Content-Disposition');
const parts: string[] = contentDispositionHeader.split(';');
const filename = parts[1].split('=')[1];
console.log(filename);
const blob = new Blob([response._body], { type: 'text/plain' });
saveAs(blob, filename);
}
in the reponse i can see the content of the txt file, but i get always the response error:
HttpErrorResponse {headers: HttpHeaders, status: 200, statusText: "OK", url: "https://localhost:5001/file/download/test.txt/4?responseType=text", ok: false, …}
error
:
{error: SyntaxError: Unexpected token T in JSON at position 0 at JSON.parse (<anonymous>) at XMLHttp…, text: "Test"}
headers
:
HttpHeaders {normalizedNames: Map(0), lazyUpdate: null, lazyInit: ƒ}
message
:
"Http failure during parsing for https://localhost:5001/file/download/test.txt/4?responseType=text"
name
:
"HttpErrorResponse"
in my eyes the fault must be on the phalcon side. The angular client seems to work if i test the download function at another side. Could the mistake be, that after the download action, he sends a json in the after Middleware?
these are the Headers: Genereal:
HttpErrorResponse {headers: HttpHeaders, status: 200, statusText: "OK", url: "https://localhost:5001/file/download/test.txt/4?responseType=text", ok: false, …}
error
:
{error: SyntaxError: Unexpected token T in JSON at position 0 at JSON.parse (<anonymous>) at XMLHttp…, text: "Test"}
headers
:
HttpHeaders {normalizedNames: Map(0), lazyUpdate: null, lazyInit: ƒ}
message
:
"Http failure during parsing for https://localhost:5001/file/download/test.txt/4?responseType=text"
name
:
"HttpErrorResponse"
response header:
Accept-Ranges: bytes
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Range, Content-Disposition, Content-Type, Authorization
Access-Control-Allow-Methods: GET,PUT,POST,DELETE,OPTIONS
Access-Control-Allow-Origin: https://localhost:4200
Cache-Control: public, must-revalidate, post-check=0, pre-check=0
Connection: Keep-Alive
Content-Disposotion: attachment; filename="test.txt"
Content-Encoding: gzip
Content-Type: text/plain;charset=UTF-8
Date: Mon, 27 Aug 2018 15:18:25 GMT
Expires: -1
Keep-Alive: timeout=5, max=99
Pragma: public
Server: Apache/2.4.25 (Debian)
Set-Cookie: PHPSESSID=abcf0b3b4692d7c4be79f208f2ecbb42; path=/
Transfer-Encoding: chunked
Vary: Authorization,Accept-Encoding
X-Powered-By: PHP/7.1.19
request header:
Accept: application/json, text/plain, */*
Accept-Encoding: gzip, deflate, br
Accept-Language: de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7
Authorization: 41dbfbcde82a7b2f8e817b613c3e2ccf17ffe41ee51cb447feff43c53f549990
Connection: keep-alive
Host: localhost:5001
Origin: https://localhost:4200
Referer: https://localhost:4200/home
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36
sorry for the whole text but I do not know how to continue.
Thank oyu very much in advance