Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 133 additions & 2 deletions src/createServer.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,139 @@
'use strict';

const http = require('http');
const zlib = require('zlib');
const path = require('path');
const fs = require('fs');
const { pipeline } = require('stream');
const formidable = require('formidable');

/**
* Creates an HTTP server that handles file compression requests.
* @returns {http.Server} The configured HTTP server.
*/
function createServer() {
/* Write your code here */
// Return instance of http.Server class
const server = http.createServer((req, res) => {
// Обробляємо запит на головну сторінку
if (req.method === 'GET' && req.url === '/') {
const indexPath = path.join(__dirname, 'index.html');

fs.readFile(indexPath, (err, data) => {
if (err) {
res.writeHead(404, 'Not Found', { 'Content-Type': 'text/plain' });
res.end('404 Not Found: index.html not found');

return;
}
res.writeHead(200, 'OK', { 'Content-Type': 'text/html' });
res.end(data);
});

return;
}

// Обробляємо неіснуючі маршрути (404)
if (req.url !== '/compress') {
res.writeHead(404, 'Not Found', { 'Content-Type': 'text/plain' });
res.end('404 Not Found');

return;
}

// Обробляємо GET-запити до /compress (400)
if (req.method === 'GET') {
res.writeHead(400, 'Bad Request', { 'Content-Type': 'text/plain' });
res.end('400 Bad Request: Use POST method to /compress');

return;
}

// Обробляємо POST-запит до /compress
const form = new formidable.IncomingForm({
multiples: false,
keepExtensions: true,
});

form.parse(req, (err, fields, files) => {
if (err) {
res.writeHead(400, 'Bad Request', { 'Content-Type': 'text/plain' });
res.end('400 Bad Request: Error parsing form');

return;
}

// Перевіряємо валідність форми
const compressionType = Array.isArray(fields.compressionType)
? fields.compressionType[0]
: fields.compressionType;
const file = Array.isArray(files.file) ? files.file[0] : files.file;

if (!compressionType || !file) {
res.writeHead(400, 'Bad Request', { 'Content-Type': 'text/plain' });

res.end(
'400 Bad Request: Missing required fields (file or compressionType)',
);

return;
}

const supportedTypes = ['gzip', 'deflate', 'br'];

if (!supportedTypes.includes(compressionType)) {
res.writeHead(400, 'Bad Request', { 'Content-Type': 'text/plain' });
res.end('400 Bad Request: Unsupported compression type');

return;
}

const originalName = path.basename(
file.originalFilename || 'unknown.txt',
);
const extension =
compressionType === 'gzip'
? '.gzip'
: compressionType === 'deflate'
? '.deflate'
: '.br';
const compressedFileName = `${originalName}${extension}`;

// Визначаємо метод стиснення
let compressor;

if (compressionType === 'gzip') {
compressor = zlib.createGzip();
} else if (compressionType === 'deflate') {
compressor = zlib.createDeflate();
} else {
compressor = zlib.createBrotliCompress();
}

// Налаштовуємо заголовки для скачування файлу без лапок
res.writeHead(200, 'OK', {
'Content-Type': 'application/octet-stream',
'Content-Disposition': `attachment; filename=${compressedFileName}`, // Видалено лапки
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue: The filename in the Content-Disposition header is not wrapped in quotes. According to RFC 6266, if the filename contains spaces or special characters, it should be quoted. If your task or checklist requires strict compliance with RFC or handling of filenames with spaces, consider wrapping the filename in double quotes.

});

// Створюємо потоки для читання, стиснення і запису у відповідь
const readStream = fs.createReadStream(file.filepath);

// eslint-disable-next-line no-shadow
pipeline(readStream, compressor, res, (err) => {
if (err) {
if (!res.writableEnded) {
res.writeHead(500, 'Internal Server Error', {
'Content-Type': 'text/plain',
});
res.end('500 Internal Server Error');
}
} else if (!res.writableEnded) {
res.end(); // Гарантуємо закриття відповіді
}
});
});
});

return server;
}

module.exports = {
Expand Down
28 changes: 28 additions & 0 deletions src/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>File Compression App</title>
</head>
<body>
<h1>File Compression App</h1>
<form action="/compress" method="POST" enctype="multipart/form-data">
<div>
<label for="file">Select a file to compress:</label>
<input type="file" id="file" name="file" required>
</div>
<div>
<label for="compressionType">Choose compression type:</label>
<select id="compressionType" name="compressionType" required>
<option value="gzip">Gzip (.gz)</option>
<option value="deflate">Deflate (.dfl)</option>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Issue: The Deflate option displays the extension as '.dfl', but the server uses '.deflate' as the extension. If your task or checklist requires consistency between the UI and server-side file extensions, consider updating this to '.deflate'.

<option value="br">Brotli (.br)</option>
</select>
</div>
<div>
<button type="submit">Compress File</button>
</div>
</form>
</body>
</html>