From 503a886585d1ccbabedafa41df29de070b05caea Mon Sep 17 00:00:00 2001 From: nikolay Date: Thu, 19 Mar 2026 12:35:49 +0100 Subject: [PATCH 1/6] task complete --- src/createServer.js | 131 +++++++++++++++++++++++++++++++++++++++++- src/public/index.html | 56 ++++++++++++++++++ src/public/script.js | 8 +++ src/public/style.css | 128 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 321 insertions(+), 2 deletions(-) create mode 100644 src/public/index.html create mode 100644 src/public/script.js create mode 100644 src/public/style.css diff --git a/src/createServer.js b/src/createServer.js index 1cf1dda..42d084c 100644 --- a/src/createServer.js +++ b/src/createServer.js @@ -1,8 +1,135 @@ 'use strict'; +const fs = require('fs'); +const path = require('path'); +const mime = require('mime-types'); +const formidable = require('formidable'); +const zlib = require('zlib'); +const { Server } = require('node:http'); + +const filesRoute = { + '/index.html': '/index.html', + '/style.css': '/style.css', + '/script.js': '/script.js', +}; +const compressionMap = new Map([ + ['gzip', () => zlib.createGzip()], + ['deflate', () => zlib.createDeflate()], + ['br', () => zlib.createBrotliCompress()], +]); + +const extensionMap = { + gzip: 'gzip', + deflate: 'deflate', + br: 'br', +}; + +const handleStaticFiles = (req, res) => { + const url = new URL(req.url || '', `http://${req.headers.host}`); + const requestPath = + url.pathname === '/' ? 'index.html' : url.pathname.slice(1); + const realPath = path.join(__dirname, 'public', requestPath); + + if (fs.existsSync(realPath)) { + const mimeType = mime.contentType(path.extname(realPath)) || 'text/plain'; + + res.writeHead(200, { 'Content-Type': mimeType }); + + fs.createReadStream(realPath) + .on('error', (err) => { + if (err) { + res.statusCode = 500; + res.end('Internal Server Error'); + } + }) + .pipe(res); + } else { + res.statusCode = 400; + res.end('Bad request: Page not found'); + } +}; + +const handleFileCompress = (req, res) => { + const form = new formidable.IncomingForm(); + + form.parse(req, (err, fields, files) => { + if (err) { + res.writeHead(400, { 'Content-Type': 'text/plain' }); + res.end('Bad request: Form data is invalid'); + + return; + } + + const compression = Array.isArray(fields.compressionType) + ? fields.compressionType[0] + : fields.compressionType; + + const file = Array.isArray(files.file) ? files.file[0] : files.file; + + if (!file || !compression || compression === '') { + res.writeHead(400, { 'Content-Type': 'text/plain' }); + res.end('Bad request: File or compression missing'); + + return; + } + + if (!compressionMap.has(compression)) { + res.writeHead(400, { 'Content-Type': 'text/plain' }); + res.end('Bad request: Unsupported compression type'); + + return; + } + + const filePath = file.filepath || file.path; + const fileName = file.originalFilename || file.name; + + const compressStream = compressionMap.get(compression)(); + const ext = extensionMap[compression]; + + res.writeHead(200, { + 'Content-Type': 'application/octet-stream', + 'Content-Disposition': `attachment; filename=${fileName}.${ext}`, + }); + + fs.createReadStream(filePath) + .on('error', () => { + if (!res.headersSent) { + res.writeHead(500); + res.end('Internal Server Error'); + } + }) + .pipe(compressStream) + .pipe(res); + }); +}; + function createServer() { - /* Write your code here */ - // Return instance of http.Server class + const server = new Server(); + + server.on('request', (req, res) => { + const url = new URL(req.url, `http://${req.headers.host || 'localhost'}`); + const pathname = url.pathname; + + if (filesRoute[pathname] || pathname === '/') { + return handleStaticFiles(req, res); + } + + if (req.method === 'POST' && pathname === '/compress') { + return handleFileCompress(req, res); + } + + if (req.method === 'GET' && pathname === '/compress') { + res.writeHead(400, { 'Content-Type': 'text/plain' }); + res.end(''); + + return; + } + + res.writeHead(404, { 'Content-Type': 'text/plain' }); + res.end('Bad request: Page not found'); + }); + + return server; } module.exports = { diff --git a/src/public/index.html b/src/public/index.html new file mode 100644 index 0000000..5e0d04c --- /dev/null +++ b/src/public/index.html @@ -0,0 +1,56 @@ + + + + + + Form + + + +

Compression App

+ +
+
Form
+
+
+ Select a file: + +
+ +
+ Choose a compression: + +
+ + +
+ + + + diff --git a/src/public/script.js b/src/public/script.js new file mode 100644 index 0000000..c7b83bf --- /dev/null +++ b/src/public/script.js @@ -0,0 +1,8 @@ +const input = document.querySelector('.file__input'); +const fileStatus = document.querySelector('.file__status'); + +input.addEventListener('change', () => { + if (input.files.length > 0) { + fileStatus.textContent = input.files[0].name; + } +}); diff --git a/src/public/style.css b/src/public/style.css new file mode 100644 index 0000000..c7b3e5d --- /dev/null +++ b/src/public/style.css @@ -0,0 +1,128 @@ +body { + box-sizing: border-box; + padding-top: 30px; + color: #747474; +} + +.title { + margin: 0; + padding-bottom: 25px; + text-align: center; + font-family: 'Montserrat', Tahoma, Geneva, Verdana, sans-serif; + font-size: 25px; + color: black; +} + +.app { + height: auto; + width: 700px; + margin: 0 auto; + border: 1px solid #dedbde; + border-radius: 6px; +} + +.app__top { + display: flex; + width: auto; + height: 40px; + padding-left: 20px; + font-size: 20px; + align-items: center; + color: #34aced; + background-color: #f2f2f2; + border-radius: 6px; +} + +.app__content { + display: flex; + flex-direction: column; + gap: 25px; + margin-block: 20px; + padding: 15px; +} + +.file__section { + display: flex; + flex-direction: column; + gap: 8px; +} + +.file__title { + font-size: 17px; +} + +.file__wrapper { + display: flex; + justify-content: space-between; + align-items: center; + width: auto; + height: 35px; + border: 1px solid #dcdcdc; + border-radius: 3px; + transition: all .3s ease; +} + +.file__wrapper:hover { + cursor: pointer; + border-color: #d0cfcf; +} + +.file__input { + display: none; +} + +.file__status { + padding-left: 25px; +} + +.file__browse { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + width: 80px; + background-color: #f5f5f5; + border-left: 1px solid #dcdcdc; +} + +.type__section { + display: flex; + flex-direction: column; + gap: 8px; +} + +.type__title { + font-size: 17px; +} + +.type__select { + width: 100px; + height: 30px; + border: 1px solid #dcdcdc; + border-radius: 3px; + transition: all .3s ease; +} + +.type__select:hover { + cursor: pointer; + border-color: #d0cfcf; +} + +.submit__button { + margin-top: 10px; + height: 35px; + width: 150px; + font-family: inherit; + font-size: 19px; + letter-spacing: 0.7px; + color: inherit; + background-color: #f5f5f5; + border: 1px solid #dcdcdc; + border-radius: 3px; + transition: all .3s ease; +} + +.submit__button:hover { + cursor: pointer; + border-color: #d0cfcf; +} From 5af09665e52b54e8de08dbfd75fbea8063853d40 Mon Sep 17 00:00:00 2001 From: nikolay Date: Thu, 19 Mar 2026 13:39:43 +0100 Subject: [PATCH 2/6] task complete --- src/createServer.js | 6 +++--- src/public/index.html | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/createServer.js b/src/createServer.js index 42d084c..dca634a 100644 --- a/src/createServer.js +++ b/src/createServer.js @@ -44,8 +44,8 @@ const handleStaticFiles = (req, res) => { }) .pipe(res); } else { - res.statusCode = 400; - res.end('Bad request: Page not found'); + res.statusCode = 404; + res.end('Not found'); } }; @@ -126,7 +126,7 @@ function createServer() { } res.writeHead(404, { 'Content-Type': 'text/plain' }); - res.end('Bad request: Page not found'); + res.end('Not found'); }); return server; diff --git a/src/public/index.html b/src/public/index.html index 5e0d04c..d031273 100644 --- a/src/public/index.html +++ b/src/public/index.html @@ -50,7 +50,7 @@

Compression App

Submit - + From 082c164f3aad05f35534f806b1fd88c74394f7c9 Mon Sep 17 00:00:00 2001 From: nikolay Date: Thu, 19 Mar 2026 14:25:09 +0100 Subject: [PATCH 3/6] task complete --- src/createServer.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/createServer.js b/src/createServer.js index dca634a..57fe285 100644 --- a/src/createServer.js +++ b/src/createServer.js @@ -19,8 +19,8 @@ const compressionMap = new Map([ ]); const extensionMap = { - gzip: 'gzip', - deflate: 'deflate', + gzip: 'gz', + deflate: 'dfl', br: 'br', }; From d9bc9051388ff604375f336cfb783673a7dc1f76 Mon Sep 17 00:00:00 2001 From: nikolay Date: Thu, 19 Mar 2026 14:27:21 +0100 Subject: [PATCH 4/6] task complete --- src/createServer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/createServer.js b/src/createServer.js index 57fe285..5907069 100644 --- a/src/createServer.js +++ b/src/createServer.js @@ -75,7 +75,7 @@ const handleFileCompress = (req, res) => { if (!compressionMap.has(compression)) { res.writeHead(400, { 'Content-Type': 'text/plain' }); - res.end('Bad request: Unsupported compression type'); + res.end('Bad request: Unsupported compression type'); return; } From a44c81c8cc084c308e5cd5de7a431fc9e07085e6 Mon Sep 17 00:00:00 2001 From: nikolay Date: Fri, 20 Mar 2026 13:29:27 +0100 Subject: [PATCH 5/6] task complete --- src/public/index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/src/public/index.html b/src/public/index.html index d031273..448a8aa 100644 --- a/src/public/index.html +++ b/src/public/index.html @@ -24,6 +24,7 @@

Compression App

type="file" name="file" /> + File is not selected Browse... From d9bed36c0aacb1560246a35bfafcf5f2267d9236 Mon Sep 17 00:00:00 2001 From: nikolay Date: Fri, 20 Mar 2026 13:32:25 +0100 Subject: [PATCH 6/6] task complete --- src/public/index.html | 1 - 1 file changed, 1 deletion(-) diff --git a/src/public/index.html b/src/public/index.html index 448a8aa..d031273 100644 --- a/src/public/index.html +++ b/src/public/index.html @@ -24,7 +24,6 @@

Compression App

type="file" name="file" /> - File is not selected Browse...