Skip to content

Commit 759e24a

Browse files
committed
add ws-with-koa project
1 parent a3ef3eb commit 759e24a

39 files changed

+10713
-0
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"version": "0.2.0",
3+
"configurations": [
4+
{
5+
"name": "Start",
6+
"type": "node",
7+
"request": "launch",
8+
"program": "${workspaceRoot}/start.js",
9+
"stopOnEntry": false,
10+
"args": [],
11+
"cwd": "${workspaceRoot}",
12+
"preLaunchTask": null,
13+
"runtimeExecutable": null,
14+
"runtimeArgs": [
15+
"--nolazy"
16+
],
17+
"env": {
18+
"NODE_ENV": "development"
19+
},
20+
"externalConsole": false,
21+
"sourceMaps": false,
22+
"outDir": null
23+
}
24+
]
25+
}
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
const url = require('url');
2+
3+
const ws = require('ws');
4+
5+
const Cookies = require('cookies');
6+
7+
const Koa = require('koa');
8+
9+
const bodyParser = require('koa-bodyparser');
10+
11+
const router = require('koa-router')();
12+
13+
const templating = require('./templating.js');
14+
15+
const fs = require('fs');
16+
17+
const WebSocketServer = ws.Server;
18+
19+
const app = new Koa();
20+
21+
// log request URL:
22+
app.use(async (ctx, next) => {
23+
console.log(`Process ${ctx.request.method} ${ctx.request.url}...`);
24+
await next();
25+
});
26+
27+
// parse user from cookie:
28+
app.use(async (ctx, next) => {
29+
ctx.state.user = parseUser(ctx.cookies.get('name') || '');
30+
await next();
31+
});
32+
33+
// static file support:
34+
let staticFiles = require('./static-files');
35+
app.use(staticFiles('/static/', __dirname + '/static'));
36+
37+
// parse request body:
38+
app.use(bodyParser());
39+
40+
// add nunjucks as view:
41+
app.use(templating('view', {
42+
noCache: true,
43+
watch: true
44+
}));
45+
46+
// add url-route in /controllers:
47+
48+
function addMapping(router, mapping) {
49+
for (var url in mapping) {
50+
if (url.startsWith('GET ')) {
51+
var path = url.substring(4);
52+
router.get(path, mapping[url]);
53+
console.log(`register URL mapping: GET ${path}`);
54+
} else if (url.startsWith('POST ')) {
55+
var path = url.substring(5);
56+
router.post(path, mapping[url]);
57+
console.log(`register URL mapping: POST ${path}`);
58+
} else {
59+
console.log(`invalid URL: ${url}`);
60+
}
61+
}
62+
}
63+
64+
function addControllers(router) {
65+
var files = fs.readdirSync(__dirname + '/controllers');
66+
var js_files = files.filter((f) => {
67+
return f.endsWith('.js');
68+
}, files);
69+
70+
for (var f of js_files) {
71+
console.log(`process controller: ${f}...`);
72+
let mapping = require(__dirname + '/controllers/' + f);
73+
addMapping(router, mapping);
74+
}
75+
}
76+
77+
addControllers(router);
78+
79+
// add router middleware:
80+
app.use(router.routes());
81+
82+
let server = app.listen(3000);
83+
84+
function parseUser(obj) {
85+
if (!obj) {
86+
return;
87+
}
88+
console.log('try parse: ' + obj);
89+
let s = '';
90+
if (typeof obj === 'string') {
91+
s = obj;
92+
} else if (obj.headers) {
93+
let cookies = new Cookies(obj, null);
94+
s = cookies.get('name');
95+
}
96+
if (s) {
97+
try {
98+
let user = JSON.parse(Buffer.from(s, 'base64').toString());
99+
console.log(`User: ${user.name}, ID: ${user.id}`);
100+
return user;
101+
} catch (e) {
102+
// ignore
103+
}
104+
}
105+
}
106+
107+
function createWebSocketServer(server, onConnection, onMessage, onClose, onError) {
108+
let wss = new WebSocketServer({
109+
server: server
110+
});
111+
wss.broadcast = function broadcast(data) {
112+
wss.clients.forEach(function each(client) {
113+
client.send(data);
114+
});
115+
};
116+
onConnection = onConnection || function () {
117+
console.log('[WebSocket] connected.');
118+
};
119+
onMessage = onMessage || function (msg) {
120+
console.log('[WebSocket] message received: ' + msg);
121+
};
122+
onClose = onClose || function (code, message) {
123+
console.log(`[WebSocket] closed: ${code} - ${message}`);
124+
};
125+
onError = onError || function (err) {
126+
console.log('[WebSocket] error: ' + err);
127+
};
128+
wss.on('connection', function (ws) {
129+
let location = url.parse(ws.upgradeReq.url, true);
130+
console.log('[WebSocketServer] connection: ' + location.href);
131+
ws.on('message', onMessage);
132+
ws.on('close', onClose);
133+
ws.on('error', onError);
134+
if (location.pathname !== '/ws/chat') {
135+
// close ws:
136+
ws.close(4000, 'Invalid URL');
137+
}
138+
// check user:
139+
let user = parseUser(ws.upgradeReq);
140+
if (!user) {
141+
ws.close(4001, 'Invalid user');
142+
}
143+
ws.user = user;
144+
ws.wss = wss;
145+
onConnection.apply(ws);
146+
});
147+
console.log('WebSocketServer was attached.');
148+
return wss;
149+
}
150+
151+
var messageIndex = 0;
152+
153+
function createMessage(type, user, data) {
154+
messageIndex ++;
155+
return JSON.stringify({
156+
id: messageIndex,
157+
type: type,
158+
user: user,
159+
data: data
160+
});
161+
}
162+
163+
function onConnect() {
164+
let user = this.user;
165+
let msg = createMessage('join', user, `${user.name} joined.`);
166+
this.wss.broadcast(msg);
167+
// build user list:
168+
let users = this.wss.clients.map(function (client) {
169+
return client.user;
170+
});
171+
this.send(createMessage('list', user, users));
172+
}
173+
174+
function onMessage(message) {
175+
console.log(message);
176+
if (message && message.trim()) {
177+
let msg = createMessage('chat', this.user, message.trim());
178+
this.wss.broadcast(msg);
179+
}
180+
}
181+
182+
function onClose() {
183+
let user = this.user;
184+
let msg = createMessage('left', user, `${user.name} is left.`);
185+
this.wss.broadcast(msg);
186+
}
187+
188+
app.wss = createWebSocketServer(server, onConnect, onMessage, onClose);
189+
190+
console.log('app started at port 3000...');
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// index:
2+
3+
module.exports = {
4+
'GET /': async (ctx, next) => {
5+
let user = ctx.state.user;
6+
if (user) {
7+
ctx.render('room.html', {
8+
user: user
9+
});
10+
} else {
11+
ctx.response.redirect('/signin');
12+
}
13+
}
14+
};
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// sign in:
2+
3+
var index = 0;
4+
5+
module.exports = {
6+
'GET /signin': async (ctx, next) => {
7+
let names = '甲乙丙丁戊己庚辛壬癸';
8+
let name = names[index % 10];
9+
ctx.render('signin.html', {
10+
name: `路人${name}`
11+
});
12+
},
13+
14+
'POST /signin': async (ctx, next) => {
15+
index ++;
16+
let name = ctx.request.body.name || '路人甲';
17+
let user = {
18+
id: index,
19+
name: name,
20+
image: index % 10
21+
};
22+
let value = Buffer.from(JSON.stringify(user)).toString('base64');
23+
console.log(`Set cookie value: ${value}`);
24+
ctx.cookies.set('name', value);
25+
ctx.response.redirect('/');
26+
},
27+
28+
'GET /signout': async (ctx, next) => {
29+
ctx.cookies.set('name', '');
30+
ctx.response.redirect('/signin');
31+
}
32+
};
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{
2+
"name": "ws-with-koa",
3+
"version": "1.0.0",
4+
"description": "websocket with koa integration",
5+
"main": "start.js",
6+
"scripts": {
7+
"start": "node start.js"
8+
},
9+
"keywords": [
10+
"websocket",
11+
"ws",
12+
"koa",
13+
"async",
14+
"nunjucks"
15+
],
16+
"author": "Michael Liao",
17+
"license": "Apache-2.0",
18+
"repository": {
19+
"type": "git",
20+
"url": "https://github.com/michaelliao/learn-javascript.git"
21+
},
22+
"dependencies": {
23+
"babel-core": "6.13.2",
24+
"babel-polyfill": "6.13.0",
25+
"babel-preset-es2015-node6": "0.3.0",
26+
"babel-preset-stage-3": "6.5.0",
27+
"ws": "1.1.1",
28+
"koa": "2.0.0",
29+
"koa-bodyparser": "3.2.0",
30+
"koa-router": "7.0.0",
31+
"nunjucks": "2.4.2",
32+
"mime": "1.3.4",
33+
"mz": "2.4.0"
34+
}
35+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
require('babel-core/register')({
2+
presets: ['stage-3']
3+
});
4+
5+
require('./app.js');
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
const path = require('path');
2+
const mime = require('mime');
3+
const fs = require('mz/fs');
4+
5+
function staticFiles(url, dir) {
6+
return async (ctx, next) => {
7+
let rpath = ctx.request.path;
8+
if (rpath.startsWith(url)) {
9+
let fp = path.join(dir, rpath.substring(url.length));
10+
if (await fs.exists(fp)) {
11+
ctx.response.type = mime.lookup(rpath);
12+
ctx.response.body = await fs.readFile(fp);
13+
} else {
14+
ctx.response.status = 404;
15+
}
16+
} else {
17+
await next();
18+
}
19+
};
20+
}
21+
22+
module.exports = staticFiles;

0 commit comments

Comments
 (0)