Compare commits
13
Commits
v1.2.0
...
better-errors
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
722e0ad3e3 | ||
|
|
065d6ab286 | ||
|
|
1d1ffa00b5 | ||
|
|
983f490737 | ||
|
|
5c9528bce4 | ||
|
|
8fb96f3cbf | ||
|
|
9d732fb01a | ||
|
|
0bd4fa02e9 | ||
|
|
4a305b32a6 | ||
|
|
4c5d5302ec | ||
|
|
994e8e00f1 | ||
|
|
9ad14daeeb | ||
|
|
0ac6c7a55c |
@@ -18,7 +18,7 @@ This software is written in Go - the WYSIWYG lang
|
|||||||
* Web Hooks;
|
* Web Hooks;
|
||||||
* Client events;
|
* Client events;
|
||||||
* Complete REST API;
|
* Complete REST API;
|
||||||
* Easy instalation;
|
* Easy installation;
|
||||||
* A single binary without dependencies;
|
* A single binary without dependencies;
|
||||||
* Easy configuration;
|
* Easy configuration;
|
||||||
* Protocol version 7;
|
* Protocol version 7;
|
||||||
@@ -50,7 +50,8 @@ $ go install github.com/dimiro1/ipe
|
|||||||
```javascript
|
```javascript
|
||||||
{
|
{
|
||||||
"Host": ":8080", // Required
|
"Host": ":8080", // Required
|
||||||
"SSL": false, // Required but can be false
|
"SSL": false, // Not Required, default is false
|
||||||
|
"Profiling": false, // Mount pprof at /debug. Not Required, default is false
|
||||||
"SSLHost": ":4433", // Required if SSL is true
|
"SSLHost": ":4433", // Required if SSL is true
|
||||||
"SSLKeyFile": "A key.pem file", // Required if SSL is true
|
"SSLKeyFile": "A key.pem file", // Required if SSL is true
|
||||||
"SSLCertFile": "A cert.pem file", // Required if SSL is true
|
"SSLCertFile": "A cert.pem file", // Required if SSL is true
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
require 'rake/clean'
|
require 'rake/clean'
|
||||||
|
|
||||||
VERSION = 'v1.2.0'
|
VERSION = 'v1.3.0-SNAPSHOT'
|
||||||
GITHASH = `git rev-parse --short HEAD`
|
GITHASH = `git rev-parse --short HEAD`
|
||||||
DATE = Time.now.strftime '%Y%m%d%H%M%S'
|
DATE = Time.now.strftime '%Y%m%d%H%M%S'
|
||||||
|
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
IPÊ
|
|
||||||
---
|
|
||||||
|
|
||||||
* TODO [12/14]
|
|
||||||
* [X] Autenticação API Rest
|
|
||||||
* [X] Autenticação Websockets
|
|
||||||
* [X] Ping e Pong
|
|
||||||
* [ ] Escrever testes automatizados
|
|
||||||
* [X] SSL
|
|
||||||
* [X] Expvar - Canais, inscritos
|
|
||||||
* [X] Otimizações [3/3]
|
|
||||||
* [X] Refatorar partes do código, remover repetições
|
|
||||||
* [X] Alterar tipos de dados de slices para mapas em alguns locais.
|
|
||||||
* [X] Remover Canais vazios.
|
|
||||||
* [ ] Segurança, tempo de expiração, etc
|
|
||||||
* [X] Dados extra na conexão do usuário. Ver Websockets onOpen
|
|
||||||
* [X] Webhooks [5/5]
|
|
||||||
* [X] Member added
|
|
||||||
* [X] Member removed
|
|
||||||
* [X] Channel Occupied
|
|
||||||
* [X] Channel vacated
|
|
||||||
* [X] Clients Events
|
|
||||||
* [X] Events Presence channels [3/3]
|
|
||||||
* [X] pusher_internal:subscription_succeeded para canais de presença
|
|
||||||
* [X] pusher_internal:member_added
|
|
||||||
* [X] pusher_internal:member_removed
|
|
||||||
* [X] Remover inscrições quando o web socket for fechado
|
|
||||||
* [X] Alterar os dados extras da inscrição são relacionados ao canal e não diretamente a inscrição
|
|
||||||
* [X] Escrever um README
|
|
||||||
|
|
||||||
* Dúvidas
|
|
||||||
* [X] Atualmente o ID do usuário em canais de presença está como string, o protocolo não deixa claro, já ví exemplos onde a chave é um inteiro. Nesse caso essa chave terá que ser um RawMessage, O campo Data inteiro??
|
|
||||||
|
|
||||||
* Objetivos [7/7]
|
|
||||||
* [X] Implementação Funcional.
|
|
||||||
* [X] WebHooks
|
|
||||||
* [X] Presence channels
|
|
||||||
* [X] Private Channels
|
|
||||||
* [X] Public Channels
|
|
||||||
* [X] Easy Instalation
|
|
||||||
* [X] Easy configuration
|
|
||||||
@@ -34,7 +34,7 @@ func pusherPresenceAuth(res http.ResponseWriter, req *http.Request) {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(res, string(response))
|
fmt.Fprint(res, string(response))
|
||||||
}
|
}
|
||||||
|
|
||||||
func pusherPrivateAuth(res http.ResponseWriter, req *http.Request) {
|
func pusherPrivateAuth(res http.ResponseWriter, req *http.Request) {
|
||||||
@@ -48,13 +48,13 @@ func pusherPrivateAuth(res http.ResponseWriter, req *http.Request) {
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintf(res, string(response))
|
fmt.Fprint(res, string(response))
|
||||||
}
|
}
|
||||||
|
|
||||||
func triggerMessage(res http.ResponseWriter, req *http.Request) {
|
func triggerMessage(res http.ResponseWriter, _ *http.Request) {
|
||||||
client.Trigger("private-messages", "messages", "The message from server")
|
client.Trigger("private-messages", "messages", "The message from server")
|
||||||
|
|
||||||
fmt.Fprintf(res, "OK")
|
fmt.Fprint(res, "OK")
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"Host": ":8080",
|
"Host": ":8080",
|
||||||
"Encrypted": false,
|
"SSL": false,
|
||||||
|
"Profiling": true,
|
||||||
"SSLHost": ":8090",
|
"SSLHost": ":8090",
|
||||||
"SSLKeyFile": "key.pem",
|
"SSLKeyFile": "key.pem",
|
||||||
"SSLCertFile": "cert.pem",
|
"SSLCertFile": "cert.pem",
|
||||||
|
|||||||
Generated
+5
-13
@@ -1,20 +1,12 @@
|
|||||||
hash: 0a451841a1f9493d419d658fab8da6863ef5df0645ddea506a225865078c9dcb
|
hash: ee2de935e70f2a39cbfd2dd16add0736c698b32f5c02409d0c25844d883c91a1
|
||||||
updated: 2016-08-10T22:23:10.989237578-03:00
|
updated: 2016-08-22T22:55:07.279113421-03:00
|
||||||
imports:
|
imports:
|
||||||
- name: github.com/golang/glog
|
- name: github.com/golang/glog
|
||||||
version: 23def4e6c14b4da8ac2ed8007337bc5eb5007998
|
version: 23def4e6c14b4da8ac2ed8007337bc5eb5007998
|
||||||
- name: github.com/gorilla/websocket
|
- name: github.com/gorilla/websocket
|
||||||
version: a69d25be2fe2923a97c2af6849b2f52426f68fc0
|
version: a69d25be2fe2923a97c2af6849b2f52426f68fc0
|
||||||
- name: github.com/pusher/pusher-http-go
|
- name: github.com/pressly/chi
|
||||||
version: 2bba5f217f6f0f4f0c0a9bb11b945b206b32bec5
|
version: 12aad88c7d86de2affe686f855b6ed94a07cba9c
|
||||||
- name: goji.io
|
|
||||||
version: e355964ac565b94cf0fc7f218346626529125086
|
|
||||||
subpackages:
|
subpackages:
|
||||||
- pat
|
- middleware
|
||||||
- pattern
|
|
||||||
- internal
|
|
||||||
- name: golang.org/x/net
|
|
||||||
version: 075e191f18186a8ff2becaf64478e30f4545cdad
|
|
||||||
subpackages:
|
|
||||||
- context
|
|
||||||
testImports: []
|
testImports: []
|
||||||
|
|||||||
+3
-7
@@ -2,10 +2,6 @@ package: github.com/dimiro1/ipe
|
|||||||
import:
|
import:
|
||||||
- package: github.com/golang/glog
|
- package: github.com/golang/glog
|
||||||
- package: github.com/gorilla/websocket
|
- package: github.com/gorilla/websocket
|
||||||
- package: github.com/pusher/pusher-http-go
|
- package: github.com/pressly/chi
|
||||||
- package: goji.io
|
excludeDirs:
|
||||||
subpackages:
|
- functional
|
||||||
- pat
|
|
||||||
- package: golang.org/x/net
|
|
||||||
subpackages:
|
|
||||||
- context
|
|
||||||
+4
-4
@@ -74,7 +74,10 @@ func (c *channel) Subscribe(a *app, conn *connection, channelData string) error
|
|||||||
subscription := newSubscription(conn, channelData)
|
subscription := newSubscription(conn, channelData)
|
||||||
c.Subscriptions[conn.SocketID] = subscription
|
c.Subscriptions[conn.SocketID] = subscription
|
||||||
|
|
||||||
if c.IsPresence() {
|
if !c.IsPresence() {
|
||||||
|
conn.Publish(newSubscriptionSucceededEvent(c.ChannelID, "{}"))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
// User Info Data
|
// User Info Data
|
||||||
var info struct {
|
var info struct {
|
||||||
UserID string `json:"user_id"`
|
UserID string `json:"user_id"`
|
||||||
@@ -116,9 +119,6 @@ func (c *channel) Subscribe(a *app, conn *connection, channelData string) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
conn.Publish(newSubscriptionSucceededEvent(c.ChannelID, string(js)))
|
conn.Publish(newSubscriptionSucceededEvent(c.ChannelID, string(js)))
|
||||||
} else {
|
|
||||||
conn.Publish(newSubscriptionSucceededEvent(c.ChannelID, "{}"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// WebHook
|
// WebHook
|
||||||
if c.TotalSubscriptions() == 1 {
|
if c.TotalSubscriptions() == 1 {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"Host": ":8080",
|
"Host": ":8080",
|
||||||
"SSL": false,
|
"SSL": false,
|
||||||
|
"Profiling": false,
|
||||||
"SSLHost": ":4433",
|
"SSLHost": ":4433",
|
||||||
"SSLKeyFile": "A key.pem file",
|
"SSLKeyFile": "A key.pem file",
|
||||||
"SSLCertFile": "A cert.pem file",
|
"SSLCertFile": "A cert.pem file",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ type configFile struct {
|
|||||||
Host string // The host, eg: :8080 will start on 0.0.0.0:8080
|
Host string // The host, eg: :8080 will start on 0.0.0.0:8080
|
||||||
User string
|
User string
|
||||||
SSL bool
|
SSL bool
|
||||||
|
Profiling bool
|
||||||
SSLHost string
|
SSLHost string
|
||||||
SSLKeyFile string
|
SSLKeyFile string
|
||||||
SSLCertFile string
|
SSLCertFile string
|
||||||
|
|||||||
@@ -4,41 +4,6 @@
|
|||||||
|
|
||||||
package ipe
|
package ipe
|
||||||
|
|
||||||
// Error Codes
|
|
||||||
const (
|
|
||||||
// 4000 - 4099
|
|
||||||
// Indicates an error resulting in the connection being closed by Pusher,
|
|
||||||
// and that attempting to reconnect using the same parameters will not succeed.
|
|
||||||
applicationOnlyAcceptsSSL = 4000
|
|
||||||
applicationDoesNotExists = 4001
|
|
||||||
applicationDisabled = 4003
|
|
||||||
applicationIsOverConnectionQuota = 4004 // Not Implemented
|
|
||||||
pathNotFound = 4005 // Not Implemented
|
|
||||||
invalidVersionStringFormat = 4006
|
|
||||||
unsupportedProtocolVersion = 4007
|
|
||||||
noProtocolVersionSupplied = 4008
|
|
||||||
|
|
||||||
// 4100 - 4199
|
|
||||||
// Indicates an error resulting in the connection being closed by Pusher,
|
|
||||||
// and the client may reconnect after 1s or more
|
|
||||||
overCapacity = 4100 // Not Implemented
|
|
||||||
|
|
||||||
// 4200 - 4299
|
|
||||||
// Indicate an error resulting in the connection being closed by Pusher,
|
|
||||||
// and the client my reconnect immediately
|
|
||||||
genericReconnectImmediately = 4200
|
|
||||||
pongReplyNotReceived = 4201 // Ping was sent to the client, but no reply was received; Not Implemented
|
|
||||||
closedAfterInactivity = 4202 // Client has been inactive for a long time (24 hours) and client does not suppot ping.; Not Implemented
|
|
||||||
|
|
||||||
// 4300 - 4399
|
|
||||||
// Any other type of error
|
|
||||||
clientRejectedDueToRateLimit = 4301 // Not Implemented
|
|
||||||
|
|
||||||
// Pusher send null, This app use this error code to send the null value
|
|
||||||
// see ErrorEvent
|
|
||||||
otherError = 0
|
|
||||||
)
|
|
||||||
|
|
||||||
// Only this version is supported
|
// Only this version is supported
|
||||||
const supportedProtocolVersion = 7
|
const supportedProtocolVersion = 7
|
||||||
|
|
||||||
|
|||||||
@@ -18,50 +18,38 @@ type db interface {
|
|||||||
AddApp(*app) error
|
AddApp(*app) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// memdb is an in memory implementation of db interface
|
|
||||||
type memdb struct {
|
type memdb struct {
|
||||||
IDMutex sync.Mutex
|
sync.Mutex
|
||||||
KeyMutex sync.Mutex
|
Apps []*app
|
||||||
AppsByAppID map[string]*app
|
|
||||||
AppsByKey map[string]*app
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMemdb() *memdb {
|
func newMemdb() db {
|
||||||
return &memdb{
|
return &memdb{}
|
||||||
AppsByAppID: make(map[string]*app),
|
|
||||||
AppsByKey: make(map[string]*app),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *memdb) AddApp(a *app) error {
|
func (db *memdb) AddApp(a *app) error {
|
||||||
db.IDMutex.Lock()
|
db.Lock()
|
||||||
db.AppsByAppID[a.AppID] = a
|
db.Apps = append(db.Apps, a)
|
||||||
db.IDMutex.Unlock()
|
db.Unlock()
|
||||||
|
|
||||||
db.KeyMutex.Lock()
|
|
||||||
db.AppsByKey[a.Key] = a
|
|
||||||
db.KeyMutex.Unlock()
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAppByAppID returns an App with by appID
|
// GetAppByAppID returns an App with by appID
|
||||||
func (db *memdb) GetAppByAppID(appID string) (*app, error) {
|
func (db *memdb) GetAppByAppID(appID string) (*app, error) {
|
||||||
db.IDMutex.Lock()
|
for _, a := range db.Apps {
|
||||||
a, ok := db.AppsByAppID[appID]
|
if a.AppID == appID {
|
||||||
db.IDMutex.Unlock()
|
|
||||||
if ok {
|
|
||||||
return a, nil
|
return a, nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return nil, errors.New("App not found")
|
return nil, errors.New("App not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAppByKey returns an App with by key
|
// GetAppByKey returns an App with by key
|
||||||
func (db *memdb) GetAppByKey(key string) (*app, error) {
|
func (db *memdb) GetAppByKey(key string) (*app, error) {
|
||||||
db.KeyMutex.Lock()
|
for _, a := range db.Apps {
|
||||||
a, ok := db.AppsByKey[key]
|
if a.Key == key {
|
||||||
db.KeyMutex.Unlock()
|
|
||||||
if ok {
|
|
||||||
return a, nil
|
return a, nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return nil, errors.New("App not found")
|
return nil, errors.New("App not found")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,19 @@ package ipe
|
|||||||
|
|
||||||
import "testing"
|
import "testing"
|
||||||
|
|
||||||
|
func Benchmark_memdb_GetAppByAppID(b *testing.B) {
|
||||||
|
db := newMemdb()
|
||||||
|
db.AddApp(&app{AppID: "123456", Name: "Example"})
|
||||||
|
db.AddApp(&app{AppID: "654321", Name: "Example2"})
|
||||||
|
db.AddApp(&app{AppID: "678901", Name: "Example3"})
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
db.GetAppByAppID("123456")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Test_db_GetAppByAppID(t *testing.T) {
|
func Test_db_GetAppByAppID(t *testing.T) {
|
||||||
app := &app{AppID: "123456", Name: "Example"}
|
app := &app{AppID: "123456", Name: "Example"}
|
||||||
|
|
||||||
|
|||||||
+24
-78
@@ -4,115 +4,61 @@
|
|||||||
|
|
||||||
package ipe
|
package ipe
|
||||||
|
|
||||||
// Base interface
|
import "fmt"
|
||||||
type websocketError interface {
|
|
||||||
GetCode() int
|
|
||||||
GetMsg() string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Base struct
|
// Base struct
|
||||||
type baseWebsocketError struct {
|
type websocketError struct {
|
||||||
Code int
|
Code *int
|
||||||
Msg string
|
Msg string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e baseWebsocketError) GetCode() int {
|
func (e websocketError) GetCode() *int {
|
||||||
return e.Code
|
return e.Code
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e baseWebsocketError) GetMsg() string {
|
func (e websocketError) GetMsg() string {
|
||||||
return e.Msg
|
return e.Msg
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unsupprted protocol version
|
func (e websocketError) Error() string {
|
||||||
type unsupportedProtocolVersionError struct {
|
return fmt.Sprintf("%d: %s", e.Code, e.Msg)
|
||||||
baseWebsocketError
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newUnsupportedProtocolVersionError() unsupportedProtocolVersionError {
|
func newWebsocketError(code int, msg string) websocketError {
|
||||||
return unsupportedProtocolVersionError{
|
return websocketError{Code: &code, Msg: msg}
|
||||||
baseWebsocketError{Code: unsupportedProtocolVersion, Msg: "Unsupported protocol version"},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Unsupprted protocol version
|
||||||
|
unsupportedProtocolVersionError = newWebsocketError(4007, "Unsupported protocol version")
|
||||||
|
|
||||||
// The application does not exists
|
// The application does not exists
|
||||||
// See the configuration file
|
// See the configuration file
|
||||||
type applicationDoesNotExistsError struct {
|
applicationDoesNotExistsError = newWebsocketError(4001, "Could not found an app with the given key")
|
||||||
baseWebsocketError
|
|
||||||
}
|
|
||||||
|
|
||||||
func newApplicationDoesNotExistsError() applicationDoesNotExistsError {
|
|
||||||
return applicationDoesNotExistsError{
|
|
||||||
baseWebsocketError{Code: applicationDoesNotExists, Msg: "Could not found an app with the given key"},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The user did not send the protocol version
|
// The user did not send the protocol version
|
||||||
type noProtocolVersionSuppliedError struct {
|
noProtocolVersionSuppliedError = newWebsocketError(4008, "No protocol version supplied")
|
||||||
baseWebsocketError
|
|
||||||
}
|
|
||||||
|
|
||||||
func newNoProtocolVersionSuppliedError() noProtocolVersionSuppliedError {
|
|
||||||
return noProtocolVersionSuppliedError{
|
|
||||||
baseWebsocketError{Code: noProtocolVersionSupplied, Msg: "No protocol version supplied"},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// When the application is disabled.
|
// When the application is disabled.
|
||||||
// See the configuration file
|
// See the configuration file
|
||||||
type applicationDisabledError struct {
|
applicationDisabledError = newWebsocketError(4003, "Application disabled")
|
||||||
baseWebsocketError
|
|
||||||
}
|
|
||||||
|
|
||||||
func newApplicationDisabledError() noProtocolVersionSuppliedError {
|
|
||||||
return noProtocolVersionSuppliedError{
|
|
||||||
baseWebsocketError{Code: applicationDisabled, Msg: "Application disabled"},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// When the application only accepts SSL connections
|
// When the application only accepts SSL connections
|
||||||
type applicationOnlyAccepsSSLError struct {
|
applicationOnlyAccepsSSLError = newWebsocketError(4000, "Application only accepts SSL connections, reconnect using wss://")
|
||||||
baseWebsocketError
|
|
||||||
}
|
|
||||||
|
|
||||||
func newApplicationOnlyAccepsSSLError() applicationOnlyAccepsSSLError {
|
|
||||||
return applicationOnlyAccepsSSLError{
|
|
||||||
baseWebsocketError{Code: applicationOnlyAcceptsSSL, Msg: "Application only accepts SSL connections, reconnect using wss://"},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// When the user send an invalid version
|
// When the user send an invalid version
|
||||||
type invalidVersionStringFormatError struct {
|
invalidVersionStringFormatError = newWebsocketError(4006, "Invalid version string format")
|
||||||
baseWebsocketError
|
|
||||||
}
|
|
||||||
|
|
||||||
func newInvalidVersionStringFormatError() invalidVersionStringFormatError {
|
|
||||||
return invalidVersionStringFormatError{
|
|
||||||
baseWebsocketError{Code: invalidVersionStringFormat, Msg: "Invalid version string format"},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used when the error was internal
|
// Used when the error was internal
|
||||||
// * Decoding json
|
// * Decoding json
|
||||||
// * Writing to output
|
// * Writing to output
|
||||||
type genericReconnectImmediatelyError struct {
|
genericReconnectImmediatelyError = newWebsocketError(4200, "Generic reconnect immediately")
|
||||||
baseWebsocketError
|
|
||||||
}
|
|
||||||
|
|
||||||
func newGenericReconnectImmediatelyError() genericReconnectImmediatelyError {
|
|
||||||
return genericReconnectImmediatelyError{
|
|
||||||
baseWebsocketError{Code: genericReconnectImmediately, Msg: "Generic reconnect immediately"},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// When pusher wants to send an Generic error, it only send the message, the code become nil
|
// When pusher wants to send an Generic error, it only send the message, the code become nil
|
||||||
// Currently I do not know how to send nil, so I send GENERIC_ERROR
|
// Currently I do not know how to send nil, so I send GENERIC_ERROR
|
||||||
type genericError struct {
|
genericError = newWebsocketError(0, "Generic Error")
|
||||||
baseWebsocketError
|
|
||||||
}
|
|
||||||
|
|
||||||
func newGenericError(msg string) genericError {
|
disabledClientEventsError = websocketError{Msg: "To send client events, you must enable this feature in the Settings."}
|
||||||
return genericError{
|
|
||||||
baseWebsocketError{Code: otherError, Msg: msg},
|
couldNotFoundChannelError = websocketError{Msg: "Could not find a channel with the given id"}
|
||||||
}
|
)
|
||||||
}
|
|
||||||
|
|||||||
+6
-18
@@ -160,29 +160,17 @@ type errorEvent struct {
|
|||||||
// Create a new error event
|
// Create a new error event
|
||||||
// Pusher protocol is very strange in some parts
|
// Pusher protocol is very strange in some parts
|
||||||
// It send null in some errors.
|
// It send null in some errors.
|
||||||
// So I created this GENERIC_ERROR thing, just to verify if the json must have null on the error code
|
func newErrorEvent(code *int, message string) errorEvent {
|
||||||
func newErrorEvent(code int, message string) errorEvent {
|
|
||||||
var data interface{}
|
|
||||||
|
|
||||||
if code == otherError {
|
type dataErrorEvent struct {
|
||||||
data = struct {
|
|
||||||
Code *int `json:"code"`
|
Code *int `json:"code"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
}{
|
|
||||||
nil,
|
|
||||||
message,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
data = struct {
|
|
||||||
Code int `json:"code"`
|
|
||||||
Message string `json:"message"`
|
|
||||||
}{
|
|
||||||
code,
|
|
||||||
message,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return errorEvent{Event: "pusher:error", Data: data}
|
return errorEvent{Event: "pusher:error", Data: dataErrorEvent{
|
||||||
|
Code: code,
|
||||||
|
Message: message,
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
// {
|
// {
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package ipe
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Test_newErrorEvent_with_invalid_code(t *testing.T) {
|
||||||
|
event := newErrorEvent(0, "The error message")
|
||||||
|
|
||||||
|
data, _ := json.Marshal(event)
|
||||||
|
expected := `{"event":"pusher:error","data":{"code":null,"message":"The error message"}}`
|
||||||
|
|
||||||
|
if bytes.Compare(data, []byte(expected)) != 0 {
|
||||||
|
t.Errorf("%s != %s", string(data), expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_newErrorEvent_with_valid_code(t *testing.T) {
|
||||||
|
event := newErrorEvent(4007, "Unsupported protocol version")
|
||||||
|
|
||||||
|
data, _ := json.Marshal(event)
|
||||||
|
expected := `{"event":"pusher:error","data":{"code":4007,"message":"Unsupported protocol version"}}`
|
||||||
|
|
||||||
|
if bytes.Compare(data, []byte(expected)) != 0 {
|
||||||
|
t.Errorf("%s != %s", string(data), expected)
|
||||||
|
}
|
||||||
|
}
|
||||||
+25
-56
@@ -12,12 +12,8 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
goji "goji.io"
|
|
||||||
|
|
||||||
"goji.io/pat"
|
|
||||||
|
|
||||||
log "github.com/golang/glog"
|
log "github.com/golang/glog"
|
||||||
"golang.org/x/net/context"
|
"github.com/pressly/chi"
|
||||||
|
|
||||||
"github.com/dimiro1/ipe/utils"
|
"github.com/dimiro1/ipe/utils"
|
||||||
)
|
)
|
||||||
@@ -51,9 +47,10 @@ func prepareQueryString(params url.Values) string {
|
|||||||
// * The request path (e.g. /some/resource)
|
// * The request path (e.g. /some/resource)
|
||||||
// * The query parameters sorted by key, with keys converted to lowercase, then joined as in the query string.
|
// * The query parameters sorted by key, with keys converted to lowercase, then joined as in the query string.
|
||||||
// Note that the string must not be url escaped (e.g. given the keys auth_key: foo, Name: Something else, you get auth_key=foo&name=Something else)
|
// Note that the string must not be url escaped (e.g. given the keys auth_key: foo, Name: Something else, you get auth_key=foo&name=Something else)
|
||||||
func restAuthenticationHandler(DB db, next goji.Handler) goji.HandlerFunc {
|
func authenticationHandler(DB db) func(http.Handler) http.Handler {
|
||||||
return func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
return func(next http.Handler) http.Handler {
|
||||||
appID := pat.Param(ctx, "app_id")
|
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
appID := chi.URLParam(r, "app_id")
|
||||||
|
|
||||||
app, err := DB.GetAppByAppID(appID)
|
app, err := DB.GetAppByAppID(appID)
|
||||||
|
|
||||||
@@ -73,18 +70,22 @@ func restAuthenticationHandler(DB db, next goji.Handler) goji.HandlerFunc {
|
|||||||
toSign := strings.ToUpper(r.Method) + "\n" + r.URL.Path + "\n" + queryString
|
toSign := strings.ToUpper(r.Method) + "\n" + r.URL.Path + "\n" + queryString
|
||||||
|
|
||||||
if utils.HashMAC([]byte(toSign), []byte(app.Secret)) == signature {
|
if utils.HashMAC([]byte(toSign), []byte(app.Secret)) == signature {
|
||||||
next.ServeHTTPC(ctx, w, r)
|
next.ServeHTTP(w, r)
|
||||||
} else {
|
} else {
|
||||||
log.Error("Not authorized")
|
log.Error("Not authorized")
|
||||||
http.Error(w, "Not authorized", http.StatusUnauthorized)
|
http.Error(w, "Not authorized", http.StatusUnauthorized)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return http.HandlerFunc(fn)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the application is disabled
|
// Check if the application is disabled
|
||||||
func restCheckAppDisabledHandler(DB db, next goji.Handler) goji.HandlerFunc {
|
func checkAppDisabled(DB db) func(http.Handler) http.Handler {
|
||||||
return func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
return func(next http.Handler) http.Handler {
|
||||||
appID := pat.Param(ctx, "app_id")
|
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
appID := chi.URLParam(r, "app_id")
|
||||||
|
|
||||||
currentApp, err := DB.GetAppByAppID(appID)
|
currentApp, err := DB.GetAppByAppID(appID)
|
||||||
|
|
||||||
@@ -98,30 +99,10 @@ func restCheckAppDisabledHandler(DB db, next goji.Handler) goji.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
next.ServeHTTPC(ctx, w, r)
|
next.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
return http.HandlerFunc(fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func recoverHandler(next goji.Handler) goji.HandlerFunc {
|
|
||||||
return func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
log.Errorf("Please verify the url parameters error was: %s", r)
|
|
||||||
http.Error(w, "Not authorized", http.StatusUnauthorized)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
next.ServeHTTPC(ctx, w, r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// commonHandlers combine restCheckAppDisabledHandler and restAuthenticationHandler handlers
|
|
||||||
func commonHandlers(DB db, next goji.Handler) goji.HandlerFunc {
|
|
||||||
return recoverHandler(restCheckAppDisabledHandler(DB, restAuthenticationHandler(DB, next)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func newPostEventsHandler(DB db) goji.HandlerFunc {
|
|
||||||
return commonHandlers(DB, &postEventsHandler{DB})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type postEventsHandler struct{ DB db }
|
type postEventsHandler struct{ DB db }
|
||||||
@@ -141,8 +122,8 @@ type postEventsHandler struct{ DB db }
|
|||||||
// Response is an empty JSON hash.
|
// Response is an empty JSON hash.
|
||||||
//
|
//
|
||||||
// POST /apps/{app_id}/events
|
// POST /apps/{app_id}/events
|
||||||
func (h *postEventsHandler) ServeHTTPC(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
func (h *postEventsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
appID := pat.Param(ctx, "app_id")
|
appID := chi.URLParam(r, "app_id")
|
||||||
|
|
||||||
app, err := h.DB.GetAppByAppID(appID)
|
app, err := h.DB.GetAppByAppID(appID)
|
||||||
|
|
||||||
@@ -187,10 +168,6 @@ func (h *postEventsHandler) ServeHTTPC(ctx context.Context, w http.ResponseWrite
|
|||||||
w.Write([]byte("{}"))
|
w.Write([]byte("{}"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func newGetChannelsHandler(DB db) goji.HandlerFunc {
|
|
||||||
return commonHandlers(DB, &getChannelsHandler{DB})
|
|
||||||
}
|
|
||||||
|
|
||||||
type getChannelsHandler struct{ DB db }
|
type getChannelsHandler struct{ DB db }
|
||||||
|
|
||||||
// Allows fetching a hash of occupied channels (optionally filtered by prefix),
|
// Allows fetching a hash of occupied channels (optionally filtered by prefix),
|
||||||
@@ -212,10 +189,10 @@ type getChannelsHandler struct{ DB db }
|
|||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// GET /apps/{app_id}/channels
|
// GET /apps/{app_id}/channels
|
||||||
func (h *getChannelsHandler) ServeHTTPC(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
func (h *getChannelsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
query := r.URL.Query()
|
query := r.URL.Query()
|
||||||
|
|
||||||
appID := pat.Param(ctx, "app_id")
|
appID := chi.URLParam(r, "app_id")
|
||||||
filter := query.Get("filter_by_prefix")
|
filter := query.Get("filter_by_prefix")
|
||||||
info := query.Get("info")
|
info := query.Get("info")
|
||||||
|
|
||||||
@@ -282,10 +259,6 @@ func (h *getChannelsHandler) ServeHTTPC(ctx context.Context, w http.ResponseWrit
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newGetChannelHandler(DB db) goji.HandlerFunc {
|
|
||||||
return commonHandlers(DB, &getChannelHandler{DB})
|
|
||||||
}
|
|
||||||
|
|
||||||
type getChannelHandler struct{ DB db }
|
type getChannelHandler struct{ DB db }
|
||||||
|
|
||||||
// Fetch info for one channel
|
// Fetch info for one channel
|
||||||
@@ -298,19 +271,19 @@ type getChannelHandler struct{ DB db }
|
|||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// GET /apps/{app_id}/channels/{channel_name}
|
// GET /apps/{app_id}/channels/{channel_name}
|
||||||
func (h *getChannelHandler) ServeHTTPC(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
func (h *getChannelHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
|
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
|
||||||
|
|
||||||
query := r.URL.Query()
|
query := r.URL.Query()
|
||||||
|
|
||||||
appID := pat.Param(ctx, "app_id")
|
appID := chi.URLParam(r, "app_id")
|
||||||
app, err := h.DB.GetAppByAppID(appID)
|
app, err := h.DB.GetAppByAppID(appID)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, fmt.Sprintf("Could not found an app with app_id: %s", appID), http.StatusBadRequest)
|
http.Error(w, fmt.Sprintf("Could not found an app with app_id: %s", appID), http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
channelName := pat.Param(ctx, "channel_name")
|
channelName := chi.URLParam(r, "channel_name")
|
||||||
|
|
||||||
// Channel name could not be empty
|
// Channel name could not be empty
|
||||||
if strings.TrimSpace(channelName) == "" {
|
if strings.TrimSpace(channelName) == "" {
|
||||||
@@ -376,10 +349,6 @@ func (h *getChannelHandler) ServeHTTPC(ctx context.Context, w http.ResponseWrite
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newGetChannelUsersHandler(DB db) goji.HandlerFunc {
|
|
||||||
return commonHandlers(DB, &getChannelUsersHandler{DB})
|
|
||||||
}
|
|
||||||
|
|
||||||
type getChannelUsersHandler struct{ DB db }
|
type getChannelUsersHandler struct{ DB db }
|
||||||
|
|
||||||
// Allowed only for presence-channels
|
// Allowed only for presence-channels
|
||||||
@@ -393,9 +362,9 @@ type getChannelUsersHandler struct{ DB db }
|
|||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// GET /apps/{app_id}/channels/{channel_name}/users
|
// GET /apps/{app_id}/channels/{channel_name}/users
|
||||||
func (h *getChannelUsersHandler) ServeHTTPC(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
func (h *getChannelUsersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
appID := pat.Param(ctx, "app_id")
|
appID := chi.URLParam(r, "app_id")
|
||||||
channelName := pat.Param(ctx, "channel_name")
|
channelName := chi.URLParam(r, "channel_name")
|
||||||
|
|
||||||
isPresence := utils.IsPresenceChannel(channelName)
|
isPresence := utils.IsPresenceChannel(channelName)
|
||||||
|
|
||||||
|
|||||||
+26
-21
@@ -1,15 +1,14 @@
|
|||||||
package ipe
|
package ipe
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"goji.io/pattern"
|
"github.com/pressly/chi"
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -41,14 +40,15 @@ func init() {
|
|||||||
func Test_getChannels_all(t *testing.T) {
|
func Test_getChannels_all(t *testing.T) {
|
||||||
appID := testApp.AppID
|
appID := testApp.AppID
|
||||||
|
|
||||||
ctx := context.Background()
|
rctx := chi.NewRouteContext()
|
||||||
ctx = context.WithValue(ctx, pattern.Variable("app_id"), appID)
|
rctx.URLParams.Add("app_id", appID)
|
||||||
|
|
||||||
r, _ := http.NewRequest("GET", fmt.Sprintf("/apps/%s/channels", appID), nil)
|
r, _ := http.NewRequest("GET", fmt.Sprintf("/apps/%s/channels", appID), nil)
|
||||||
|
r = r.WithContext(context.WithValue(context.Background(), chi.RouteCtxKey, rctx))
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
handler := &getChannelsHandler{database}
|
handler := &getChannelsHandler{database}
|
||||||
handler.ServeHTTPC(ctx, w, r)
|
handler.ServeHTTP(w, r)
|
||||||
|
|
||||||
if w.Code != http.StatusOK {
|
if w.Code != http.StatusOK {
|
||||||
t.Errorf("w.Code == %d, wants %d", w.Code, http.StatusOK)
|
t.Errorf("w.Code == %d, wants %d", w.Code, http.StatusOK)
|
||||||
@@ -67,14 +67,15 @@ func Test_getChannels_all(t *testing.T) {
|
|||||||
func Test_getChannels_filter_by_presence_prefix(t *testing.T) {
|
func Test_getChannels_filter_by_presence_prefix(t *testing.T) {
|
||||||
appID := testApp.AppID
|
appID := testApp.AppID
|
||||||
|
|
||||||
ctx := context.Background()
|
rctx := chi.NewRouteContext()
|
||||||
ctx = context.WithValue(ctx, pattern.Variable("app_id"), appID)
|
rctx.URLParams.Add("app_id", appID)
|
||||||
|
|
||||||
r, _ := http.NewRequest("GET", fmt.Sprintf("/apps/%s/channels?filter_by_prefix=presence-", appID), nil)
|
r, _ := http.NewRequest("GET", fmt.Sprintf("/apps/%s/channels?filter_by_prefix=presence-", appID), nil)
|
||||||
|
r = r.WithContext(context.WithValue(context.Background(), chi.RouteCtxKey, rctx))
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
handler := &getChannelsHandler{database}
|
handler := &getChannelsHandler{database}
|
||||||
handler.ServeHTTPC(ctx, w, r)
|
handler.ServeHTTP(w, r)
|
||||||
|
|
||||||
if w.Code != http.StatusOK {
|
if w.Code != http.StatusOK {
|
||||||
t.Errorf("w.Code == %d, wants %d", w.Code, http.StatusOK)
|
t.Errorf("w.Code == %d, wants %d", w.Code, http.StatusOK)
|
||||||
@@ -94,14 +95,15 @@ func Test_getChannels_filter_by_presence_prefix(t *testing.T) {
|
|||||||
func Test_getChannels_filter_by_presence_prefix_and_user_count(t *testing.T) {
|
func Test_getChannels_filter_by_presence_prefix_and_user_count(t *testing.T) {
|
||||||
appID := testApp.AppID
|
appID := testApp.AppID
|
||||||
|
|
||||||
ctx := context.Background()
|
rctx := chi.NewRouteContext()
|
||||||
ctx = context.WithValue(ctx, pattern.Variable("app_id"), appID)
|
rctx.URLParams.Add("app_id", appID)
|
||||||
|
|
||||||
r, _ := http.NewRequest("GET", fmt.Sprintf("/apps/%s/channels?filter_by_prefix=presence-&info=user_count", appID), nil)
|
r, _ := http.NewRequest("GET", fmt.Sprintf("/apps/%s/channels?filter_by_prefix=presence-&info=user_count", appID), nil)
|
||||||
|
r = r.WithContext(context.WithValue(context.Background(), chi.RouteCtxKey, rctx))
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
handler := &getChannelsHandler{database}
|
handler := &getChannelsHandler{database}
|
||||||
handler.ServeHTTPC(ctx, w, r)
|
handler.ServeHTTP(w, r)
|
||||||
|
|
||||||
if w.Code != http.StatusOK {
|
if w.Code != http.StatusOK {
|
||||||
t.Errorf("w.Code == %d, wants %d", w.Code, http.StatusOK)
|
t.Errorf("w.Code == %d, wants %d", w.Code, http.StatusOK)
|
||||||
@@ -133,14 +135,15 @@ func Test_getChannels_filter_by_presence_prefix_and_user_count(t *testing.T) {
|
|||||||
func Test_getChannels_filter_by_private_prefix_and_info_user_count(t *testing.T) {
|
func Test_getChannels_filter_by_private_prefix_and_info_user_count(t *testing.T) {
|
||||||
appID := testApp.AppID
|
appID := testApp.AppID
|
||||||
|
|
||||||
ctx := context.Background()
|
rctx := chi.NewRouteContext()
|
||||||
ctx = context.WithValue(ctx, pattern.Variable("app_id"), appID)
|
rctx.URLParams.Add("app_id", appID)
|
||||||
|
|
||||||
r, _ := http.NewRequest("GET", fmt.Sprintf("/apps/%s/channels?filter_by_prefix=private-&info=user_count", appID), nil)
|
r, _ := http.NewRequest("GET", fmt.Sprintf("/apps/%s/channels?filter_by_prefix=private-&info=user_count", appID), nil)
|
||||||
|
r = r.WithContext(context.WithValue(context.Background(), chi.RouteCtxKey, rctx))
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
handler := &getChannelsHandler{database}
|
handler := &getChannelsHandler{database}
|
||||||
handler.ServeHTTPC(ctx, w, r)
|
handler.ServeHTTP(w, r)
|
||||||
|
|
||||||
if w.Code != http.StatusBadRequest {
|
if w.Code != http.StatusBadRequest {
|
||||||
t.Errorf("w.Code == %d, wants %d", w.Code, http.StatusBadRequest)
|
t.Errorf("w.Code == %d, wants %d", w.Code, http.StatusBadRequest)
|
||||||
@@ -150,14 +153,15 @@ func Test_getChannels_filter_by_private_prefix_and_info_user_count(t *testing.T)
|
|||||||
func Test_getChannels_filter_by_public_prefix(t *testing.T) {
|
func Test_getChannels_filter_by_public_prefix(t *testing.T) {
|
||||||
appID := testApp.AppID
|
appID := testApp.AppID
|
||||||
|
|
||||||
ctx := context.Background()
|
rctx := chi.NewRouteContext()
|
||||||
ctx = context.WithValue(ctx, pattern.Variable("app_id"), appID)
|
rctx.URLParams.Add("app_id", appID)
|
||||||
|
|
||||||
r, _ := http.NewRequest("GET", fmt.Sprintf("/apps/%s/channels?filter_by_prefix=public-", appID), nil)
|
r, _ := http.NewRequest("GET", fmt.Sprintf("/apps/%s/channels?filter_by_prefix=public-", appID), nil)
|
||||||
|
r = r.WithContext(context.WithValue(context.Background(), chi.RouteCtxKey, rctx))
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
handler := &getChannelsHandler{database}
|
handler := &getChannelsHandler{database}
|
||||||
handler.ServeHTTPC(ctx, w, r)
|
handler.ServeHTTP(w, r)
|
||||||
|
|
||||||
if w.Code != http.StatusOK {
|
if w.Code != http.StatusOK {
|
||||||
t.Errorf("w.Code == %d, wants %d", w.Code, http.StatusOK)
|
t.Errorf("w.Code == %d, wants %d", w.Code, http.StatusOK)
|
||||||
@@ -183,14 +187,15 @@ func Test_getChannels_filter_by_public_prefix(t *testing.T) {
|
|||||||
func Test_getChannels_filter_by_private_prefix(t *testing.T) {
|
func Test_getChannels_filter_by_private_prefix(t *testing.T) {
|
||||||
appID := testApp.AppID
|
appID := testApp.AppID
|
||||||
|
|
||||||
ctx := context.Background()
|
rctx := chi.NewRouteContext()
|
||||||
ctx = context.WithValue(ctx, pattern.Variable("app_id"), appID)
|
rctx.URLParams.Add("app_id", appID)
|
||||||
|
|
||||||
r, _ := http.NewRequest("GET", fmt.Sprintf("/apps/%s/channels?filter_by_prefix=private-", appID), nil)
|
r, _ := http.NewRequest("GET", fmt.Sprintf("/apps/%s/channels?filter_by_prefix=private-", appID), nil)
|
||||||
|
r = r.WithContext(context.WithValue(context.Background(), chi.RouteCtxKey, rctx))
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
handler := &getChannelsHandler{database}
|
handler := &getChannelsHandler{database}
|
||||||
handler.ServeHTTPC(ctx, w, r)
|
handler.ServeHTTP(w, r)
|
||||||
|
|
||||||
if w.Code != http.StatusOK {
|
if w.Code != http.StatusOK {
|
||||||
t.Errorf("w.Code == %d, wants %d", w.Code, http.StatusOK)
|
t.Errorf("w.Code == %d, wants %d", w.Code, http.StatusOK)
|
||||||
|
|||||||
+25
-14
@@ -11,11 +11,9 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"goji.io/pat"
|
|
||||||
|
|
||||||
goji "goji.io"
|
|
||||||
|
|
||||||
log "github.com/golang/glog"
|
log "github.com/golang/glog"
|
||||||
|
"github.com/pressly/chi"
|
||||||
|
"github.com/pressly/chi/middleware"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Start Parse the configuration file and starts the ipe server
|
// Start Parse the configuration file and starts the ipe server
|
||||||
@@ -27,14 +25,16 @@ func Start(filename string) {
|
|||||||
file, err := os.Open(filename)
|
file, err := os.Open(filename)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Error(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
// Reading config
|
// Reading config
|
||||||
if err := json.NewDecoder(file).Decode(&conf); err != nil {
|
if err := json.NewDecoder(file).Decode(&conf); err != nil {
|
||||||
log.Fatal(err)
|
log.Error(err)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Using a in memory database
|
// Using a in memory database
|
||||||
@@ -45,20 +45,31 @@ func Start(filename string) {
|
|||||||
db.AddApp(newAppFromConfig(a))
|
db.AddApp(newAppFromConfig(a))
|
||||||
}
|
}
|
||||||
|
|
||||||
router := goji.NewMux()
|
r := chi.NewRouter()
|
||||||
router.HandleFuncC(pat.Post("/apps/:app_id/events"), newPostEventsHandler(db))
|
r.Use(middleware.Recoverer)
|
||||||
router.HandleFuncC(pat.Get("/apps/:app_id/channels"), newGetChannelsHandler(db))
|
|
||||||
router.HandleFuncC(pat.Get("/apps/:app_id/channels/:channel_name"), newGetChannelHandler(db))
|
r.Get("/app/:key", (&websocketHandler{db}).ServeHTTP)
|
||||||
router.HandleFuncC(pat.Get("/apps/:app_id/channels/:channel_name/users"), newGetChannelUsersHandler(db))
|
r.Group(func(r chi.Router) {
|
||||||
router.HandleC(pat.Get("/app/:key"), newWebsocketHandler(db))
|
r.Use(checkAppDisabled(db))
|
||||||
|
r.Use(authenticationHandler(db))
|
||||||
|
|
||||||
|
r.Post("/apps/:app_id/events", (&postEventsHandler{db}).ServeHTTP)
|
||||||
|
r.Get("/apps/:app_id/channels", (&getChannelsHandler{db}).ServeHTTP)
|
||||||
|
r.Get("/apps/:app_id/channels/:channel_name", (&getChannelHandler{db}).ServeHTTP)
|
||||||
|
r.Get("/apps/:app_id/channels/:channel_name/users", (&getChannelUsersHandler{db}).ServeHTTP)
|
||||||
|
})
|
||||||
|
|
||||||
|
if conf.Profiling {
|
||||||
|
r.Mount("/debug", middleware.Profiler())
|
||||||
|
}
|
||||||
|
|
||||||
if conf.SSL {
|
if conf.SSL {
|
||||||
go func() {
|
go func() {
|
||||||
log.Infof("Starting HTTPS service on %s ...", conf.SSLHost)
|
log.Infof("Starting HTTPS service on %s ...", conf.SSLHost)
|
||||||
log.Fatal(http.ListenAndServeTLS(conf.SSLHost, conf.SSLCertFile, conf.SSLKeyFile, router))
|
log.Fatal(http.ListenAndServeTLS(conf.SSLHost, conf.SSLCertFile, conf.SSLKeyFile, r))
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("Starting HTTP service on %s ...", conf.Host)
|
log.Infof("Starting HTTP service on %s ...", conf.Host)
|
||||||
log.Fatal(http.ListenAndServe(conf.Host, router))
|
log.Fatal(http.ListenAndServe(conf.Host, r))
|
||||||
}
|
}
|
||||||
|
|||||||
+41
-9
@@ -10,10 +10,15 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/dimiro1/ipe/utils"
|
"github.com/dimiro1/ipe/utils"
|
||||||
log "github.com/golang/glog"
|
log "github.com/golang/glog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const maxTimeout = 3 * time.Second
|
||||||
|
|
||||||
// A WebHook is sent as a HTTP POST request to the url which you specify.
|
// A WebHook is sent as a HTTP POST request to the url which you specify.
|
||||||
// The POST request payload (body) contains a JSON document, and follows the following format:
|
// The POST request payload (body) contains a JSON document, and follows the following format:
|
||||||
// {
|
// {
|
||||||
@@ -71,14 +76,19 @@ func newClientHook(channel *channel, s *subscription, event string, data interfa
|
|||||||
// { "name": "channel_occupied", "channel": "test_channel" }
|
// { "name": "channel_occupied", "channel": "test_channel" }
|
||||||
func (a *app) TriggerChannelOccupiedHook(c *channel) {
|
func (a *app) TriggerChannelOccupiedHook(c *channel) {
|
||||||
event := newChannelOcuppiedHook(c)
|
event := newChannelOcuppiedHook(c)
|
||||||
triggerHook(event.Name, a, c, event)
|
ctx, cancel := context.WithTimeout(context.Background(), maxTimeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
triggerHook(ctx, a, event)
|
||||||
}
|
}
|
||||||
|
|
||||||
// channel_vacated
|
// channel_vacated
|
||||||
// { "name": "channel_vacated", "channel": "test_channel" }
|
// { "name": "channel_vacated", "channel": "test_channel" }
|
||||||
func (a *app) TriggerChannelVacatedHook(c *channel) {
|
func (a *app) TriggerChannelVacatedHook(c *channel) {
|
||||||
event := newChannelVacatedHook(c)
|
event := newChannelVacatedHook(c)
|
||||||
triggerHook(event.Name, a, c, event)
|
ctx, cancel := context.WithTimeout(context.Background(), maxTimeout)
|
||||||
|
defer cancel()
|
||||||
|
triggerHook(ctx, a, event)
|
||||||
}
|
}
|
||||||
|
|
||||||
// {
|
// {
|
||||||
@@ -96,7 +106,9 @@ func (a *app) TriggerClientEventHook(c *channel, s *subscription, clientEvent st
|
|||||||
event.UserID = s.ID
|
event.UserID = s.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
triggerHook(event.Name, a, c, event)
|
ctx, cancel := context.WithTimeout(context.Background(), maxTimeout)
|
||||||
|
defer cancel()
|
||||||
|
triggerHook(ctx, a, event)
|
||||||
}
|
}
|
||||||
|
|
||||||
// {
|
// {
|
||||||
@@ -106,7 +118,9 @@ func (a *app) TriggerClientEventHook(c *channel, s *subscription, clientEvent st
|
|||||||
// }
|
// }
|
||||||
func (a *app) TriggerMemberAddedHook(c *channel, s *subscription) {
|
func (a *app) TriggerMemberAddedHook(c *channel, s *subscription) {
|
||||||
event := newMemberAddedHook(c, s)
|
event := newMemberAddedHook(c, s)
|
||||||
triggerHook(event.Name, a, c, event)
|
ctx, cancel := context.WithTimeout(context.Background(), maxTimeout)
|
||||||
|
defer cancel()
|
||||||
|
triggerHook(ctx, a, event)
|
||||||
}
|
}
|
||||||
|
|
||||||
// {
|
// {
|
||||||
@@ -116,17 +130,22 @@ func (a *app) TriggerMemberAddedHook(c *channel, s *subscription) {
|
|||||||
// }
|
// }
|
||||||
func (a *app) TriggerMemberRemovedHook(c *channel, s *subscription) {
|
func (a *app) TriggerMemberRemovedHook(c *channel, s *subscription) {
|
||||||
event := newMemberRemovedHook(c, s)
|
event := newMemberRemovedHook(c, s)
|
||||||
triggerHook(event.Name, a, c, event)
|
ctx, cancel := context.WithTimeout(context.Background(), maxTimeout)
|
||||||
|
defer cancel()
|
||||||
|
triggerHook(ctx, a, event)
|
||||||
}
|
}
|
||||||
|
|
||||||
func triggerHook(name string, a *app, c *channel, event hookEvent) {
|
func triggerHook(ctx context.Context, a *app, event hookEvent) error {
|
||||||
if !a.WebHooks {
|
if !a.WebHooks {
|
||||||
log.Infof("Webhooks are not enabled for app: %s", a.Name)
|
log.Infof("Webhooks are not enabled for app: %s", a.Name)
|
||||||
return
|
return fmt.Errorf("Webhooks are not enabled for app: %s", a.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var done chan bool
|
||||||
|
defer close(done)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
log.Infof("Triggering %s event", name)
|
log.Infof("Triggering %s event", event.Name)
|
||||||
|
|
||||||
hook := webHook{TimeMs: time.Now().Unix()}
|
hook := webHook{TimeMs: time.Now().Unix()}
|
||||||
|
|
||||||
@@ -145,11 +164,14 @@ func triggerHook(name string, a *app, c *channel, event hookEvent) {
|
|||||||
var req *http.Request
|
var req *http.Request
|
||||||
|
|
||||||
req, err = http.NewRequest("POST", a.URLWebHook, bytes.NewReader(js))
|
req, err = http.NewRequest("POST", a.URLWebHook, bytes.NewReader(js))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error creating request: %+v", err)
|
log.Errorf("Error creating request: %+v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
req.WithContext(ctx)
|
||||||
|
|
||||||
req.Header.Set("User-Agent", "Ipe UA; (+https://github.com/dimiro1/ipe)")
|
req.Header.Set("User-Agent", "Ipe UA; (+https://github.com/dimiro1/ipe)")
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
req.Header.Set("X-Pusher-Key", a.Key)
|
req.Header.Set("X-Pusher-Key", a.Key)
|
||||||
@@ -166,7 +188,17 @@ func triggerHook(name string, a *app, c *channel, event hookEvent) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Error posting %s event: %+v", name, err)
|
log.Errorf("Error posting %s event: %+v", event.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Successfully terminated
|
||||||
|
done <- true
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
case <-done:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+140
-122
@@ -12,13 +12,9 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
goji "goji.io"
|
|
||||||
|
|
||||||
"goji.io/pat"
|
|
||||||
|
|
||||||
log "github.com/golang/glog"
|
log "github.com/golang/glog"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"golang.org/x/net/context"
|
"github.com/pressly/chi"
|
||||||
|
|
||||||
"github.com/dimiro1/ipe/utils"
|
"github.com/dimiro1/ipe/utils"
|
||||||
)
|
)
|
||||||
@@ -26,11 +22,58 @@ import (
|
|||||||
var upgrader = websocket.Upgrader{
|
var upgrader = websocket.Upgrader{
|
||||||
ReadBufferSize: 1024,
|
ReadBufferSize: 1024,
|
||||||
WriteBufferSize: 1024,
|
WriteBufferSize: 1024,
|
||||||
CheckOrigin: func(r *http.Request) bool { return true },
|
CheckOrigin: func(_ *http.Request) bool {
|
||||||
|
return true
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle open Subscriber.
|
func handleMessages(conn *websocket.Conn, sessionID string, app *app) {
|
||||||
func onOpen(conn *websocket.Conn, w http.ResponseWriter, r *http.Request, sessionID string, app *app) websocketError {
|
var event struct {
|
||||||
|
Event string `json:"event"`
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
_, message, err := conn.ReadMessage()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
handleError(conn, sessionID, app, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(message, &event); err != nil {
|
||||||
|
emitWSError(newGenericReconnectImmediatelyError(), conn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("websockets: Handling %s event", event.Event)
|
||||||
|
|
||||||
|
switch event.Event {
|
||||||
|
case "pusher:ping":
|
||||||
|
onPing(conn)
|
||||||
|
case "pusher:subscribe":
|
||||||
|
onSubscribe(conn, sessionID, app, message)
|
||||||
|
case "pusher:unsubscribe":
|
||||||
|
onUnsubscribe(conn, sessionID, app, message)
|
||||||
|
default:
|
||||||
|
if utils.IsClientEvent(event.Event) {
|
||||||
|
onClientEvent(conn, sessionID, app, message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleError(conn *websocket.Conn, sessionID string, app *app, err error) {
|
||||||
|
log.Errorf("%+v", err)
|
||||||
|
if err == io.EOF {
|
||||||
|
onClose(sessionID, app)
|
||||||
|
} else if _, ok := err.(*websocket.CloseError); ok {
|
||||||
|
onClose(sessionID, app)
|
||||||
|
} else {
|
||||||
|
emitWSError(newGenericReconnectImmediatelyError(), conn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func onOpen(conn *websocket.Conn, r *http.Request, sessionID string, app *app) error {
|
||||||
params := r.URL.Query()
|
params := r.URL.Query()
|
||||||
p := params.Get("protocol")
|
p := params.Get("protocol")
|
||||||
|
|
||||||
@@ -44,7 +87,7 @@ func onOpen(conn *websocket.Conn, w http.ResponseWriter, r *http.Request, sessio
|
|||||||
case strings.TrimSpace(p) == "":
|
case strings.TrimSpace(p) == "":
|
||||||
return newNoProtocolVersionSuppliedError()
|
return newNoProtocolVersionSuppliedError()
|
||||||
case protocol != supportedProtocolVersion:
|
case protocol != supportedProtocolVersion:
|
||||||
return newUnsupportedProtocolVersionError()
|
return unsupportedProtocolVersionError
|
||||||
case app.ApplicationDisabled:
|
case app.ApplicationDisabled:
|
||||||
return newApplicationDisabledError()
|
return newApplicationDisabledError()
|
||||||
case app.OnlySSL:
|
case app.OnlySSL:
|
||||||
@@ -65,93 +108,48 @@ func onOpen(conn *websocket.Conn, w http.ResponseWriter, r *http.Request, sessio
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle the close event
|
|
||||||
func onClose(sessionID string, app *app) {
|
func onClose(sessionID string, app *app) {
|
||||||
app.Disconnect(sessionID)
|
app.Disconnect(sessionID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle messages
|
func onPing(conn *websocket.Conn) {
|
||||||
//
|
|
||||||
// If there is an unrecoverable error then break the loop,
|
|
||||||
// otherwise just keep going.
|
|
||||||
func onMessage(conn *websocket.Conn, w http.ResponseWriter, r *http.Request, sessionID string, app *app) {
|
|
||||||
var event struct {
|
|
||||||
Event string `json:"event"`
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
_, message, err := conn.ReadMessage()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("%+v", err)
|
|
||||||
if err == io.EOF {
|
|
||||||
onClose(sessionID, app)
|
|
||||||
} else if _, ok := err.(*websocket.CloseError); ok {
|
|
||||||
onClose(sessionID, app)
|
|
||||||
} else {
|
|
||||||
emitWSError(newGenericReconnectImmediatelyError(), conn)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := json.Unmarshal(message, &event); err != nil {
|
|
||||||
emitWSError(newGenericReconnectImmediatelyError(), conn)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("websockets: Handling %s event", event.Event)
|
|
||||||
|
|
||||||
switch event.Event {
|
|
||||||
case "pusher:ping":
|
|
||||||
if err := conn.WriteJSON(newPongEvent()); err != nil {
|
if err := conn.WriteJSON(newPongEvent()); err != nil {
|
||||||
emitWSError(newGenericReconnectImmediatelyError(), conn)
|
emitWSError(newGenericReconnectImmediatelyError(), conn)
|
||||||
}
|
}
|
||||||
case "pusher:subscribe":
|
|
||||||
subscribeEvent := subscribeEvent{}
|
|
||||||
|
|
||||||
if err := json.Unmarshal(message, &subscribeEvent); err != nil {
|
|
||||||
emitWSError(newGenericReconnectImmediatelyError(), conn)
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
|
||||||
connection, err := app.FindConnection(sessionID)
|
func onClientEvent(conn *websocket.Conn, sessionID string, app *app, message []byte) {
|
||||||
|
if !app.UserEvents {
|
||||||
|
emitWSError(newGenericError("To send client events, you must enable this feature in the Settings."), conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
clientEvent := rawEvent{}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(message, &clientEvent); err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
emitWSError(newGenericReconnectImmediatelyError(), conn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
channel, err := app.FindChannelByChannelID(clientEvent.Channel)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
emitWSError(newGenericError(fmt.Sprintf("Could not find a channel with the id %s", clientEvent.Channel)), conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !channel.IsPresenceOrPrivate() {
|
||||||
|
emitWSError(newGenericError("Client event rejected - only supported on private and presence channels"), conn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := app.Publish(channel, clientEvent, sessionID); err != nil {
|
||||||
|
log.Error(err)
|
||||||
emitWSError(newGenericReconnectImmediatelyError(), conn)
|
emitWSError(newGenericReconnectImmediatelyError(), conn)
|
||||||
break
|
return
|
||||||
}
|
|
||||||
|
|
||||||
channelName := strings.TrimSpace(subscribeEvent.Data.Channel)
|
|
||||||
|
|
||||||
if !utils.IsChannelNameValid(channelName) {
|
|
||||||
emitWSError(newGenericError(fmt.Sprintf("This channel name is not valid")), conn)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
isPresence := utils.IsPresenceChannel(channelName)
|
|
||||||
isPrivate := utils.IsPrivateChannel(channelName)
|
|
||||||
|
|
||||||
if isPresence || isPrivate {
|
|
||||||
toSign := []string{connection.SocketID, channelName}
|
|
||||||
|
|
||||||
if isPresence || len(subscribeEvent.Data.ChannelData) > 0 {
|
|
||||||
toSign = append(toSign, subscribeEvent.Data.ChannelData)
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedAuthKey := fmt.Sprintf("%s:%s", app.Key, utils.HashMAC([]byte(strings.Join(toSign, ":")), []byte(app.Secret)))
|
|
||||||
if subscribeEvent.Data.Auth != expectedAuthKey {
|
|
||||||
emitWSError(newGenericError(fmt.Sprintf("Auth value for subscription to %s is invalid", channelName)), conn)
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
channel := app.FindOrCreateChannelByChannelID(channelName)
|
func onUnsubscribe(conn *websocket.Conn, sessionID string, app *app, message []byte) {
|
||||||
log.Info(subscribeEvent.Data.ChannelData)
|
|
||||||
|
|
||||||
if err := app.Subscribe(channel, connection, subscribeEvent.Data.ChannelData); err != nil {
|
|
||||||
emitWSError(newGenericReconnectImmediatelyError(), conn)
|
|
||||||
}
|
|
||||||
case "pusher:unsubscribe":
|
|
||||||
unsubscribeEvent := unsubscribeEvent{}
|
unsubscribeEvent := unsubscribeEvent{}
|
||||||
|
|
||||||
if err := json.Unmarshal(message, &unsubscribeEvent); err != nil {
|
if err := json.Unmarshal(message, &unsubscribeEvent); err != nil {
|
||||||
@@ -172,53 +170,83 @@ func onMessage(conn *websocket.Conn, w http.ResponseWriter, r *http.Request, ses
|
|||||||
|
|
||||||
if err := app.Unsubscribe(channel, connection); err != nil {
|
if err := app.Unsubscribe(channel, connection); err != nil {
|
||||||
emitWSError(newGenericReconnectImmediatelyError(), conn)
|
emitWSError(newGenericReconnectImmediatelyError(), conn)
|
||||||
break
|
return
|
||||||
}
|
}
|
||||||
default: // CLient Events ??
|
|
||||||
// see http://pusher.com/docs/client_api_guide/client_events#trigger-events
|
|
||||||
if utils.IsClientEvent(event.Event) {
|
|
||||||
if !app.UserEvents {
|
|
||||||
emitWSError(newGenericError("To send client events, you must enable this feature in the Settings."), conn)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
clientEvent := rawEvent{}
|
func onSubscribe(conn *websocket.Conn, sessionID string, app *app, message []byte) {
|
||||||
|
subscribeEvent := subscribeEvent{}
|
||||||
|
|
||||||
if err := json.Unmarshal(message, &clientEvent); err != nil {
|
if err := json.Unmarshal(message, &subscribeEvent); err != nil {
|
||||||
log.Error(err)
|
|
||||||
emitWSError(newGenericReconnectImmediatelyError(), conn)
|
emitWSError(newGenericReconnectImmediatelyError(), conn)
|
||||||
break
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
channel, err := app.FindChannelByChannelID(clientEvent.Channel)
|
connection, err := app.FindConnection(sessionID)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
emitWSError(newGenericError(fmt.Sprintf("Could not find a channel with the id %s", clientEvent.Channel)), conn)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !channel.IsPresenceOrPrivate() {
|
|
||||||
emitWSError(newGenericError("Client event rejected - only supported on private and presence channels"), conn)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := app.Publish(channel, clientEvent, sessionID); err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
emitWSError(newGenericReconnectImmediatelyError(), conn)
|
emitWSError(newGenericReconnectImmediatelyError(), conn)
|
||||||
break
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
channelName := strings.TrimSpace(subscribeEvent.Data.Channel)
|
||||||
|
|
||||||
|
if !utils.IsChannelNameValid(channelName) {
|
||||||
|
emitWSError(newGenericError("This channel name is not valid"), conn)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isPresence := utils.IsPresenceChannel(channelName)
|
||||||
|
isPrivate := utils.IsPrivateChannel(channelName)
|
||||||
|
|
||||||
|
if isPresence || isPrivate {
|
||||||
|
toSign := []string{connection.SocketID, channelName}
|
||||||
|
|
||||||
|
if isPresence || len(subscribeEvent.Data.ChannelData) > 0 {
|
||||||
|
toSign = append(toSign, subscribeEvent.Data.ChannelData)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !validateAuthKey(subscribeEvent.Data.Auth, toSign, app) {
|
||||||
|
emitWSError(newGenericError(fmt.Sprintf("Auth value for subscription to %s is invalid", channelName)), conn)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // switch
|
channel := app.FindOrCreateChannelByChannelID(channelName)
|
||||||
} // For
|
log.Info(subscribeEvent.Data.ChannelData)
|
||||||
|
|
||||||
|
if err := app.Subscribe(channel, connection, subscribeEvent.Data.ChannelData); err != nil {
|
||||||
|
emitWSError(newGenericReconnectImmediatelyError(), conn)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newWebsocketHandler(DB db) goji.Handler {
|
func validateAuthKey(givenAuthKey string, toSign []string, app *app) bool {
|
||||||
return &websocketHandler{DB}
|
expectedAuthKey := fmt.Sprintf("%s:%s", app.Key, utils.HashMAC([]byte(strings.Join(toSign, ":")), []byte(app.Secret)))
|
||||||
|
return givenAuthKey == expectedAuthKey
|
||||||
}
|
}
|
||||||
|
|
||||||
type websocketHandler struct{ DB db }
|
// Emit an Websocket ErrorEvent
|
||||||
|
func emitWSError(err error, conn *websocket.Conn) {
|
||||||
|
e, ok := err.(websocketError)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
log.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
event := newErrorEvent(e.GetCode(), e.GetMsg())
|
||||||
|
|
||||||
|
if err := conn.WriteJSON(event); err != nil {
|
||||||
|
log.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type websocketHandler struct {
|
||||||
|
DB db
|
||||||
|
}
|
||||||
|
|
||||||
// Websocket GET /app/{key}
|
// Websocket GET /app/{key}
|
||||||
func (h *websocketHandler) ServeHTTPC(ctx context.Context, w http.ResponseWriter, r *http.Request) {
|
func (h *websocketHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
conn, err := upgrader.Upgrade(w, r, nil)
|
conn, err := upgrader.Upgrade(w, r, nil)
|
||||||
defer func() {
|
defer func() {
|
||||||
if conn != nil {
|
if conn != nil {
|
||||||
@@ -231,7 +259,7 @@ func (h *websocketHandler) ServeHTTPC(ctx context.Context, w http.ResponseWriter
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
appKey := pat.Param(ctx, "key")
|
appKey := chi.URLParam(r, "key")
|
||||||
|
|
||||||
app, err := h.DB.GetAppByKey(appKey)
|
app, err := h.DB.GetAppByKey(appKey)
|
||||||
|
|
||||||
@@ -243,20 +271,10 @@ func (h *websocketHandler) ServeHTTPC(ctx context.Context, w http.ResponseWriter
|
|||||||
|
|
||||||
sessionID := utils.GenerateSessionID()
|
sessionID := utils.GenerateSessionID()
|
||||||
|
|
||||||
if err := onOpen(conn, w, r, sessionID, app); err != nil {
|
if err := onOpen(conn, r, sessionID, app); err != nil {
|
||||||
emitWSError(err, conn)
|
emitWSError(err, conn)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
onMessage(conn, w, r, sessionID, app)
|
handleMessages(conn, sessionID, app)
|
||||||
}
|
|
||||||
|
|
||||||
// Emit an Websocket ErrorEvent
|
|
||||||
func emitWSError(err websocketError, conn *websocket.Conn) {
|
|
||||||
|
|
||||||
event := newErrorEvent(err.GetCode(), err.GetMsg())
|
|
||||||
|
|
||||||
if err := conn.WriteJSON(event); err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user