Skip to content
Open
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
109 changes: 104 additions & 5 deletions src/createServer.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,109 @@
'use strict';

const http = require('http');
const zlib = require('zlib');
const { pipeline, Readable } = require('stream');

function createServer() {
/* Write your code here */
// Return instance of http.Server class
return http.createServer((req, res) => {
const url = new URL(req.url, `http://${req.headers.host}`);

if (url.pathname === '/' && req.method === 'GET') {
res.statusCode = 200;

return res.end('OK');
}

if (url.pathname !== '/compress') {
res.statusCode = 404;

return res.end('Not Found');
}

if (req.method === 'GET') {
res.statusCode = 400;

return res.end('Bad Request');
}

// Збираємо тіло запиту в один Buffer (бінарно)
const chunks = [];

req.on('data', (chunk) => chunks.push(chunk));

req.on('end', () => {
const fullBody = Buffer.concat(chunks);
// Перетворюємо в рядок тільки для пошуку метаданих,
// але сам файл будемо витягувати як Buffer
const bodyString = fullBody.toString('binary');

// 1. Витягуємо тип стиснення
// Шукаємо рядок після name="compressionType"
const compMatch = bodyString.match(/name="compressionType"\r\n\r\n(\w+)/);
const compressionType = compMatch ? compMatch[1].trim() : null;

// 2. Витягуємо файл та його назву
// Шукаємо назву файлу
const filenameMatch = bodyString.match(/name="file"; filename="(.+?)"/);
const filename = filenameMatch ? filenameMatch[1] : null;

// Знаходимо початок і кінець вмісту файлу
// Вміст файлу починається після \r\n\r\n після заголовків частини
const fileHeaderMarker = 'name="file"';
const headerEndIndex =
bodyString.indexOf('\r\n\r\n', bodyString.indexOf(fileHeaderMarker)) +
4;

// Кінець файлу — це наступний розділювач (починається з --)
const boundary = req.headers['content-type'].split('boundary=')[1];
const footerStartIndex =
bodyString.indexOf('--' + boundary, headerEndIndex) - 2; // -2 для \r\n

if (
!compressionType ||
!filename ||
headerEndIndex < 4 ||
footerStartIndex < 0
) {
res.statusCode = 400;

return res.end('Bad Request: Invalid Form');
}

const fileContent = fullBody.slice(headerEndIndex, footerStartIndex);

// 3. Налаштовуємо стиснення
let compressStream;

if (compressionType === 'gzip') {
compressStream = zlib.createGzip();
} else if (compressionType === 'deflate') {
compressStream = zlib.createDeflate();
} else if (compressionType === 'br') {
compressStream = zlib.createBrotliCompress();
} else {
res.statusCode = 400;

return res.end('Unsupported compression type');
}

res.setHeader(
'Content-Disposition',
`attachment; filename=${filename}.${compressionType}`,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The filename extension doesn't fully match the requirements. According to the description, gzip compression should result in a .gz extension, and deflate in a .dfl extension. Currently, the compressionType value is used directly, which creates incorrect extensions like .gzip and .deflate.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I tried using short extensions (.gz, .dfl) as you suggested, but the automated tests fail because they expect full names (.gzip, .deflate) that match the compressionType field. So I returned the full names so the tests would pass.

);
res.statusCode = 200;

// Пускаємо потік: Buffer -> Stream -> Zlib -> Response
pipeline(Readable.from(fileContent), compressStream, res, (err) => {
if (err) {
if (!res.headersSent) {
res.statusCode = 500;
res.end();
}
}
});
});
});
}

module.exports = {
createServer,
};
module.exports = { createServer };