Skip to content

Commit

Permalink
add a protobuf example to showcase that msg.Body can be any data
Browse files Browse the repository at this point in the history
  • Loading branch information
kataras committed May 31, 2019
1 parent 2c18b0b commit 7b5a64d
Show file tree
Hide file tree
Showing 6 changed files with 279 additions and 0 deletions.
165 changes: 165 additions & 0 deletions _examples/protobuf/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package main

import (
"bufio"
"bytes"
"fmt"
"log"
"net/http"
"os"

"github.com/kataras/neffos"
"github.com/kataras/neffos/gorilla"

"github.com/golang/protobuf/proto"
)

// protoc --go_out=. user_message.proto
// go build
//
// Windows
// protobuf.exe server# once
// protobuf.exe client # two or more times
//
// Unix
// ./protobuf server # once
// ./protobuf client # two or more times
//
// At short, the `Message.Body` is the raw data client/server send,
// users of this library can use any format to unmarshal on read and marshal to send;
// protocolbuffers, encoding/json, encoding/xml and etc.
const (
endpoint = "localhost:8080"
namespace = "default"
)

var serverAndClientEvents = neffos.Namespaces{
namespace: neffos.Events{
neffos.OnNamespaceConnected: func(c *neffos.NSConn, msg neffos.Message) error {
// ready to send data to this namespace.

log.Printf("[%s] connected to [%s].\n", c, msg.Namespace)

// if is not client-side and returns a
// non-nil error then it refuses the client to connect to this specific namespace.
return nil
},
neffos.OnNamespaceDisconnect: func(c *neffos.NSConn, msg neffos.Message) error {
log.Printf("[%s] disconnected from [%s].\n", c, msg.Namespace)
return nil
},
"chat": func(c *neffos.NSConn, msg neffos.Message) error {
if msg.Err != nil {
log.Printf("remote error: %v\n", msg.Err)
return nil
}

if !c.Conn.IsClient() {
// broadcast to all clients except this one, when first parameter is not nil.
c.Conn.Server().Broadcast(c, msg)
} else {
// client received from server's broadcast.
var userMsg UserMessage
if err := proto.Unmarshal(msg.Body, &userMsg); err != nil {
return err
}
fmt.Printf("[%s] says: %s\n", userMsg.Username, userMsg.Text)
}

// if returns an error then the remote side's `msg.Err` will be filled with
// this error's text.
return nil
},
},
}

func main() {
args := os.Args[1:]
if len(args) == 0 {
log.Fatalf("expected program to start with 'server' or 'client' argument")
}
side := args[0]

switch side {
case "server":
startServer()
case "client":
startClient()
default:
log.Fatalf("unexpected argument, expected 'server' or 'client' but got '%s'", side)
}
}

func startServer() {
server := neffos.New(gorilla.DefaultUpgrader, serverAndClientEvents)
server.OnConnect = func(c *neffos.Conn) error {
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.", endpoint)
log.Fatal(http.ListenAndServe(endpoint, server))
}

func startClient() {
// init the websocket connection by dialing the server.
client, err := neffos.Dial(nil, gorilla.DefaultDialer, endpoint, serverAndClientEvents)
if err != nil {
log.Fatal(err)
}

go func() {
<-client.NotifyClose
os.Exit(0)
}()

// connect to the "default" namespace.
c, err := client.Connect(nil, namespace)
if err != nil {
log.Fatal(err)
}

fmt.Fprintf(os.Stdout, "Please specify a username: ")
usernameBytes, _, _ := bufio.NewReader(os.Stdin).ReadLine()
userMsg := &UserMessage{
Username: string(usernameBytes),
// only `Text` field is dynamic, therefore we can reuse this instance value,
// the `Text` field can be filled right before the namespace's `Emit`, check below.
}

fmt.Fprint(os.Stdout, ">> ")
scanner := bufio.NewScanner(os.Stdin)
for {
if !scanner.Scan() {
log.Printf("ERROR: %v", scanner.Err())
return
}

text := scanner.Bytes()

if bytes.Equal(text, []byte("exit")) {
client.Close() // or c.Conn.Close(), it's exactly the same.
// or to disconnect from the specific namespace:
// if err := c.Disconnect(nil); err != nil {
// log.Printf("ERROR: %v", err)
// }

break
}

// send data to the "chat" event.
userMsg.Text = string(text)
body, err := proto.Marshal(userMsg)
if err != nil {
log.Fatal(err)
}
c.Emit("chat", body)

fmt.Fprint(os.Stdout, ">> ")
}
}
85 changes: 85 additions & 0 deletions _examples/protobuf/user_message.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions _examples/protobuf/user_message.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
syntax="proto3";

package main;

message UserMessage {
string Username =1;
string Text = 2;
}
1 change: 1 addition & 0 deletions gobwas/dialer.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ var DefaultDialer = Dialer(gobwas.DefaultDialer)

// Dialer is a `neffos.Dialer` type for the gobwas/ws subprotocol implementation.
// Should be used on `Dial` to create a new client/client-side connection.
// To send headers to the server set the dialer's `Header` field to a `gobwas.HandshakeHeaderHTTP`.
func Dialer(dialer gobwas.Dialer) neffos.Dialer {
return func(ctx context.Context, url string) (neffos.Socket, error) {
underline, _, _, err := dialer.Dial(ctx, url)
Expand Down
12 changes: 12 additions & 0 deletions gobwas/helpers_go19.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// +build go1.9

package gobwas

import gobwas "github.com/gobwas/ws"

// Options is just an alias for the `gobwas/ws.Dialer` struct type.
type Options = gobwas.Dialer

// Header is an alias to the adapter that allows the use of `http.Header` as
// `gobwas/ws.Dialer.HandshakeHeader`.
type Header = gobwas.HandshakeHeaderHTTP
8 changes: 8 additions & 0 deletions gorilla/helpers_go19.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// +build go1.9

package gorilla

import gorilla "github.com/gorilla/websocket"

// Options is just an alias for the `gorilla/websocket.Dialer` struct type.
type Options = gorilla.Dialer

0 comments on commit 7b5a64d

Please sign in to comment.