diff --git a/src/createServer.js b/src/createServer.js index 1cf1dda..5907069 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: 'gz', + deflate: 'dfl', + 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 = 404; + res.end('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('Not found'); + }); + + return server; } module.exports = { diff --git a/src/public/index.html b/src/public/index.html new file mode 100644 index 0000000..d031273 --- /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; +}