-
Notifications
You must be signed in to change notification settings - Fork 218
solution #170
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
solution #170
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,172 @@ | ||
| 'use strict'; | ||
|
|
||
| const http = require('http'); | ||
| const fs = require('fs/promises'); | ||
| const path = require('path'); | ||
|
|
||
| class CustomServer extends http.Server { | ||
| #endpoints = {}; | ||
|
|
||
| get(endpoint, cb) { | ||
| if (!this.#endpoints.hasOwnProperty(endpoint)) { | ||
| this.#endpoints[endpoint] = {}; | ||
| } | ||
|
|
||
| this.#endpoints[endpoint].get = cb; | ||
| } | ||
|
|
||
| post(endpoint, cb) { | ||
| if (!this.#endpoints.hasOwnProperty(endpoint)) { | ||
| this.#endpoints[endpoint] = {}; | ||
| } | ||
|
|
||
| this.#endpoints[endpoint].post = cb; | ||
| } | ||
|
|
||
| useCb(req, res) { | ||
| const normalizeMethod = req.method.toLowerCase(); | ||
| const normalizeUrl = new URL(req.url, `http://${req.headers.host}`); | ||
| const urlPath = normalizeUrl.pathname || '/'; | ||
|
|
||
| if (!this.#endpoints.hasOwnProperty(urlPath)) { | ||
| throw new Error(404); | ||
| } else if (!this.#endpoints[urlPath].hasOwnProperty(normalizeMethod)) { | ||
| throw new Error(405); | ||
| } | ||
|
|
||
| const cb = this.#endpoints[urlPath][normalizeMethod]; | ||
|
|
||
| return cb(req, res); | ||
| } | ||
|
|
||
| initServer() { | ||
| this.on('request', async (req, res) => { | ||
| try { | ||
| this.useCb(req, res); | ||
| } catch (err) { | ||
| const status = Number(err.message) || 520; | ||
|
|
||
| res.statusCode = status; | ||
|
|
||
| switch (status) { | ||
| case 404: | ||
| res.end('The requested resource could not be found on this server'); | ||
| break; | ||
| case 405: | ||
| res.end( | ||
| 'The requested HTTP method is not supported for this endpoint', | ||
| ); | ||
| break; | ||
|
|
||
| default: | ||
| res.statusCode = 520; | ||
| res.end('The server returned an unknown error.'); | ||
| } | ||
| } | ||
| }); | ||
| } | ||
| } | ||
|
|
||
| function createServer() { | ||
| /* Write your code here */ | ||
| // Return instance of http.Server class | ||
| const server = new CustomServer(); | ||
|
|
||
| server.initServer(); | ||
|
|
||
| server.get('/', (_r, res) => { | ||
| const form = `<form method='POST' action="http://localhost:5701/add-expense"> | ||
| <input name="date" required type="date" /> | ||
| <input name="title" required type="text" /> | ||
| <input name="amount" required type="text" /> | ||
| <input type="submit" /> | ||
| </form>`; | ||
|
|
||
| res.writeHead(200, { 'Content-Type': 'text/html' }); | ||
| res.end(form); | ||
| }); | ||
|
|
||
| server.post('/add-expense', async (req, res) => { | ||
| const pathToDb = path.resolve(__dirname, '..', 'db', 'expense.json'); | ||
| const chunks = []; | ||
|
|
||
| for await (const chunk of req) { | ||
| chunks.push(chunk); | ||
| } | ||
|
|
||
| const data = Buffer.concat(chunks).toString(); | ||
| let dataObject; | ||
|
|
||
| switch (req.headers['content-type']) { | ||
| case 'application/json': | ||
| dataObject = JSON.parse(data); | ||
| break; | ||
| case 'application/x-www-form-urlencoded': | ||
| const entriesForObj = new URLSearchParams(data); | ||
|
|
||
| dataObject = Object.fromEntries(entriesForObj); | ||
| break; | ||
| default: | ||
| res.writeHead(400, { 'Content-Type': 'text/plain' }); | ||
| res.end('Сontent type not supported'); | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| const { date, title, amount } = dataObject; | ||
|
|
||
| if (!date || !title || !amount) { | ||
| res.writeHead(400, { 'Content-Type': 'text/plain' }); | ||
| res.end('Invalid data'); | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| try { | ||
| await fs.access(path.join(pathToDb, '..'), fs.constants.F_OK); | ||
| } catch (_e) { | ||
| try { | ||
| await fs.mkdir(path.join(pathToDb, '..'), { recursive: true }); | ||
| } catch (_err) { | ||
| res.writeHead(500, { 'Content-Type': 'text/plain' }); | ||
| res.end('Something went wrong'); | ||
|
|
||
| return; | ||
| } | ||
| } | ||
|
|
||
| let expensesList; | ||
| let readedData = await fs.readFile(pathToDb).catch((_e) => { | ||
| readedData = `[]`; | ||
| }); | ||
|
|
||
| try { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This |
||
| expensesList = JSON.parse(readedData); | ||
|
|
||
| if (!Array.isArray(expensesList)) { | ||
| expensesList = JSON.stringify([dataObject], null, 2); | ||
| } else { | ||
| const copyList = [...expensesList, dataObject]; | ||
|
|
||
| expensesList = JSON.stringify(copyList, null, 2); | ||
| } | ||
| } catch (err) { | ||
| expensesList = JSON.stringify([dataObject], null, 2); | ||
| } | ||
|
|
||
| try { | ||
| await fs.writeFile(pathToDb, expensesList); | ||
| } catch (_er) { | ||
| res.writeHead(500, { 'Content-Type': 'text/plain' }); | ||
| res.end('Something went wrong'); | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| res.statusCode = 200; | ||
| res.setHeader('Content-Type', 'text/html'); | ||
| res.end(`<pre>${expensesList}</pre>`); | ||
| }); | ||
|
|
||
| return server; | ||
| } | ||
|
|
||
| module.exports = { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a subtle issue with how this
.catch()is used. The arrow function(_e) => { readedData = '[]'; }doesn't return a value, so iffs.readFilefails,readedDatawill be assignedundefined.The code only works by chance because the subsequent
JSON.parse(undefined)throws an error that is caught later. To make the code work as intended, you should return the default value from thecatchblock.A cleaner implementation would be: