เมื่อตรวจสอบบันทึกแอปพลิเคชันของฉันเอง ฉันพบว่าจะใช้เวลาไม่กี่วินาทีในการโหลดเสมอหลังจากเข้าสู่หน้าบันทึก (อินเทอร์เฟซไม่มีการแบ่งหน้า) ดังนั้นฉันจึงเปิดแผงเครือข่ายเพื่อตรวจสอบ
จากนั้นฉันพบว่าข้อมูลที่ส่งคืนโดยอินเทอร์เฟซไม่ได้รับการบีบอัด ฉันคิดว่าอินเทอร์เฟซใช้พร็อกซีย้อนกลับ Nginx และ Nginx จะช่วยฉันทำเลเยอร์นี้โดยอัตโนมัติ (ฉันจะสำรวจสิ่งนี้ในภายหลัง ซึ่งเป็นไปได้ในทางทฤษฎี
) นี่คือ Node บริการ
บทความนี้จะแบ่งปันความรู้เกี่ยวกับ HTTP数据压缩
และความรู้เบื้องต้น Node侧的实践
Node
เมื่อไคลเอ็นต์เริ่มต้นคำขอไปยังเซิร์ฟเวอร์ ไคลเอ็นต์จะเพิ่มฟิลด์ accept-encoding
ให้กับส่วนหัวของคำขอ ซึ่งค่าจะระบุ支持的压缩内容编码
实际压缩使用的编码算法
โดยการเพิ่ม content-encoding
ในส่วนหัวการตอบ
deflate
ใช้ทั้งอัลกอริธึม LZ77
และ哈夫曼编码(Huffman Coding)
อัลกอริธึมการบีบอัดข้อมูลตาม哈夫曼编码(Huffman Coding)
gzip
เป็นอัลกอริทึมที่ใช้ DEFLATE
br
ซึ่งหมายถึง Brotli
รูปแบบข้อมูลนี้มีจุดมุ่งหมายเพื่อปรับปรุงอัตราส่วนการบีบอัดเพิ่มเติม เมื่อเปรียบเทียบกับ deflate
การบีบอัดข้อความสามารถเพิ่มความหนาแน่นของการบีบอัดได้ 20%
ในขณะที่ความเร็วการบีบอัดและคลายการบีบอัดยังคงไม่เปลี่ยนแปลง
Node.js มี zlib 模块
ซึ่งมีฟังก์ชันการบีบอัดที่ใช้งานโดยใช้ Gzip
, Deflate/Inflate
และ Brotli
Deflate/Inflate
Brotli
นี้ gzip
ถูกใช้เป็นตัวอย่างเพื่อแสดงรายการวิธีการใช้งานต่างๆ ตามสถานการณ์ ในลักษณะเดียวกัน แต่ API นั้น
ขึ้นอยู่กับการทำงานของ stream
การดำเนินการตาม buffer
แนะนำโมดูลที่จำเป็นหลายโมดูล
const zlib = need('zlib') const fs = ต้องการ ('fs') const stream = ต้องการ ('สตรีม') const testFile = 'tests/origin.log' const targetFile = `${testFile}.gz` Const decodeFile = `${testFile}.un.gz`
/compression results view ใช้คำสั่ง du
เพื่อนับผลลัพธ์โดยตรงก่อนและหลังการบีบอัด
# Execute du -ah tests # ผลลัพธ์มีดังนี้ 108K tests/origin.log.gz 2.2M การทดสอบ/origin.log 2.2M การทดสอบ/origin.log.un.gz 4.6M ทดสอบ
流(stream)
โดยใช้ createGzip
และ createUnzip
zlib
API ทั้งหมด ยกเว้นที่ซิงโครนัสอย่างชัดเจน ให้ใช้เธรดพูลภายใน Node.js และถือได้ว่าเป็นอะซิงวิธีที่ 1: ใช้วิธี pipe
บนอินสแตนซ์โดยตรงเพื่อถ่ายโอนสตรีม
// การบีบอัด const readStream = fs.createReadStream(testFile) const writeStream = fs.createWriteStream (ไฟล์เป้าหมาย) readStream.pipe(zlib.createGzip()).ไปป์(writeStream) // คลายการบีบอัด const readStream = fs.createReadStream (targetFile) const writeStream = fs.createWriteStream (ถอดรหัสไฟล์) readStream.pipe(zlib.createUnzip()).pipe(writeStream)
วิธีที่ 2: การใช้ pipeline
บน stream
คุณสามารถทำการประมวลผลอื่นแยกกันในการเรียกกลับ
// การบีบอัด const readStream = fs.createReadStream(testFile) const writeStream = fs.createWriteStream (ไฟล์เป้าหมาย) stream.pipeline (readStream, zlib.createGzip (), writeStream, ผิดพลาด => { ถ้า (ผิดพลาด) { console.error(ผิดพลาด); - - // คลายการบีบอัด const readStream = fs.createReadStream (targetFile) const writeStream = fs.createWriteStream (ถอดรหัสไฟล์) stream.pipeline (readStream, zlib.createUnzip (), writeStream, ข้อผิดพลาด => { ถ้า (ผิดพลาด) { console.error(ผิดพลาด); - })
วิธีที่ 3: วิธี pipeline
สัญญา
const { สัญญา } = ต้องการ ('util') ไปป์ไลน์ const = สัญญา (stream.pipeline) // บีบอัด const readStream = fs.createReadStream (testFile) const writeStream = fs.createWriteStream (ไฟล์เป้าหมาย) ไปป์ไลน์ (readStream, zlib.createGzip(), writeStream) .catch(ผิดพลาด => { console.error(ผิดพลาด); - // คลายการบีบอัด const readStream = fs.createReadStream (targetFile) const writeStream = fs.createWriteStream (ถอดรหัสไฟล์) ไปป์ไลน์ (readStream, zlib.createUnzip(), writeStream) .catch(ผิดพลาด => { console.error(ผิดพลาด); })
Buffer
ใช้ gzip
และ unzip
API สองวิธีนี้同步
异步
gzip
gzipSync
unzip
unzipSync
วิธีที่ 1: แปลง readStream
เป็น Buffer
จากนั้นดำเนินการเพิ่มเติม
// Compression const buff = [] readStream.on('data', (อัน) => { buff.push(ก้อน) - readStream.on('สิ้นสุด', () => { zlib.gzip(Buffer.concat(บัฟ), targetFile, (ผิดพลาด, resBuff) => { ถ้า(ผิดพลาด){ console.error(ผิดพลาด); กระบวนการทางออก() - fs.writeFileSync (ไฟล์เป้าหมาย, resBuff) - })
// การบีบอัด const buff = [] readStream.on('data', (อัน) => { buff.push(ก้อน) - readStream.on('สิ้นสุด', () => { fs.writeFileSync(targetFile,zlib.gzipSync(Buffer.concat(บัฟ))) })
วิธีที่ 2: อ่านโดยตรงผ่าน readFileSync
// Compressed const readBuffer = fs.readFileSync(testFile) const decodeBuffer = zlib.gzipSync (readBuffer) fs.writeFileSync (ไฟล์เป้าหมาย, decodeBuffer) // คลายการบีบอัด const readBuffer = fs.readFileSync (targetFile) const decodeBuffer = zlib.gzipSync (ถอดรหัสไฟล์) fs.writeFileSync(targetFile,decodeBuffer)
นอกเหนือจากการบีบอัดไฟล์ บางครั้งอาจจำเป็นต้องขยายขนาดเนื้อหาที่ส่งโดยตรง
นี่คือเนื้อหาข้อความที่ถูกบีบอัดเป็นตัวอย่าง
// Test data const testData = fs readFileSync( testFile, { encoding: 'utf-8' })
流(stream)
เพียงพิจารณาการแปลงของ string
=> buffer
=> stream
string
=> buffer
const buffer = Buffer.from(testData)
buffer
=> stream
const TransformerStream = สตรีมใหม่ PassThrough() TransformerStream.write (บัฟเฟอร์) // หรือ const TransformerStream = สตรีมใหม่ ดูเพล็กซ์ () TransformerStream.push (Buffer.from (testData)) TransformerStream.push(null)
นี่คือตัวอย่างของการเขียนลงไฟล์ แน่นอนว่ายังสามารถเขียนลงในสตรีมอื่นๆ ได้ เช่น HTTP的Response
(จะแนะนำแยกต่างหากในภายหลัง)
TransformerStream .pipe(zlib.createGzip()) .pipe(fs.createWriteStream(targetFile))
ยังใช้ Buffer.from
เพื่อแปลงสตริงเป็น buffer
Buffer
const buffer = Buffer.from(testData)
จากนั้นใช้ API การซิงโครไนซ์โดยตรงสำหรับการแปลง ผลลัพธ์ที่นี่คือการบีบอัด content
const result = zlib.gzipSync(buffer)
สามารถเขียนไฟล์ได้ และใน HTTP Server
เนื้อหาที่บีบอัดก็สามารถส่งคืนได้โดยตรง
fs.writeFileSync(targetFile, result)
ที่นี่เราใช้โมดูล http
ใน Node โดยตรงเพื่อสร้าง เซิร์ฟเวอร์แบบธรรมดา สำหรับการสาธิต
ใน Node Web
Framework อื่นๆ แนวคิดในการประมวลผลจะคล้ายกัน โดยทั่วไปแล้วจะมีปลั๊กอินสำเร็จรูปที่สามารถเข้าถึงได้ด้วยคลิกเดียว
const http = ต้องการ ('http') const { PassThrough, ไปป์ไลน์ } = ต้องการ ('สตรีม') const zlib = ต้องการ('zlib') //ข้อมูลทดสอบ const testTxt = 'ข้อมูลทดสอบ 123'.repeat(1,000) แอป const = http.createServer ((req, res) => { const { url } = คำขอ // อ่านอัลกอริธึมการบีบอัดที่รองรับ const AcceptEncoding = req.headers['accept-encoding'].match(/(br|deflate|gzip)/g) //ประเภทข้อมูลการตอบสนองเริ่มต้น res.setHeader('Content-Type', 'application/json; charset=utf-8') // ตัวอย่างเส้นทางหลายเส้นทาง con con = [ ['/gzip', () => { ถ้า (acceptEncoding.includes('gzip')) { res.setHeader('การเข้ารหัสเนื้อหา', 'gzip') // ใช้ API การซิงโครไนซ์เพื่อบีบอัดเนื้อหาข้อความโดยตรง res.end(zlib.gzipSync(Buffer.from(testTxt))) กลับ - res.end(testTxt) - ['/ยุบ', () => { ถ้า (acceptEncoding.includes('deflate')) { res.setHeader('การเข้ารหัสเนื้อหา', 'ยุบ') // การดำเนินการเดี่ยวตามสตรีม const originStream = new PassThrough() originStream.write (บัฟเฟอร์จาก (testTxt)) originStream.pipe(zlib.createDeflate()).ไปป์(res) originStream.end() กลับ - res.end(testTxt) - ['/br', () => { ถ้า (acceptEncoding.includes('br')) { res.setHeader('การเข้ารหัสเนื้อหา', 'br') res.setHeader('ประเภทเนื้อหา', 'ข้อความ/html; charset=utf-8') // การดำเนินการเขียนหลายรายการตามสตรีม const originStream = new PassThrough() ไปป์ไลน์ (originStream, zlib.createBrotliCompress (), res, (ผิดพลาด) => { ถ้า (ผิดพลาด) { console.error(ผิดพลาด); - - originStream.write(Buffer.from('<h1>BrotliCompress</h1>')) originStream.write(Buffer.from('<h2>ทดสอบข้อมูล</h2>')) originStream.write (บัฟเฟอร์จาก (testTxt)) originStream.end() กลับ - res.end(testTxt) - - เส้นทาง const = เส้นทาง.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> - res.end() - แอพฟัง (3000)