Skip to content

Commit

Permalink
Merge pull request #2 from RafaelGomides/feature/GWSH-alpha0-first-files
Browse files Browse the repository at this point in the history
[ADD] First Files
  • Loading branch information
mrgomides committed Oct 3, 2019
2 parents e130c3a + 0f97e39 commit 1c233fa
Show file tree
Hide file tree
Showing 9 changed files with 383 additions and 1 deletion.
5 changes: 5 additions & 0 deletions .gitignore
Expand Up @@ -10,3 +10,8 @@

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Development structure
command/*

go.sum
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Expand Up @@ -25,7 +25,7 @@ For change proposals, send an email to [mrrafagomides@gmail.com](mailto:mrrafago

- `Fork` this repo to your account;
- `Clone`;
- Create a branch with this prefix: `GWSH-XXX`, where `XXX` is the issue number;
- Create a branch with this prefix: `[feature, bugfix, hotfix]/GWSH-XXX`, where `XXX` is the issue number;
- Work on your updates;
- `Commit`;
- Update your repo with `git push`;
Expand Down
18 changes: 18 additions & 0 deletions Makefile
@@ -0,0 +1,18 @@
.SILENT:

# Removes all files in dev
clean-development-old:
rm -rf command

create-command-main:
chmod +x scripts/generate-command.sh
./scripts/generate-command.sh

## Installs a development environment
install: clean-development-old create-command-main

run-dev:
@cd command; \
go build -v -o go-wsh-example
@./command/go-wsh-example

119 changes: 119 additions & 0 deletions client.go
@@ -0,0 +1,119 @@
package gowsh

import (
"encoding/json"
"fmt"
"log"

"github.com/gorilla/websocket"
)

// ClientGroup é responsável por manter
// as informações dos usuários do mesmo grupo
// para que possam fazer broadcast das mensagens
type ClientGroup struct {
// O ID do grupo serve para identifica-lo em meio a outros grupos
ID string
// Lista com todas as sessões de clientes conectados nesse grupo
ClientSessions []*ClientSession
}

// NewClientGroup retorna um novo grupo sem sessões
func NewClientGroup(groupID string) *ClientGroup {
if groupID == "" {
return &ClientGroup{
ID: GenerateULID(),
}
}
return &ClientGroup{
ID: groupID,
}
}

// AddClientSession coloca uma sessão nova dentro do grupo
func (cg *ClientGroup) AddClientSession(clientSession *ClientSession) {
cg.ClientSessions = append(cg.ClientSessions, clientSession)
}

func (cg *ClientGroup) sendMessageToGroup(message *EventMessage) {
for _, clientSession := range cg.ClientSessions {
clientSession.SendMessage(message)
}
}

// ClientSession é responsável por manter as
// informações do usuário que fez a solicitação
type ClientSession struct {
// ID serve para diferencia-lo dos outros dãã...
ID string
// Esse é o grupo que esse client está inserido
Group string
// WebsocketConnection carrega a conexão WS do cliente para que ele possa continuar se comunicando
WebsocketConnection *websocket.Conn
// SendResponse envia para o usuário as respostas das chamadas
SendResponse chan []byte
// FinishClientSession finaliza o hub de operação dele
FinishClientSession chan bool
// Este cara vai receber a solicitação e vai trata-la
EventsHub *EventHub
}

// NewClientSession cria uum novo usuário
func NewClientSession() *ClientSession {
return &ClientSession{
ID: GenerateULID(),
SendResponse: make(chan []byte),
FinishClientSession: make(chan bool),
}
}

// SendMessage envia uma mensagem no padrão EventMessage para o Client
func (cs *ClientSession) SendMessage(message *EventMessage) {
msg, err := json.Marshal(message)
if err != nil {
log.Printf("[ERRO] SendMessage can't marshal message: %v", err)
return
}
cs.SendResponse <- msg
}

// SendBroadcast envia uma mensagem no padrão EventMessage para todos os Clients do mesmo grupo
func (cs *ClientSession) SendBroadcast(message *EventMessage) {
cs.EventsHub.ClientGroups[cs.Group].sendMessageToGroup(message)
}

// ReadFromSocket Pega as mensagens que vem do websocket
func (cs *ClientSession) ReadFromSocket() {
eventMessageRaw := &EventMessage{}
for {
err := cs.WebsocketConnection.ReadJSON(eventMessageRaw)
if err != nil {
log.Printf("[ERRO] ReadFromSocket can't read message: %v\n", err)
}
cs.SendResponse <- []byte(fmt.Sprintf(`{"event": "%s_PROCESSING"}`, eventMessageRaw.Event))
go log.Printf("[WS] New Event: %s\n", eventMessageRaw.Event)
eventMessageRaw.Client = cs
cs.EventsHub.Messaging <- eventMessageRaw
}
}

// WriteToSocket Envia a mensagem para o cliente
func (cs *ClientSession) WriteToSocket() {
defer func() {
close(cs.SendResponse)
close(cs.FinishClientSession)
}()
for {
select {
case message, isOk := <-cs.SendResponse:
if !isOk {
log.Printf("[ERRO] WriteToSocket can't get message: %+v", cs.ID)
}
// Nessa parte deve ser utilizado a conexão
cs.WebsocketConnection.WriteMessage(websocket.TextMessage, message)
// Aqui a sessão do cliente é fechada
case <-cs.FinishClientSession:
return
}
}
}
17 changes: 17 additions & 0 deletions configs.go
@@ -0,0 +1,17 @@
package gowsh

import (
"net/http"

"github.com/gorilla/websocket"
)

var (
// ClientGroupsLength é o tamanho dos grupos que estão no Hub
ClientGroupsLength = 5
Upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool { return true },
}
)
90 changes: 90 additions & 0 deletions event.go
@@ -0,0 +1,90 @@
package gowsh

// EventMessage é o modelo de
// mensagens que serão compartilhados no webscoket
type EventMessage struct {
// Event é o tipo de evento que está relacionado a essa chamada
Event string `json:"event"`

// Data é a informação que a mensagem está transportando
Data interface{} `json:"data"`

// Client é o usuário que fez a solicitação
Client *ClientSession `json:"-"`
}

// EventHub é o centralizador das mensagens,
// ele é responsável por pegar as mensagens e as enviar
// para as rotas
type EventHub struct {
// Este é o canal que vai distribuir as mensagens
Messaging chan *EventMessage
// Este canal finaliza o hub
Finish chan bool
// Essa é a lista com todos os Handlers
Handlers *EventHandlers
// Armazena todos os grupos de mensagem
ClientGroups map[string]*ClientGroup
}

// AddGroup update group list with one new group
func (eh *EventHub) AddGroup(clientGroup *ClientGroup) {
eh.ClientGroups[clientGroup.ID] = clientGroup
}

// EventHandlers carrega a lista com todas as possiveis
// chamadas e seus handlers
type EventHandlers struct {
HandlerList map[string]func(*EventMessage)
}

// NewEventHub cria o novo EventHub com o channel já iniciado
func NewEventHub() *EventHub {
return &EventHub{
Messaging: make(chan *EventMessage),
Finish: make(chan bool),
Handlers: &EventHandlers{
HandlerList: make(map[string]func(*EventMessage)),
},
ClientGroups: make(map[string]*ClientGroup, ClientGroupsLength),
}
}

// AddHandler adiciona um novo handler para as chamadas
func (eh *EventHub) AddHandler(event string, f func(*EventMessage)) {
eh.Handlers.HandlerList[event] = f
}

// Run aqui é o hub onde as mensagens vão ser lidas do messaging
// e posteriormente serem enviadas
func (eh *EventHub) Run() {
defer func() {
close(eh.Messaging)
close(eh.Finish)
}()
for {
select {
// Sempre que uma mensagem for recebida ela deve ser enviada aqté este lugar
case message := <-eh.Messaging:
// Aqui devem ficar as verificações sobre quais eventos estão sendo enviados, pois assim podemos direcionar para cada handler'
go EventDispatcher(eh.Handlers, message)
// Se receber algo nesse canal o hub é finalizado
// Essa chamada deve ser feita apenas em casos especificos pois
// assim que o hub for fechado a aplicação é encerrada
case <-eh.Finish:
return
}
}
}

// EventDispatcher é o responsável por tratar as mensagens recebidas
// pelo websocket e direcionalas ao handler correto
//
// Essa função poderia estar um um arquivo próprio dentro deste pacote, pois a mesma vai acabar ficando muito grande
func EventDispatcher(handlers *EventHandlers, message *EventMessage) {
if f, ok := handlers.HandlerList[message.Event]; ok {
f(message)
} else {
message.Client.SendMessage(&EventMessage{Event: "EVENT_NOT_FOUND"})
}
}
9 changes: 9 additions & 0 deletions go.mod
@@ -0,0 +1,9 @@
module go-wsh

go 1.13

require (
github.com/gorilla/mux v1.7.3
github.com/gorilla/websocket v1.4.1
github.com/oklog/ulid v1.3.1
)
109 changes: 109 additions & 0 deletions scripts/generate-command.sh
@@ -0,0 +1,109 @@
#!/bin/bash

# Create command Folder with files
mkdir command
touch command/main.go

cat <<EOM > command/main.go
package main
import (
gowsh "go-wsh"
"log"
"net/http"
"sync"
"time"
"github.com/gorilla/mux"
)
type runtime struct {
wg *sync.WaitGroup
http.Server
}
func newRuntime() *runtime {
return &runtime{
wg: &sync.WaitGroup{},
}
}
func (rt *runtime) loadConfiguration() {
log.Println("[INFO] Loading Configurations")
rt.Addr = "localhost:8080"
rt.WriteTimeout = time.Second * 15
rt.ReadTimeout = time.Second * 15
rt.IdleTimeout = time.Second * 60
}
func (rt *runtime) serveHTTP(routerHandles *mux.Router) {
defer rt.wg.Done()
rt.Handler = routerHandles
log.Printf("[INFO] HTTP server started at \"%s\"\n", rt.Addr)
log.Fatal(rt.ListenAndServe())
}
var (
hub *gowsh.EventHub
)
func main() {
log.Println("[INFO] Starting API")
rt := newRuntime()
rt.loadConfiguration()
rt.wg.Add(2)
log.Println("[INFO] Starting HTTP server")
go rt.serveHTTP(router())
log.Println("[INFO] Starting Websocket Hub")
hub = gowsh.NewEventHub()
go hub.Run()
rt.wg.Wait()
}
func router() *mux.Router {
r := mux.NewRouter()
r.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
ServeWs(hub, w, r)
})
r.HandleFunc("/ws/{group_id}", func(w http.ResponseWriter, r *http.Request) {
ServeWs(hub, w, r)
})
return r
}
var upgrader = gowsh.Upgrader
func ServeWs(hub *gowsh.EventHub, w http.ResponseWriter, r *http.Request) {
wsConn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Printf("[ERRO] Creating websocket session: %v", err)
}
newClientSes := gowsh.NewClientSession()
newClientSes.WebsocketConnection = wsConn
newClientSes.EventsHub = hub
groupID := mux.Vars(r)["group_id"]
if groupID == "" {
newClientSes.Group = addClientGroup(groupID, newClientSes, hub)
} else {
group, isAdded := hub.ClientGroups[groupID]
if isAdded {
group.AddClientSession(newClientSes)
} else {
addClientGroup(groupID, newClientSes, hub)
}
newClientSes.Group = groupID
}
go newClientSes.WriteToSocket()
newClientSes.ReadFromSocket()
}
func addClientGroup(groupID string, newClientSes *gowsh.ClientSession, hub *gowsh.EventHub) string {
newClientGroup := gowsh.NewClientGroup(groupID)
newClientGroup.AddClientSession(newClientSes)
hub.AddGroup(newClientGroup)
return newClientGroup.ID
}
EOM

0 comments on commit 1c233fa

Please sign in to comment.