-
-
Notifications
You must be signed in to change notification settings - Fork 49
Encoding
With neffos you can send any type of data, remember? The neffos.Message.Body
field is just a []byte
. At short, the neffos.Message.Body
is the raw data client/server sends, users of the neffos package can use any format to unmarshal on read and marshal to send, such as protocol-buffers
, encoding/json
, encoding/xml
and etc.
In this section you will learn how to send JSON
data per Room using the Emit
with neffos.Marshal
and how to read those data inside an event callback's with the neffos.Message.Unmarshal
method. The neffos.DefaultMarshaler/Unnmarshaler
will be used on neffos.Marshal
and neffos.Message.Unmarshal
if the value you are trying to send/read does not complete the neffos.MessageObjectMarshaler
on send and neffos.MessageObjectUnmarshaler
on read, therefore the default encoding that neffos using for struct values is the JSON one. Below you will find a brief outline of the above:
package neffos
var (
DefaultMarshaler = json.Marshal
DefaultUnmarshaler = json.Unmarshal
)
type (
MessageObjectMarshaler interface {
Marshal() ([]byte, error)
}
MessageObjectUnmarshaler interface {
Unmarshal(body []byte) error
}
)
type Message struct {
// [...]
}
func (m *Message) Unmarshal(outPtr interface{}) error {
if unmarshaler, ok := outPtr.(MessageObjectUnmarshaler); ok {
return unmarshaler.Unmarshal(m.Body)
}
return DefaultUnmarshaler(m.Body, outPtr)
}
// file: main.go
// userMessage implements the `neffos.MessageBodyMarshaler` `neffos.MessageBodyUnmarshaler`.
type userMessage struct {
From string `json:"from"`
Text string `json:"text"`
}
// Defaults to `DefaultUnmarshaler & DefaultMarshaler` that are calling the json.Unmarshal & json.Marshal respectfully
// if the instance's Marshal and Unmarshal methods are missing,
// however for the example shake we complete the MessageBodyMarshaler and MessageBodyUnmarshaler too,
// these can help you customize the out and in format.
func (u *userMessage) Marshal() ([]byte, error) {
return json.Marshal(u)
}
func (u *userMessage) Unmarshal(b []byte) error {
return json.Unmarshal(b, u)
}
// file: main.go
var serverAndClientEvents = neffos.Namespaces{
namespace: neffos.Events{
neffos.OnNamespaceConnected: func(c *neffos.NSConn, msg neffos.Message) error {
log.Printf("[%s] connected to namespace [%s].", c, msg.Namespace)
return nil
},
neffos.OnNamespaceDisconnect: func(c *neffos.NSConn, msg neffos.Message) error {
log.Printf("[%s] disconnected from namespace [%s].", c, msg.Namespace)
return nil
},
neffos.OnRoomJoined: func(c *neffos.NSConn, msg neffos.Message) error {
text := fmt.Sprintf("[%s] joined to room [%s].", c, msg.Room)
log.Printf("\n%s", text)
// notify others.
if !c.Conn.IsClient() {
c.Conn.Server().Broadcast(c, neffos.Message{
Namespace: msg.Namespace,
Room: msg.Room,
Event: "notify",
Body: []byte(text),
})
}
return nil
},
neffos.OnRoomLeft: func(c *neffos.NSConn, msg neffos.Message) error {
text := fmt.Sprintf("[%s] left from room [%s].", c, msg.Room)
log.Printf("\n%s", text)
// notify others.
if !c.Conn.IsClient() {
c.Conn.Server().Broadcast(c, neffos.Message{
Namespace: msg.Namespace,
Room: msg.Room,
Event: "notify",
Body: []byte(text),
})
}
return nil
},
"chat": func(c *neffos.NSConn, msg neffos.Message) error {
if !c.Conn.IsClient() {
c.Conn.Server().Broadcast(c, msg)
} else {
var userMsg userMessage
err := msg.Unmarshal(&userMsg)
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s >> [%s] says: %s\n", msg.Room, userMsg.From, userMsg.Text)
}
return nil
},
// client-side only event to catch any server messages comes from the custom "notify" event.
"notify": func(c *neffos.NSConn, msg neffos.Message) error {
if !c.Conn.IsClient() {
return nil
}
fmt.Println(string(msg.Body))
return nil
},
},
}
// file: main.go
func startServer() {
server := neffos.New(gobwas.DefaultUpgrader, serverAndClientEvents)
server.IDGenerator = func(w http.ResponseWriter, r *http.Request) string {
if userID := r.Header.Get("X-Username"); userID != "" {
return userID
}
return neffos.DefaultIDGenerator(w, r)
}
server.OnUpgradeError = func(err error) {
log.Printf("ERROR: %v", err)
}
server.OnConnect = func(c *neffos.Conn) error {
if c.WasReconnected() {
log.Printf("[%s] connection is a result of a client-side re-connection, with tries: %d", c.ID(), c.ReconnectTries)
}
log.Printf("[%s] connected to the server.", c)
// if returns non-nil error then it refuses the client to connect to the server.
return nil
}
server.OnDisconnect = func(c *neffos.Conn) {
log.Printf("[%s] disconnected from the server.", c)
}
log.Printf("Listening on: %s\nPress CTRL/CMD+C to interrupt.", addr)
http.Handle("/", http.FileServer(http.Dir("./browser")))
http.Handle(endpoint, server)
log.Fatal(http.ListenAndServe(addr, nil))
}
// file: main.go
func startClient() {
scanner := bufio.NewScanner(os.Stdin)
fmt.Print("Please specify a username: ")
if !scanner.Scan() {
return
}
username := scanner.Text()
// init the websocket connection by dialing the server.
client, err := neffos.Dial(
// Optional context cancelation and deadline for dialing.
nil,
// The underline dialer, can be also a gobwas.Dialer/DefautlDialer or a gorilla.Dialer/DefaultDialer.
// Here we wrap a custom gobwas dialer in order to send the username among, on the handshake state,
// see `startServer().server.IDGenerator`.
gobwas.Dialer(gobwas.Options{Header: gobwas.Header{"X-Username": []string{username}}}),
// The endpoint, i.e ws://localhost:8080/path.
addr+endpoint,
// The namespaces and events, can be optionally shared with the server's.
serverAndClientEvents)
if err != nil {
log.Fatal(err)
}
defer client.Close()
go func() {
<-client.NotifyClose
os.Exit(0)
}()
// connect to the "default" namespace.
c, err := client.Connect(nil, namespace)
if err != nil {
log.Fatal(err)
}
askRoom:
fmt.Print("Please specify a room to join, i.e room1: ")
if !scanner.Scan() {
log.Fatal(scanner.Err())
}
roomToJoin := scanner.Text()
room, err := c.JoinRoom(nil, roomToJoin)
if err != nil {
log.Fatal(err)
}
fmt.Fprint(os.Stdout, ">> ")
for {
if !scanner.Scan() {
log.Printf("ERROR: %v", scanner.Err())
break
}
text := scanner.Text()
if text == "exit" {
break
}
if text == "leave" {
room.Leave(nil)
goto askRoom
}
// username is the connection's ID ==
// room.String() returns -> NSConn.String() returns -> Conn.String() returns -> Conn.ID()
// which generated by server-side via `Server#IDGenerator`.
userMsg := userMessage{From: username, Text: text}
room.Emit("chat", neffos.Marshal(userMsg))
fmt.Fprint(os.Stdout, ">> ")
}
}
In this example, we make an exception and we include the browser-side as well, so you can have a small taste of it.
Read more about neffos.js.
// file: browser/app.js
const neffos = require('neffos.js');
var scheme = document.location.protocol == "https:" ? "wss" : "ws";
var port = document.location.port ? ":" + document.location.port : "";
var wsURL = scheme + "://" + document.location.hostname + port + "/echo";
var outputTxt = document.getElementById("output");
function addMessage(msg) {
outputTxt.innerHTML += msg + "\n";
}
function handleError(reason) {
console.log(reason);
window.alert(reason);
}
class UserMessage {
constructor(from, text) {
this.from = from;
this.text = text;
}
}
async function handleNamespaceConnectedConn(nsConn) {
const roomToJoin = prompt("Please specify a room to join, i.e room1: ");
nsConn.joinRoom(roomToJoin);
let inputTxt = document.getElementById("input");
let sendBtn = document.getElementById("sendBtn");
sendBtn.disabled = false;
sendBtn.onclick = function () {
const input = inputTxt.value;
inputTxt.value = "";
switch (input) {
case "leave":
nsConn.room(roomToJoin).leave();
// or room.leave();
break;
default:
const userMsg = new UserMessage(nsConn.conn.ID, input);
nsConn.emit("chat", neffos.marshal(userMsg));
addMessage("Me: " + input);
}
};
}
async function runExample() {
// You can omit the "default" and simply define only Events, the namespace will be an empty string"",
// however if you decide to make any changes on this example make sure the changes are reflecting inside the ../server.go file as well.
try {
const username = prompt("Please specify a username: ");
const conn = await neffos.dial(wsURL, {
default: { // "default" namespace.
_OnNamespaceConnected: function (nsConn, msg) {
addMessage("connected to namespace: " + msg.Namespace);
handleNamespaceConnectedConn(nsConn);
},
_OnNamespaceDisconnect: function (nsConn, msg) {
addMessage("disconnected from namespace: " + msg.Namespace);
},
_OnRoomJoined: function (nsConn, msg) {
addMessage("joined to room: " + msg.Room);
},
_OnRoomLeft: function (nsConn, msg) {
addMessage("left from room: " + msg.Room);
},
notify: function (nsConn, msg) {
addMessage(msg.Body);
},
chat: function (nsConn, msg) { // "chat" event.
const userMsg = msg.unmarshal()
addMessage(userMsg.from + ": " + userMsg.text);
}
}
}, {
headers: {
'X-Username': username
},
// if > 0 then on network failures it tries to reconnect every 5 seconds, defaults to 0 (disabled).
reconnect: 5000
});
conn.connect("default");
} catch (err) {
handleError(err);
}
}
runExample();
Open a terminal window instance and execute:
$ cd ./browser
# build the browser-side client: ./browser/bundle.js which ./browser/index.html imports.
$ npm install && npm run-script build
$ cd ../
$ go run main.go server # start the neffos websocket server.
Open some web browser windows and navigate to http://localhost:8080, each window will ask for a username and a room to join, each window(client connection) and server get notified for namespace connected/disconnected, room joined/left and chat events.
To start the go client side just open a new terminal window and execute:
$ go run main.go client
It will ask you for username and a room to join as well, it acts exactly the same as the ./browser/app.js
browser-side application.
Read the full source code of this example by navigating to the repository's _examples/basic directory.
You can continue by learning how to send and receive Protobufs.
Home | About | Project | Getting Started | Technical Docs | Copyright © 2019-2023 Gerasimos Maropoulos. Documentation terms of use.