Tiny WebSocket library for Go.

Overview

ws

GoDoc CI

RFC6455 WebSocket implementation in Go.

Features

  • Zero-copy upgrade
  • No intermediate allocations during I/O
  • Low-level API which allows to build your own logic of packet handling and buffers reuse
  • High-level wrappers and helpers around API in wsutil package, which allow to start fast without digging the protocol internals

Documentation

GoDoc.

Why

Existing WebSocket implementations do not allow users to reuse I/O buffers between connections in clear way. This library aims to export efficient low-level interface for working with the protocol without forcing only one way it could be used.

By the way, if you want get the higher-level tools, you can use wsutil package.

Status

Library is tagged as v1* so its API must not be broken during some improvements or refactoring.

This implementation of RFC6455 passes Autobahn Test Suite and currently has about 78% coverage.

Examples

Example applications using ws are developed in separate repository ws-examples.

Usage

The higher-level example of WebSocket echo server:

package main

import (
	"net/http"

	"github.com/gobwas/ws"
	"github.com/gobwas/ws/wsutil"
)

func main() {
	http.ListenAndServe(":8080", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		conn, _, _, err := ws.UpgradeHTTP(r, w)
		if err != nil {
			// handle error
		}
		go func() {
			defer conn.Close()

			for {
				msg, op, err := wsutil.ReadClientData(conn)
				if err != nil {
					// handle error
				}
				err = wsutil.WriteServerMessage(conn, op, msg)
				if err != nil {
					// handle error
				}
			}
		}()
	}))
}

Lower-level, but still high-level example:

import (
	"net/http"
	"io"

	"github.com/gobwas/ws"
	"github.com/gobwas/ws/wsutil"
)

func main() {
	http.ListenAndServe(":8080", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		conn, _, _, err := ws.UpgradeHTTP(r, w)
		if err != nil {
			// handle error
		}
		go func() {
			defer conn.Close()

			var (
				state  = ws.StateServerSide
				reader = wsutil.NewReader(conn, state)
				writer = wsutil.NewWriter(conn, state, ws.OpText)
			)
			for {
				header, err := reader.NextFrame()
				if err != nil {
					// handle error
				}

				// Reset writer to write frame with right operation code.
				writer.Reset(conn, state, header.OpCode)

				if _, err = io.Copy(writer, reader); err != nil {
					// handle error
				}
				if err = writer.Flush(); err != nil {
					// handle error
				}
			}
		}()
	}))
}

We can apply the same pattern to read and write structured responses through a JSON encoder and decoder.:

	...
	var (
		r = wsutil.NewReader(conn, ws.StateServerSide)
		w = wsutil.NewWriter(conn, ws.StateServerSide, ws.OpText)
		decoder = json.NewDecoder(r)
		encoder = json.NewEncoder(w)
	)
	for {
		hdr, err = r.NextFrame()
		if err != nil {
			return err
		}
		if hdr.OpCode == ws.OpClose {
			return io.EOF
		}
		var req Request
		if err := decoder.Decode(&req); err != nil {
			return err
		}
		var resp Response
		if err := encoder.Encode(&resp); err != nil {
			return err
		}
		if err = w.Flush(); err != nil {
			return err
		}
	}
	...

The lower-level example without wsutil:

package main

import (
	"net"
	"io"

	"github.com/gobwas/ws"
)

func main() {
	ln, err := net.Listen("tcp", "localhost:8080")
	if err != nil {
		log.Fatal(err)
	}

	for {
		conn, err := ln.Accept()
		if err != nil {
			// handle error
		}
		_, err = ws.Upgrade(conn)
		if err != nil {
			// handle error
		}

		go func() {
			defer conn.Close()

			for {
				header, err := ws.ReadHeader(conn)
				if err != nil {
					// handle error
				}

				payload := make([]byte, header.Length)
				_, err = io.ReadFull(conn, payload)
				if err != nil {
					// handle error
				}
				if header.Masked {
					ws.Cipher(payload, header.Mask, 0)
				}

				// Reset the Masked flag, server frames must not be masked as
				// RFC6455 says.
				header.Masked = false

				if err := ws.WriteHeader(conn, header); err != nil {
					// handle error
				}
				if _, err := conn.Write(payload); err != nil {
					// handle error
				}

				if header.OpCode == ws.OpClose {
					return
				}
			}
		}()
	}
}

Zero-copy upgrade

Zero-copy upgrade helps to avoid unnecessary allocations and copying while handling HTTP Upgrade request.

Processing of all non-websocket headers is made in place with use of registered user callbacks whose arguments are only valid until callback returns.

The simple example looks like this:

package main

import (
	"net"
	"log"

	"github.com/gobwas/ws"
)

func main() {
	ln, err := net.Listen("tcp", "localhost:8080")
	if err != nil {
		log.Fatal(err)
	}
	u := ws.Upgrader{
		OnHeader: func(key, value []byte) (err error) {
			log.Printf("non-websocket header: %q=%q", key, value)
			return
		},
	}
	for {
		conn, err := ln.Accept()
		if err != nil {
			// handle error
		}

		_, err = u.Upgrade(conn)
		if err != nil {
			// handle error
		}
	}
}

Usage of ws.Upgrader here brings ability to control incoming connections on tcp level and simply not to accept them by some logic.

Zero-copy upgrade is for high-load services which have to control many resources such as connections buffers.

The real life example could be like this:

package main

import (
	"fmt"
	"io"
	"log"
	"net"
	"net/http"
	"runtime"

	"github.com/gobwas/httphead"
	"github.com/gobwas/ws"
)

func main() {
	ln, err := net.Listen("tcp", "localhost:8080")
	if err != nil {
		// handle error
	}

	// Prepare handshake header writer from http.Header mapping.
	header := ws.HandshakeHeaderHTTP(http.Header{
		"X-Go-Version": []string{runtime.Version()},
	})

	u := ws.Upgrader{
		OnHost: func(host []byte) error {
			if string(host) == "github.com" {
				return nil
			}
			return ws.RejectConnectionError(
				ws.RejectionStatus(403),
				ws.RejectionHeader(ws.HandshakeHeaderString(
					"X-Want-Host: github.com\r\n",
				)),
			)
		},
		OnHeader: func(key, value []byte) error {
			if string(key) != "Cookie" {
				return nil
			}
			ok := httphead.ScanCookie(value, func(key, value []byte) bool {
				// Check session here or do some other stuff with cookies.
				// Maybe copy some values for future use.
				return true
			})
			if ok {
				return nil
			}
			return ws.RejectConnectionError(
				ws.RejectionReason("bad cookie"),
				ws.RejectionStatus(400),
			)
		},
		OnBeforeUpgrade: func() (ws.HandshakeHeader, error) {
			return header, nil
		},
	}
	for {
		conn, err := ln.Accept()
		if err != nil {
			log.Fatal(err)
		}
		_, err = u.Upgrade(conn)
		if err != nil {
			log.Printf("upgrade error: %s", err)
		}
	}
}

Compression

There is a ws/wsflate package to support Permessage-Deflate Compression Extension.

It provides minimalistic I/O wrappers to be used in conjunction with any deflate implementation (for example, the standard library's compress/flate.

package main

import (
	"bytes"
	"log"
	"net"

	"github.com/gobwas/ws"
	"github.com/gobwas/ws/wsflate"
)

func main() {
	ln, err := net.Listen("tcp", "localhost:8080")
	if err != nil {
		// handle error
	}
	e := wsflate.Extension{
		// We are using default parameters here since we use
		// wsflate.{Compress,Decompress}Frame helpers below in the code.
		// This assumes that we use standard compress/flate package as flate
		// implementation.
		Parameters: wsflate.DefaultParameters,
	}
	u := ws.Upgrader{
		Negotiate: e.Negotiate,
	}
	for {
		conn, err := ln.Accept()
		if err != nil {
			log.Fatal(err)
		}

		// Reset extension after previous upgrades.
		e.Reset()

		_, err = u.Upgrade(conn)
		if err != nil {
			log.Printf("upgrade error: %s", err)
			continue
		}
		if _, ok := e.Accepted(); !ok {
			log.Printf("didn't negotiate compression for %s", conn.RemoteAddr())
			conn.Close()
			continue
		}

		go func() {
			defer conn.Close()
			for {
				frame, err := ws.ReadFrame(conn)
				if err != nil {
					// Handle error.
					return
				}
				frame = ws.UnmaskFrameInPlace(frame)
				frame, err = wsflate.DecompressFrame(frame)
				if err != nil {
					// Handle error.
					return
				}

				// Do something with frame...

				ack := ws.NewTextFrame([]byte("this is an acknowledgement"))
				ack, err = wsflate.CompressFrame(ack)
				if err != nil {
					// Handle error.
					return
				}
				if err = ws.WriteFrame(conn, ack); err != nil {
					// Handle error.
					return
				}
			}
		}()
	}
}
Issues
  • Multiple epoll events received for large message size, reads getting blocked after first event

    Multiple epoll events received for large message size, reads getting blocked after first event

    Hi we are implementing websocket server and using netpoll (epoll) for watching for read events on connected clients file descriptor. When the client sends large message size we are getting multiple read events for the same message. We are able to read the entire message after receiving the first event from epoll. For the subsequent events the read gets blocked on nextframe() call as there is no message on underlying connection. Any help is much appreciated.

    opened by codefetcher 26
  • Fix bugs in the Grow feature

    Fix bugs in the Grow feature

    Thanks for your excellent work!

    https://github.com/chromedp/chromedp depends on this package, and it fails to send large requests due to the lack of fragmentation support in Chrome (reported here: https://github.com/ChromeDevTools/devtools-protocol/issues/175). Now that Grow() is added to this package, I want to address this issue on the client side. It works like a charm! Thank you very much!

    I found some issues though. So here is the pull request.

    BTW, DisableFlush() is supposed to be used to enable auto growing of the buffer, right? Is it better to call it EnableAutoGrow()?

    opened by ZekeLu 22
  • [wip] API refactoring before v1.0

    [wip] API refactoring before v1.0

    Any comments or reviews are welcome!

    opened by gobwas 22
  • wss:// and netpoll

    wss:// and netpoll

    I was having issues with my app and found out that using wss:// with nginx as a proxy leads to missing messages. I am using the chat example with netpoll.

    Running the golang code below with ws:// returns the number of sent requests, whilst using wss:// returns only first 2-3 messages.

    High-level example with wsutil works as expected, I think that might be the issue with netpoll. Hopefully, that makes more sense to you. Thanks

    slightly modified chat example from gobwas/ws-examples

    server.go

    	poller, _ := netpoll.New(nil)
    
    	pool := gopool.NewPool(128, 1, 1)
    
    	handle := func(conn net.Conn) {
    		userSeq := 0
    
    		hs, err := ws.Upgrade(conn)
    
    		if err != nil {
    			panic(err)
    		}
    
    		logger.Debugf("%s: established websocket connection: %+v", nameConn(conn), hs)
    
    		desc := netpoll.Must(netpoll.HandleRead(conn))
    
    		// Subscribe to events about conn.
    		pErr := poller.Start(desc, func(ev netpoll.Event) {
    			fmt.Println(userSeq)
    
    			userSeq++
    		})
    
    		if pErr != nil {
    			panic(pErr)
    		}
    	}
    
    	// Create incoming connections listener.
    	ln, err := net.Listen("tcp", ":8088")
    
    	if err != nil {
    		log.Fatal(err)
    	}
    
    	log.Printf("websocket is listening on %s", ln.Addr().String())
    
    	// Create netpoll descriptor for the listener.
    	// We use OneShot here to manually resume events stream when we want to.
    	acceptDesc := netpoll.Must(netpoll.HandleListener(
    		ln, netpoll.EventRead|netpoll.EventOneShot,
    	))
    
    	// accept is a channel to signal about next incoming connection Accept()
    	// results.
    	accept := make(chan error, 1)
    
    	// Subscribe to events about listener.
    	err = poller.Start(acceptDesc, func(e netpoll.Event) {
    		err := pool.ScheduleTimeout(time.Millisecond, func() {
    			conn, err := ln.Accept()
    			if err != nil {
    				accept <- err
    				return
    			}
    
    			accept <- nil
    			handle(conn)
    		})
    		if err == nil {
    			err = <-accept
    		}
    		if err != nil {
    			if err != gopool.ErrScheduleTimeout {
    				goto cooldown
    			}
    			if ne, ok := err.(net.Error); ok && ne.Temporary() {
    				goto cooldown
    			}
    
    			log.Fatalf("accept error: %v", err)
    
    		cooldown:
    			delay := 5 * time.Millisecond
    			log.Printf("accept error: %v; retrying in %s", err, delay)
    			time.Sleep(delay)
    		}
    
    		err = poller.Resume(acceptDesc)
    
    		if err != nil {
    			log.Fatal(err)
    		}
    	})
    
    	if err != nil {
    		log.Fatal(err)
    	}
    

    client.js

    process.env['NODE_TLS_REJECT_UNAUTHORIZED'] = 0
    
    const WebSocket = require('ws');
    const ws = new WebSocket( 'wss://vm.dev/' );
    
    ws.on('open', function open() {
        for (let i = 0; i < 10; i++) {
            ws.send("ping");
        }
    })
    

    Nginx config

    server {
        listen 80;
        listen 443 ssl;
    
        # ... ssl config here ...
    
        location / {
            proxy_pass http://192.168.0.100:8088/;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_cache_bypass $http_upgrade;
            proxy_set_header X-Real-IP $remote_addr;
        }
    }
    
    opened by macabre2077 20
  • fix: panic caused by uintptr

    fix: panic caused by uintptr

    When I used this library on windows, ReadHeader returns full zero Header after about 3-5 calls, and sometimes the program panic. After some debug I found it's caused by the usage of uintptr.

    According to doc of unsafe.Pointer, most of the previous uintptr usage are incorrect. This PR fixes the issue.

    Change-Id: Iddd9c07d7c1c1d3f1cf5239b6524e9399b49725d

    opened by jxskiss 19
  • Looking forward to your response

    Looking forward to your response

    Hello @gobwas - I'm Jeeva, I'm planning to bring Websocket feature on next release of aah framework. I have read this article and went through your ws codebase.

    I'm interested in integrating with aah framework, however you have mentioned in the readme "The library is not tagged as v1.0.0 yet so it can be broken during some improvements or refactoring." I think it is production ready. However I would like to hear from you and also could you please tag it?

    Thank you!

    question 
    opened by jeevatkm 14
  • Compression built around flate package

    Compression built around flate package

    Simple reader and writer that support deflate compression. Can be used as the wrapper around the wsutil.Writer and wsutil.Reader structs.

    Code highly inspired by Gorilla's WebSocket approach, but I not properly sure about pools for flate.Reader/flate.Writer.

    opened by ligser 14
  • Send headers with Sec-WebSocket-* capitalization

    Send headers with Sec-WebSocket-* capitalization

    According to the RFC, the Sec-WebSocket-* headers should have a capital S inside the word WebSocket. This is a shame, because most other headers are canonicalized differently.

    Some websocket servers are particularly picky about the capitalization. The agar.io server (or maybe the Cloudflare servers it's behind) will reject connections with the canonical spelling, giving a 400 in response.

    You can try it. This will succeed:

    curl -v --http1.1 'https://live-arena-1cm5igj.agar.io/'
        -H 'Host: live-arena-1cm5igj.agar.io:443'
        -H 'Upgrade: websocket'
        -H 'Connection: Upgrade'
        -H 'Sec-WebSocket-Version: 13'
        -H 'Sec-WebSocket-Key: 1u4hDlp0kUjbZafgII9lLw=='
    

    But this will fail, giving a 400 Bad Request:

    curl -v --http1.1 'https://live-arena-1cm5igj.agar.io/'
        -H 'Host: live-arena-1cm5igj.agar.io:443'
        -H 'Upgrade: websocket'
        -H 'Connection: Upgrade'
        -H 'Sec-Websocket-Version: 13'
        -H 'Sec-Websocket-Key: 1u4hDlp0kUjbZafgII9lLw=='
    

    This patch fixes this: ws will now accept any capitalization it receives, but use the RFC-correct capitalization when sending headers either as a response or as an upgrade request.

    Thanks for this library! The zero-copy functionality is really nice for us.

    opened by qguv 14
  • Stack based slice does not work.

    Stack based slice does not work.

    func ReadHeader(r io.Reader) (h Header, err error) {
    	var b [MaxHeaderSize - 2]byte
    
           // ************ OLD CODE (Go 1.12 does not work!) ************
    	//bp := uintptr(unsafe.Pointer(&b))
    	//bh := &reflect.SliceHeader{Data: bp, Len: 2, Cap: MaxHeaderSize - 2}
    	//bts := *(*[]byte)(unsafe.Pointer(bh))
    
            // ************ NEW CODE (Go 1.12 work it!) ************
    	var bts []byte
    	bh := (*reflect.SliceHeader)(unsafe.Pointer(&bts))
    	bh.Data = uintptr(unsafe.Pointer(&b))
    	bh.Cap = MaxHeaderSize - 2
    	bh.Len = 2
    
    	// Prepare to hold first 2 bytes to choose size of next read.
    	_, err = io.ReadFull(r, bts)
    	if err != nil {
    		return
    	}
            ....
    }```
    opened by limpo1989 11
  • Network Send Overhead

    Network Send Overhead

    The API calls for a separate write for the header. This results in either an extra TCP package (with a few bytes) or (Nagle) overhead.

    opened by pascaldekloe 11
  • webSocket works with firefox, not with chrome and safari

    webSocket works with firefox, not with chrome and safari

    I'm just testing ws with a very basic websocket application. This is the code on the server side:

    func webSocket(w http.ResponseWriter, r *http.Request) {
    	conn, _, _, err := ws.UpgradeHTTP(r, w)
    	if err != nil {
    		log.Println("error:", err)
    		http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
    		return
    	}
    	log.Println("webSocket opened")
    	go func() {
    		defer conn.Close()
    
    		var i int
    		for {
    			msg := fmt.Sprintf("message %d", i)
    			err = wsutil.WriteServerText(conn, []byte(msg))
    			if err != nil {
    				log.Println("error:", err)
    				break
    			}
    			time.Sleep(2 * time.Second)
    			i++
    		}
    	}()
    }
    

    The output I get is

    $ go run .
    2021/09/05 09:27:50 Listing URL: http://localhost:3000
    2021/09/05 09:28:10 webSocket opened
    2021/09/05 09:28:18 error: write tcp [::1]:3000->[::1]:55717: write: broken pipe
    

    On the client side, I have this code in a vuex plugin:

    var webSocket = new WebSocket("ws://localhost:3000/status", "protocolOne");
    
    export default function createWebSocketPlugin() {
        return store => {
            webSocket.onmessage = function (event) {
                console.log("webSocket: on message: ", event);
            }
            webSocket.onopen = function (event) {
                console.log("webSocket: on open: ", event)
                store.dispatch('connectionOpened');
            }
            webSocket.onclose = function (event) {
                console.log("webSocket: on close: ", event)
                store.dispatch('connectionClosed');
            }
            webSocket.onrror = function (event) {
                console.log("webSocket: on error: ", event)
            }
        };
    }
    

    The function createWebSocketPluginis called once at startup. With firefox, I do see the messages printed to the console as expected. With chrome, I have an error message in the console saying that it failed to connect to "ws://localhost:3000/status". On the server side, I see that the upgrade succeeds, but it reports a broken connection. With safari, it also fails since I have the broken connection error on the server side, but I can't see the console.

    Could it be because chrome and safari don't support unsecure websockets (ws) ?

    opened by chmike 5
  • got error message

    got error message "control frame is not final" when using websocketUtil.ReadServerText

    client side:

    func startTest(c *gin.Context) {
    	type Request struct {
    		Cnt int
    		Url string
    	}
    	req := new(Request)
    	if err := c.ShouldBind(req); err != nil {
    		c.JSON(http.StatusOK, gin.H{
    			"Errcode": errno.ErrWrongParam.Code(),
    			"Errmsg":  errno.ErrWrongParam.Error(),
    		})
    		return
    	}
    	statistics := make([]chan int, req.Cnt)
    	for i := 0; i < req.Cnt; i++ {
    		statistics[i] = make(chan int)
    		go func(index int, url string, res chan int) {
    			conn, _, _, err := websocket.DefaultDialer.Dial(context.Background(), url)
    			if err != nil {
    				log.Println("dial fail index = ", index)
    				res <- 1
    				return
    			}
    			defer conn.Close()
    			text := "hello world"
    			err = websocketUtil.WriteClientMessage(conn, websocket.OpText, []byte(text))
    			if err != nil {
    				log.Println("WriteMessage failed 111:", err)
    				res <- 2
    				return
    			}
    
    			message, err := websocketUtil.ReadServerText(conn)
    			if err != nil {
    				log.Println("ReadMessage failed 222:", err)
    				res <- 3
    				return
    			}
    			log.Println("ReadMessage :", string(message))
    			sleepSec := rand.Int31n(40)
    			log.Println("start sleep =", sleepSec, " s")
    			time.Sleep(time.Duration(sleepSec) * time.Second)
    			res <- 4
    		}(i, req.Url, statistics[i])
    	}
    	dialFailCnt := float64(0)
    	writeFailCnt := float64(0)
    	readFailCnt := float64(0)
    	finishCnt := float64(0)
    	for _, ch := range statistics {
    		res := <-ch
    		switch res {
    		case 1:
    			dialFailCnt++
    		case 2:
    			writeFailCnt++
    		case 3:
    			readFailCnt++
    		case 4:
    			finishCnt++
    		}
    	}
    	floatTotal := float64(req.Cnt)
    	log.Println(fmt.Sprintf("total=%d,dialFailRate=%f,writeFailRate=%f,readFailRate=%f,successRate=%f", req.Cnt, dialFailCnt/floatTotal, writeFailCnt/floatTotal, readFailCnt/floatTotal, finishCnt/floatTotal))
    }
    

    server side:

    func readClientData(Conn *net.Conn) {
    	for {
    		message, msgType, err := websocketUtil.ReadClientData(Conn)
    		if err != nil {
    			break
    		}
            if msgType == websocket.OpText {
    			_ = websocketUtil.WriteServerMessage(Conn, msgType, []byte("send echo"))
            }
    	}
    }
    

    i start a benchmark test in my 4c8g machine with code above,(obviously i removed some project-related code) when i test 5000 connection got result total=5000,dialFailRate=0.000000,writeFailRate=0.000000,readFailRate=0.004600,successRate=0.995400 all realFail errors are control frame is not final. it's a bug or my wrong usage? besides, after upgrading websocket, i bind a unique id with each connection which has lock competition that raising high cpu load, but according to the statistical results, it seems irrelevant to the question.

    opened by Jekinnnnnn 1
  • wsutil: buffer grow leads to its reduce

    wsutil: buffer grow leads to its reduce

    Under using chromedp package wich use that lib as dependency got panic in some cases with the follow stack trace

    panic: wsutil: buffer grow leads to its reduce                              
    goroutine 42 [running]:                                                     
    github.com/gobwas/ws/wsutil.(*Writer).Grow(0xc0004c22e8, 0x1b)              
            /home/max/go/pkg/mod/github.com/gobwas/[email protected]/wsutil/writer.
    github.com/gobwas/ws/wsutil.(*Writer).Write(0xc0004c22e8, 0xc000ad4000, 0x8d
            /home/max/go/pkg/mod/github.com/gobwas/[email protected]/wsutil/writer.
    net.(*Buffers).WriteTo(0xc0328f45c0, 0xee9ec0, 0xc0004c22e8, 0x1ffad, 0x1ffa
            /usr/local/go/src/net/net.go:675 +0xd9                              
    github.com/mailru/easyjson/buffer.(*Buffer).DumpTo(0xc0004c2410, 0xee9ec0, 0
            /home/max/go/pkg/mod/github.com/mailru/[email protected]/buffer/pool.g
    github.com/mailru/easyjson/jwriter.(*Writer).DumpTo(...)                    
            /home/max/go/pkg/mod/github.com/mailru/[email protected]/jwriter/write
    github.com/chromedp/chromedp.(*Conn).Write(0xc0004c2240, 0xef56a0, 0xc000928
            /home/max/go/pkg/mod/github.com/chromedp/[email protected]/conn.go:137
    github.com/chromedp/chromedp.(*Browser).run(0xc0006bb900, 0xef56a0, 0xc00092
            /home/max/go/pkg/mod/github.com/chromedp/[email protected]/browser.go:
    created by github.com/chromedp/chromedp.NewBrowser                          
            /home/max/go/pkg/mod/github.com/chromedp/[email protected]/browser.go:
    %# v &{info systemd 45 0xc0000c8820 0xc0000c8870 0xc0006bc760 0xc000495950}
    

    I think it`s problem with call size in chromedp, but generally lib should detect grow size lower than current without panic.

    opened by maxjust 8
  • added option for dialer to follow redirects

    added option for dialer to follow redirects

    Hey guys, First of all, thank you for this package! Great job!

    This PR introduces ability for a client to follow redirects.

    Protocol wise, following redirects is acceptable. Previous behaviour is unmodified

    Usage: Simply add dialer.FollowRedirects = true

    This is quite a naive implementation. If you feel changes are required to make it more acceptable, I would happily put the time in.

    opened by NivKeidan 4
  • autobahn: implement client test suite

    autobahn: implement client test suite

    help wanted testing 
    opened by gobwas 0
Releases(v1.1.0)
  • v1.1.0(Jul 12, 2021)

    Notes

    Compression support

    This release adds wsflate package implementing WebSocket Compression Extension logic.

    API

    • Added ws.UnmaskFrame() and ws.UnmaskFrameInPlace() helpers to be symmetrical with already existing ws.MaskFrame() and ws.MaskFrameInPlace()
    • Added ws/wsutil.Reader.MaxFrameSize field (thanks to @agnivade)
    • Added ability to obtain WebSocket handshake rejection HTTP status code from errors returned during upgrade

    Bug Fixes

    • Fixed incorrect buffer recalculation logic inside wsutil.Writer.Grow(), which was leading to panic with message "wsutil: buffer grow leads to its reduce" (thanks to @ZekeLu)

    Development

    • Added go.mod declaration which makes ws to be a Go module
    • Moved from Travis CI to GitHub Actions
    • Update a dependencies for Autobahn tests
    Source code(tar.gz)
    Source code(zip)
  • v1.1.0-rc.6(Jul 10, 2021)

  • v1.1.0-rc.5(Feb 11, 2021)

  • v1.1.0-rc.4(Feb 6, 2021)

  • v1.1.0-rc.3(Feb 5, 2021)

  • v1.1.0-rc.2(Jan 25, 2021)

  • v1.1.0-rc.1(Dec 8, 2020)

  • v1.0.4(Sep 7, 2020)

  • v1.0.3(Mar 3, 2020)

  • v1.0.2(Jun 14, 2019)

    This release contains a patch that removes unsafe usage from many places. This is done to bring more stable and compatible behavior on other than Linux operating systems.

    Source code(tar.gz)
    Source code(zip)
  • v1.0.1(May 19, 2019)

    This release contains patch that makes ws.Dial() and other client functions send headers in spec compliant capitalization (Sec-Websocket-* to Sec-WebSocket-*). See #71.

    Source code(tar.gz)
    Source code(zip)
  • v1.0.0(Aug 5, 2018)

    Breaking changes:

    • HTTPUpgrader: UpgradeHTTP() and HTTPUpgrader.Upgrade() accepts now only http.ResponseWriter and *http.Request without http.Header option. To add custom headers per upgrade use separate instances of HTTPUpgrader with Header field set to custom headers.

    • Upgrader:

      • Upgrader.Header field now not a callback, but HandshakeHeader interface. See the docs for more info.
      • Upgrader.OnRequest, Upgrader.OnHost, Upgrader.OnHeader and Upgrader.OnBeforeUpgrade are now return only error, without rejection code. To use rejection code use an RejectConnectionError. See the docs for more info.
    • wsutil.ClosedError: Code and Reason methods are now exported fields of a struct.

    • wsutil.ControlHandler: no more callbacks, all logic moved to the same called struct.

    Features

    • Add MustWriteFrame() and MustReadFrame() helper functions.
    • Add State helper methods: s.ClientSide(), s.ServerSide() and others.

    Bugfixes

    • wsutil.ControlHandler: now deals well with masked application data.
    • wsutil.ReadMessage (and brothers): deals well with io.ReadAll at fragmented messages.
    • wsutil.Reader: works well with io.ReadAll when body has invalid utf8 sequence.

    Improvements

    • Running autobahn testsuite on every travis ci builds.
    • More unit tests.
    Source code(tar.gz)
    Source code(zip)
  • v0.1.0(May 27, 2018)

Owner
Sergey Kamardin
Sergey Kamardin
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
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
: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
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
Tiny WebSocket library for Go.

RFC6455 WebSocket implementation in Go.

Sergey Kamardin 4.1k Sep 22, 2021
websocket消息推送服务

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

null 11 Apr 4, 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
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
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
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
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
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