Kelanjutan artikel Bagaimana tampilan arsip zip dan apa yang dapat kita lakukan dengannya. Bagian 2 - Deskriptor dan Kompresi Data .Pembaca yang budiman, saya sekali lagi menyambut Anda untuk pemindahan Pemrograman Tidak Konvensional dalam PHP. Untuk memahami apa yang terjadi, saya sarankan Anda membaca dua artikel sebelumnya tentang arsip zip:
Seperti apa bentuk arsip zip dan apa yang bisa kita lakukan dengannya ?
Seperti apa arsip zip itu dan apa yang bisa kita lakukan dengannya. Bagian 2 - Deskriptor dan Kompresi DataSebelumnya, saya berbicara tentang cara membuat arsip hanya menggunakan kode PHP dan tidak menggunakan perpustakaan dan ekstensi (termasuk
zip standar), dan juga menyebutkan beberapa skenario penggunaan. Hari ini saya akan mencoba memberikan contoh dari salah satu skenario ini.
Kami akan menyimpan gambar dalam arsip di server jauh, dan jika perlu, menunjukkan gambar tertentu kepada pengguna tanpa mengunduh atau membongkar arsip, tetapi hanya menerima data dari server itu sendiri, secara khusus mengambil gambar dan tidak lebih (well, tidak ada yang membatalkan overhead untuk header) tapi tetap saja).
Memasak
Salah satu proyek kesayangan saya memiliki skrip sederhana yang saya gunakan untuk mengunduh banyak gambar dan menyimpannya ke arsip. Skrip menerima daftar tautan di json untuk memasukkan STDIN, memberikan arsipnya sendiri ke STDOUT, dan json dengan struktur array di STDERR (tentu saja, ini sedikit berlebihan dan tidak perlu membuat arsip menggunakan PHP asli, Anda dapat menggunakan alat yang lebih sesuai untuk ini, dan kemudian hanya baca strukturnya - saya akan mencoba untuk menyoroti poin ini di masa depan, tetapi pada tahap ini, menurut saya, itu akan menjadi lebih jelas).
Script ini, dengan beberapa modifikasi, saya kutip di bawah ini:
zip.php<?php $buffer = ''; while (!feof(STDIN)) { $buffer .= fread(STDIN, 4096); } $photos = json_decode($buffer, true, 512, JSON_THROW_ON_ERROR); $skeleton = ['files' => []]; $written = 0; [$written, $skeleton] = writeZip($written, $skeleton, $photos); $CDFH_Offset = $written; foreach ($skeleton['files'] as $index => $info) { $written += fwrite(STDOUT, $info['CDFH']); $skeleton['files'][$index]['CDFH'] = base64_encode($info['CDFH']); } $skeleton['EOCD'] = pack('LSSSSLLS', 0x06054b50, 0, 0, $records = count($skeleton['files']), $records, $written - $CDFH_Offset, $CDFH_Offset, 0); $written += fwrite(STDOUT, $skeleton['EOCD']); $skeleton['EOCD'] = base64_encode($skeleton['EOCD']); fwrite(STDERR, json_encode($skeleton)); function writeZip($written, $skeleton, $files) { $c = curl_init(); curl_setopt_array($c, [ CURLOPT_RETURNTRANSFER => 1, CURLOPT_TIMEOUT => 50, CURLOPT_FOLLOWLOCATION => true, ]); foreach ($files as $index => $url) { $fileName = $index . '.jpg'; for ($i = 0; $i < 1; $i++ ) { try { curl_setopt($c, CURLOPT_URL, $url); [$content, $code, $contentLength] = [ curl_exec($c), (int) curl_getinfo($c, CURLINFO_HTTP_CODE), (int) curl_getinfo($c, CURLINFO_CONTENT_LENGTH_DOWNLOAD) ]; if ($code !== 200) { throw new \Exception("[{$index}] " . 'Photo download error (' . $code . '): ' . curl_error($c)); } if (strlen($content) !== $contentLength) { var_dump(strlen($content), $contentLength); throw new \Exception("[{$index}] " . 'Different content-length'); } if ((false === $imageSize = @getimagesizefromstring($content)) || $imageSize[0] < 1 || $imageSize[1] < 1) { throw new \Exception("[{$index}] " . 'Broken image'); } [$width, $height] = $imageSize; $t = null; break; } catch (\Throwable $t) {} } if ($t !== null) { throw new \Exception('Error: ' . $index . ' > ' . $url, 0, $t); } $fileInfo = [ 'versionToExtract' => 10, 'generalPurposeBitFlag' => 0, 'compressionMethod' => 0, 'modificationTime' => 28021, 'modificationDate' => 20072, 'crc32' => hexdec(hash('crc32b', $content)), 'compressedSize' => $size = strlen($content), 'uncompressedSize' => $size, 'filenameLength' => strlen($fileName), 'extraFieldLength' => 0, ]; $LFH_Offset = $written; $skeleton['files'][$index] = [ 'LFH' => pack('LSSSSSLLLSSa*', 0x04034b50, ...array_values($fileInfo + ['filename' => $fileName])), 'CDFH' => pack('LSSSSSSLLLSSSSSLLa*', 0x02014b50, 798, ...array_values($fileInfo + [ 'fileCommentLength' => 0, 'diskNumber' => 0, 'internalFileAttributes' => 0, 'externalFileAttributes' => 2176057344, 'localFileHeaderOffset' => $LFH_Offset, 'filename' => $fileName, ])), 'width' => $width, 'height' => $height, ]; $written += fwrite(STDOUT, $skeleton['files'][$index]['LFH']); $written += fwrite(STDOUT, $content); $skeleton['files'][$index]['LFH'] = base64_encode($skeleton['files'][$index]['LFH']); } curl_close($c); return [$written, $skeleton]; }
Mari kita simpan di suatu tempat, panggil zip.php dan coba jalankan.
Saya sudah memiliki daftar tautan yang sudah jadi, saya sarankan menggunakannya, karena ada gambar tentang ~ 18MB, dan kita perlu ukuran arsip total tidak melebihi 20MB (sedikit kemudian saya akan memberi tahu alasannya) -
https://gist.githubusercontent.com /userqq/d0ca3aba6b6762c9ce5bc3ace92a9f9e/raw/70f446eb98f1ba6838ad3c19c3346cba371fd263/photos.jsonKami akan berlari seperti ini:
$ curl -s \ https://gist.githubusercontent.com/userqq/d0ca3aba6b6762c9ce5bc3ace92a9f9e/raw/70f446eb98f1ba6838ad3c19c3346cba371fd263/photos.json \ | php zip.php \ 2> structure.json \ 1> photos.zip
Ketika skrip selesai bekerja, pada output kita harus mendapatkan dua file -
photos.zip , saya sarankan memeriksanya dengan perintah:
$ unzip -tq photos.zip
dan
structure.json , di mana kami menyimpan json dengan base64 dari semua yang ada di arsip kami, kecuali data itu sendiri - semua struktur LFH dan CDFH, serta EOCD. Saya belum melihat aplikasi praktis EOCD secara terpisah dari arsip, tetapi posisi LFH diimbangi relatif terhadap awal file dan panjang data ditunjukkan dalam CDFH. Jadi, mengetahui panjangnya LFH, kita tentu bisa tahu dari posisi apa data akan mulai dan di mana itu akan berakhir.
Sekarang kita perlu mengunggah file kita ke beberapa server jarak jauh.
Sebagai contoh, saya akan menggunakan bot telegram - ini adalah yang termudah, itulah mengapa sangat penting untuk masuk ke dalam batas 20MB.
Daftarkan bot dengan @BotFather, jika Anda belum memilikinya, tulis sesuatu untuk bot Anda, cari pesan Anda di
https://api.telegram.org/bot{{TOKENβΊβΊ/getUpdates , dari mana kami mengisolasi pesan tersebut dari properti. .id = ini adalah id dari bot obrolan Anda.
Isi arsip kami dengan Anda, diperoleh pada langkah sebelumnya:
$ curl -F document=@"photos.zip" "https://api.telegram.org/bot{{TOKEN}}/sendDocument?chat_id={{CHAT ID}}" > stored.json
Sekarang kita sudah memiliki dua file json -
structure.json dan
disimpan.json .
Dan jika semuanya berjalan dengan baik, maka file tersimpan.json akan berisi json dengan bidang ok sama dengan true, dan juga result.document.file_id, yang kita butuhkan.
Prototipe
Sekarang semuanya sudah siap, mari kita mulai bisnis:
<?php define('TOKEN', '');
Dalam skrip ini, kami memilih file acak dari daftar yang kami miliki di arsip, menemukan posisi awal LFH, menambahkan panjang LFH ke dalamnya, dan dengan demikian mendapatkan posisi awal data dari file tertentu dalam arsip.
Menambahkan panjang file ke ini, kita mendapatkan posisi akhir data.
Tetap mendapatkan alamat file itu sendiri (ini dalam kasus tertentu dengan telegram) dan kemudian cukup dengan membuat permintaan dengan
Range: bytes=<->-<->
tajuk
Range: bytes=<->-<->
(dengan asumsi server mendukung permintaan Rentang)
Akibatnya, ketika mengakses skrip yang ditentukan, kita akan melihat konten gambar acak dari arsip kami.
Tidak, yah, ini tidak serius ...
Tidak perlu dikatakan bahwa permintaan untuk
getFile harus di-cache, karena telegram menjamin bahwa tautan tersebut akan hidup selama setidaknya satu jam. Secara umum, contoh ini baik untuk apa-apa selain menunjukkan bahwa itu berfungsi.
Mari kita coba sedikit lebih keras - jalankan semuanya dengan amphp. Lagi pula, orang-orang mendistribusikan statika dalam produksi di node.js, tetapi apa yang mencegah kita (tentu saja, terlepas dari akal sehat)? Kami juga memiliki sinkronisasi di sini, Anda tahu.
Kami membutuhkan 3 paket:
- amphp / http-server - jelas, server tempat kami akan mendistribusikan file
- amphp / artax - http-client yang dengannya kami akan mengeluarkan data, serta memperbarui tautan langsung ke file dalam cache.
- amphp / parallel adalah library untuk multithreading dan sebagainya . Tapi jangan takut, kita hanya perlu SharedMemoryParcel darinya, yang akan melayani kita sebagai cache im-memory.
Kami menempatkan dependensi yang diperlukan:
$ composer require amphp/http-server amphp/artax amphp/parallel
Dan tulis ini
Skrip <?php define('TOKEN', '');
Apa yang kami dapatkan sebagai hasilnya: kami sekarang memiliki cache tempat kami menyimpan tautan ke dokumen selama 55 menit, yang juga memastikan bahwa kami tidak akan meminta tautan beberapa kali berturut-turut jika kami menerima banyak permintaan pada saat cache berakhir. Nah, semua ini jelas lebih mudah daripada readfile dengan PHP-FPM (atau, Tuhan melarang, PHP-CGI).
Lebih lanjut, Anda dapat meningkatkan kumpulan instance amphp - SharedMemoryParcel, dengan namanya, mengisyaratkan bahwa cache kami akan meleset di antara proses / utas. Nah, atau, jika Anda masih memiliki keprihatinan yang sehat tentang keandalan desain ini, maka
proxy_pass dan nginx .
Secara umum, idenya masih jauh dari baru dan bahkan di tahun-tahun berjenggot, DownloadMaster memungkinkan Anda untuk mengunduh file tertentu dari arsip Zip tanpa mengunduh seluruh arsip, oleh karena itu tidak perlu menyimpan struktur secara terpisah, tetapi menyimpan beberapa permintaan. Bagaimana jika, misalnya, kami ingin mem-proxy file ke pengguna dengan cepat, itu sangat memengaruhi kecepatan permintaan.
Mengapa ini bisa berguna? Untuk menyimpan inode, misalnya, ketika Anda memiliki puluhan juta file kecil dan tidak ingin menyimpannya secara langsung dalam database, Anda dapat menyimpannya dalam arsip, mengetahui offset untuk menerima satu file.
Atau jika Anda menyimpan file dari jarak jauh. Dalam telegram, 20mb, tentu saja, Anda tidak bisa berkeliling, tetapi siapa tahu, ada opsi lain.