-
Notifications
You must be signed in to change notification settings - Fork 218
solution #166
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 #166
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,129 @@ | ||
| 'use strict'; | ||
|
|
||
| const http = require('http'); | ||
| const url = require('url'); | ||
| const path = require('path'); | ||
| const fs = require('fs'); | ||
| const mime = require('mime-types'); | ||
|
|
||
| function createServer() { | ||
| /* Write your code here */ | ||
| // Return instance of http.Server class | ||
| const server = http.createServer(async (req, res) => { | ||
| function checkValidation(obj) { | ||
| if (typeof obj.date !== 'string' || !obj.date.trim()) { | ||
| throw new Error(`Field of date not correct: ${obj.date}`); | ||
| } | ||
|
|
||
| if (typeof obj.title !== 'string' || !obj.title.trim()) { | ||
| throw new Error(`Field of title not correct: ${obj.title}`); | ||
| } | ||
|
|
||
| const number = +obj.amount; | ||
|
|
||
| if (!Number.isFinite(number) || number <= 0) { | ||
| throw new Error(`Field of amount not correct: ${number}`); | ||
| } | ||
|
|
||
| const date = new Date(obj.date); | ||
|
|
||
| if (isNaN(date.getTime())) { | ||
| throw new Error(`Field of date not correct: ${obj.date}`); | ||
| } | ||
|
|
||
| return true; | ||
| } | ||
|
|
||
| if (req.method === 'POST') { | ||
| let rawBody = ''; | ||
|
|
||
| const contentType = req.headers['content-type']; | ||
|
|
||
| req.on('data', (chunk) => { | ||
| rawBody = rawBody + chunk; | ||
| }); | ||
|
|
||
| req.on('end', () => { | ||
| let data = null; | ||
|
|
||
| const normalizedContentType = contentType.includes(';') | ||
| ? contentType.split(';')[0] | ||
| : contentType; | ||
|
Comment on lines
+47
to
+49
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. Fragile normalization and matching: |
||
|
|
||
| if (normalizedContentType === 'application/json') { | ||
| data = JSON.parse(rawBody); | ||
|
Comment on lines
+51
to
+52
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. Uncaught JSON parse: |
||
| } | ||
|
|
||
| if (normalizedContentType === 'application/x-www-form-urlencoded') { | ||
| data = Object.fromEntries(new URLSearchParams(rawBody)); | ||
| } | ||
|
|
||
| if (data === null) { | ||
| res.writeHead(400, { 'Content-Type': 'text/plain' }); | ||
| res.end(`Unsupported Content-Type: ${normalizedContentType}`); | ||
|
Comment on lines
+60
to
+61
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. Unsupported Content-Type response is sent as |
||
|
|
||
| return; | ||
| } | ||
|
|
||
| try { | ||
| checkValidation(data); | ||
| } catch (err) { | ||
| res.writeHead(400, { 'Content-Type': 'text/html' }); | ||
|
|
||
| res.end( | ||
| `<pre>${err.message}</pre> <pre>${JSON.stringify( | ||
| { | ||
|
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. The error response always includes |
||
| invalid: data, | ||
| }, | ||
| null, | ||
| 2, | ||
| )}</pre>`, | ||
| ); | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| const folder = path.join(__dirname, '..', 'db'); | ||
| const filePath = path.join(folder, 'expense.json'); | ||
|
|
||
| if (!fs.existsSync(folder)) { | ||
| fs.mkdirSync(folder, { recursive: true }); | ||
| } | ||
|
|
||
| fs.writeFileSync(filePath, JSON.stringify(data, null, 2)); | ||
|
|
||
| res.writeHead(200, { 'Content-Type': 'text/html' }); | ||
|
|
||
| res.end(`<pre>${JSON.stringify(data, null, 2)}</pre>`); | ||
|
Comment on lines
+93
to
+95
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. Correct behavior: on successful POST you set |
||
| }); | ||
| } | ||
|
|
||
| if (req.method === 'GET') { | ||
| const base = req.headers.host | ||
| ? `http://${req.headers.host}` | ||
| : 'http://localhost:5701'; | ||
|
|
||
| const normalizedUrl = new url.URL(req.url || '', base); | ||
| const origin = | ||
| path.basename(normalizedUrl.pathname.slice(1)) || 'index.html'; | ||
|
|
||
| const mimeType = mime.lookup(origin) || 'text/plain'; | ||
|
|
||
| const originPathName = path.join(__dirname, origin); | ||
|
|
||
| const fsStream = fs.createReadStream(originPathName); | ||
|
|
||
| fsStream.on('error', (err) => { | ||
| res.writeHead(404, { 'Content-Type': mimeType }); | ||
| res.end(`Error reading file: ${String(err)}`); | ||
| }); | ||
|
|
||
| fsStream.on('open', () => { | ||
| res.writeHead(200, { 'Content-Type': 'text/html' }); | ||
|
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. When serving static files you always set |
||
| fsStream.pipe(res); | ||
| }); | ||
| } | ||
| }); | ||
|
|
||
| return server; | ||
| } | ||
|
|
||
| module.exports = { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| <!doctype html> | ||
| <html lang="en"> | ||
| <head> | ||
| <meta charset="UTF-8" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1.0" /> | ||
| <title>Form data</title> | ||
| </head> | ||
| <body> | ||
| <form | ||
| style=" | ||
| display: flex; | ||
| margin-left: 20px; | ||
| flex-direction: column; | ||
| gap: 10px; | ||
| width: 200px; | ||
| " | ||
| action="/order" | ||
| method="POST" | ||
| > | ||
| <label for="date">Date:</label> | ||
| <input type="date" id="date" name="date" required /> | ||
| <label for="title">Title:</label> | ||
| <input type="text" name="title" id="title" required /> | ||
| <label for="amount">Amount:</label> | ||
| <input | ||
| type="number" | ||
| name="amount" | ||
| id="amount" | ||
| min="1" | ||
| step="0.01" | ||
| required | ||
| /> | ||
|
|
||
| <button type="submit">submit</button> | ||
| </form> | ||
| </body> | ||
| </html> |
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.
Potential crash:
contentTypeis read fromreq.headers['content-type']but may be undefined. Right after that you callcontentType.includes(...)(line 47) which will throw if the header is missing. Guard the value before calling string methods or provide a default string. For example, normalize safely usingconst contentType = req.headers['content-type'] || ''or checkif (!contentType) { ... }first. This is critical because a missing header will crash the request handler and break the server.