2020-01-15 04:56:50 +00:00
|
|
|
// Package wsutil provides abstractions around the Websocket, including rate
|
|
|
|
// limits.
|
2020-01-09 05:24:45 +00:00
|
|
|
package wsutil
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2020-04-24 06:34:08 +00:00
|
|
|
"log"
|
2020-04-06 19:03:42 +00:00
|
|
|
"net/url"
|
2020-01-09 05:24:45 +00:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
"golang.org/x/time/rate"
|
|
|
|
)
|
|
|
|
|
2020-04-24 06:34:08 +00:00
|
|
|
var (
|
|
|
|
// WSTimeout is the timeout for connecting and writing to the Websocket,
|
|
|
|
// before Gateway cancels and fails.
|
2020-07-15 01:47:52 +00:00
|
|
|
WSTimeout = 5 * time.Minute
|
2020-04-24 06:34:08 +00:00
|
|
|
// WSBuffer is the size of the Event channel. This has to be at least 1 to
|
|
|
|
// make space for the first Event: Ready or Resumed.
|
|
|
|
WSBuffer = 10
|
|
|
|
// WSError is the default error handler
|
|
|
|
WSError = func(err error) { log.Println("Gateway error:", err) }
|
|
|
|
// WSExtraReadTimeout is the duration to be added to Hello, as a read
|
|
|
|
// timeout for the websocket.
|
|
|
|
WSExtraReadTimeout = time.Second
|
|
|
|
// WSDebug is used for extra debug logging. This is expected to behave
|
|
|
|
// similarly to log.Println().
|
|
|
|
WSDebug = func(v ...interface{}) {}
|
|
|
|
)
|
2020-01-09 05:24:45 +00:00
|
|
|
|
|
|
|
type Event struct {
|
|
|
|
Data []byte
|
|
|
|
|
|
|
|
// Error is non-nil if Data is nil.
|
|
|
|
Error error
|
|
|
|
}
|
|
|
|
|
|
|
|
type Websocket struct {
|
2020-01-15 04:43:34 +00:00
|
|
|
Conn Connection
|
|
|
|
Addr string
|
2020-01-09 05:24:45 +00:00
|
|
|
|
2020-04-24 06:34:08 +00:00
|
|
|
// Timeout for connecting and writing to the Websocket, uses default
|
|
|
|
// WSTimeout (global).
|
|
|
|
Timeout time.Duration
|
|
|
|
|
2020-01-15 04:43:34 +00:00
|
|
|
SendLimiter *rate.Limiter
|
|
|
|
DialLimiter *rate.Limiter
|
2020-01-09 05:24:45 +00:00
|
|
|
}
|
|
|
|
|
2020-04-06 19:03:42 +00:00
|
|
|
func New(addr string) *Websocket {
|
2020-04-24 06:34:08 +00:00
|
|
|
return NewCustom(NewConn(), addr)
|
2020-01-15 04:43:34 +00:00
|
|
|
}
|
2020-01-09 05:24:45 +00:00
|
|
|
|
2020-01-15 04:43:34 +00:00
|
|
|
// NewCustom creates a new undialed Websocket.
|
2020-04-06 19:03:42 +00:00
|
|
|
func NewCustom(conn Connection, addr string) *Websocket {
|
|
|
|
return &Websocket{
|
2020-01-15 04:43:34 +00:00
|
|
|
Conn: conn,
|
|
|
|
Addr: addr,
|
|
|
|
|
2020-04-24 06:34:08 +00:00
|
|
|
Timeout: WSTimeout,
|
|
|
|
|
2020-01-15 04:43:34 +00:00
|
|
|
SendLimiter: NewSendLimiter(),
|
|
|
|
DialLimiter: NewDialLimiter(),
|
2020-01-09 05:24:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-20 11:06:20 +00:00
|
|
|
func (ws *Websocket) Dial(ctx context.Context) error {
|
2020-04-24 06:34:08 +00:00
|
|
|
if ws.Timeout > 0 {
|
|
|
|
tctx, cancel := context.WithTimeout(ctx, ws.Timeout)
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
ctx = tctx
|
|
|
|
}
|
|
|
|
|
2020-01-15 04:43:34 +00:00
|
|
|
if err := ws.DialLimiter.Wait(ctx); err != nil {
|
|
|
|
// Expired, fatal error
|
2020-05-16 21:14:49 +00:00
|
|
|
return errors.Wrap(err, "failed to wait")
|
2020-01-15 04:43:34 +00:00
|
|
|
}
|
2020-01-09 05:24:45 +00:00
|
|
|
|
2020-01-15 04:43:34 +00:00
|
|
|
if err := ws.Conn.Dial(ctx, ws.Addr); err != nil {
|
2020-05-16 21:14:49 +00:00
|
|
|
return errors.Wrap(err, "failed to dial")
|
2020-01-09 05:24:45 +00:00
|
|
|
}
|
2020-01-15 04:43:34 +00:00
|
|
|
|
2020-03-02 00:39:40 +00:00
|
|
|
// Reset the SendLimiter:
|
|
|
|
ws.SendLimiter = NewSendLimiter()
|
|
|
|
|
2020-01-15 04:43:34 +00:00
|
|
|
return nil
|
2020-01-09 05:24:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (ws *Websocket) Listen() <-chan Event {
|
2020-01-29 03:54:22 +00:00
|
|
|
return ws.Conn.Listen()
|
2020-01-09 05:24:45 +00:00
|
|
|
}
|
|
|
|
|
2020-04-06 19:03:42 +00:00
|
|
|
func (ws *Websocket) Send(b []byte) error {
|
2020-07-11 19:49:28 +00:00
|
|
|
return ws.SendCtx(context.Background(), b)
|
2020-04-24 06:34:08 +00:00
|
|
|
}
|
|
|
|
|
2020-07-11 19:49:28 +00:00
|
|
|
func (ws *Websocket) SendCtx(ctx context.Context, b []byte) error {
|
2020-04-24 06:34:08 +00:00
|
|
|
if err := ws.SendLimiter.Wait(ctx); err != nil {
|
2020-01-09 05:24:45 +00:00
|
|
|
return errors.Wrap(err, "SendLimiter failed")
|
|
|
|
}
|
|
|
|
|
2020-04-24 06:34:08 +00:00
|
|
|
return ws.Conn.Send(ctx, b)
|
2020-04-06 19:03:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (ws *Websocket) Close() error {
|
2020-04-11 03:03:52 +00:00
|
|
|
return ws.Conn.Close()
|
2020-01-15 04:43:34 +00:00
|
|
|
}
|
|
|
|
|
2020-04-06 19:03:42 +00:00
|
|
|
func InjectValues(rawurl string, values url.Values) string {
|
|
|
|
u, err := url.Parse(rawurl)
|
|
|
|
if err != nil {
|
|
|
|
// Unknown URL, return as-is.
|
|
|
|
return rawurl
|
|
|
|
}
|
|
|
|
|
|
|
|
// Append additional parameters:
|
|
|
|
var q = u.Query()
|
|
|
|
for k, v := range values {
|
|
|
|
q[k] = append(q[k], v...)
|
|
|
|
}
|
|
|
|
|
|
|
|
u.RawQuery = q.Encode()
|
|
|
|
return u.String()
|
2020-01-09 05:24:45 +00:00
|
|
|
}
|