From ac8e91be04138cac81693c09831b504b540054f9 Mon Sep 17 00:00:00 2001 From: Yana Date: Thu, 13 Aug 2026 19:52:18 +0300 Subject: [PATCH] Solution --- .github/workflows/test.yml-template | 23 ++++ package-lock.json | 9 +- package.json | 2 +- src/createServer.js | 176 +++++++++++++++++++++++++++- 4 files changed, 203 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/test.yml-template diff --git a/.github/workflows/test.yml-template b/.github/workflows/test.yml-template new file mode 100644 index 0000000..bb13dfc --- /dev/null +++ b/.github/workflows/test.yml-template @@ -0,0 +1,23 @@ +name: Test + +on: + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [20.x] + + steps: + - uses: actions/checkout@v2 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 + with: + node-version: ${{ matrix.node-version }} + - run: npm install + - run: npm test diff --git a/package-lock.json b/package-lock.json index d0b3b95..3650660 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "devDependencies": { "@faker-js/faker": "^8.4.1", "@mate-academy/eslint-config": "latest", - "@mate-academy/scripts": "^1.8.6", + "@mate-academy/scripts": "^2.1.3", "axios": "^1.7.2", "eslint": "^8.57.0", "eslint-plugin-jest": "^28.6.0", @@ -1487,10 +1487,11 @@ } }, "node_modules/@mate-academy/scripts": { - "version": "1.8.6", - "resolved": "https://registry.npmjs.org/@mate-academy/scripts/-/scripts-1.8.6.tgz", - "integrity": "sha512-b4om/whj4G9emyi84ORE3FRZzCRwRIesr8tJHXa8EvJdOaAPDpzcJ8A0sFfMsWH9NUOVmOwkBtOXDu5eZZ00Ig==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@mate-academy/scripts/-/scripts-2.1.3.tgz", + "integrity": "sha512-a07wHTj/1QUK2Aac5zHad+sGw4rIvcNl5lJmJpAD7OxeSbnCdyI6RXUHwXhjF5MaVo9YHrJ0xVahyERS2IIyBQ==", "dev": true, + "license": "MIT", "dependencies": { "@octokit/rest": "^17.11.2", "@types/get-port": "^4.2.0", diff --git a/package.json b/package.json index 1d03d64..8e6392d 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "devDependencies": { "@faker-js/faker": "^8.4.1", "@mate-academy/eslint-config": "latest", - "@mate-academy/scripts": "^1.8.6", + "@mate-academy/scripts": "^2.1.3", "axios": "^1.7.2", "eslint": "^8.57.0", "eslint-plugin-jest": "^28.6.0", diff --git a/src/createServer.js b/src/createServer.js index 1cf1dda..0e54a1d 100644 --- a/src/createServer.js +++ b/src/createServer.js @@ -1,8 +1,180 @@ 'use strict'; +const http = require('http'); +const zlib = require('zlib'); +const { 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 || 'localhost'}`); + const path = url.pathname; + + if (path === '/') { + if (req.method !== 'GET') { + res.statusCode = 404; + res.end('Not found'); + + return; + } + + res.writeHead(200, { 'Content-Type': 'text/html' }); + + res.end(` + + + Compression App + +
+ + + +
+ + + `); + + return; + } + + if (path === '/compress') { + if (req.method === 'GET') { + res.statusCode = 400; + res.end('Bad Request'); + + return; + } + + if (req.method === 'POST') { + const contentType = req.headers['content-type'] || ''; + + if (!contentType.includes('multipart/form-data')) { + res.statusCode = 400; + res.end('Invalid form'); + + return; + } + + const match = contentType.match(/boundary=(?:"([^"]+)"|([^;]+))/); + const boundary = match ? match[1] || match[2] : null; + + if (!boundary) { + res.statusCode = 400; + res.end('Invalid form'); + + return; + } + + const bodyChunks = []; + + req.on('data', (chunk) => bodyChunks.push(chunk)); + + req.on('end', () => { + const buffer = Buffer.concat(bodyChunks); + const boundaryBuffer = Buffer.from('--' + boundary); + + const parts = []; + let start = buffer.indexOf(boundaryBuffer); + + while (start !== -1) { + start += boundaryBuffer.length; + + const next = buffer.indexOf(boundaryBuffer, start); + + if (next === -1) { + break; + } + parts.push(buffer.slice(start, next)); + start = next; + } + + let fileData = null; + let fileName = null; + let compressionType = null; + + for (const part of parts) { + let offset = 0; + + if (part[0] === 13 && part[1] === 10) { + offset = 2; + } // пропускаємо \r\n + + const headerEnd = part.indexOf(Buffer.from('\r\n\r\n'), offset); + + if (headerEnd !== -1) { + const headers = part.slice(offset, headerEnd).toString(); + let content = part.slice(headerEnd + 4); + + if ( + content.length >= 2 && + content[content.length - 2] === 13 && + content[content.length - 1] === 10 + ) { + content = content.slice(0, content.length - 2); + } + + if (headers.includes('name="file"')) { + const nameMatch = headers.match(/filename="([^"]+)"/); + + if (nameMatch) { + fileName = nameMatch[1]; + fileData = content; + } + } else if (headers.includes('name="compressionType"')) { + compressionType = content.toString().trim(); + } + } + } + + if (!fileData || !fileName || !compressionType) { + res.statusCode = 400; + res.end('Invalid form'); + + return; + } + + let compressor; + let ext; + + if (compressionType === 'gzip') { + compressor = zlib.createGzip(); + ext = '.gz'; + } else if (compressionType === 'deflate') { + compressor = zlib.createDeflate(); + ext = '.dfl'; + } else if (compressionType === 'br') { + compressor = zlib.createBrotliCompress(); + ext = '.br'; + } else { + res.statusCode = 400; + res.end('Unsupported compression'); + + return; + } + + res.writeHead(200, { + 'Content-Disposition': `attachment; filename=${fileName}${ext}`, + 'Content-Type': 'application/octet-stream', + }); + + Readable.from(fileData).pipe(compressor).pipe(res); + }); + + req.on('error', () => { + res.statusCode = 400; + res.end('Error reading request'); + }); + + return; + } + } + + res.statusCode = 404; + res.end('Not Found'); + }); } module.exports = {