Skip to content

Repository files navigation

#WebSockets v0.2.0

Written in: Fantompod: v0.2.0Licence: ISC

Overview

WebSockets is a Fantom implementation of the W3C WebSocket API and adheres to RFC 6455.

The same WebSocket class may be used as a Fantom desktop client, a Javascript web client, or from a web server.

WebSockets does not currently support frame fragmentation or continuations.

Install

Install WebSockets with the Fantom Pod Manager ( FPM ):

C:\> fpm install afWebSockets

Or install WebSockets with fanr:

C:\> fanr install -r http://eggbox.fantomfactory.org/fanr/ afWebSockets

To use in a Fantom project, add a dependency to build.fan:

depends = ["sys 1.0", ..., "afWebSockets 0.2"]

Documentation

Full API & fandocs are available on the Eggbox - the Fantom Pod Repository.

Quick Start

This example is a simple instant messaging application called Chatbox!

It has a basic client that, using the same Fantom code, runs in a browser and as a desktop application. A BedSheet web application is used to service the WebSockets and serve the Chatbox web client.

Due to the web client being Javascript compiled from Fantom code, Chatbox must first be compiled to a pod. All the interesting WebSocket code is in the ChatboxRoutes and ChatboxClient classes.

  1. Create a text file called Chatbox.fan containing the Chatbox code.

  2. Compile the script to create a wsChatbox pod, the warnings are expected and may be ignored.

     C:\> fan Chatbox.fan -build
    WARN: Type 'afConcurrent::Synchronized' not available in Js
    WARN: Unknown build option -build
    compile [wsChatbox]
    Compile [wsChatbox]
    FindSourceFiles [1 files]
    CompileJs
    WritePod [file:/C:/Apps/Fantom/fan/lib/fan/wsChatbox.pod]
    BUILD SUCCESS [237ms]!
    
  3. Start the Chatbox server:

     C:\> fan wsChatbox -server
    [info] [afBedSheet] Found mod 'wsChatbox::AppModule'
    [info] [afBedSheet] Starting Bed App 'wsChatbox' on port 8069
    [info] [web] http started on port 8069
    [info] [afIoc] Adding module definitions from pod 'afWebSockets'
    [info] [afIoc] Adding module definition for wsChatbox::AppModule
    [info] [afIoc] Adding module afWebSockets::WebSocketsModule
    [info] [afIoc] Adding module afConcurrent::ConcurrentModule
    [info] [afIoc] Adding module afBedSheet::BedSheetModule
    [info] [afIoc] Adding module afIocConfig::ConfigModule
    [info] [afIoc] Adding module afIocEnv::IocEnvModule
    [info] [afIoc] Adding module afDuvet::DuvetModule
    [info] [afIoc] Adding module wsChatbox::AppModule
    [info] [afIoc] Adding module afBedSheet::BedSheetEnvModule
    ....
    ___ __ _____ _
    / _ | / /_____ _____ / ___/__ ___/ /_________ __ __
    / _ | / // / -_|/ _ /===/ __// _ \/ _/ __/ _ / __|/ // /
    /_/ |_|/_//_/\__|/_//_/ /_/ \_,_/__/\__/____/_/ \_, /
    Alien-Factory BedSheet v1.5.0, IoC v3.0.0 /___/
    IoC Registry built in 200ms and started up in 78ms
    Bed App 'ChatBox - A WebSocket Demo' listening on http://localhost:8069/
    
  4. Visit http://localhost:8069/ to load a Chatbox web client:

Chatbox Web Client

You may also start a Chatbox desktop client:

 C:\> fan wsChatbox -client

Chatbox Desktop Client

Messages are sent to the server, which then broadcasts it back out all connected clients. Instant messaging!

Usage

The WebSocket class adheres to the W3C WebSocket API and may be used as a client, or on the server, and even from Javascript. It has hooks that allow you to respond to various WebSocket events:

Fantom Client

To create a Fantom WebSocket client:

webSock := WebSocket()
webSock.onMessage |MsgEvent e| { ... }
webSock.open(`ws:localhost:8069`)
// block in an event loop until the socket is closed
webSock.read

Note that WebSocket.read() enters a read event loop that blocks the current thread / Actor until the WebSocket is closed. Therefore it may be advantageous to call the read() method in an asynchronous fashion. Alien-Factory's Concurrent library is the easiest way to do this:

using afConcurrent::Synchronized
// call the blocking read() method in a background thread
safeSock := Unsafe(webSock)
Synchronized(ActorPool()).async |->| {
safeSock.val->read
}

Javascript Client

To create a Javascript WebSocket client:

webSock := WebSocket()
webSock.onMessage |MsgEvent e| { ... }
webSock.open(`ws:localhost:8069`)

Note that due to the asynchronous of Javascript, there is no need to call the read() method.

BedSheet Server

To use in a BedSheet web application to service HTTP requests, simply create a Route handler that returns a WebSocket instance:

using afWebSockets
const class WsRoutes {
WebSocket serviceWs() {
webSock := WebSocket()
webSock.onMessage |MsgEvent e| { ... }
return webSock
}
}

The instance will be saved in the WebSockets service so messages may be sent to the client at any time.

@Inject private const WebSockets webSockets
...
Void sayHello(Uri webSockId) {
webSockets[webSockId].sendText("Hello Pips!")
}

The WebSocket instance will be automatically disposed of when the connection is closed, either by the client or server.

WebMod Server

To use in a WebMod, the WebSocket instance needs to be upgraded manually:

using afWebSockets
const class WsWebMod : WebMod {
Void serviceWs() {
webSock := WebSocket()
webSock.onMessage |MsgEvent e| { ... }
webSock.upgrade(req, res)
webSock.read
}
}

Or you could store in a WebSockets instance:

using afWebSockets
using web
const class WsWebMod : WebMod {
const WebSockets webSockets := WebSockets(ActorPool())
Void serviceWs() {
webSock := WebSocket()
webSock.onMessage |MsgEvent e| { ... }
webSockets.service(webSock, req, res)
}
override Void onStop() {
webSockets.shutdown
}
}

Chatbox Code

A fully working client and server instant messaging program for the web and desktop!

using afWebSockets
using afIoc
using afBedSheet
using afBedSheet::Text as BsText
using afConcurrent::Synchronized
using afDuvet::DuvetModule
using afDuvet::HtmlInjector
using concurrent::ActorPool
using fwt
using build::BuildPod
class Main {
Void main(Str[] args) {
if (args.first == "-client")
ChatboxClient().main
if (args.first == "-server")
BedSheetBuilder(AppModule#.qname).addModulesFromPod("afWebSockets").startWisp(8069)
if (args.first == "-build")
Builder().main
}
}
const class AppModule {
@Contribute { serviceType=Routes# }
Void contributeRoutes(Configuration conf) {
conf.add(Route(`/`, ChatboxRoutes#indexPage))
conf.add(Route(`/ws`, ChatboxRoutes#serviceWebSocket))
}
}
const class ChatboxRoutes {
@Inject private const WebSockets webSockets
@Inject private const HtmlInjector htmlInjector
new make(|This|in) { in(this) }
BsText indexPage() {
htmlInjector.injectFantomMethod(ChatboxClient#main)
return BsText.fromHtml(
"<!DOCTYPE html>
<html>
<head>
<title>ChatBox - A WebSocket Demo</title>
</head>
<body>
</body>
</html>")
}
WebSocket serviceWebSocket() {
WebSocket() {
ws := it
onMessage = |MsgEvent me| {
webSockets.broadcast("${ws.id} says, '${me.txt}'")
}
}
}
}
@Js
class ChatboxClient {
Void main() {
webSock := WebSocket().open(`ws://localhost:8069/ws`)
convBox := Text { text = "The conversation:\r\n"; multiLine = true; editable = false }
textBox := Text { text = "Say something!" }
sendMsg := |Event e| {
webSock.sendText(textBox.text)
textBox.text = ""
}
webSock.onMessage = |MsgEvent msgEnv| {
convBox.text += "\r\n" + msgEnv.txt
}
textBox.onAction.add(sendMsg)
window := Window {
title = "ChatBox - A WebSocket Demo"
InsetPane {
EdgePane {
center = convBox
bottom = EdgePane {
center = textBox
right = Button { text = "Send"; onAction.add(sendMsg) }
}
},
},
}
// desktop only code
if (Env.cur.runtime != "js") {
// ensure event funcs are run in the UI thread
safeFunc := Unsafe(webSock.onMessage)
webSock.onMessage = |MsgEvent msgEnv| {
safeMess := Unsafe(msgEnv)
Desktop.callAsync |->| { safeFunc.val->call(safeMess.val) }
}
// call the blocking read() method in a background thread
safeSock := Unsafe(webSock)
Synchronized(ActorPool()).async |->| {
safeSock.val->read
}
}
window.open
}
}
class Builder : BuildPod {
new make() {
podName = "wsChatbox"
summary = "A WebSocket Demo"
meta = [
"proj.name" : "ChatBox - A WebSocket Demo",
"afIoc.module" : "wsChatbox::AppModule",
]
depends = [
"sys 1.0.70 - 1.0",
"fwt 1.0.70 - 1.0",
"web 1.0.70 - 1.0",
"build 1.0.70 - 1.0",
"concurrent 1.0.70 - 1.0",
"afIoc 3.0.6 - 3.0",
"afConcurrent 1.0.20 - 1.0",
"afBedSheet 1.5.10 - 1.5",
"afDuvet 1.1.8 - 1.1",
"afWebSockets 0.2.0 - 0.1",
]
srcDirs = [`Chatbox.fan`]
}
}

About

A pure Fantom implementation of the W3C WebSocket API for use by clients and servers

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages