-
Notifications
You must be signed in to change notification settings - Fork 275
feat: implement compression app #180
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,120 @@ | ||
| 'use strict'; | ||
|
|
||
| const http = require('http'); | ||
| const fs = require('fs'); | ||
| const path = require('path'); | ||
| const zlib = require('zlib'); | ||
| const { pipeline } = require('stream'); | ||
| const multiparty = require('multiparty'); | ||
|
|
||
| const SUPPORTED_TYPES = { | ||
| gzip: { ext: '.gzip', method: zlib.createGzip }, | ||
| deflate: { ext: '.deflate', method: zlib.createDeflate }, | ||
| br: { ext: '.br', method: zlib.createBrotliCompress }, | ||
| }; | ||
|
|
||
| function createServer() { | ||
| /* Write your code here */ | ||
| // Return instance of http.Server class | ||
| const server = new http.Server(); | ||
|
|
||
| server.on('request', (req, res) => { | ||
| const url = new URL(req.url, `http://${req.headers.host}`); | ||
|
|
||
| if ( | ||
| req.method === 'GET' && | ||
| (url.pathname === '/' || url.pathname === '/index.html') | ||
| ) { | ||
| res.statusCode = 200; | ||
| res.setHeader('Content-Type', 'text/html'); | ||
|
|
||
| res.end(); | ||
|
|
||
| return; | ||
|
Comment on lines
+22
to
+31
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. GET / and /index.html returns an empty page — serve the HTML form You set |
||
| } | ||
|
|
||
| if (url.pathname === '/compress') { | ||
| if (req.method !== 'POST') { | ||
| res.writeHead(400); | ||
| res.end('Use POST method'); | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| const form = new multiparty.Form(); | ||
|
|
||
| form.parse(req, (err, fields, files) => { | ||
|
Comment on lines
+42
to
+44
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No explicit Content-Type validation for multipart/form-data The requirements mandate returning 400 when |
||
| if (err) { | ||
| res.writeHead(400); | ||
| res.end('Problem with form'); | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| const compressionType = fields.compressionType?.[0]; | ||
| const file = files.file?.[0]; | ||
|
|
||
| if (!compressionType || !file) { | ||
| res.writeHead(400); | ||
| res.end('Incorrectly filled out form'); | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| const compression = SUPPORTED_TYPES[compressionType]; | ||
|
|
||
| if (!compression) { | ||
| res.writeHead(400); | ||
| res.end('Wrong compression type'); | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| const originalFilename = path.basename(file.originalFilename); | ||
| const compressedFilename = originalFilename + compression.ext; | ||
|
Comment on lines
+71
to
+72
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing validation of You call |
||
|
|
||
| res.writeHead(200, { | ||
| 'Content-Type': 'application/octet-stream', | ||
| 'Content-Disposition': `attachment; filename=${compressedFilename}`, | ||
|
Comment on lines
+74
to
+76
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Content-Disposition filename must be quoted and use correct extension The header is set as 'Content-Disposition': `attachment; filename="${compressedFilename}"`Apply this change when you correct the extension mapping. |
||
| }); | ||
|
|
||
| const source = fs.createReadStream(file.path); | ||
| const compress = compression.method(); | ||
|
|
||
| pipeline(source, compress, res, (erro) => { | ||
| fs.unlink(file.path, () => {}); | ||
|
|
||
| if (erro) { | ||
| res.writeHead(400); | ||
| res.end('Compression error'); | ||
| } | ||
| }); | ||
|
Comment on lines
+82
to
+89
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pipeline error handling is fragile — close response when headers already sent; use 500 for server errors In the
Example pattern: pipeline(source, compress, res, (err) => {
fs.unlink(file.path, () => {});
if (err) {
if (res.headersSent) {
res.destroy(err);
} else {
res.writeHead(500);
res.end('Compression error');
}
}
});This ensures the connection does not hang and errors are signaled with appropriate status codes. |
||
| }); | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| const fileName = url.pathname.slice(1) || 'index.html'; | ||
| const filePath = path.resolve('public', fileName); | ||
|
|
||
| if (!fs.existsSync(filePath)) { | ||
| res.statusCode = 404; | ||
| res.end('file not found'); | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| const fileStream = fs.createReadStream(filePath); | ||
|
|
||
| fileStream.pipe(res); | ||
|
|
||
| fileStream.on('error', () => { | ||
| res.statusCode = 500; | ||
| res.end('server error'); | ||
| }); | ||
|
|
||
| res.on('close', () => fileStream.destroy()); | ||
| }); | ||
|
|
||
| return server; | ||
| } | ||
|
|
||
| module.exports = { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Incorrect extension strings — must match requirements
SUPPORTED_TYPESuses.gzipand.deflatebut the task requires exact extensions.gzand.dfl(and.brfor brotli). This will produce incorrect response filenames and fail tests. Update the mapping to exactly:(Requirement: compression mapping: gzip → .gz, deflate → .dfl, br → .br.)