: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 }
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
    
    opened by lesismal 13
  • 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
  • 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
  • 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
  • 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
  • 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!

    opened by UedaTakeyuki 2
  • 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!

    opened by themartorana 2
  • When is HandleDisconnect called?

    When is HandleDisconnect called?

    I couldn't find anywhere in the code a line where *Melody.disconnectHandler is called. That function isn't exported, so it is not being called by another package, right?

    opened by fiatjaf 2
  • Proper way

    Proper way

    Hi guys,

    love your API compared with gorilla/websockets. Also loved that you guys built on top of that solid base instead of doing everything from scratch.

    Let me ask: is there a proper way to "deny" the upgrade? I will only allow authenticated users to open a websocket connection:

    	r.Get("/ws", func(w http.ResponseWriter, r *http.Request) {
    		var input struct {
    			Token string `json:"token"`
    		}
    
    		if err := render.Bind(r, &input); err != nil {
    			http.Error(w, http.StatusText(401), 401)
    			return
    		}
                   ...
    

    Again, thanks for the sweet project.

    opened by hugows 2
  • 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...

    bug 
    opened by kent58909 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.

    bug 
    opened by riking 0
  • 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?

    enhancement 
    opened by iwasaki-kenta 0
  • 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?

    enhancement 
    opened by cert-lv 3
Owner
Ola
:cyclone:
Ola
Awesome WebSocket CLient - an interactive command line client for testing websocket servers

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

Morgan 242 Sep 6, 2022
Encrypted-websocket-chat - Encrypted websocket chat using golang

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

Artyom Artamonov 13 Sep 15, 2022
Websocket-chat - A simple websocket chat application

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

null 1 Jan 25, 2022
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 400 Sep 18, 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

Gorilla Web Toolkit 18.1k Sep 27, 2022
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 16.4k Sep 26, 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 (

Kwang Yul Seo 17 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

jm33-ng 43 Sep 19, 2022
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 49 Sep 17, 2022
Tiny WebSocket library for Go.

RFC6455 WebSocket implementation in Go.

Sergey Kamardin 4.7k Sep 23, 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.

Jan Stamer 23 Jun 8, 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

Anmol Sethi 2.3k Sep 25, 2022
Terminal on browser via websocket

Terminal on browser via websocket. Supportted OS Linux Mac

skanehira 146 Sep 15, 2022
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 20 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 deploy For deploy this you only need to run the command : $ d

ranon rat 7 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

gate.io 52 Sep 6, 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

Darío 41 Sep 3, 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

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

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

null 1 Aug 31, 2022