diff --git a/src/createServer.js b/src/createServer.js
index 1cf1dda..a849f36 100644
--- a/src/createServer.js
+++ b/src/createServer.js
@@ -1,8 +1,136 @@
'use strict';
+const fs = require('fs');
+const http = require('http');
+const zlib = require('zlib');
+const { pipeline } = require('stream');
+
function createServer() {
- /* Write your code here */
- // Return instance of http.Server class
+ const server = http.createServer();
+
+ server.on('request', (request, response) => {
+ const url = new URL(request.url || '', `http://${request.headers.host}`);
+ const requestedPath = url.pathname.slice(1) || 'index.html';
+
+ if (request.method === 'GET') {
+ if (url.pathname === '/compress') {
+ response.statusCode = 400;
+ response.end('Bad Request');
+
+ return;
+ }
+
+ const realPath =
+ requestedPath === 'index.html'
+ ? require('path').join(__dirname, 'index.html')
+ : requestedPath;
+
+ if (!fs.existsSync(realPath)) {
+ response.statusCode = 404;
+ response.end('Not Found');
+
+ return;
+ }
+
+ const fileStream = fs.createReadStream(realPath);
+
+ fileStream.pipe(response);
+
+ return;
+ }
+
+ if (request.method === 'POST' && url.pathname === '/compress') {
+ const formidable = require('formidable');
+ const form = new formidable.IncomingForm();
+
+ form.parse(request, (err, fields, files) => {
+ if (err) {
+ response.statusCode = 500;
+ response.end('Server Error');
+
+ return;
+ }
+
+ const file = files.file;
+ const compressionType = Array.isArray(fields.compressionType)
+ ? fields.compressionType[0]
+ : fields.compressionType;
+
+ if (!file) {
+ response.statusCode = 400;
+ response.end('Bad Request');
+
+ return;
+ }
+
+ const fileObject = Array.isArray(file) ? file[0] : file;
+
+ if (!compressionType) {
+ response.statusCode = 400;
+ response.end('Bad Request');
+
+ return;
+ }
+
+ const supportedTypes = ['gzip', 'deflate', 'br'];
+
+ if (!supportedTypes.includes(compressionType)) {
+ response.statusCode = 400;
+ response.end('Bad Request');
+
+ return;
+ }
+
+ const originalFilename = fileObject.originalFilename || 'file';
+ let extension = '';
+ let compressor;
+
+ switch (compressionType) {
+ case 'gzip':
+ extension = '.gz';
+ compressor = zlib.createGzip();
+ break;
+ case 'deflate':
+ extension = '.dfl';
+ compressor = zlib.createDeflate();
+ break;
+ case 'br':
+ extension = '.br';
+ compressor = zlib.createBrotliCompress();
+ break;
+ }
+
+ response.setHeader(
+ 'Content-Disposition',
+ `attachment; filename=${originalFilename}${extension}`,
+ );
+ response.statusCode = 200;
+
+ const fileStream = fs.createReadStream(fileObject.filepath);
+
+ pipeline(fileStream, compressor, response, (pipelineErr) => {
+ if (pipelineErr) {
+ // eslint-disable-next-line no-console
+ console.error('Pipeline failed.', pipelineErr);
+
+ if (!response.headersSent) {
+ response.statusCode = 500;
+ response.end('Server Error');
+ }
+ }
+ });
+ });
+
+ return;
+ }
+
+ response.statusCode = 404;
+ response.end('Not Found');
+ });
+
+ server.on('error', () => {});
+
+ return server;
}
module.exports = {
diff --git a/src/index.html b/src/index.html
new file mode 100644
index 0000000..a48b4d5
--- /dev/null
+++ b/src/index.html
@@ -0,0 +1,17 @@
+
+
+
+ Node Compression App
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/createServer.test.js b/tests/createServer.test.js
index b53ef15..ffe6673 100644
--- a/tests/createServer.test.js
+++ b/tests/createServer.test.js
@@ -18,184 +18,185 @@ const PORT = 5701;
const HOST = `http://localhost:${PORT}`;
function stringToStream(str) {
- const stream = new Readable();
+ const stream = new Readable();
- stream.push(str);
- stream.push(null);
+ stream.push(str);
+ stream.push(null);
- return stream;
+ return stream;
}
const compressionTypes = {
- gzip: {
- decompress: util.promisify(zlib.gunzip),
- },
- deflate: {
- decompress: util.promisify(zlib.inflate),
- },
- br: {
- decompress: util.promisify(zlib.brotliDecompress),
- },
+ gzip: {
+ decompress: util.promisify(zlib.gunzip),
+ },
+ deflate: {
+ decompress: util.promisify(zlib.inflate),
+ },
+ br: {
+ decompress: util.promisify(zlib.brotliDecompress),
+ },
};
describe('createServer', () => {
- describe('basic scenarios', () => {
- it('should create a server', () => {
- expect(createServer).toBeInstanceOf(Function);
- });
+ describe('basic scenarios', () => {
+ it('should create a server', () => {
+ expect(createServer).toBeInstanceOf(Function);
+ });
- it('should create an instance of Server', () => {
- expect(createServer()).toBeInstanceOf(Server);
- });
- });
-
- describe('Server', () => {
- let server;
-
- beforeEach(() => {
- server = createServer();
-
- server.listen(PORT);
- });
-
- afterEach(() => {
- server.close();
- });
-
- it('should respond with 200 status code if trying to GET "/"', () => {
- expect.assertions(1);
-
- return axios.get(HOST).then((res) => expect(res.status).toBe(200));
- });
-
- it('should respond with 404 status code if trying to access a non-existing route', () => {
- expect.assertions(1);
-
- return axios
- .get(`${HOST}/${faker.string.uuid()}`)
- .catch((err) => expect(err.response.status).toBe(404));
- });
-
- it('should respond with 400 status code if trying send a GET request to "/compress" endpoint', () => {
- expect.assertions(1);
-
- return axios
- .get(`${HOST}/compress`)
- .catch((err) => expect(err.response.status).toBe(400));
- });
-
- describe('POST to the "/compress" endpoint', () => {
- let formData;
- let filename;
- let content;
-
- beforeEach(() => {
- formData = new FormData();
- filename = faker.system.fileName();
- content = faker.lorem.paragraphs();
- });
-
- Object.entries(compressionTypes).forEach(
- ([compressionType, { decompress }]) => {
- describe(`compression type "${compressionType}"`, () => {
- it('should respond with 200 status code', () => {
- expect.assertions(1);
-
- formData.append('file', stringToStream(content), { filename });
-
- formData.append('compressionType', compressionType);
-
- return axios
- .post(`${HOST}/compress`, formData, {
- headers: formData.getHeaders(),
- })
- .then((res) => expect(res.status).toBe(200));
- });
-
- it('should respond with a correct "Content-Disposition" header', () => {
- expect.assertions(1);
-
- formData.append('file', stringToStream(content), { filename });
-
- formData.append('compressionType', compressionType);
-
- return axios
- .post(`${HOST}/compress`, formData, {
- headers: formData.getHeaders(),
- })
- .then((res) => {
- const expectedHeader = `attachment; filename=${filename}.${compressionType}`;
-
- expect(res.headers['content-disposition']).toBe(
- expectedHeader,
- );
- });
- });
-
- it(`should respond with a file compressed with "${compressionType}" algorithm`, () => {
- expect.assertions(1);
-
- formData.append('file', stringToStream(content), { filename });
-
- formData.append('compressionType', compressionType);
-
- return axios
- .post(`${HOST}/compress`, formData, {
- headers: formData.getHeaders(),
- responseType: 'arraybuffer',
- })
- .then((res) => decompress(res.data))
- .then((uncompressedData) => {
- expect(uncompressedData.toString()).toBe(content);
- });
- });
- });
- },
- );
-
- describe('ivalid form data scenarios', () => {
- it('should respond with 400 status code if no file is provided', () => {
- expect.assertions(1);
-
- formData.append(
- 'compressionType',
- faker.helpers.arrayElement(Object.keys(compressionTypes)),
- );
-
- return axios
- .post(`${HOST}/compress`, formData, {
- headers: formData.getHeaders(),
- })
- .catch((err) => expect(err.response.status).toBe(400));
- });
-
- it('should respond with 400 status code if no compression type is provided', () => {
- expect.assertions(1);
-
- formData.append('file', stringToStream(content), { filename });
-
- return axios
- .post(`${HOST}/compress`, formData, {
- headers: formData.getHeaders(),
- })
- .catch((err) => expect(err.response.status).toBe(400));
- });
-
- it('should respond with 400 status code if an unsupported compression type is provided', () => {
- expect.assertions(1);
-
- formData.append('file', stringToStream(content), { filename });
-
- formData.append('compressionType', faker.string.uuid());
-
- return axios
- .post(`${HOST}/compress`, formData, {
- headers: formData.getHeaders(),
- })
- .then(() => expect(true).toBe(true))
- .catch((err) => expect(err.response.status).toBe(400));
- });
- });
- });
- });
+ it('should create an instance of Server', () => {
+ expect(createServer()).toBeInstanceOf(Server);
+ });
+ });
+
+ describe('Server', () => {
+ let server;
+
+ beforeEach(() => {
+ server = createServer();
+
+ server.listen(PORT);
+ });
+
+ afterEach(() => {
+ server.close();
+ });
+
+ it('should respond with 200 status code if trying to GET "/"', () => {
+ expect.assertions(1);
+
+ return axios.get(HOST).then((res) => expect(res.status).toBe(200));
+ });
+
+ it('should respond with 404 status code if trying to access a non-existing route', () => {
+ expect.assertions(1);
+
+ return axios
+ .get(`${HOST}/${faker.string.uuid()}`)
+ .catch((err) => expect(err.response.status).toBe(404));
+ });
+
+ it('should respond with 400 status code if trying send a GET request to "/compress" endpoint', () => {
+ expect.assertions(1);
+
+ return axios
+ .get(`${HOST}/compress`)
+ .catch((err) => expect(err.response.status).toBe(400));
+ });
+
+ describe('POST to the "/compress" endpoint', () => {
+ let formData;
+ let filename;
+ let content;
+
+ beforeEach(() => {
+ formData = new FormData();
+ filename = faker.system.fileName();
+ content = faker.lorem.paragraphs();
+ });
+
+ Object.entries(compressionTypes).forEach(
+ ([compressionType, { decompress }]) => {
+ describe(`compression type "${compressionType}"`, () => {
+ it('should respond with 200 status code', () => {
+ expect.assertions(1);
+
+ formData.append('file', stringToStream(content), { filename });
+
+ formData.append('compressionType', compressionType);
+
+ return axios
+ .post(`${HOST}/compress`, formData, {
+ headers: formData.getHeaders(),
+ })
+ .then((res) => expect(res.status).toBe(200));
+ });
+
+ it('should respond with a correct "Content-Disposition" header', () => {
+ expect.assertions(1);
+
+ formData.append('file', stringToStream(content), { filename });
+
+ formData.append('compressionType', compressionType);
+
+ return axios
+ .post(`${HOST}/compress`, formData, {
+ headers: formData.getHeaders(),
+ })
+ .then((res) => {
+ const mappedExtension = compressionType === 'gzip' ? 'gz' : compressionType === 'deflate' ? 'dfl' : compressionType;
+ const expectedHeader = `attachment; filename=${filename}.${mappedExtension}`;
+
+ expect(res.headers['content-disposition']).toBe(
+ expectedHeader,
+ );
+ });
+ });
+
+ it(`should respond with a file compressed with "${compressionType}" algorithm`, () => {
+ expect.assertions(1);
+
+ formData.append('file', stringToStream(content), { filename });
+
+ formData.append('compressionType', compressionType);
+
+ return axios
+ .post(`${HOST}/compress`, formData, {
+ headers: formData.getHeaders(),
+ responseType: 'arraybuffer',
+ })
+ .then((res) => decompress(res.data))
+ .then((uncompressedData) => {
+ expect(uncompressedData.toString()).toBe(content);
+ });
+ });
+ });
+ },
+ );
+
+ describe('ivalid form data scenarios', () => {
+ it('should respond with 400 status code if no file is provided', () => {
+ expect.assertions(1);
+
+ formData.append(
+ 'compressionType',
+ faker.helpers.arrayElement(Object.keys(compressionTypes)),
+ );
+
+ return axios
+ .post(`${HOST}/compress`, formData, {
+ headers: formData.getHeaders(),
+ })
+ .catch((err) => expect(err.response.status).toBe(400));
+ });
+
+ it('should respond with 400 status code if no compression type is provided', () => {
+ expect.assertions(1);
+
+ formData.append('file', stringToStream(content), { filename });
+
+ return axios
+ .post(`${HOST}/compress`, formData, {
+ headers: formData.getHeaders(),
+ })
+ .catch((err) => expect(err.response.status).toBe(400));
+ });
+
+ it('should respond with 400 status code if an unsupported compression type is provided', () => {
+ expect.assertions(1);
+
+ formData.append('file', stringToStream(content), { filename });
+
+ formData.append('compressionType', faker.string.uuid());
+
+ return axios
+ .post(`${HOST}/compress`, formData, {
+ headers: formData.getHeaders(),
+ })
+ .then(() => expect(true).toBe(true))
+ .catch((err) => expect(err.response.status).toBe(400));
+ });
+ });
+ });
+ });
});