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
106 changes: 104 additions & 2 deletions src/createServer.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,110 @@
'use strict';

const http = require('http');
const fs = require('fs');
const zlib = require('zlib');
const formidable = require('formidable');

function createServer() {
/* Write your code here */
// Return instance of http.Server class
return http.createServer((req, res) => {
if (req.url === '/' && req.method === 'GET') {
res.writeHead(200, { 'Content-Type': 'text/html' });

res.end(`
<form action="/compress" method="POST" enctype="multipart/form-data">
<input type="file" name="file" required />
<select name="compressionType" required>
<option value="gzip">gzip</option>
<option value="deflate">deflate</option>
<option value="br">br</option>
</select>
<button type="submit">Compress</button>
</form>
Comment on lines +14 to +22

Choose a reason for hiding this comment

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

Positive: the HTML form at / contains a file input and a select named compressionType with the exact options gzip, deflate, br, and submits via POST to /compress (lines 14–22). That satisfies the front-end checklist items for the form. Keep this as-is.

`);
Comment on lines +13 to +23

Choose a reason for hiding this comment

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

The HTML form served at / correctly uses name="file" and name="compressionType" with options gzip, deflate, br, and sends POST to /compress with enctype="multipart/form-data". The form itself meets the task requirements — ensure the server parsing is adjusted to match these exact field names (see parsing comment above).

} else if (req.url === '/compress' && req.method === 'POST') {
const form = new formidable.IncomingForm();

form.parse(req, (error, fields, files) => {
if (error) {
res.statusCode = 400;

return res.end('Error parsing form');
}

const compressionType = String(fields.compressionType || '')
.trim()
.toLowerCase();

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

if (!supported.includes(compressionType)) {
res.statusCode = 400;

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

const fileData = files.file?.[0];

if (!fileData) {
res.statusCode = 400;

return res.end('Error parsing file');
}

const uploadedFile = files.file?.[0];
const uploadedFilePath = uploadedFile?.filepath;

const originalNameSafe = uploadedFile.originalFilename || 'file';
Comment on lines +46 to +57

Choose a reason for hiding this comment

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

File extraction: files.file is a single file object, not an array. The code uses files.file?.[0] at lines 46 and 54 and duplicates variables (fileData and uploadedFile). Use a single normalized value such as:

const fileData = files.file; // or files.file[0] only if your formidable version actually returns an array
const uploadedFilePath = fileData && (fileData.filepath || fileData.path);
const originalNameSafe = fileData && (fileData.originalFilename || fileData.originalname || 'file');

Then validate uploadedFilePath. This fixes missing-file validation and ensures you create the read stream from the correct path (required behavior).

Comment on lines +46 to +57

Choose a reason for hiding this comment

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

Incorrect access to the uploaded file object. You use files.file?.[0] in multiple places (lines 46 and 54). Formidable normally provides a single file object at files.file (not an array), so indexing with [0] can make fileData undefined and break validation. Also originalFilename is read without ensuring the file object exists (line 57). This violates the requirement to reliably parse multipart form fields and the uploaded file. Consider normalizing once, e.g. const uploadedFile = files.file; const uploadedFilePath = uploadedFile && (uploadedFile.filepath || uploadedFile.path); const originalNameSafe = uploadedFile?.originalFilename || 'file'; and then validate uploadedFilePath/originalNameSafe. This will avoid unsafe indexing and satisfy the form parsing checklist.


if (!uploadedFilePath) {
res.statusCode = 400;

return res.end('File path not found');
}

let compressStream;

switch (compressionType) {
case 'gzip':
compressStream = zlib.createGzip();
break;
case 'deflate':
compressStream = zlib.createDeflate();
break;
case 'br':
compressStream = zlib.createBrotliCompress();
break;
}

const readStream = fs.createReadStream(uploadedFilePath);

readStream.on('error', () => {
res.statusCode = 500;
res.end('Error reading file');
});

compressStream.on('error', () => {
res.statusCode = 500;
res.end('Error compressing file');
});

res.writeHead(200, {
'Content-Type': 'application/octet-stream',
'Content-Disposition': `attachment; filename=${originalNameSafe}.${compressionType}`,
Comment on lines +91 to +93

Choose a reason for hiding this comment

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

Incorrect output filename extension in Content-Disposition (line 96). You currently use filename=${originalNameSafe}.${compressionType}, e.g. file.txt.gzip. The task requires explicit mapping to extensions: gzip -> .gz, deflate -> .dfl, br -> .br. Add a mapping and use it when building the filename, e.g. const extMap = { gzip: '.gz', deflate: '.dfl', br: '.br' }; const outName = originalNameSafe + extMap[compressionType]; and wrap filename in quotes: attachment; filename="${outName}". This ensures the client receives the correctly named file.

});
Comment on lines +91 to +94

Choose a reason for hiding this comment

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

Filename extension mapping is incorrect. The response currently appends the raw compressionType (e.g. .gzip or .deflate) at line 93. The requirement is to append specific extensions: gzip -> .gz, deflate -> .dfl, br -> .br. Add a mapping and use it when setting Content-Disposition, for example:

const extMap = { gzip: 'gz', deflate: 'dfl', br: 'br' };
const ext = extMap[compressionType];
'Content-Disposition': `attachment; filename="${originalNameSafe}.${ext}"`,

Update the header so the client receives the correctly named file.


readStream.pipe(compressStream).pipe(res);
Comment on lines +79 to +96

Choose a reason for hiding this comment

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

Use stream.pipeline (or equivalent) and delay sending success headers until streaming starts. Current code creates readStream, attaches separate error handlers and calls res.writeHead(200) before piping. If an error occurs later headers are already sent and you cannot change the status code. Replace the manual pipe with:

const { pipeline } = require('stream');
res.setHeader('Content-Type', 'application/octet-stream');
res.setHeader('Content-Disposition', `attachment; filename="${originalNameSafe}.${ext}"`);
pipeline(readStream, compressStream, res, (err) => {
  if (err) {
    // if headers not sent, send 500; otherwise destroy response
    if (!res.headersSent) {
      res.statusCode = 500;
      res.end('Error processing file');
    } else {
      res.destroy(err);
    }
  }
});

This ensures robust error handling for streams and prevents the server from crashing (required).

Comment on lines +79 to +96

Choose a reason for hiding this comment

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

Fragile stream handling: currently you attach separate error handlers to readStream and compressStream and call readStream.pipe(compressStream).pipe(res) (lines 82–99). This can lead to a 200 response already sent even if the pipeline later fails. Use stream.pipeline(readStream, compressStream, res, cb) from the stream module to centralize error handling. Example approach:

  • Build readStream and compressStream.
  • Set response headers (Content-Type and Content-Disposition) before piping.
  • Call pipeline(readStream, compressStream, res, (err) => { if (err) { if (!res.headersSent) { res.writeHead(500); res.end('Error compressing file'); } else { res.destroy(); } } });

This ensures stream errors are handled properly and the server does not incorrectly return 200 on failure, meeting the streams and error-handling requirements.

});
} else if (req.url === '/compress') {
res.statusCode = 400;

return res.end('Only POST allowed on /compress');
} else {
res.statusCode = 404;

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

module.exports = {
Expand Down