Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
133 changes: 131 additions & 2 deletions src/createServer.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,137 @@
'use strict';

const fs = require('fs');
const path = require('path');
const http = require('http');
const zlib = require('node:zlib');
const { IncomingForm } = require('formidable');

const compressionTypes = {
gzip: {
compress: zlib.gzip,
extension: 'gz',
},
deflate: {
compress: zlib.deflate,
extension: 'dfl',
},
br: {
compress: zlib.brotliCompress,
extension: 'br',
},
};
Comment on lines +9 to +22
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 compressionTypes object maps to zlib.gzip, zlib.deflate, and zlib.brotliCompress (buffer-based functions). To comply with the Streams requirement (checklist item #11) replace these with the streaming compressors (zlib.createGzip, zlib.createDeflate, zlib.createBrotliCompress) and update the request handling to pipe streams instead of compressing buffers.


function sendHtml(response) {
const filePath = path.join(__dirname, 'index.html');

fs.readFile(filePath, 'utf8', (error, content) => {
if (error) {
response.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
response.end('Internal Server Error');

return;
}

response.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
response.end(content);
});
}

function sendBadRequest(response) {
response.writeHead(400, { 'Content-Type': 'text/plain; charset=utf-8' });
response.end('Bad Request');
}

function sendNotFound(response) {
response.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
response.end('Not Found');
}

function createServer() {
/* Write your code here */
// Return instance of http.Server class
return http.createServer((request, response) => {
const { method, url } = request;

if (method === 'GET' && url === '/') {
sendHtml(response);

return;
}

if (method === 'GET' && url === '/compress') {
sendBadRequest(response);

return;
}

if (method === 'POST' && url === '/compress') {
const form = new IncomingForm({ multiples: false });

form.parse(request, async (error, fields, files) => {
if (error) {
sendBadRequest(response);

return;
}

const compressionType = Array.isArray(fields.compressionType)
? fields.compressionType[0]
: fields.compressionType;
const file = Array.isArray(files.file) ? files.file[0] : files.file;

if (!file || !compressionType) {
sendBadRequest(response);

return;
}

if (!compressionTypes[compressionType]) {
sendBadRequest(response);

return;
}

const filePath = file.filepath || file.file?.path || file.path;
const filename =
file.originalFilename ||
file.name ||
path.basename(filePath || 'unknown');

if (!filePath || !filename) {
sendBadRequest(response);

return;
}

fs.readFile(filePath, (readError, fileBuffer) => {
if (readError) {
sendBadRequest(response);

return;
}

const { compress, extension } = compressionTypes[compressionType];

compress(fileBuffer, (compressError, compressed) => {
if (compressError) {
sendBadRequest(response);

return;
}

response.writeHead(200, {
'Content-Type': 'application/octet-stream',
'Content-Disposition': `attachment; filename=${filename}.${extension}`,
});
response.end(compressed);
});
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 server reads the entire uploaded file into memory using fs.readFile and then calls the non-stream zlib functions. This doesn't meet checklist item #11 which requires using Streams. Consider using fs.createReadStream(filePath) and piping it through the appropriate zlib transform (zlib.createGzip(), zlib.createDeflate(), or zlib.createBrotliCompress()) into the HTTP response to stream the compressed data.

});
});

return;
}

sendNotFound(response);
});
}

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>Compression App</title>
</head>
<body>
<h1>Compress a file</h1>
<form action="/compress" method="post" enctype="multipart/form-data">
<label>
Choose file:
<input type="file" name="file" required />
</label>
<br />
<label>
Compression type:
<select name="compressionType" required>
<option value="gzip">gzip</option>
<option value="deflate">deflate</option>
<option value="br">br</option>
</select>
Comment on lines +9 to +22
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

compressionTypes maps compress to the callback-based zlib functions (zlib.gzip, zlib.deflate, zlib.brotliCompress). To use streams you should instead store the stream factory functions (zlib.createGzip, zlib.createDeflate, zlib.createBrotliCompress) or otherwise adapt the code to create appropriate transform streams. This change is necessary for implementing streaming compression (checklist item #11).

</label>
<br />
<button type="submit">Compress</button>
</form>
</body>
</html>
Loading