Müzik dosyalarının CDN üzerinden cache'lenmesi
Müzik dosyaları artık Cloudflare'ın dünya genelindeki sunucularında (CDN) saklanıyor. Kullanıcı bir şarkı çaldığında, dosya en yakın Cloudflare sunucusundan geliyor.
// 🎵 MP3 AUDIO FILES - Cache-friendly path (no query string, no session)
// Path: /audio/songs/{id}/{expires}/{signature}
// HLS ile aynı mantık: session yok, Cloudflare cache'leyebilir
\Illuminate\Support\Facades\Route::middleware([
\Stancl\Tenancy\Middleware\InitializeTenancyByDomain::class,
'throttle:120,1', // 120 req/min
])
->domain($domain)
->get('/audio/songs/{id}/{expires}/{signature}', [
\Modules\Muzibu\app\Http\Controllers\Api\SongController::class,
'serveAudio'
])
->where([
'id' => '[0-9]+',
'expires' => '[0-9]+',
'signature' => '[a-f0-9]+'
])
->name(($index === 0 ? '' : "d{$index}.") . 'muzibu.songs.audio-serve');
/**
* 🎵 Serve MP3 Audio - Cache-friendly endpoint (no query string)
* Path: /audio/songs/{id}/{expires}/{signature}
* Cloudflare cache için optimize edilmiş - HLS segment mantığı ile aynı
*
* @param int $id
* @param int $expires Unix timestamp
* @param string $signature HMAC signature
* @return \Symfony\Component\HttpFoundation\BinaryFileResponse
*/
public function serveAudio(int $id, int $expires, string $signature)
{
try {
// 🔐 İmza doğrulama (path parametrelerinden)
$expectedSignature = hash_hmac('sha256', "audio|{$id}|{$expires}", config('app.key'));
if (!hash_equals($expectedSignature, $signature)) {
\Log::warning('Audio serve: Invalid signature', [
'song_id' => $id,
'ip' => request()->ip(),
]);
abort(403, 'Invalid signature');
}
// ⏰ Süre kontrolü
if (time() > $expires) {
\Log::warning('Audio serve: URL expired', [
'song_id' => $id,
'expires' => $expires,
'now' => time(),
]);
abort(403, 'URL expired');
}
// 🎵 Şarkıyı bul
$song = Song::where('song_id', $id)
->where('is_active', 1)
->first();
if (!$song || !$song->file_path) {
abort(404, 'Song not found');
}
// 📁 Dosya yolunu belirle
if (str_starts_with($song->file_path, '/')) {
$filePath = $song->file_path;
} else {
$filePath = storage_path('app/public/muzibu/songs/' . $song->file_path);
}
if (!file_exists($filePath)) {
\Log::error('Audio serve: File not found', [
'song_id' => $song->song_id,
'file_path' => $filePath,
]);
abort(404, 'File not found');
}
// 🚀 MP3 döndür - Cloudflare cache için optimize header'lar
return response()->file($filePath, [
'Content-Type' => 'audio/mpeg',
'Accept-Ranges' => 'bytes',
'Cache-Control' => 'public, max-age=31536000, immutable',
'CDN-Cache-Control' => 'public, max-age=31536000',
'Access-Control-Allow-Origin' => '*',
]);
} catch (\Exception $e) {
\Log::error('Audio serve error:', ['song_id' => $id, 'message' => $e->getMessage()]);
abort(500, 'Internal error');
}
}
/**
* Generate signed URL for song stream
* 🎵 Cache-friendly: /audio/songs/{id}/{expires}/{signature} (no query string)
*
* @param int $songId
* @param int $expiresInMinutes (default: 30 dakika)
* @param bool $forceMP3 Force MP3 output even if HLS available (for fallback) - DEPRECATED
* @return string
*/
public function generateStreamUrl(int $songId, int $expiresInMinutes = 30, bool $forceMP3 = false): string
{
$expiration = Carbon::now()->addMinutes($expiresInMinutes);
$expires = $expiration->timestamp;
// 🚀 CACHED: Use TenantHelpers for cached domain lookup
$tenantDomain = TenantHelpers::getTenantDomain();
$domain = $tenantDomain ? 'https://' . $tenantDomain : request()->getSchemeAndHttpHost();
// 🔐 Generate signature (must match SongController::serveAudio)
// Format: audio|songId|expires → HMAC-SHA256
$signature = hash_hmac('sha256', "audio|{$songId}|{$expires}", config('app.key'));
// 🎵 Cache-friendly URL: no query string, Cloudflare can cache
// Path: /audio/songs/{id}/{expires}/{signature}
return "{$domain}/audio/songs/{$songId}/{$expires}/{$signature}";
}
// 🎵 MP3/Audio serve endpoint'leri - Cloudflare cache için (1 yıl)
// Yeni cache-friendly path: /audio/songs/{id}/{expires}/{signature}
// Eski path (backward compat): /api/muzibu/songs/{id}/serve
if ($request->is('audio/songs/*') || $request->is('api/muzibu/songs/*/serve*')) {
$response->headers->set('Cache-Control', 'public, max-age=31536000, immutable');
$response->headers->set('CDN-Cache-Control', 'public, max-age=31536000');
$response->headers->remove('Pragma');
$response->headers->remove('Expires');
$response->headers->remove('Set-Cookie'); // Cookie gönderme, cache bozar
$response->setPublic();
return $response;
}
Muzibu Audio Route Cache (1 Year)
1 (First) - ÖNEMLİ!
(http.request.uri.path contains "/audio/songs/")
Eligible for cache
Ignore cache-control → 1 year (31536000s)
Override origin → 1 year
Enabled
/api/ path'leri Cloudflare'da varsayılan olarak DYNAMIC/api/ isteklerinde otomatik Cookie gönderiyor/audio/ path'i bu sorunları bypass ediyor (HLS gibi)Yukarıdaki 4 dosyayı güncelle (production'dan kopyala veya git pull)
(http.request.uri.path contains "/audio/songs/")/audio/songs/* filtrelecf-cache-status: MISScf-cache-status: HIT ✅Cloudflare Cache Rule aktif değil veya sıra yanlış. Rule'un "First" konumda olduğundan emin ol. "API No Cache" kuralından ÖNCE olmalı.
İmza doğrulaması başarısız. SignedUrlService ve SongController'daki signature formatının aynı olduğundan emin ol:hash_hmac('sha256', "audio|{$songId}|{$expires}", config('app.key'))
URL süresi dolmuş. Varsayılan 30 dakika. Sunucu saati doğru mu kontrol et.
Route tanınmıyor. php artisan route:clear && php artisan route:list | grep audio ile kontrol et.
Cache temizle ve sayfayı yenile. Eski URL'ler 30 dakika sonra expire olur.