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);
});

信用https://github.com/oakserver/oak/pull/137