:notes: Minimalist websocket framework for Go

Overview

melody

Build Status Coverage Status GoDoc

🎶 Minimalist websocket framework for Go.

Melody is websocket framework based on github.com/gorilla/websocket that abstracts away the tedious parts of handling websockets. It gets out of your way so you can write real-time apps. Features include:

  • Clear and easy interface similar to net/http or Gin.
  • A simple way to broadcast to all or selected connected sessions.
  • Message buffers making concurrent writing safe.
  • Automatic handling of ping/pong and session timeouts.
  • Store data on sessions.

Install

go get gopkg.in/olahol/melody.v1

Example: chat

Chat

Using Gin:

package main

import (
	"github.com/gin-gonic/gin"
	"gopkg.in/olahol/melody.v1"
	"net/http"
)

func main() {
	r := gin.Default()
	m := melody.New()

	r.GET("/", func(c *gin.Context) {
		http.ServeFile(c.Writer, c.Request, "index.html")
	})

	r.GET("/ws", func(c *gin.Context) {
		m.HandleRequest(c.Writer, c.Request)
	})

	m.HandleMessage(func(s *melody.Session, msg []byte) {
		m.Broadcast(msg)
	})

	r.Run(":5000")
}

Using Echo:

package main

import (
	"github.com/labstack/echo"
	"github.com/labstack/echo/engine/standard"
	"github.com/labstack/echo/middleware"
	"gopkg.in/olahol/melody.v1"
	"net/http"
)

func main() {
	e := echo.New()
	m := melody.New()

	e.Use(middleware.Logger())
	e.Use(middleware.Recover())

	e.GET("/", func(c echo.Context) error {
		http.ServeFile(c.Response().(*standard.Response).ResponseWriter, c.Request().(*standard.Request).Request, "index.html")
		return nil
	})

	e.GET("/ws", func(c echo.Context) error {
		m.HandleRequest(c.Response().(*standard.Response).ResponseWriter, c.Request().(*standard.Request).Request)
		return nil
	})

	m.HandleMessage(func(s *melody.Session, msg []byte) {
		m.Broadcast(msg)
	})

	e.Run(standard.New(":5000"))
}

Example: gophers

Gophers

package main

import (
	"github.com/gin-gonic/gin"
	"gopkg.in/olahol/melody.v1"
	"net/http"
	"strconv"
	"strings"
	"sync"
)

type GopherInfo struct {
	ID, X, Y string
}

func main() {
	router := gin.Default()
	mrouter := melody.New()
	gophers := make(map[*melody.Session]*GopherInfo)
	lock := new(sync.Mutex)
	counter := 0

	router.GET("/", func(c *gin.Context) {
		http.ServeFile(c.Writer, c.Request, "index.html")
	})

	router.GET("/ws", func(c *gin.Context) {
		mrouter.HandleRequest(c.Writer, c.Request)
	})

	mrouter.HandleConnect(func(s *melody.Session) {
		lock.Lock()
		for _, info := range gophers {
			s.Write([]byte("set " + info.ID + " " + info.X + " " + info.Y))
		}
		gophers[s] = &GopherInfo{strconv.Itoa(counter), "0", "0"}
		s.Write([]byte("iam " + gophers[s].ID))
		counter += 1
		lock.Unlock()
	})

	mrouter.HandleDisconnect(func(s *melody.Session) {
		lock.Lock()
		mrouter.BroadcastOthers([]byte("dis "+gophers[s].ID), s)
		delete(gophers, s)
		lock.Unlock()
	})

	mrouter.HandleMessage(func(s *melody.Session, msg []byte) {
		p := strings.Split(string(msg), " ")
		lock.Lock()
		info := gophers[s]
		if len(p) == 2 {
			info.X = p[0]
			info.Y = p[1]
			mrouter.BroadcastOthers([]byte("set "+info.ID+" "+info.X+" "+info.Y), s)
		}
		lock.Unlock()
	})

	router.Run(":5000")
}

More examples

Documentation

Contributors

  • Ola Holmström (@olahol)
  • Shogo Iwano (@shiwano)
  • Matt Caldwell (@mattcaldwell)
  • Heikki Uljas (@huljas)
  • Robbie Trencheny (@robbiet480)
  • yangjinecho (@yangjinecho)

FAQ

If you are getting a 403 when trying to connect to your websocket you can change allow all origin hosts:

m := melody.New()
m.Upgrader.CheckOrigin = func(r *http.Request) bool { return true }
Issues
  • Ability to close hub

    Ability to close hub

    It looks like once you start a melody server, that hub.run() goroutine will continue forever. This usually makes sense, but in my tests I often want to start up a hub, through some websocket connections at it, then tair it down at the end of the test.

    What do you think about adding the ability to call Run with some sort of cancellation? Maybe either a context or just a chan struct{} that you would close to stop the run?

    opened by saulshanabrook 13
  • Add hook for extending PongHandler

    Add hook for extending PongHandler

    This allows the user to know when pongs are received and add their own functionality when this occurs. The handler can be set when connection is established (HandleConnect function), since the value set by readPump will be able to be overwritten at this point.

    Example usage:

    m.HandleConnect(func(s *melody.Session) {
        s.SetPongHandler(func() error {
            // Do your thing...
            return nil
        })
    })
    
    opened by madirey 8
  • Sending message in its own goroutine can result in wrong message order.

    Sending message in its own goroutine can result in wrong message order.

    Hello! It seems that sending message in its own goroutine:

            case m := <-h.broadcast:
                for s := range h.sessions {
                    if m.filter != nil {
                        if m.filter(s) {
                            go s.writeMessage(m)
                        }
                    } else {
                        go s.writeMessage(m)
                    }
                }
    

    can theoretically result in wrong message order when message rate is high as code relies on the order of goroutine execution.

    Maybe I miss something or it's not an issue for melody – just noticed and decided to ask.

    opened by FZambia 5
  • Wrong data in Gin log

    Wrong data in Gin log

    Hello!

    This is a bit cosmetic, but when trying out the basic example I've found that when you close the page (the ws connection) Gin is giving a log message with 200 code routing to /ws, with the amount of time the connection was alive. You can try it out. Is this intended or can be fixed?

    It actually also gives a 304 for a root route: https://s.vadimyer.com/1632849b-d353-4882-bee1-72bf006e645c.png

    opened by vadimyer 5
  • Tagging a release as v1

    Tagging a release as v1

    With my recent changes (even before them honestly) I think melody is at a good spot for a tagging a v1 release, which would allow users to use gopkg.in to do stable imports of the package. What do you think @olahol?

    opened by robbiet480 5
  • Make melody.hub public

    Make melody.hub public

    Hey, I need a way to figure out if a there are active sessions because I only want to do work if there are any. I almost implemented something like the melody.hub myself, but I think that it would be better to make this public or provide some kind of API. Thanks

    opened by metalmatze 5
  • No need Mutex if you use Channel. Am I right?

    No need Mutex if you use Channel. Am I right?

    I'm talk about https://github.com/olahol/melody/blob/master/hub.go#L34-L36 and code below

    opened by ianberdin 4
  • SSL support?

    SSL support?

    Hi, I run a melody server on EC2 which uses websockets and I was wondering how I would add SSL support. Is it the same way you would SSL like in a golang http server like so:

    http.ListenAndServeTLS(":8081", "cert.pem", "key.pem", nil)

    Or do you use something different?

    opened by gg2001 2
  • Allow any Origin by default

    Allow any Origin by default

    While this does make things a bit less secure I think that this is better than not allowing any origins by default, especially since I think we can assume most people are using this with a framework like Gin which allows people to handle CORS up the chain. Not allowing any Origins by default is a higher barrier to entry than the alternative.

    Closes #23

    opened by robbiet480 2
  • Fix concurrent panic

    Fix concurrent panic

    if two goroutines exec these two lines nearly the same time: https://github.com/olahol/melody/blob/master/session.go#L59 https://github.com/olahol/melody/blob/master/session.go#L24

    the two goroutines will get the same opening state, and if close(s.output) exec first https://github.com/olahol/melody/blob/master/session.go#L63

    case s.output <- message will panic: https://github.com/olahol/melody/blob/master/session.go#L30

    try this example to reproduce this bug:

    package main
    
    import (
    	"log"
    	"sync"
    	"time"
    
    	"github.com/gin-gonic/gin"
    	"github.com/gorilla/websocket"
    	"github.com/olahol/melody"
    )
    
    func client(wg *sync.WaitGroup) {
    	defer wg.Done()
    	c, _, err := websocket.DefaultDialer.Dial("ws://localhost:5000/ws", nil)
    	if err != nil {
    		log.Fatal("dial:", err)
    	}
    	defer c.Close()
    
    	text := "test"
    	err = c.WriteMessage(websocket.TextMessage, []byte(text))
    	if err != nil {
    		log.Printf("write: %v", err)
    	}
    
    	// try to trigger that melody Session at the server side getting the same opening state before close(s.input)
    	time.AfterFunc(time.Second*2, func() {
    		c.Close()
    	})
    
    	for {
    		_, _, err := c.ReadMessage()
    		if err != nil {
    			return
    		}
    	}
    }
    
    func main() {
    	r := gin.Default()
    	m := melody.New()
    
    	r.GET("/ws", func(c *gin.Context) {
    		m.HandleRequest(c.Writer, c.Request)
    	})
    
    	m.HandleMessage(func(s *melody.Session, msg []byte) {
    		go func() {
    			// try to trigger that getting the same opening state before s.input<-
    			for s.Write(msg) == nil {
    			}
    		}()
    	})
    
    	go func() {
    		for {
    			wg := &sync.WaitGroup{}
    			for i := 0; i < 20; i++ {
    				wg.Add(1)
    				go client(wg)
    			}
    			wg.Wait()
    		}
    	}()
    
    	r.Run("localhost:5000")
    }
    

    then wait for enough time, we will get:

    panic: send on closed channel
    
    goroutine 2105 [running]:
    github.com/olahol/melody.(*Session).writeMessage(0xc000483500, 0xc000ba55f0)
            somedir/src/github.com/olahol/melody/session.go:30 +0x6c
    github.com/olahol/melody.(*Session).Write(0xc000483500, 0xc000714400, 0x4, 0x200, 0x0, 0x0)
            somedir/src/github.com/olahol/melody/session.go:149 +0x90
    main.main.func2.1(0xc000483500, 0xc000714400, 0x4, 0x200)
            somedir/src/github.com/olahol/melody/examples/chat/main.go:50 +0x50
    created by main.main.func2
            somedir/src/github.com/olahol/melody/examples/chat/main.go:48 +0x65
    
    opened by lesismal 0
  • how to keep a connecting, it always timeout after 1 minute

    how to keep a connecting, it always timeout after 1 minute

    it always timeout.. not proxy via nginx, only melody + gin, the same code as your example...

    this pic:

    image

    after 1 minute, timeout, disconnect, and reconnect...

    opened by bernadit 0
  • how get active sessions

    how get active sessions

    hi how i get active sessions for use in private chat?

    thanks

    opened by imanborumand 3
  • [fix] example echo because echo v3

    [fix] example echo because echo v3

    Summary

    example echo because echo v3.

    ref

    cannot find package "github.com/labstack/echo/engine/standard" https://github.com/labstack/echo/issues/880

    test

    |test item | test method | test result | date | | --- | --- | --- | --- | | main.go works fine. | cd examples/chat-echo && go run main.go | 🆗| 2020-01-11 |

    opened by ttatsato 0
  • why the comunication chan is sync, not async

    why the comunication chan is sync, not async

    In your code: func newHub() *hub { return &hub{ sessions: make(map[*Session]bool), broadcast: make(chan *envelope), register: make(chan *Session), unregister: make(chan *Session), exit: make(chan *envelope), open: true, rwmutex: &sync.RWMutex{}, } }

    But I thank " broadcast: make(chan *envelope, 1024) " is better, or I misunderstand something. Thanks for your project.

    opened by yiippee 0
  • Check returned errors before deferring Close()

    Check returned errors before deferring Close()

    This pattern is repeated several times in melody_test.go:

    conn, err := NewDialer(server.URL)
    defer conn.Close()
    if err != nil {
    	t.Error(err)
    	return false
    }
    

    However, if there is an error, this is liable to cause a null reference panic.

    opened by riking 0
  • Examples don't build

    Examples don't build

    examples/filewatch/main.go imports "github.com/go-fsnotify/fsnotify". However, this import path is deprecated and deleted. This makes the Go tool error out because the package contains no source code.

    Update the import path to "github.com/fsnotify/fsnotify".

    opened by riking 0
  • Is this still active?

    Is this still active?

    @olahol , or anyone. Did you reach the end of the scope of this project?

    opened by siredwin 3
  • it will be great!

    it will be great!

    Please connect https://github.com/valyala/fasthttp https://github.com/gorilla/websocket

    opened by averole 0
  • Dependent change

    Dependent change

    "github.com/go-fsnotify/fsnotify" ->"github.com/fsnotify/fsnotify"

    opened by z774379121 0
Owner
Ola
:cyclone:
Ola
A fast, well-tested and widely used WebSocket implementation for Go.

Gorilla WebSocket Gorilla WebSocket is a Go implementation of the WebSocket protocol. Documentation API Reference Chat example Command example Client

Gorilla Web Toolkit 15.5k Sep 17, 2021
:notes: Minimalist websocket framework for Go

melody ?? Minimalist websocket framework for Go. Melody is websocket framework based on github.com/gorilla/websocket that abstracts away the tedious p

Ola 2.2k Sep 20, 2021
Turn any program that uses STDIN/STDOUT into a WebSocket server. Like inetd, but for WebSockets.

websocketd websocketd is a small command-line tool that will wrap an existing command-line interface program, and allow it to be accessed via a WebSoc

Joe Walnes 15.7k Sep 14, 2021
Go client for an OBS WebSockets server

goobs It's a Go client for Palakis/obs-websocket, allowing us to interact with OBS Studio via Go. disclaimer This project is still a work-in-progress.

Andrey Kaipov 24 Sep 12, 2021
websocket消息推送服务

balloons-websocket 用于构建实时应用程序的基础架构和API,balloons提供了最好的基础架构和API,以大规模地提供实时体验。向最终用户提供快速稳定的实时消息。让我们处理实时消息传递的复杂性,以便您可以专注于代码。 balloons的实时API向开发人员公开了整个balloon

null 11 Apr 4, 2021
Tiny WebSocket library for Go.

RFC6455 WebSocket implementation in Go.

Sergey Kamardin 4.1k Sep 22, 2021
gatews - Gate.io WebSocket SDK

gatews - Gate.io WebSocket SDK gatews provides new Gate.io WebSocket V4 implementations. It is intended to work along with gateapi-* series to provide

gate.io 19 Aug 29, 2021
Simple example for using Turbos Streams in Go with the Gorilla WebSocket toolkit.

Go Example for TurboStreams over WebSockets Simple example for using Turbos Streams in Go with the Gorilla WebSocket toolkit.

Jan Stamer 16 Jun 29, 2021
A modern, fast and scalable websocket framework with elegant API written in Go

About neffos Neffos is a cross-platform real-time framework with expressive, elegant API written in Go. Neffos takes the pain out of development by ea

Gerasimos (Makis) Maropoulos 367 Sep 16, 2021
run shell scripts by websocket with go lauguage

go_shell_socket run shell scripts by websocket with go lauguage Usage pull project get gin and websocket with go get config config.json file build it

soQ 18 Jun 3, 2021
WebSocket Command Line Client written in Go

ws-cli WebSocket Command Line Client written in Go Installation go get github.com/kseo/ws-cli Usage $ ws-cli -url ws://echo.websocket.org connected (

Kwang Yul Seo 16 Jun 13, 2021
proxy your traffic through CDN using websocket

go-cdn2proxy proxy your traffic through CDN using websocket what does it do example server client thanks what does it do you can use this as a library

jm33-ng 31 Sep 9, 2021
Terminal on browser via websocket

Terminal on browser via websocket. Supportted OS Linux Mac

skanehira 122 Aug 27, 2021
simpleChatInGo - This is a simple chat that i made for fun asnd learn more about websocket

simpleChatInGo This is a simple chat that i made for fun asnd learn more about websocket deploy For deploy this you only need to run the command : $ d

ranon rat 6 Aug 7, 2021
Chat bots (& more) for Zoom by figuring out their websocket protocol

zoomer - Bot library for Zoom meetings Good bot support is part of what makes Discord so nice to use. Unfortunately, the official Zoom API is basicall

Christopher Tarry 41 Sep 2, 2021
go-socket.io is library an implementation of Socket.IO in Golang

go-socket.io is library an implementation of Socket.IO in Golang, which is a realtime application framework.

Googol Lee 4.3k Sep 24, 2021