:notes: Minimalist websocket framework for Go

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 }
Owner
Comments
  • 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
    
  • 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?

  • 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
        })
    })
    
  • 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?

  • 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

  • Any chance for a client?

    Any chance for a client?

    Heya,

    I have been looking for something that would provide a clean wrapper around the x/net/websocket or gorilla websocket, but I have not found any good solution yet.

  • 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

  • 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.

  • Session Keys and access methods Get and Set is not concurrency safe

    Session Keys and access methods Get and Set is not concurrency safe

    Hi,

    We just received the following error in our server:

    fatal error: concurrent map read and map write

    goroutine 900594 [running]:
    runtime.throw(0xff43c6, 0x21)
    	/usr/local/go/src/runtime/panic.go:1116 +0x72 fp=0xc0050ad860 sp=0xc0050ad830 pc=0x437c52
    runtime.mapaccess2_faststr(0xe996c0, 0xc005367290, 0xfd8f40, 0x5, 0x0, 0x0)
    	/usr/local/go/src/runtime/map_faststr.go:116 +0x4a5 fp=0xc0050ad8d0 sp=0xc0050ad860 pc=0x415de5
    gopkg.in/olahol/melody%2ev1.(*Session).Get(...)
    	/home/erdem/Documents/gopath/code/pkg/mod/gopkg.in/olahol/[email protected]/session.go:201
    

    When we checked it seems like the rwmutex in the session struct is not used to protect the Keys map inside it but only used to set and get the closed state. I couldn't figure out if this is deliberate and the consumer of the package is supposed to protect the Keys access with their own mutex. If it is then maybe a two new access methods can be added like SafeGet and SafeSet that uses the session rw mutex to protect access.

    I can submit a pull request if you like for this issue.

  • Help request: How can I increase websocket read limit?

    Help request: How can I increase websocket read limit?

    I can't receive big (about 1Kbyte) data with the following error:

    websocket: read limit exceeded

    How can I configure my Melody to inclease this limit? Thank you for your help!

  • Active Management?

    Active Management?

    There seems to be no active maintenance of this repo, but quite a few active forks. Can we transfer ownership of this repo to someone that is currently working on keeping it current?

    Thanks!

  • Example for implementing idle session timeout

    Example for implementing idle session timeout

    Hi,

    Thank you for a great package, it is much cleaner and simpler to use compared to gorilla/websocket library.

    I am developing a game server and need to implement an idle timeout, I've set PingPeriod and PongWait values however it doesn't timeout the session or close the connection. In the readme feature list it looks like this is included "Automatic handling of ping/pong and session timeouts" however I can't seem to get it to work. Can you add an example that includes a simple implementation.

  • 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...

  • 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.

  • Allow for synchronous broadcasts.

    Allow for synchronous broadcasts.

    It looks like all broadcasts so far comprise of wrapping a specified byte slice into an envelope and sending it over the channel *Melody.hub.broadcast.

    For performance reasons, I'm looking to minimize the number of allocations by using sync.Pool and need to know when a broadcast has been done synchronously.

    Would you be open to a PR for this?

  • Ping msg timeout detection

    Ping msg timeout detection

    Hi,

    Melody has a ping-pong functionality to keep the connection open and there is a config option PongWait. But how can I detect timeouts? For example, if client's network connection is or becomes too slow - how can I detect it with Melody?

Awesome WebSocket CLient - an interactive command line client for testing websocket servers
Awesome WebSocket CLient - an interactive command line client for testing websocket servers

Awesome WebSocket CLient - an interactive command line client for testing websocket servers

Dec 30, 2022
Encrypted-websocket-chat - Encrypted websocket chat using golang

Encrypted websocket chat First version written in python This version should be

Sep 15, 2022
Websocket-chat - A simple websocket chat application
Websocket-chat - A simple websocket chat application

WebSocket Chat App This is a simple chat app based on websockets. It allows user

Jan 25, 2022
A modern, fast and scalable websocket framework with elegant API written in Go
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

Dec 29, 2022
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

Jan 2, 2023
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

Dec 31, 2022
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 (

Nov 12, 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

Dec 7, 2022
Chat bots (& more) for Zoom by figuring out their websocket protocol
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

Dec 14, 2022
Tiny WebSocket library for Go.

RFC6455 WebSocket implementation in Go.

Dec 28, 2022
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.

Dec 22, 2022
Minimal and idiomatic WebSocket library for Go

websocket websocket is a minimal and idiomatic WebSocket library for Go. Install go get nhooyr.io/websocket Highlights Minimal and idiomatic API First

Dec 31, 2022
Terminal on browser via websocket

Terminal on browser via websocket. Supportted OS Linux Mac

Dec 27, 2022
run shell scripts by websocket with go lauguage
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

Mar 9, 2022
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

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

Sep 21, 2022
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

Dec 31, 2022
WebSocket for fasthttp

websocket WebSocket library for fasthttp and net/http. Checkout examples to inspire yourself. Install go get github.com/dgrr/websocket Why another Web

Dec 28, 2022
A tiny command line websocket client written in Go

wsc A simplistic tool for sending and receiving websocket messages from a command line. Mainly useful to test websocket servers. Getting started: $ go

Jan 12, 2022
An online multiplayer, websocket based, interpretation of the Tic Tac Toe minigame from "Machinarium" :D
An online multiplayer, websocket based, interpretation of the Tic Tac Toe minigame from

Tik Tak Toe An interpretation of the tic tac toe minigame from Amanita Design's Machinarium, multiplayer, online, as a website. Here's a screenshot of

Aug 31, 2022