Deno:如何在 oak 中使用 WebSocket?
Deno: How to use WebSocket with oak?
由于 Deno 于上周三发布,我尝试使用它并重做聊天应用程序的小示例,我尝试了这个:
import { Application, Router, send } from 'https://deno.land/x/oak/mod.ts';
import { listenAndServe } from 'https://deno.land/std/http/server.ts'
const app = new Application();
const router = new Router();
router
.get('/ws', handleSocket);
app.use(router.routes());
app.use(router.allowedMethods());
await app.listen({ port: HTTP_PORT });
app.ts
import { WebSocket, acceptWebSocket, isWebSocketCloseEvent, acceptable } from 'https://deno.land/std/ws/mod.ts'
import { v4 } from 'https://deno.land/std/uuid/mod.ts'
const users = new Map<string, WebSocket>()
export const handleSocket = async (ctx: any) => {
if (acceptable(ctx.request.serverRequest)) {
const { conn, r: bufReader, w: bufWriter, headers } = ctx.request.serverRequest;
const socket = await acceptWebSocket({
conn,
bufReader,
bufWriter,
headers,
});
await socketEventHandlers(socket);
} else {
throw new Error('Error when connecting websocket');
}
}
...
export const socketEventHandlers = async (ws: WebSocket): Promise<void> => {
// Register user connection
const userId = v4.generate()
users.set(userId, ws)
await broadcast(`> User with the id ${userId} is connected`)
// Wait for new messages
for await (const event of ws) {
const message = typeof event === 'string' ? event : ''
await broadcast(message, userId)
// Unregister user conection
if (!message && isWebSocketCloseEvent(event)) {
users.delete(userId)
await broadcast(`> User with the id ${userId} is disconnected`)
}
}
}
socket.ts
websocket 连接与 import { listenAndServe } from 'https://deno.land/std/http/server.ts'
完美配合,但使用上面的代码我遇到了 WebSocket connection to 'ws://localhost:3000/ws' failed: Invalid frame header
.
之类的错误
有人有什么解决办法吗?谢谢 ;)
出现此问题是因为您使用了错误版本的库。在 Deno 中始终使用版本控制的 URL。
对于 Deno 1.0.0
,您需要使用 oak v4.0.0
& std v0.51.0
app.ts
import { Application, Router, send } from 'https://deno.land/x/oak@v4.0.0/mod.ts';
socket.ts
import { WebSocket, acceptWebSocket, isWebSocketCloseEvent, acceptable } from 'https://deno.land/std@0.51.0/ws/mod.ts'
import { v4 } from 'https://deno.land/std@0.51.0/uuid/mod.ts'
完成这些更改后,您将能够正确连接到 WebSocket 服务器。
const ws = new WebSocket("ws://127.0.0.1:8080/ws")
ws.onopen = function () {
ws.send('OAK is working!')
}
在解决将 http 连接升级到同一端口上的 websocket 的相同问题时,以下过于简化的解决方案适用于 Deno 1.2 和 Oak。
/*
running with Deno 1.2
deno run --inspect --allow-net ./beautiful-socket.js
*/
import { Application, Router, HttpError, send, Status } from "https://deno.land/x/oak@v6.0.1/mod.ts";
import { createWebSocket, isWebSocketCloseEvent, acceptWebSocket, acceptable } from "https://deno.land/std@0.61.0/ws/mod.ts";
const port = 8123;
const users = new Set();
const app = new Application({state:{users}});
const router = new Router();
function broadcastEach(user){
user.send(this);
}
function broadcast(msg){
console.log('---broadcasting--->', typeof msg, msg);
users.forEach(broadcastEach, msg);
}
router.get('/socket', async (context, next) => {
if( !context.isUpgradable ){
throw new Error('bummers opening socket :(');
}
const socket = await context.upgrade();
users.add(socket);
broadcast(`hello! ${ socket.conn.rid }`);
for await (const ev of socket) {
if(isWebSocketCloseEvent(ev)){
users.delete(socket);
broadcast(`bye! ${ socket.conn.rid }`);
}else{
broadcast(ev);
};
}
});
router.get('/', async (context) => {
context.response.body = `<!doctype html>
<html><body>
<p>let's chat...open the console to chat it up</p>
<script>
console.log(123);
const pipe = new WebSocket("ws://${context.request.url.host}/socket");
function fire(ev){
switch(ev.type){
case 'message':
switch(typeof ev.data){
case 'string':
console.log('msg text', ev.data);
break;
case 'object':
ev.data.arrayBuffer().then(ab=>{ console.log(new Uint8Array(ab)); });
break;
}
break;
default:
console.log(ev.type ,ev);
}
}
function hello(msg){
if(msg === undefined){
msg = new ArrayBuffer(4);
const uint = new Uint8Array(msg);
uint[0] = 4;
uint[1] = 3;
uint[2] = 2;
uint[3] = 1;
}
pipe.send(msg);
}
pipe.addEventListener('open', fire);
pipe.addEventListener('close', fire);
pipe.addEventListener('message', fire);
pipe.addEventListener('error', fire);
</script>
</body></html>
`;
});
app.use(router.routes());
app.use(router.allowedMethods());
app.addEventListener('error', (ev)=>{
console.error(ev);
debugger;
});
app.addEventListener('listen', (server)=>{
console.log(`open ${ server.secure ? 'https':'http' }://${ server.hostname }:${ server.port }`);
});
const whenClosed = app.listen(`:${port}`);
await whenClosed;
console.log(`closed http :${port}, bye`);
TL;DR - 自答案被接受以来已经更新,现在简单多了。
router.get('/ws', async ctx => {
const sock = await ctx.upgrade();
handleSocket(sock);
});
由于 Deno 于上周三发布,我尝试使用它并重做聊天应用程序的小示例,我尝试了这个:
import { Application, Router, send } from 'https://deno.land/x/oak/mod.ts';
import { listenAndServe } from 'https://deno.land/std/http/server.ts'
const app = new Application();
const router = new Router();
router
.get('/ws', handleSocket);
app.use(router.routes());
app.use(router.allowedMethods());
await app.listen({ port: HTTP_PORT });
app.ts
import { WebSocket, acceptWebSocket, isWebSocketCloseEvent, acceptable } from 'https://deno.land/std/ws/mod.ts'
import { v4 } from 'https://deno.land/std/uuid/mod.ts'
const users = new Map<string, WebSocket>()
export const handleSocket = async (ctx: any) => {
if (acceptable(ctx.request.serverRequest)) {
const { conn, r: bufReader, w: bufWriter, headers } = ctx.request.serverRequest;
const socket = await acceptWebSocket({
conn,
bufReader,
bufWriter,
headers,
});
await socketEventHandlers(socket);
} else {
throw new Error('Error when connecting websocket');
}
}
...
export const socketEventHandlers = async (ws: WebSocket): Promise<void> => {
// Register user connection
const userId = v4.generate()
users.set(userId, ws)
await broadcast(`> User with the id ${userId} is connected`)
// Wait for new messages
for await (const event of ws) {
const message = typeof event === 'string' ? event : ''
await broadcast(message, userId)
// Unregister user conection
if (!message && isWebSocketCloseEvent(event)) {
users.delete(userId)
await broadcast(`> User with the id ${userId} is disconnected`)
}
}
}
socket.ts
websocket 连接与 import { listenAndServe } from 'https://deno.land/std/http/server.ts'
完美配合,但使用上面的代码我遇到了 WebSocket connection to 'ws://localhost:3000/ws' failed: Invalid frame header
.
有人有什么解决办法吗?谢谢 ;)
出现此问题是因为您使用了错误版本的库。在 Deno 中始终使用版本控制的 URL。
对于 Deno 1.0.0
,您需要使用 oak v4.0.0
& std v0.51.0
app.ts
import { Application, Router, send } from 'https://deno.land/x/oak@v4.0.0/mod.ts';
socket.ts
import { WebSocket, acceptWebSocket, isWebSocketCloseEvent, acceptable } from 'https://deno.land/std@0.51.0/ws/mod.ts'
import { v4 } from 'https://deno.land/std@0.51.0/uuid/mod.ts'
完成这些更改后,您将能够正确连接到 WebSocket 服务器。
const ws = new WebSocket("ws://127.0.0.1:8080/ws")
ws.onopen = function () {
ws.send('OAK is working!')
}
在解决将 http 连接升级到同一端口上的 websocket 的相同问题时,以下过于简化的解决方案适用于 Deno 1.2 和 Oak。
/*
running with Deno 1.2
deno run --inspect --allow-net ./beautiful-socket.js
*/
import { Application, Router, HttpError, send, Status } from "https://deno.land/x/oak@v6.0.1/mod.ts";
import { createWebSocket, isWebSocketCloseEvent, acceptWebSocket, acceptable } from "https://deno.land/std@0.61.0/ws/mod.ts";
const port = 8123;
const users = new Set();
const app = new Application({state:{users}});
const router = new Router();
function broadcastEach(user){
user.send(this);
}
function broadcast(msg){
console.log('---broadcasting--->', typeof msg, msg);
users.forEach(broadcastEach, msg);
}
router.get('/socket', async (context, next) => {
if( !context.isUpgradable ){
throw new Error('bummers opening socket :(');
}
const socket = await context.upgrade();
users.add(socket);
broadcast(`hello! ${ socket.conn.rid }`);
for await (const ev of socket) {
if(isWebSocketCloseEvent(ev)){
users.delete(socket);
broadcast(`bye! ${ socket.conn.rid }`);
}else{
broadcast(ev);
};
}
});
router.get('/', async (context) => {
context.response.body = `<!doctype html>
<html><body>
<p>let's chat...open the console to chat it up</p>
<script>
console.log(123);
const pipe = new WebSocket("ws://${context.request.url.host}/socket");
function fire(ev){
switch(ev.type){
case 'message':
switch(typeof ev.data){
case 'string':
console.log('msg text', ev.data);
break;
case 'object':
ev.data.arrayBuffer().then(ab=>{ console.log(new Uint8Array(ab)); });
break;
}
break;
default:
console.log(ev.type ,ev);
}
}
function hello(msg){
if(msg === undefined){
msg = new ArrayBuffer(4);
const uint = new Uint8Array(msg);
uint[0] = 4;
uint[1] = 3;
uint[2] = 2;
uint[3] = 1;
}
pipe.send(msg);
}
pipe.addEventListener('open', fire);
pipe.addEventListener('close', fire);
pipe.addEventListener('message', fire);
pipe.addEventListener('error', fire);
</script>
</body></html>
`;
});
app.use(router.routes());
app.use(router.allowedMethods());
app.addEventListener('error', (ev)=>{
console.error(ev);
debugger;
});
app.addEventListener('listen', (server)=>{
console.log(`open ${ server.secure ? 'https':'http' }://${ server.hostname }:${ server.port }`);
});
const whenClosed = app.listen(`:${port}`);
await whenClosed;
console.log(`closed http :${port}, bye`);
TL;DR - 自答案被接受以来已经更新,现在简单多了。
router.get('/ws', async ctx => {
const sock = await ctx.upgrade();
handleSocket(sock);
});