عند التحقق من سجلات التطبيق الخاصة بي، وجدت أن التحميل يستغرق دائمًا بضع ثوانٍ بعد الدخول إلى صفحة السجل (الواجهة غير مقسمة إلى صفحات)، لذلك فتحت لوحة الشبكة للتحقق
عندها فقط وجدت أن البيانات التي أعادتها الواجهة لم يتمضغطها
، واعتقدت أن الواجهة تستخدم وكيل Nginx العكسي، وسيساعدني Nginx تلقائيًا في القيام بهذه الطبقة (سأستكشف هذا لاحقًا، وهو أمر ممكن من الناحية النظرية).
هنا خدمة Node.
ستشارك هذه المقالة المعرفة حول HTTP数据压缩
والمعرفة Node侧的实践
. يشير جميع العملاء التاليين إلى
عندما يبدأ العميل طلبًا إلى الخادم، فإنه سيضيف حقل accept-encoding
إلى رأس الطلب، والذي تشير قيمته支持的压缩内容编码
实际压缩使用的编码算法
للمحتوى عن طريق إضافة content-encoding
إلى رأس الاستجابة
يستخدم deflate
كلاً من خوارزمية LZ77
و哈夫曼编码(Huffman Coding)
A خوارزمية ضغط البيانات على أساس哈夫曼编码(Huffman Coding)
.
gzip
هي خوارزمية تعتمد على DEFLATE
br
تشير إلى Brotli
، ويهدف تنسيق البيانات هذا إلى تحسين نسبة الضغط بشكل أكبر. بالمقارنة مع deflate
، يمكن أن يؤدي ضغط النص إلى زيادة كثافة الضغط بنسبة 20%
، بينما تظل سرعة الضغط وإلغاء الضغط دون تغيير تقريبًا
Node.js على zlib 模块
، التي توفر وظائف الضغط التي يتم تنفيذها باستخدام Gzip
و Deflate/Inflate
و Brotli
Deflate/Inflate
Brotli
يتم استخدام gzip
كمثال لسرد طرق الاستخدام المختلفة وفقًا للسيناريوهات المستخدمة بنفس الطريقة، ولكن API
يعتمد على stream
العمليات القائمة على buffer
تقديم العديد من الوحدات المطلوبة
const zlib = require('zlib') const خ = يتطلب ('fs') تيار ثابت = يتطلب ("دفق") const testFile = 'الاختبارات/origin.log' const targetFile = `${testFile}.gz` Const decodeFile = `${testFile}.un.gz`عرض نتائج
/الضغط، استخدم هنا أمر du
لحساب النتائج مباشرة قبل وبعد فك الضغط
# تنفيذ اختبارات du -ah # النتائج هي كما يلي 108K tests/origin.log.gz 2.2 مليون اختبار/origin.log 2.2 مليون اختبار/origin.log.un.gz 4.6M يختبر
流(stream)
باستخدام createGzip
و createUnzip
zlib
، باستثناء تلك المتزامنة بشكل صريح، تستخدم تجمع الخيوط الداخلية لـ Node.js ويمكن اعتبارها غير متزامنةالطريقة الأولى: استخدم طريقة pipe
مباشرة في المثيل لنقل الدفق
// Compression const readStream = fs.createReadStream(testFile) const writeStream = fs.createWriteStream(targetFile) readStream.pipe(zlib.createGzip()).pipe(writeStream) // فك الضغط const readStream = fs.createReadStream(targetFile) const writeStream = fs.createWriteStream(decodeFile) readStream.pipe(zlib.createUnzip()).pipe(writeStream)
الطريقة الثانية: باستخدام pipeline
الموجود في stream
، يمكنك إجراء معالجة أخرى بشكل منفصل في رد الاتصال
// الضغط const readStream = fs.createReadStream(testFile) const writeStream = fs.createWriteStream(targetFile) stream.pipeline(readStream, zlib.createGzip(), writeStream, err => { إذا (يخطئ) { console.error(err); } }) // فك الضغط const readStream = fs.createReadStream(targetFile) const writeStream = fs.createWriteStream(decodeFile) Stream.pipeline(readStream, zlib.createUnzip(), writeStream, err => { إذا (يخطئ) { console.error(err); } })
الطريقة الثالثة: طريقة pipeline
الوعد
const {promisify } = require('util') خط أنابيب ثابت = وعد (stream.pipeline) // ضغط const readStream = fs.createReadStream(testFile) const writeStream = fs.createWriteStream(targetFile) خط الأنابيب (readStream، zlib.createGzip ()، writeStream) .catch(يخطئ => { console.error(err); }) // فك الضغط const readStream = fs.createReadStream(targetFile) const writeStream = fs.createWriteStream(decodeFile) خط الأنابيب (readStream، zlib.createUnzip ()، writeStream) .catch(يخطئ => { console.error(err); })تستخدم
Buffer
واجهات برمجة تطبيقات gzip
و unzip
. تتضمن同步
异步
gzip
gzipSync
unzip
unzipSync
الطريقة 1: تحويل readStream
إلى Buffer
، ثم إجراء المزيد من العمليات
// Compression const buff = [] readStream.on('data', (chunk) => { برتقالي.دفع (قطعة) }) readStream.on('end', () => { zlib.gzip(Buffer.concat(buff)، targetFile، (err، resBuff) => { إذا (خطأ){ console.error(err); عملية الخروج () } fs.writeFileSync(targetFile,resBuff) }) })
// الضغط const buff = [] readStream.on('data', (chunk) => { برتقالي.دفع (قطعة) }) readStream.on('end', () => { fs.writeFileSync(targetFile,zlib.gzipSync(Buffer.concat(برتقالي))) })
الطريقة الثانية: القراءة مباشرة من خلال readFileSync
// مضغوط const readBuffer = fs.readFileSync(testFile) const decodeBuffer = zlib.gzipSync(readBuffer) fs.writeFileSync(targetFile,decodeBuffer) // فك الضغط const readBuffer = fs.readFileSync(targetFile) const decodeBuffer = zlib.gzipSync(decodeFile) fs.writeFileSync(targetFile,decodeBuffer)
بالإضافة إلى ضغط الملفات، قد يكون من الضروري أحيانًا فك ضغط المحتوى المرسل مباشرةً،
إليك محتوى النص المضغوط كمثال
// بيانات الاختبار const testData = fs. readFileSync(testFile, {encoding: 'utf-8' })
流(stream)
، ما عليك سوى التفكير في تحويل string
=> buffer
= stream
string
الدفق => buffer
const buffer = Buffer.from(testData)
buffer
=> stream
const TransformStream = تيار جديد.PassThrough() تحويل ستريم.كتابة (مخزن مؤقت) // أو const TransformStream = تيار جديد.Duplex () تحويلStream.push(Buffer.from(testData)) TransformStream.push(null)
هنا مثال على الكتابة إلى ملف، بالطبع، يمكن أيضًا كتابته إلى تدفقات أخرى، مثل HTTP的Response
(سيتم تقديمها بشكل منفصل لاحقًا)
TransformStream .pipe(zlib.createGzip())يستخدم
.pipe(fs.createWriteStream(targetFile))
أيضًا Buffer.from
لتحويل السلسلة إلى buffer
Buffer
const buffer = Buffer.from(testData)
ثم يستخدم مباشرة واجهة برمجة تطبيقات المزامنة للتحويل، والنتيجة هنا هي المضغوطة
نتيجة
المحتوى
const = zlib.gzipSync(buffer)يمكنها كتابة الملفات، وفي HTTP Server
يمكن أيضًا إرجاع المحتوى المضغوط مباشرة
fs.writeFileSync(targetFile, result)
هنا نستخدم وحدة http
مباشرة في Node للإنشاء خادم بسيط بالنسبة للعروض التوضيحية
في أطر عمل Node Web
الأخرى، فإن أفكار المعالجة متشابهة بالطبع، هناك بشكل عام مكونات إضافية جاهزة يمكن الوصول إليها بنقرة واحدة.
ثابت http = يتطلب ('http') const {المرور، خط الأنابيب } = يتطلب('الدفق') const zlib = يتطلب ('zlib') // بيانات الاختبار const testTxt = 'بيانات الاختبار 123'.repeat(1000) تطبيق const = http.createServer((req, res) => { const {url } = req // اقرأ خوارزمية الضغط المدعومة const AcceptEncoding = req.headers['accept-encoding'].match(/(br|deflate|gzip)/g) // نوع بيانات الاستجابة الافتراضية res.setHeader('Content-Type', 'application/json; charset=utf-8') // عدة أمثلة للمسارات constways = [ ['/gzip', () => { إذا (acceptEncoding.includes('gzip')) { res.setHeader('ترميز المحتوى'، 'gzip') // استخدم واجهة برمجة تطبيقات المزامنة لضغط محتوى النص مباشرة res.end(zlib.gzipSync(Buffer.from(testTxt))) يعود } res.end(testTxt) }]، ["/ انكماش"، () => { إذا (acceptEncoding.includes('deflate')) { res.setHeader('ترميز المحتوى'، 'انكماش') // عملية واحدة تعتمد على الدفق const OriginStream = new PassThrough() OriginStream.write(Buffer.from(testTxt)) OriginStream.pipe(zlib.createDeflate()).pipe(res) OriginStream.end() يعود } res.end(testTxt) }]، ['/ر'، () => { إذا (acceptEncoding.includes('br')) { res.setHeader('ترميز المحتوى'، 'br') res.setHeader('نوع المحتوى', 'نص/html; مجموعة الأحرف=utf-8') // عمليات كتابة متعددة تعتمد على التدفقات const OriginStream = new PassThrough() خط الأنابيب (OriginStream، zlib.createBrotliCompress()، res، (err) => { إذا (يخطئ) { console.error(err); } }) OriginStream.write(Buffer.from('<h1>BrotliCompress</h1>')) OriginStream.write(Buffer.from('<h2>بيانات الاختبار</h2>')) OriginStream.write(Buffer.from(testTxt)) OriginStream.end() يعود } res.end(testTxt) }] ] مسار ثابت =ways.find(v => url.startsWith(v[0])) إذا (الطريق) { الطريق[1]() يعود } // العودة إلى الأعلى res.setHeader('Content-Type', 'text/html; charset=utf-8') res.end(`<h1>404: ${url}</h1> <h2>المسار المسجل</h2> <ul> ${routes.map(r => `<li><a href="${r[0]}">${r[0]}</a></li>`).join('') } </ul> `) الدقة. النهاية () }) التطبيق الاستماع(3000)