json1-2 Exercise 2: Reading and Sending JSON Over HTTP ===================================================== Run with Node 24.18.0: node ex2.mjs (the servers use a random local port; it does not appear in this output) --- ex2.mjs --- // Exercise 2: reading JSON over HTTP safely import http from 'node:http'; const routes = { '/ok': [200, 'application/json', '{"id":7,"name":"Ada"}'], '/notfound': [404, 'application/json', '{"error":"no such user"}'], '/html': [502, 'text/html', '
Bad Gateway'], '/empty': [204, 'application/json', ''], '/badjson': [200, 'application/json', '{"id":7,'], '/wrongtype': [200, 'text/plain', '{"id":8}'], }; const server = http.createServer((req, res) => { const [s, t, b] = routes[req.url]; res.writeHead(s, { 'content-type': t }); res.end(b); }); await new Promise(r => server.listen(0, '127.0.0.1', r)); const base = 'http://127.0.0.1:' + server.address().port; async function naive(url) { return (await fetch(url)).json(); } async function getJson(url) { const res = await fetch(url); const type = res.headers.get('content-type') ?? ''; const body = await res.text(); // read once, as text if (!res.ok) throw new Error('HTTP ' + res.status + ': ' + (type.includes('json') ? body : body.slice(0, 40))); if (res.status === 204 || body === '') return null; try { return JSON.parse(body); } catch (e) { throw new Error('bad JSON (' + e.message + '), starts with ' + JSON.stringify(body.slice(0, 12))); } } const show = async (fn, path) => { try { return JSON.stringify(await fn(base + path)); } catch (e) { return 'THREW ' + e.message; } }; console.log('path'.padEnd(11) + 'naive: (await fetch(u)).json()'.padEnd(95) + 'getJson(u)'); for (const p of Object.keys(routes)) console.log(p.padEnd(11) + (await show(naive, p)).padEnd(95) + await show(getJson, p)); const r1 = await fetch(base + '/ok'); await r1.json(); try { await r1.json(); } catch (e) { console.log('\nreading the same body twice: ' + e.constructor.name + ': ' + e.message); } // sending JSON: Content-Type matters const echo = http.createServer((req, res) => { let b = ''; req.on('data', c => b += c); req.on('end', () => { res.writeHead(200, { 'content-type': 'application/json' }); res.end(JSON.stringify({ type: req.headers['content-type'], body: b })); }); }); await new Promise(r => echo.listen(0, '127.0.0.1', r)); const u = 'http://127.0.0.1:' + echo.address().port; const send = async init => JSON.stringify(await (await fetch(u, { method: 'POST', ...init })).json()); console.log('\nPOST body JSON.stringify, no header :', await send({ body: JSON.stringify({ a: 1 }) })); console.log('POST body JSON.stringify, header :', await send({ headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ a: 1 }) })); console.log('POST body an object (no stringify) :', await send({ headers: { 'Content-Type': 'application/json' }, body: { a: 1 } })); server.close(); echo.close(); Output: path naive: (await fetch(u)).json() getJson(u) /ok {"id":7,"name":"Ada"} {"id":7,"name":"Ada"} /notfound {"error":"no such user"} THREW HTTP 404: {"error":"no such user"} /html THREW Unexpected token '<', "