Skip to content

Commit 852f7f8

Browse files
committed
add complex example, extend examples
1 parent 2dd170d commit 852f7f8

File tree

4 files changed

+387
-63
lines changed

4 files changed

+387
-63
lines changed

examples/rubico-http-server/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@
1111
"directory": "examples/rubico-http-server"
1212
},
1313
"scripts": {
14-
"test": "node runserver.test.js"
14+
"test": "mocha *.test.js"
1515
},
1616
"dependencies": {
1717
"rubico": "^2.7.3"
1818
},
1919
"devDependencies": {
20-
"thunk-test": "^1.3.1"
20+
"mocha": "^11.7.0"
2121
}
2222
}
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
#!/usr/bin/env node
2+
3+
const http = require('http')
4+
5+
/**
6+
* @name runserver
7+
*
8+
* @synopsis
9+
* ```coffeescript [specscript]
10+
* module http
11+
*
12+
* runserver(options? {
13+
* port?: number,
14+
* }) -> { server http.Server }
15+
* ```
16+
*/
17+
function runserver(options = {}) {
18+
const { port = 8080 } = options
19+
20+
const userDb = new Map()
21+
22+
const userTable = {
23+
async put(userRecord) {
24+
return userDb.set(userRecord.id, userRecord)
25+
},
26+
async getById(userId) {
27+
return userDb.get(userId)
28+
},
29+
}
30+
31+
const complexHandler = async function (request, response) {
32+
try {
33+
if (request.url.startsWith('/health')) {
34+
// GET /health
35+
36+
response.writeHead(200, {
37+
'Content-Type': 'text/plain',
38+
})
39+
response.end('ok')
40+
41+
} else if (request.method == 'OPTIONS') {
42+
// OPTIONS
43+
44+
response.writeHead(204, {
45+
'Access-Control-Allow-Origin': '*',
46+
'Access-Control-Allow-Methods': '*',
47+
'Access-Control-Allow-Headers': '*',
48+
'Access-Control-Max-Age': '86400',
49+
})
50+
response.end()
51+
52+
} else if (request.method == 'GET' && /^\/user\/\d+$/.test(request.url)) {
53+
// GET /user/:userId
54+
// retrieves a user resource
55+
56+
const userId = request.url.match(/\d+/)[0]
57+
58+
// validate
59+
if (isNaN(Number(userId))) {
60+
const error = new Error('Bad Request')
61+
error.code = 400
62+
throw error
63+
}
64+
65+
// retrieve the user record from the db
66+
const user = await userTable.getById(userId)
67+
68+
// handle not found
69+
if (user == null) {
70+
const error = new Error('Not Found')
71+
error.code = 404
72+
throw error
73+
}
74+
75+
// ensure no private user information is exposed
76+
const publicUser = {
77+
id: user.id,
78+
name: user.name,
79+
birthdate: user.birthdate,
80+
profilePictureUrl: user.profilePictureUrl,
81+
createTime: user.createTime,
82+
}
83+
84+
// send back the user resource in the response body
85+
response.writeHead(200, {
86+
'Access-Control-Allow-Origin': '*',
87+
'Content-Type': 'application/json',
88+
})
89+
response.end(JSON.stringify({
90+
user: publicUser,
91+
}))
92+
93+
} else if (request.method == 'PUT' && /^\/user\/\d+$/.test(request.url)) {
94+
// PUT /user/:userId
95+
// creates or updates a user resource
96+
97+
const userId = request.url.match(/\d+/)[0]
98+
99+
const requestBodyBuffer = await new Promise(resolve => {
100+
const binaryArray = []
101+
request.on('data', chunk => {
102+
binaryArray.push(chunk)
103+
})
104+
request.on('end', () => {
105+
resolve(Buffer.concat(binaryArray))
106+
})
107+
})
108+
const requestBodyString = requestBodyBuffer.toString('utf8')
109+
const requestBodyJSON = JSON.parse(requestBodyString)
110+
111+
// validate
112+
if (isNaN(Number(userId))) {
113+
const error = new Error('Bad Request')
114+
error.code = 400
115+
throw error
116+
}
117+
if (typeof requestBodyJSON.id != 'string') {
118+
const error = new Error('Bad Request')
119+
error.code = 400
120+
throw error
121+
}
122+
if (typeof requestBodyJSON.name != 'string') {
123+
const error = new Error('Bad Request')
124+
error.code = 400
125+
throw error
126+
}
127+
if (typeof requestBodyJSON.birthdate != 'string') {
128+
const error = new Error('Bad Request')
129+
error.code = 400
130+
throw error
131+
}
132+
if (typeof requestBodyJSON.profilePictureUrl != 'string') {
133+
const error = new Error('Bad Request')
134+
error.code = 400
135+
throw error
136+
}
137+
138+
const user = {
139+
id: requestBodyJSON.id,
140+
name: requestBodyJSON.name,
141+
birthdate: requestBodyJSON.birthdate,
142+
profilePictureUrl: requestBodyJSON.profilePictureUrl,
143+
createTime: Date.now(),
144+
}
145+
146+
// save user record to the db
147+
await userTable.put(user)
148+
149+
// send back a successful response
150+
response.writeHead(200, {
151+
'Access-Control-Allow-Origin': '*',
152+
'Content-Type': 'application/json',
153+
})
154+
response.end(JSON.stringify({
155+
message: 'success',
156+
}))
157+
158+
}
159+
else { // not found
160+
response.writeHead(404, {
161+
'Content-Type': 'text/plain',
162+
})
163+
response.end('Not Found')
164+
}
165+
} catch (error) {
166+
console.error(error)
167+
if (typeof error.code != 'number') {
168+
error.code = 500
169+
}
170+
response.writeHead(error.code, {
171+
'Access-Control-Allow-Origin': '*',
172+
'Content-Type': 'text/plain',
173+
})
174+
response.end(error.message)
175+
}
176+
}
177+
178+
const server = http.createServer(complexHandler)
179+
180+
server.listen(port)
181+
182+
return { server }
183+
}
184+
185+
module.exports = runserver

examples/rubico-http-server/runserver renamed to examples/rubico-http-server/runserver-simple

Lines changed: 78 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,15 @@ const http = require('http')
1818
function runserver(options = {}) {
1919
const { port = 8080 } = options
2020

21+
const userDb = new Map()
22+
2123
const userTable = {
24+
async put(userRecord) {
25+
return userDb.set(userRecord.id, userRecord)
26+
},
2227
async getById(userId) {
23-
return {
24-
id: userId,
25-
name: `User ${userId}`,
26-
createTime: 1,
27-
birthdate: '2020-01-01',
28-
profilePictureUrl: 'https://rubico.land/assets/rubico-logo.png',
29-
}
30-
}
28+
return userDb.get(userId)
29+
},
3130
}
3231

3332
function healthCheckHandler(request, response) {
@@ -47,6 +46,8 @@ function runserver(options = {}) {
4746
response.end()
4847
}
4948

49+
// GET /user/:userId
50+
// retrieves a user resource
5051
async function getUserHandler(request, response) {
5152
const userId = request.url.match(/\d+/)[0]
5253

@@ -86,14 +87,79 @@ function runserver(options = {}) {
8687
}))
8788
}
8889

90+
// PUT /user/:userId
91+
// creates or updates a user resource
92+
async function putUserHandler(request, response) {
93+
const userId = request.url.match(/\d+/)[0]
94+
95+
const requestBodyBuffer = await new Promise(resolve => {
96+
const binaryArray = []
97+
request.on('data', chunk => {
98+
binaryArray.push(chunk)
99+
})
100+
request.on('end', () => {
101+
resolve(Buffer.concat(binaryArray))
102+
})
103+
})
104+
const requestBodyString = requestBodyBuffer.toString('utf8')
105+
const requestBodyJSON = JSON.parse(requestBodyString)
106+
107+
// validate
108+
if (isNaN(Number(userId))) {
109+
const error = new Error('Bad Request')
110+
error.code = 400
111+
throw error
112+
}
113+
if (typeof requestBodyJSON.id != 'string') {
114+
const error = new Error('Bad Request')
115+
error.code = 400
116+
throw error
117+
}
118+
if (typeof requestBodyJSON.name != 'string') {
119+
const error = new Error('Bad Request')
120+
error.code = 400
121+
throw error
122+
}
123+
if (typeof requestBodyJSON.birthdate != 'string') {
124+
const error = new Error('Bad Request')
125+
error.code = 400
126+
throw error
127+
}
128+
if (typeof requestBodyJSON.profilePictureUrl != 'string') {
129+
const error = new Error('Bad Request')
130+
error.code = 400
131+
throw error
132+
}
133+
134+
const user = {
135+
id: requestBodyJSON.id,
136+
name: requestBodyJSON.name,
137+
birthdate: requestBodyJSON.birthdate,
138+
profilePictureUrl: requestBodyJSON.profilePictureUrl,
139+
createTime: Date.now(),
140+
}
141+
142+
// save user record to the db
143+
await userTable.put(user)
144+
145+
// send back a successful response
146+
response.writeHead(200, {
147+
'Access-Control-Allow-Origin': '*',
148+
'Content-Type': 'application/json',
149+
})
150+
response.end(JSON.stringify({
151+
message: 'success',
152+
}))
153+
}
154+
89155
function notFoundHandler(request, response) {
90156
response.writeHead(404, {
91157
'Content-Type': 'text/plain',
92158
})
93159
response.end('Not Found')
94160
}
95161

96-
function errorHandler(request, response) {
162+
function errorHandler(error, request, response) {
97163
console.error(error)
98164
if (typeof error.code != 'number') {
99165
error.code = 500
@@ -116,6 +182,9 @@ function runserver(options = {}) {
116182
request => request.method == 'GET' && /^\/user\/\d+$/.test(request.url),
117183
getUserHandler,
118184

185+
request => request.method == 'PUT' && /^\/user\/\d+$/.test(request.url),
186+
putUserHandler,
187+
119188
notFoundHandler,
120189
]),
121190

0 commit comments

Comments
 (0)