hono
hono copied to clipboard
WebSockets
Please implement WebSockets feature in honojs.
https://developers.cloudflare.com/workers/runtime-apis/websockets/
Not just cloudflare, but also Node.js and deno
You can use WebSockets with the current version of Hono on Deno.
Refer: https://github.com/orgs/honojs/discussions/755
It can also be supported on Cloudflare Workers.
More info: https://developers.cloudflare.com/workers/learning/using-websockets/
Supporting more WebSocket features is outside the scope of Hono.
For Node.js, you can't use WebSockets with the current Node.js Adapter and there are no plans to implement it. But, if you want this feature, you might be able to write the PR yourself.
Is there an example with Hono and CloudFlare workers too? I looked into the documentation of CloudFlare, but couldn't get it working with Hono.
Hi @JustJoostNL !
I've written an example. Try this!
// websocket.ts
import type { WebSocket as CFWebSocket } from '@cloudflare/workers-types'
import { Hono } from 'hono'
import { html } from 'hono/html'
const app = new Hono()
app.get('/', (c) => {
return c.html(
html`<html>
<body>
<script>
const hostname = window.location.host
const protocol = document.location.protocol === 'http:' ? 'ws://' : 'wss://'
const ws = new WebSocket(protocol + hostname + '/websocket')
ws.addEventListener('message', ({ data }) => {
console.log(data)
})
</script>
<h1>WebSocket</h1>
</body>
</html>`
)
})
app.get('/websocket', (c) => {
const upgradeHeader = c.req.header('Upgrade')
if (upgradeHeader !== 'websocket') {
return c.text('Expected websocket', 400)
}
const webSocketPair = new WebSocketPair()
const client = webSocketPair[0]
const server = webSocketPair[1] as unknown as CFWebSocket
server.accept()
server.send('Hello')
return new Response(null, {
status: 101,
webSocket: client,
})
})
export default app
wrangler dev websocket.ts --remote
Thanks!
It should be fairly trivial to make universal middleware for Workers, Deno and possibly Node. If I have the time, I could look into it as 3rd party middleware but no promise. Bun seems to be out of the question because of how their API works.
Useful links for reference:
Deno.upgradeWebSocket
Cloudflare
ws
package for Node
Bun WebSockets
Yeah, making a "universal" middleware is make sense.
Is there currently any restriction on manually implementing the connection upgrade on the route handler? I’m wondering how I could implement it with the ws package for node, also wondering about Bun.
You have to use the node adapter with the handleUpgrade
function, it'd definetly require some messing around with internals but it should be possible. As for Bun, you can put the upgrade
function into Env
from the hono server and in theory use WebSockets from the websocket
handler in Bun.serve
but the approach is completely different from Deno and Workers
WebSockets example for Node.js, it's way simpler than what I described
import { serve } from "@hono/node-server";
import { Hono } from "hono";
import { WebSocketServer } from 'ws';
const app = new Hono();
const server = serve(app, (info) => {
console.log(`Listening on http://localhost:${info.port}`);
});
const wss = new WebSocketServer({ server });
wss.on("connection", function connection(ws) {
ws.on("error", console.error);
ws.on("message", function message(data) {
console.log("received: %s", data);
});
ws.send("something");
});
Awesome this is exactly what I needed thank you 🎉
I couldn't find any documentation on the WebSocket server included with Bun. This is how I got it working, but I'm sure there's a better way to handle upgrades.
import { Hono } from 'hono'
const app = new Hono()
Bun.serve({
fetch: (req, server) => {
if (server.upgrade(req)) {
// handle authentication
}
return app.fetch(req, server)
},
websocket: {
message(ws, message) {},
open(ws) {
ws.send('Hello!')
},
close(ws, code, message) {},
drain(ws) {}
},
port: 8080
})
For someone interested in using WebSockets via node server and socket.io check out this snippet
import { serve } from "@hono/node-server";
import { Hono } from "hono";
import type { Server as HTTPSServer } from "node:http";
import { Server as SocketIOServer } from "socket.io";
const app = new Hono();
app.get("/", (c) => c.text("Hello Hono!"));
const server = serve(
{
fetch: app.fetch,
port: 5000,
},
(add) => {
console.log('Listening on http://localhost:'+${add.port});
}
);
const io = new SocketIOServer(server as HTTPSServer);
io.on("connection", (socket) => {
socket.emit("hello", "world");
socket.on("hello", (data) => {
socket.broadcast.emit("hello", data);
});
});
I would love to see a simpler way to handle WebSocket
upgrade on Bun
using a specific route (e.g. /ws
) where you can handle events inside that route. For now as i described in this discussion, i was able to upgrade inside a route using the following:
app.get('/ws', (c) => {
const success = c.env.upgrade(c.req.raw)
return true
})
Another example how to upgrade all WebSocket upgrade requests using Bun
:
Bun.serve({
port: process.env.PORT || 3000,
fetch: (req, server) => {
// check if request is websocket
if (req.headers.get('upgrade') === 'websocket') {
const success = server.upgrade(req);
if (success) {
// Bun automatically returns a 101 Switching Protocols
// if the upgrade succeeds
return undefined;
}
}
// handle HTTP request normally
return app.fetch(req, server)
},
websocket: webSocketHandler,
})
I would love to see a simpler way to handle
WebSocket
upgrade onBun
using a specific route (e.g./ws
) where you can handle events inside that route. For now as i described in this discussion, i was able to upgrade inside a route using the following:app.get('/ws', (c) => { const success = c.env.upgrade(c.req.raw) return true })
This worked in bun with typescript? That's undefined on c.env
for me
This worked in bun with typescript? That's undefined on c.env for me
I'm using JavaScript. You could try the other method i mentioned (inside the fetch
call)
I'm using JavaScript. You could try the other method i mentioned (inside the
fetch
call)
Ah I understand why, for the typescript folks, you can do this:
const app = new Hono<{
Bindings: {
server: Server
}
}>()
app.get("/ws", async (c) => {
if (!c.env.server.upgrade(c.req.raw, {
data: 'im some data for association'
})) {
logger.error("failed to upgrade!")
}
return new Response() // have to return empty response so hono doesn't get mad
})
Bun.serve({
port: process.env.PORT || "8080",
fetch: (req: Request, server: Server) => {
return app.fetch(req, {
server,
})
},
websocket: {
message(ws, msg) {
console.log("got message", ws.data, msg)
},
open(ws) {
console.log("websocket opened", ws.data)
},
},
)
was unaware of req.raw
before, now I know!
Nice you found out! I did a console.log(c)
to learn about the internals .. however not sure env is supposed to be used for this purpose??
It's because when you do the shortcut of fetch: app.fetch
you are passing in (req, server)
, with server
being the object that is assigned to the .env
of hono. Because .upgrade
is a method on server
, you are effectively making c.env
equivalent to the server
argument.
This is the type for Hono.fetch
fetch: (request: Request, Env?: E['Bindings'] | {}, executionCtx?: ExecutionContext) => Response | Promise<Response>;
I don't think it's possible to handle upgrades inside the route handler itself in Bun, but it's very doable in Node. I've made a PR to address this a while ago but I didn't have much time to update it so it's stale for now.
I really like how this is done in Elysia
Elysia has the advantage of supporting only Bun in this case because implementing a ws handler function would require some different code for every runtime. My plan was to make a helper like this without Bun support but I've yet to PR anything.
import { upgradeWebSocket } from "hono/ws";
import { Hono } from "hono";
const app = new Hono();
app.get("/ws", (c) => upgradeWebSocket(c, (ws) => {}));
Elysia has the advantage of supporting only Bun in this case ..
I see. Hono has to do more work to support multiple runtimes. Maybe Elysia is a better if only building for Bun.
If you want to have "normal" websockets (with listeners) in Bun/hono I made this little bit of code: https://gist.github.com/tobowers/2117365c8210d1758a4c6e2f859619c0
If you want to have "normal" websockets (with listeners) in Bun/hono I made this little bit of code: https://gist.github.com/tobowers/2117365c8210d1758a4c6e2f859619c0
That's pretty nice!
If you want to have "normal" websockets (with listeners) in Bun/hono
I'd rather have the 7x performance by Bun :)
If you want to have "normal" websockets (with listeners) in Bun/hono
I'd rather have the 7x performance by Bun :)
Sure, but most libraries can't handle it.
Sure, but most libraries can't handle it.
What libraries are you referring to? (I only need a simple web framework / library which can handle routes and WebSocket but is preferably "cross-runtime" and works with Bun)
I mean libraries consuming websockets