diff --git a/app/config/collections.php b/app/config/collections.php index a55ab1abd0..02e9762963 100644 --- a/app/config/collections.php +++ b/app/config/collections.php @@ -56,6 +56,17 @@ $commonCollections = [ 'array' => false, 'filters' => [], ], + [ + '$id' => ID::custom('headers'), + 'type' => Database::VAR_STRING, + 'format' => '', + 'size' => 16384, + 'signed' => true, + 'required' => false, + 'default' => [], + 'array' => false, + 'filters' => ['json'], + ], [ '$id' => 'accessedAt', 'type' => Database::VAR_DATETIME, diff --git a/app/controllers/api/storage.php b/app/controllers/api/storage.php index afd3c4687a..4d5efdaedb 100644 --- a/app/controllers/api/storage.php +++ b/app/controllers/api/storage.php @@ -1163,6 +1163,9 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view') ->groups(['api', 'storage']) ->label('scope', 'files.read') ->label('resourceType', 'buckets') + ->label('cache', true) + ->label('cache.resourceType', 'bucket/{request.bucketId}') + ->label('cache.resource', 'file/{request.fileId}') ->label('sdk.auth', [APP_AUTH_TYPE_SESSION, APP_AUTH_TYPE_KEY, APP_AUTH_TYPE_JWT]) ->label('sdk.namespace', 'storage') ->label('sdk.method', 'getFileView') @@ -1282,14 +1285,14 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view') if (!empty($source)) { if (!empty($rangeHeader)) { - $response->send(substr($source, $start, ($end - $start + 1))); + $response->file(substr($source, $start, ($end - $start + 1))); } - $response->send($source); + $response->file($source); return; } if (!empty($rangeHeader)) { - $response->send($deviceForFiles->read($path, $start, ($end - $start + 1))); + $response->file($deviceForFiles->read($path, $start, ($end - $start + 1))); return; } @@ -1306,7 +1309,7 @@ App::get('/v1/storage/buckets/:bucketId/files/:fileId/view') ); } } else { - $response->send($deviceForFiles->read($path)); + $response->file($deviceForFiles->read($path)); } }); diff --git a/app/controllers/shared/api.php b/app/controllers/shared/api.php index 7a5de8af19..d212de57ea 100644 --- a/app/controllers/shared/api.php +++ b/app/controllers/shared/api.php @@ -462,14 +462,17 @@ App::init() $useCache = $route->getLabel('cache', false); if ($useCache) { - $key = md5($request->getURI() . '*' . implode('*', $request->getParams()) . '*' . APP_CACHE_BUSTER); + $keyParams = $request->getURI() . '*' . implode('*', $request->getParams()); + if(!empty($request->getRangeStart())) $keyParams .= '*'.$request->getRangeStart(); + if(!empty($request->getRangeEnd())) $keyParams .= '*'.$request->getRangeEnd(); + $keyParams .= '*' . APP_CACHE_BUSTER; + $key = md5($keyParams); $cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key)); $cache = new Cache( new Filesystem(APP_STORAGE_CACHE . DIRECTORY_SEPARATOR . 'app-' . $project->getId()) ); $timestamp = 60 * 60 * 24 * 30; $data = $cache->load($key, $timestamp); - if (!empty($data) && !$cacheLog->isEmpty()) { $parts = explode('/', $cacheLog->getAttribute('resourceType')); $type = $parts[0] ?? null; @@ -506,12 +509,14 @@ App::init() throw new Exception(Exception::STORAGE_FILE_NOT_FOUND); } } - $response ->addHeader('Cache-Control', sprintf('private, max-age=%d', $timestamp)) - ->addHeader('X-Appwrite-Cache', 'hit') - ->setContentType($cacheLog->getAttribute('mimeType')) - ->send($data); + ->addHeader('X-Appwrite-Cache', 'hit'); + foreach ($cacheLog->getAttribute('headers') as $key => $value) { + $response->addHeader($key, $value); + } + + $response->send($data); } else { $response ->addHeader('Cache-Control', 'no-cache, no-store, must-revalidate') @@ -704,7 +709,8 @@ App::shutdown() if (!empty($queueForMessaging->getType())) { $queueForMessaging->trigger(); } - + var_dump($request->getRangeStart()); + var_dump($request->getRangeEnd()); /** * Cache label */ @@ -712,7 +718,9 @@ App::shutdown() if ($useCache) { $resource = $resourceType = null; $data = $response->getPayload(); + if (!empty($data['payload'])) { + $pattern = $route->getLabel('cache.resource', null); if (!empty($pattern)) { $resource = $parseLabel($pattern, $responsePayload, $requestParams, $user); @@ -723,17 +731,29 @@ App::shutdown() $resourceType = $parseLabel($pattern, $responsePayload, $requestParams, $user); } - $key = md5($request->getURI() . '*' . implode('*', $request->getParams()) . '*' . APP_CACHE_BUSTER); + $keyParams = $request->getURI() . '*' . implode('*', $request->getParams()); + if(!empty($request->getRangeStart())) $keyParams .= '*'.$request->getRangeStart(); + if(!empty($request->getRangeEnd())) $keyParams .= '*'.$request->getRangeEnd(); + $keyParams .= '*'. APP_CACHE_BUSTER; + $key = md5($keyParams); $signature = md5($data['payload']); $cacheLog = Authorization::skip(fn () => $dbForProject->getDocument('cache', $key)); $accessedAt = $cacheLog->getAttribute('accessedAt', ''); $now = DateTime::now(); if ($cacheLog->isEmpty()) { + $filterList = ['Cache-Control', 'Pragma', 'Expires', 'X-Appwrite-Cache']; + $headers = []; + foreach ($response->getHeaders() as $name => $value) { + if(!in_array($name, $filterList)) { + $headers[$name] = $value; + } + } + Authorization::skip(fn () => $dbForProject->createDocument('cache', new Document([ '$id' => $key, 'resource' => $resource, 'resourceType' => $resourceType, - 'mimeType' => $response->getContentType(), + 'headers' => $headers, 'accessedAt' => $now, 'signature' => $signature, ]))); @@ -751,8 +771,6 @@ App::shutdown() } } - - if ($project->getId() !== 'console') { if (!Auth::isPrivilegedUser(Authorization::getRoles())) { $fileSize = 0;