forked from centrifugal/centrifugo
/
structure.go
161 lines (138 loc) · 4.93 KB
/
structure.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
package libcentrifugo
import (
"errors"
"regexp"
"sync"
)
// ChannelOptions represent channel specific configuration for namespace or project in a whole
type ChannelOptions struct {
// Watch determines if message published into channel will be sent into admin channel
Watch bool `json:"watch"`
// Publish determines if client can publish messages into channel directly
Publish bool `json:"publish"`
// Anonymous determines is anonymous access (with empty user ID) allowed or not
Anonymous bool `json:"anonymous"`
// Presence turns on(off) presence information for channels
Presence bool `json:"presence"`
// HistorySize determines max amount of history messages for channel, 0 means no history for channel
HistorySize int64 `mapstructure:"history_size" json:"history_size"`
// HistoryLifetime determines time in seconds until expiration for history messages
HistoryLifetime int64 `mapstructure:"history_lifetime" json:"history_lifetime"`
// JoinLeave turns on(off) join/leave messages for channels
JoinLeave bool `mapstructure:"join_leave" json:"join_leave"`
}
// project represents single project
// note that although Centrifugo can work with several projects
// but it's recommended to have separate Centrifugo installation
// for every project (maybe except copy of your project for development)
type project struct {
// Name is unique project name, used as project key for client connections and API requests
Name string `json:"name"`
// Secret is a secret key for project, used to sign API requests and client connection tokens
Secret string `json:"secret"`
// ConnectionLifetime determines time until connection expire, 0 means no connection expire at all
ConnectionLifetime int64 `mapstructure:"connection_lifetime" json:"connection_lifetime"`
// Namespaces - list of namespaces for project for custom channel options
Namespaces []namespace `json:"namespaces"`
// ChannelOptions - default project channel options
ChannelOptions `mapstructure:",squash"`
}
// namespace allows to create channels with different channel options within the project
type namespace struct {
// Name is a unique namespace name in project
Name string `json:"name"`
// ChannelOptions for namespace determine channel options for channels belonging to this namespace
ChannelOptions `mapstructure:",squash"`
}
// structure contains some helper structures and methods to work with projects in namespaces
// in a fast and comfortable way
type structure struct {
sync.Mutex
ProjectList []project
ProjectMap map[string]project
NamespaceMap map[string]map[string]namespace
}
// initialize initializes structure fields based on project list
func (s *structure) initialize() {
s.Lock()
defer s.Unlock()
projectMap := map[string]project{}
namespaceMap := map[string]map[string]namespace{}
for _, p := range s.ProjectList {
projectMap[p.Name] = p
namespaceMap[p.Name] = map[string]namespace{}
for _, n := range p.Namespaces {
namespaceMap[p.Name][n.Name] = n
}
}
s.ProjectMap = projectMap
s.NamespaceMap = namespaceMap
}
func stringInSlice(a string, list []string) bool {
for _, b := range list {
if b == a {
return true
}
}
return false
}
// validate validates structure and return error if problems found
func (s *structure) validate() error {
s.Lock()
defer s.Unlock()
var projectNames []string
errPrefix := "config error: "
pattern := "^[-a-zA-Z0-9_]{2,}$"
for _, p := range s.ProjectList {
match, _ := regexp.MatchString(pattern, p.Name)
if !match {
return errors.New(errPrefix + "wrong project name – " + p.Name)
}
if p.Secret == "" {
return errors.New(errPrefix + "secret required for project – " + p.Name)
}
if stringInSlice(p.Name, projectNames) {
return errors.New(errPrefix + "project name must be unique – " + p.Name)
}
projectNames = append(projectNames, p.Name)
if p.Namespaces == nil {
continue
}
var namespaceNames []string
for _, n := range p.Namespaces {
match, _ := regexp.MatchString(pattern, n.Name)
if !match {
return errors.New(errPrefix + "wrong namespace name – " + n.Name)
}
if stringInSlice(n.Name, namespaceNames) {
return errors.New(errPrefix + "namespace name must be unique for project – " + n.Name)
}
namespaceNames = append(namespaceNames, n.Name)
}
}
return nil
}
// getProjectByKey searches for a project with specified key in structure
func (s *structure) getProjectByKey(projectKey string) (*project, bool) {
project, ok := s.ProjectMap[projectKey]
if !ok {
return nil, false
}
return &project, true
}
// getChannelOptions searches for channel options for specified project and namespace
func (s *structure) getChannelOptions(projectKey, namespaceName string) *ChannelOptions {
project, exists := s.getProjectByKey(projectKey)
if !exists {
return nil
}
if namespaceName == "" {
return &project.ChannelOptions
} else {
namespace, exists := s.NamespaceMap[projectKey][namespaceName]
if !exists {
return nil
}
return &namespace.ChannelOptions
}
}