mirror of
https://github.com/42wim/matterbridge.git
synced 2024-11-21 10:12:00 -08:00
Also update to latest melody upstream
This commit is contained in:
parent
356ada872c
commit
89e2dbac15
@ -6,7 +6,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"gopkg.in/olahol/melody.v1"
|
"github.com/olahol/melody"
|
||||||
|
|
||||||
"github.com/42wim/matterbridge/bridge"
|
"github.com/42wim/matterbridge/bridge"
|
||||||
"github.com/42wim/matterbridge/bridge/config"
|
"github.com/42wim/matterbridge/bridge/config"
|
||||||
@ -166,7 +166,9 @@ func (b *API) handleStream(c echo.Context) error {
|
|||||||
}
|
}
|
||||||
c.Response().Flush()
|
c.Response().Flush()
|
||||||
for {
|
for {
|
||||||
|
select {
|
||||||
// TODO: this causes issues, messages should be broadcasted to all connected clients
|
// TODO: this causes issues, messages should be broadcasted to all connected clients
|
||||||
|
default:
|
||||||
msg := b.Messages.Dequeue()
|
msg := b.Messages.Dequeue()
|
||||||
if msg != nil {
|
if msg != nil {
|
||||||
if err := json.NewEncoder(c.Response()).Encode(msg); err != nil {
|
if err := json.NewEncoder(c.Response()).Encode(msg); err != nil {
|
||||||
@ -174,7 +176,10 @@ func (b *API) handleStream(c echo.Context) error {
|
|||||||
}
|
}
|
||||||
c.Response().Flush()
|
c.Response().Flush()
|
||||||
}
|
}
|
||||||
time.Sleep(200 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
|
case <-c.Request().Context().Done():
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
2
go.mod
2
go.mod
@ -34,6 +34,7 @@ require (
|
|||||||
github.com/mattn/godown v0.0.1
|
github.com/mattn/godown v0.0.1
|
||||||
github.com/mdp/qrterminal v1.0.1
|
github.com/mdp/qrterminal v1.0.1
|
||||||
github.com/nelsonken/gomf v0.0.0-20190423072027-c65cc0469e94
|
github.com/nelsonken/gomf v0.0.0-20190423072027-c65cc0469e94
|
||||||
|
github.com/olahol/melody v1.1.2
|
||||||
github.com/paulrosania/go-charset v0.0.0-20190326053356-55c9d7a5834c
|
github.com/paulrosania/go-charset v0.0.0-20190326053356-55c9d7a5834c
|
||||||
github.com/rs/xid v1.4.0
|
github.com/rs/xid v1.4.0
|
||||||
github.com/russross/blackfriday v1.6.0
|
github.com/russross/blackfriday v1.6.0
|
||||||
@ -53,7 +54,6 @@ require (
|
|||||||
golang.org/x/text v0.8.0
|
golang.org/x/text v0.8.0
|
||||||
gomod.garykim.dev/nc-talk v0.3.0
|
gomod.garykim.dev/nc-talk v0.3.0
|
||||||
google.golang.org/protobuf v1.29.0
|
google.golang.org/protobuf v1.29.0
|
||||||
gopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376
|
|
||||||
layeh.com/gumble v0.0.0-20221205141517-d1df60a3cc14
|
layeh.com/gumble v0.0.0-20221205141517-d1df60a3cc14
|
||||||
modernc.org/sqlite v1.21.0
|
modernc.org/sqlite v1.21.0
|
||||||
)
|
)
|
||||||
|
4
go.sum
4
go.sum
@ -1246,6 +1246,8 @@ github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+
|
|||||||
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
||||||
github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
|
github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
|
||||||
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
|
||||||
|
github.com/olahol/melody v1.1.2 h1:9PZ5kYv/CUy0PRZcJCKja1MUxAh6olVeHkyqaQxO7n0=
|
||||||
|
github.com/olahol/melody v1.1.2/go.mod h1:GgkTl6Y7yWj/HtfD48Q5vLKPVoZOH+Qqgfa7CvJgJM4=
|
||||||
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||||
github.com/olekukonko/tablewriter v0.0.0-20180506121414-d4647c9c7a84/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
github.com/olekukonko/tablewriter v0.0.0-20180506121414-d4647c9c7a84/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
|
||||||
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
|
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
|
||||||
@ -2465,8 +2467,6 @@ gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3M
|
|||||||
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8=
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||||
gopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376 h1:sY2a+y0j4iDrajJcorb+a0hJIQ6uakU5gybjfLWHlXo=
|
|
||||||
gopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376/go.mod h1:BHKOc1m5wm8WwQkMqYBoo4vNxhmF7xg8+xhG8L+Cy3M=
|
|
||||||
gopkg.in/olivere/elastic.v6 v6.2.35/go.mod h1:2cTT8Z+/LcArSWpCgvZqBgt3VOqXiy7v00w12Lz8bd4=
|
gopkg.in/olivere/elastic.v6 v6.2.35/go.mod h1:2cTT8Z+/LcArSWpCgvZqBgt3VOqXiy7v00w12Lz8bd4=
|
||||||
gopkg.in/olivere/elastic.v6 v6.2.37/go.mod h1:2cTT8Z+/LcArSWpCgvZqBgt3VOqXiy7v00w12Lz8bd4=
|
gopkg.in/olivere/elastic.v6 v6.2.37/go.mod h1:2cTT8Z+/LcArSWpCgvZqBgt3VOqXiy7v00w12Lz8bd4=
|
||||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||||
|
@ -1,3 +1,11 @@
|
|||||||
|
## 2022-09-12 (v1.1.0)
|
||||||
|
|
||||||
|
* Create Go module.
|
||||||
|
* Update examples.
|
||||||
|
* Fix concurrent panic (PR-65).
|
||||||
|
* Add `Sessions` to get all sessions (PR-53).
|
||||||
|
* Add `LocalAddr` and `RemoteAddr` (PR-55).
|
||||||
|
|
||||||
## 2017-05-18
|
## 2017-05-18
|
||||||
|
|
||||||
* Fix `HandleSentMessageBinary`.
|
* Fix `HandleSentMessageBinary`.
|
0
vendor/gopkg.in/olahol/melody.v1/LICENSE → vendor/github.com/olahol/melody/LICENSE
generated
vendored
0
vendor/gopkg.in/olahol/melody.v1/LICENSE → vendor/github.com/olahol/melody/LICENSE
generated
vendored
157
vendor/github.com/olahol/melody/README.md
generated
vendored
Normal file
157
vendor/github.com/olahol/melody/README.md
generated
vendored
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
# melody
|
||||||
|
|
||||||
|
![Build Status](https://github.com/olahol/melody/actions/workflows/test.yml/badge.svg)
|
||||||
|
[![Codecov](https://img.shields.io/codecov/c/github/olahol/melody)](https://app.codecov.io/github/olahol/melody)
|
||||||
|
[![Go Report Card](https://goreportcard.com/badge/github.com/olahol/melody)](https://goreportcard.com/report/github.com/olahol/melody)
|
||||||
|
[![GoDoc](https://godoc.org/github.com/olahol/melody?status.svg)](https://godoc.org/github.com/olahol/melody)
|
||||||
|
|
||||||
|
> :notes: Minimalist websocket framework for Go.
|
||||||
|
|
||||||
|
Melody is websocket framework based on [github.com/gorilla/websocket](https://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:
|
||||||
|
|
||||||
|
* [x] Clear and easy interface similar to `net/http` or Gin.
|
||||||
|
* [x] A simple way to broadcast to all or selected connected sessions.
|
||||||
|
* [x] Message buffers making concurrent writing safe.
|
||||||
|
* [x] Automatic handling of sending ping/pong heartbeats that timeout broken sessions.
|
||||||
|
* [x] Store data on sessions.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go get github.com/olahol/melody
|
||||||
|
```
|
||||||
|
|
||||||
|
## [Example: chat](https://github.com/olahol/melody/tree/master/examples/chat)
|
||||||
|
|
||||||
|
[![Chat](https://cdn.rawgit.com/olahol/melody/master/examples/chat/demo.gif "Demo")](https://github.com/olahol/melody/tree/master/examples/chat)
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/olahol/melody"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
m := melody.New()
|
||||||
|
|
||||||
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
http.ServeFile(w, r, "index.html")
|
||||||
|
})
|
||||||
|
|
||||||
|
http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
m.HandleRequest(w, r)
|
||||||
|
})
|
||||||
|
|
||||||
|
m.HandleMessage(func(s *melody.Session, msg []byte) {
|
||||||
|
m.Broadcast(msg)
|
||||||
|
})
|
||||||
|
|
||||||
|
http.ListenAndServe(":5000", nil)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## [Example: gophers](https://github.com/olahol/melody/tree/master/examples/gophers)
|
||||||
|
|
||||||
|
[![Gophers](https://cdn.rawgit.com/olahol/melody/master/examples/gophers/demo.gif "Demo")](https://github.com/olahol/melody/tree/master/examples/gophers)
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/olahol/melody"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GopherInfo struct {
|
||||||
|
ID, X, Y string
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
m := melody.New()
|
||||||
|
|
||||||
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
http.ServeFile(w, r, "index.html")
|
||||||
|
})
|
||||||
|
|
||||||
|
http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
m.HandleRequest(w, r)
|
||||||
|
})
|
||||||
|
|
||||||
|
m.HandleConnect(func(s *melody.Session) {
|
||||||
|
ss, _ := m.Sessions()
|
||||||
|
|
||||||
|
for _, o := range ss {
|
||||||
|
value, exists := o.Get("info")
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
info := value.(*GopherInfo)
|
||||||
|
|
||||||
|
s.Write([]byte("set " + info.ID + " " + info.X + " " + info.Y))
|
||||||
|
}
|
||||||
|
|
||||||
|
id := uuid.NewString()
|
||||||
|
s.Set("info", &GopherInfo{id, "0", "0"})
|
||||||
|
|
||||||
|
s.Write([]byte("iam " + id))
|
||||||
|
})
|
||||||
|
|
||||||
|
m.HandleDisconnect(func(s *melody.Session) {
|
||||||
|
value, exists := s.Get("info")
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
info := value.(*GopherInfo)
|
||||||
|
|
||||||
|
m.BroadcastOthers([]byte("dis "+info.ID), s)
|
||||||
|
})
|
||||||
|
|
||||||
|
m.HandleMessage(func(s *melody.Session, msg []byte) {
|
||||||
|
p := strings.Split(string(msg), " ")
|
||||||
|
value, exists := s.Get("info")
|
||||||
|
|
||||||
|
if len(p) != 2 || !exists {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
info := value.(*GopherInfo)
|
||||||
|
info.X = p[0]
|
||||||
|
info.Y = p[1]
|
||||||
|
|
||||||
|
m.BroadcastOthers([]byte("set "+info.ID+" "+info.X+" "+info.Y), s)
|
||||||
|
})
|
||||||
|
|
||||||
|
http.ListenAndServe(":5000", nil)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### [More examples](https://github.com/olahol/melody/tree/master/examples)
|
||||||
|
|
||||||
|
## [Documentation](https://godoc.org/github.com/olahol/melody)
|
||||||
|
|
||||||
|
## Contributors
|
||||||
|
|
||||||
|
<a href="https://github.com/olahol/melody/graphs/contributors">
|
||||||
|
<img src="https://contrib.rocks/image?repo=olahol/melody" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
If you are getting a `403` when trying to connect to your websocket you can [change allow all origin hosts](http://godoc.org/github.com/gorilla/websocket#hdr-Origin_Considerations):
|
||||||
|
|
||||||
|
```go
|
||||||
|
m := melody.New()
|
||||||
|
m.Upgrader.CheckOrigin = func(r *http.Request) bool { return true }
|
||||||
|
```
|
8
vendor/gopkg.in/olahol/melody.v1/doc.go → vendor/github.com/olahol/melody/doc.go
generated
vendored
8
vendor/gopkg.in/olahol/melody.v1/doc.go → vendor/github.com/olahol/melody/doc.go
generated
vendored
@ -9,14 +9,14 @@
|
|||||||
// A broadcasting echo server:
|
// A broadcasting echo server:
|
||||||
//
|
//
|
||||||
// func main() {
|
// func main() {
|
||||||
// r := gin.Default()
|
|
||||||
// m := melody.New()
|
// m := melody.New()
|
||||||
// r.GET("/ws", func(c *gin.Context) {
|
// http.HandleFunc("/ws", func(w http.ResponseWriter, r *http.Request) {
|
||||||
// m.HandleRequest(c.Writer, c.Request)
|
// m.HandleRequest(w, r)
|
||||||
// })
|
// })
|
||||||
// m.HandleMessage(func(s *melody.Session, msg []byte) {
|
// m.HandleMessage(func(s *melody.Session, msg []byte) {
|
||||||
// m.Broadcast(msg)
|
// m.Broadcast(msg)
|
||||||
// })
|
// })
|
||||||
// r.Run(":5000")
|
// http.ListenAndServe(":5000", nil)
|
||||||
// }
|
// }
|
||||||
|
|
||||||
package melody
|
package melody
|
10
vendor/github.com/olahol/melody/errors.go
generated
vendored
Normal file
10
vendor/github.com/olahol/melody/errors.go
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package melody
|
||||||
|
|
||||||
|
import "errors"
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrClosed = errors.New("melody instance is closed")
|
||||||
|
ErrSessionClosed = errors.New("session is closed")
|
||||||
|
ErrWriteClosed = errors.New("tried to write to closed a session")
|
||||||
|
ErrMessageBufferFull = errors.New("session message buffer is full")
|
||||||
|
)
|
11
vendor/gopkg.in/olahol/melody.v1/hub.go → vendor/github.com/olahol/melody/hub.go
generated
vendored
11
vendor/gopkg.in/olahol/melody.v1/hub.go → vendor/github.com/olahol/melody/hub.go
generated
vendored
@ -78,3 +78,14 @@ func (h *hub) len() int {
|
|||||||
|
|
||||||
return len(h.sessions)
|
return len(h.sessions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *hub) all() []*Session {
|
||||||
|
h.rwmutex.RLock()
|
||||||
|
defer h.rwmutex.RUnlock()
|
||||||
|
|
||||||
|
s := make([]*Session, 0, len(h.sessions))
|
||||||
|
for k := range h.sessions {
|
||||||
|
s = append(s, k)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
26
vendor/gopkg.in/olahol/melody.v1/melody.go → vendor/github.com/olahol/melody/melody.go
generated
vendored
26
vendor/gopkg.in/olahol/melody.v1/melody.go → vendor/github.com/olahol/melody/melody.go
generated
vendored
@ -1,7 +1,6 @@
|
|||||||
package melody
|
package melody
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@ -164,10 +163,10 @@ func (m *Melody) HandleRequest(w http.ResponseWriter, r *http.Request) error {
|
|||||||
// HandleRequestWithKeys does the same as HandleRequest but populates session.Keys with keys.
|
// HandleRequestWithKeys does the same as HandleRequest but populates session.Keys with keys.
|
||||||
func (m *Melody) HandleRequestWithKeys(w http.ResponseWriter, r *http.Request, keys map[string]interface{}) error {
|
func (m *Melody) HandleRequestWithKeys(w http.ResponseWriter, r *http.Request, keys map[string]interface{}) error {
|
||||||
if m.hub.closed() {
|
if m.hub.closed() {
|
||||||
return errors.New("melody instance is closed")
|
return ErrClosed
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err := m.Upgrader.Upgrade(w, r, nil)
|
conn, err := m.Upgrader.Upgrade(w, r, w.Header())
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -178,6 +177,7 @@ func (m *Melody) HandleRequestWithKeys(w http.ResponseWriter, r *http.Request, k
|
|||||||
Keys: keys,
|
Keys: keys,
|
||||||
conn: conn,
|
conn: conn,
|
||||||
output: make(chan *envelope, m.Config.MessageBufferSize),
|
output: make(chan *envelope, m.Config.MessageBufferSize),
|
||||||
|
outputDone: make(chan struct{}),
|
||||||
melody: m,
|
melody: m,
|
||||||
open: true,
|
open: true,
|
||||||
rwmutex: &sync.RWMutex{},
|
rwmutex: &sync.RWMutex{},
|
||||||
@ -205,7 +205,7 @@ func (m *Melody) HandleRequestWithKeys(w http.ResponseWriter, r *http.Request, k
|
|||||||
// Broadcast broadcasts a text message to all sessions.
|
// Broadcast broadcasts a text message to all sessions.
|
||||||
func (m *Melody) Broadcast(msg []byte) error {
|
func (m *Melody) Broadcast(msg []byte) error {
|
||||||
if m.hub.closed() {
|
if m.hub.closed() {
|
||||||
return errors.New("melody instance is closed")
|
return ErrClosed
|
||||||
}
|
}
|
||||||
|
|
||||||
message := &envelope{t: websocket.TextMessage, msg: msg}
|
message := &envelope{t: websocket.TextMessage, msg: msg}
|
||||||
@ -217,7 +217,7 @@ func (m *Melody) Broadcast(msg []byte) error {
|
|||||||
// BroadcastFilter broadcasts a text message to all sessions that fn returns true for.
|
// BroadcastFilter broadcasts a text message to all sessions that fn returns true for.
|
||||||
func (m *Melody) BroadcastFilter(msg []byte, fn func(*Session) bool) error {
|
func (m *Melody) BroadcastFilter(msg []byte, fn func(*Session) bool) error {
|
||||||
if m.hub.closed() {
|
if m.hub.closed() {
|
||||||
return errors.New("melody instance is closed")
|
return ErrClosed
|
||||||
}
|
}
|
||||||
|
|
||||||
message := &envelope{t: websocket.TextMessage, msg: msg, filter: fn}
|
message := &envelope{t: websocket.TextMessage, msg: msg, filter: fn}
|
||||||
@ -246,7 +246,7 @@ func (m *Melody) BroadcastMultiple(msg []byte, sessions []*Session) error {
|
|||||||
// BroadcastBinary broadcasts a binary message to all sessions.
|
// BroadcastBinary broadcasts a binary message to all sessions.
|
||||||
func (m *Melody) BroadcastBinary(msg []byte) error {
|
func (m *Melody) BroadcastBinary(msg []byte) error {
|
||||||
if m.hub.closed() {
|
if m.hub.closed() {
|
||||||
return errors.New("melody instance is closed")
|
return ErrClosed
|
||||||
}
|
}
|
||||||
|
|
||||||
message := &envelope{t: websocket.BinaryMessage, msg: msg}
|
message := &envelope{t: websocket.BinaryMessage, msg: msg}
|
||||||
@ -258,7 +258,7 @@ func (m *Melody) BroadcastBinary(msg []byte) error {
|
|||||||
// BroadcastBinaryFilter broadcasts a binary message to all sessions that fn returns true for.
|
// BroadcastBinaryFilter broadcasts a binary message to all sessions that fn returns true for.
|
||||||
func (m *Melody) BroadcastBinaryFilter(msg []byte, fn func(*Session) bool) error {
|
func (m *Melody) BroadcastBinaryFilter(msg []byte, fn func(*Session) bool) error {
|
||||||
if m.hub.closed() {
|
if m.hub.closed() {
|
||||||
return errors.New("melody instance is closed")
|
return ErrClosed
|
||||||
}
|
}
|
||||||
|
|
||||||
message := &envelope{t: websocket.BinaryMessage, msg: msg, filter: fn}
|
message := &envelope{t: websocket.BinaryMessage, msg: msg, filter: fn}
|
||||||
@ -274,10 +274,18 @@ func (m *Melody) BroadcastBinaryOthers(msg []byte, s *Session) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sessions returns all sessions. An error is returned if the melody session is closed.
|
||||||
|
func (m *Melody) Sessions() ([]*Session, error) {
|
||||||
|
if m.hub.closed() {
|
||||||
|
return nil, ErrClosed
|
||||||
|
}
|
||||||
|
return m.hub.all(), nil
|
||||||
|
}
|
||||||
|
|
||||||
// Close closes the melody instance and all connected sessions.
|
// Close closes the melody instance and all connected sessions.
|
||||||
func (m *Melody) Close() error {
|
func (m *Melody) Close() error {
|
||||||
if m.hub.closed() {
|
if m.hub.closed() {
|
||||||
return errors.New("melody instance is already closed")
|
return ErrClosed
|
||||||
}
|
}
|
||||||
|
|
||||||
m.hub.exit <- &envelope{t: websocket.CloseMessage, msg: []byte{}}
|
m.hub.exit <- &envelope{t: websocket.CloseMessage, msg: []byte{}}
|
||||||
@ -289,7 +297,7 @@ func (m *Melody) Close() error {
|
|||||||
// Use the FormatCloseMessage function to format a proper close message payload.
|
// Use the FormatCloseMessage function to format a proper close message payload.
|
||||||
func (m *Melody) CloseWithMsg(msg []byte) error {
|
func (m *Melody) CloseWithMsg(msg []byte) error {
|
||||||
if m.hub.closed() {
|
if m.hub.closed() {
|
||||||
return errors.New("melody instance is already closed")
|
return ErrClosed
|
||||||
}
|
}
|
||||||
|
|
||||||
m.hub.exit <- &envelope{t: websocket.CloseMessage, msg: msg}
|
m.hub.exit <- &envelope{t: websocket.CloseMessage, msg: msg}
|
54
vendor/gopkg.in/olahol/melody.v1/session.go → vendor/github.com/olahol/melody/session.go
generated
vendored
54
vendor/gopkg.in/olahol/melody.v1/session.go → vendor/github.com/olahol/melody/session.go
generated
vendored
@ -1,7 +1,7 @@
|
|||||||
package melody
|
package melody
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@ -15,6 +15,7 @@ type Session struct {
|
|||||||
Keys map[string]interface{}
|
Keys map[string]interface{}
|
||||||
conn *websocket.Conn
|
conn *websocket.Conn
|
||||||
output chan *envelope
|
output chan *envelope
|
||||||
|
outputDone chan struct{}
|
||||||
melody *Melody
|
melody *Melody
|
||||||
open bool
|
open bool
|
||||||
rwmutex *sync.RWMutex
|
rwmutex *sync.RWMutex
|
||||||
@ -22,20 +23,20 @@ type Session struct {
|
|||||||
|
|
||||||
func (s *Session) writeMessage(message *envelope) {
|
func (s *Session) writeMessage(message *envelope) {
|
||||||
if s.closed() {
|
if s.closed() {
|
||||||
s.melody.errorHandler(s, errors.New("tried to write to closed a session"))
|
s.melody.errorHandler(s, ErrWriteClosed)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case s.output <- message:
|
case s.output <- message:
|
||||||
default:
|
default:
|
||||||
s.melody.errorHandler(s, errors.New("session message buffer is full"))
|
s.melody.errorHandler(s, ErrMessageBufferFull)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Session) writeRaw(message *envelope) error {
|
func (s *Session) writeRaw(message *envelope) error {
|
||||||
if s.closed() {
|
if s.closed() {
|
||||||
return errors.New("tried to write to a closed session")
|
return ErrWriteClosed
|
||||||
}
|
}
|
||||||
|
|
||||||
s.conn.SetWriteDeadline(time.Now().Add(s.melody.Config.WriteWait))
|
s.conn.SetWriteDeadline(time.Now().Add(s.melody.Config.WriteWait))
|
||||||
@ -56,12 +57,13 @@ func (s *Session) closed() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Session) close() {
|
func (s *Session) close() {
|
||||||
if !s.closed() {
|
|
||||||
s.rwmutex.Lock()
|
s.rwmutex.Lock()
|
||||||
|
open := s.open
|
||||||
s.open = false
|
s.open = false
|
||||||
s.conn.Close()
|
|
||||||
close(s.output)
|
|
||||||
s.rwmutex.Unlock()
|
s.rwmutex.Unlock()
|
||||||
|
if open {
|
||||||
|
s.conn.Close()
|
||||||
|
close(s.outputDone)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,11 +78,7 @@ func (s *Session) writePump() {
|
|||||||
loop:
|
loop:
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case msg, ok := <-s.output:
|
case msg := <-s.output:
|
||||||
if !ok {
|
|
||||||
break loop
|
|
||||||
}
|
|
||||||
|
|
||||||
err := s.writeRaw(msg)
|
err := s.writeRaw(msg)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -101,10 +99,16 @@ loop:
|
|||||||
}
|
}
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
s.ping()
|
s.ping()
|
||||||
|
case _, ok := <-s.outputDone:
|
||||||
|
if !ok {
|
||||||
|
break loop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.close()
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Session) readPump() {
|
func (s *Session) readPump() {
|
||||||
s.conn.SetReadLimit(s.melody.Config.MaxMessageSize)
|
s.conn.SetReadLimit(s.melody.Config.MaxMessageSize)
|
||||||
s.conn.SetReadDeadline(time.Now().Add(s.melody.Config.PongWait))
|
s.conn.SetReadDeadline(time.Now().Add(s.melody.Config.PongWait))
|
||||||
@ -142,7 +146,7 @@ func (s *Session) readPump() {
|
|||||||
// Write writes message to session.
|
// Write writes message to session.
|
||||||
func (s *Session) Write(msg []byte) error {
|
func (s *Session) Write(msg []byte) error {
|
||||||
if s.closed() {
|
if s.closed() {
|
||||||
return errors.New("session is closed")
|
return ErrSessionClosed
|
||||||
}
|
}
|
||||||
|
|
||||||
s.writeMessage(&envelope{t: websocket.TextMessage, msg: msg})
|
s.writeMessage(&envelope{t: websocket.TextMessage, msg: msg})
|
||||||
@ -153,7 +157,7 @@ func (s *Session) Write(msg []byte) error {
|
|||||||
// WriteBinary writes a binary message to session.
|
// WriteBinary writes a binary message to session.
|
||||||
func (s *Session) WriteBinary(msg []byte) error {
|
func (s *Session) WriteBinary(msg []byte) error {
|
||||||
if s.closed() {
|
if s.closed() {
|
||||||
return errors.New("session is closed")
|
return ErrSessionClosed
|
||||||
}
|
}
|
||||||
|
|
||||||
s.writeMessage(&envelope{t: websocket.BinaryMessage, msg: msg})
|
s.writeMessage(&envelope{t: websocket.BinaryMessage, msg: msg})
|
||||||
@ -164,7 +168,7 @@ func (s *Session) WriteBinary(msg []byte) error {
|
|||||||
// Close closes session.
|
// Close closes session.
|
||||||
func (s *Session) Close() error {
|
func (s *Session) Close() error {
|
||||||
if s.closed() {
|
if s.closed() {
|
||||||
return errors.New("session is already closed")
|
return ErrSessionClosed
|
||||||
}
|
}
|
||||||
|
|
||||||
s.writeMessage(&envelope{t: websocket.CloseMessage, msg: []byte{}})
|
s.writeMessage(&envelope{t: websocket.CloseMessage, msg: []byte{}})
|
||||||
@ -176,7 +180,7 @@ func (s *Session) Close() error {
|
|||||||
// Use the FormatCloseMessage function to format a proper close message payload.
|
// Use the FormatCloseMessage function to format a proper close message payload.
|
||||||
func (s *Session) CloseWithMsg(msg []byte) error {
|
func (s *Session) CloseWithMsg(msg []byte) error {
|
||||||
if s.closed() {
|
if s.closed() {
|
||||||
return errors.New("session is already closed")
|
return ErrSessionClosed
|
||||||
}
|
}
|
||||||
|
|
||||||
s.writeMessage(&envelope{t: websocket.CloseMessage, msg: msg})
|
s.writeMessage(&envelope{t: websocket.CloseMessage, msg: msg})
|
||||||
@ -184,9 +188,12 @@ func (s *Session) CloseWithMsg(msg []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set is used to store a new key/value pair exclusivelly for this session.
|
// Set is used to store a new key/value pair exclusively for this session.
|
||||||
// It also lazy initializes s.Keys if it was not used previously.
|
// It also lazy initializes s.Keys if it was not used previously.
|
||||||
func (s *Session) Set(key string, value interface{}) {
|
func (s *Session) Set(key string, value interface{}) {
|
||||||
|
s.rwmutex.Lock()
|
||||||
|
defer s.rwmutex.Unlock()
|
||||||
|
|
||||||
if s.Keys == nil {
|
if s.Keys == nil {
|
||||||
s.Keys = make(map[string]interface{})
|
s.Keys = make(map[string]interface{})
|
||||||
}
|
}
|
||||||
@ -197,6 +204,9 @@ func (s *Session) Set(key string, value interface{}) {
|
|||||||
// Get returns the value for the given key, ie: (value, true).
|
// Get returns the value for the given key, ie: (value, true).
|
||||||
// If the value does not exists it returns (nil, false)
|
// If the value does not exists it returns (nil, false)
|
||||||
func (s *Session) Get(key string) (value interface{}, exists bool) {
|
func (s *Session) Get(key string) (value interface{}, exists bool) {
|
||||||
|
s.rwmutex.RLock()
|
||||||
|
defer s.rwmutex.RUnlock()
|
||||||
|
|
||||||
if s.Keys != nil {
|
if s.Keys != nil {
|
||||||
value, exists = s.Keys[key]
|
value, exists = s.Keys[key]
|
||||||
}
|
}
|
||||||
@ -217,3 +227,13 @@ func (s *Session) MustGet(key string) interface{} {
|
|||||||
func (s *Session) IsClosed() bool {
|
func (s *Session) IsClosed() bool {
|
||||||
return s.closed()
|
return s.closed()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LocalAddr returns the local addr of the connection.
|
||||||
|
func (s *Session) LocalAddr() net.Addr {
|
||||||
|
return s.conn.LocalAddr()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoteAddr returns the remote addr of the connection.
|
||||||
|
func (s *Session) RemoteAddr() net.Addr {
|
||||||
|
return s.conn.RemoteAddr()
|
||||||
|
}
|
10
vendor/gopkg.in/olahol/melody.v1/.travis.yml
generated
vendored
10
vendor/gopkg.in/olahol/melody.v1/.travis.yml
generated
vendored
@ -1,10 +0,0 @@
|
|||||||
language: go
|
|
||||||
sudo: required
|
|
||||||
go:
|
|
||||||
- 1.6
|
|
||||||
- 1.7
|
|
||||||
- 1.8
|
|
||||||
install:
|
|
||||||
- go get github.com/gorilla/websocket
|
|
||||||
script:
|
|
||||||
- go test
|
|
185
vendor/gopkg.in/olahol/melody.v1/README.md
generated
vendored
185
vendor/gopkg.in/olahol/melody.v1/README.md
generated
vendored
@ -1,185 +0,0 @@
|
|||||||
# melody
|
|
||||||
|
|
||||||
[![Build Status](https://travis-ci.org/olahol/melody.svg)](https://travis-ci.org/olahol/melody)
|
|
||||||
[![Coverage Status](https://img.shields.io/coveralls/olahol/melody.svg?style=flat)](https://coveralls.io/r/olahol/melody)
|
|
||||||
[![GoDoc](https://godoc.org/github.com/olahol/melody?status.svg)](https://godoc.org/github.com/olahol/melody)
|
|
||||||
|
|
||||||
> :notes: Minimalist websocket framework for Go.
|
|
||||||
|
|
||||||
Melody is websocket framework based on [github.com/gorilla/websocket](https://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:
|
|
||||||
|
|
||||||
* [x] Clear and easy interface similar to `net/http` or Gin.
|
|
||||||
* [x] A simple way to broadcast to all or selected connected sessions.
|
|
||||||
* [x] Message buffers making concurrent writing safe.
|
|
||||||
* [x] Automatic handling of ping/pong and session timeouts.
|
|
||||||
* [x] Store data on sessions.
|
|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go get gopkg.in/olahol/melody.v1
|
|
||||||
```
|
|
||||||
|
|
||||||
## [Example: chat](https://github.com/olahol/melody/tree/master/examples/chat)
|
|
||||||
|
|
||||||
[![Chat](https://cdn.rawgit.com/olahol/melody/master/examples/chat/demo.gif "Demo")](https://github.com/olahol/melody/tree/master/examples/chat)
|
|
||||||
|
|
||||||
Using [Gin](https://github.com/gin-gonic/gin):
|
|
||||||
```go
|
|
||||||
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](https://github.com/labstack/echo):
|
|
||||||
```go
|
|
||||||
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](https://github.com/olahol/melody/tree/master/examples/gophers)
|
|
||||||
|
|
||||||
[![Gophers](https://cdn.rawgit.com/olahol/melody/master/examples/gophers/demo.gif "Demo")](https://github.com/olahol/melody/tree/master/examples/gophers)
|
|
||||||
|
|
||||||
```go
|
|
||||||
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](https://github.com/olahol/melody/tree/master/examples)
|
|
||||||
|
|
||||||
## [Documentation](https://godoc.org/github.com/olahol/melody)
|
|
||||||
|
|
||||||
## 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](http://godoc.org/github.com/gorilla/websocket#hdr-Origin_Considerations):
|
|
||||||
|
|
||||||
```go
|
|
||||||
m := melody.New()
|
|
||||||
m.Upgrader.CheckOrigin = func(r *http.Request) bool { return true }
|
|
||||||
```
|
|
6
vendor/modules.txt
vendored
6
vendor/modules.txt
vendored
@ -333,6 +333,9 @@ github.com/mrexodia/wray
|
|||||||
# github.com/nelsonken/gomf v0.0.0-20190423072027-c65cc0469e94
|
# github.com/nelsonken/gomf v0.0.0-20190423072027-c65cc0469e94
|
||||||
## explicit; go 1.12
|
## explicit; go 1.12
|
||||||
github.com/nelsonken/gomf
|
github.com/nelsonken/gomf
|
||||||
|
# github.com/olahol/melody v1.1.2
|
||||||
|
## explicit; go 1.19
|
||||||
|
github.com/olahol/melody
|
||||||
# github.com/opentracing/opentracing-go v1.2.0
|
# github.com/opentracing/opentracing-go v1.2.0
|
||||||
## explicit; go 1.14
|
## explicit; go 1.14
|
||||||
github.com/opentracing/opentracing-go
|
github.com/opentracing/opentracing-go
|
||||||
@ -698,9 +701,6 @@ gopkg.in/ini.v1
|
|||||||
# gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
# gopkg.in/natefinch/lumberjack.v2 v2.0.0
|
||||||
## explicit
|
## explicit
|
||||||
gopkg.in/natefinch/lumberjack.v2
|
gopkg.in/natefinch/lumberjack.v2
|
||||||
# gopkg.in/olahol/melody.v1 v1.0.0-20170518105555-d52139073376
|
|
||||||
## explicit
|
|
||||||
gopkg.in/olahol/melody.v1
|
|
||||||
# gopkg.in/yaml.v2 v2.4.0
|
# gopkg.in/yaml.v2 v2.4.0
|
||||||
## explicit; go 1.15
|
## explicit; go 1.15
|
||||||
gopkg.in/yaml.v2
|
gopkg.in/yaml.v2
|
||||||
|
Loading…
Reference in New Issue
Block a user