forked from vulcand/vulcand
-
Notifications
You must be signed in to change notification settings - Fork 1
/
app.go
211 lines (175 loc) · 6.12 KB
/
app.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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
package scroll
import (
"fmt"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/mailgun/vulcand/Godeps/_workspace/src/github.com/gorilla/mux"
"github.com/mailgun/vulcand/Godeps/_workspace/src/github.com/mailgun/log"
"github.com/mailgun/vulcand/Godeps/_workspace/src/github.com/mailgun/manners"
"github.com/mailgun/vulcand/Godeps/_workspace/src/github.com/mailgun/metrics"
"github.com/mailgun/vulcand/Godeps/_workspace/src/github.com/mailgun/scroll/registry"
"github.com/mailgun/vulcand/Godeps/_workspace/src/github.com/mailgun/scroll/vulcan/middleware"
)
const (
// Suggested result set limit for APIs that may return many entries (e.g. paging).
DefaultLimit = 100
// Suggested max allowed result set limit for APIs that may return many entries (e.g. paging).
MaxLimit = 10000
// Suggested max allowed amount of entries that batch APIs can accept (e.g. batch uploads).
MaxBatchSize = 1000
// Interval between Vulcand heartbeats (if the app if configured to register in it).
defaultRegisterInterval = 2 * time.Second
)
// Represents an app.
type App struct {
Config AppConfig
router *mux.Router
stats *appStats
heartbeater *registry.Heartbeater
}
// Represents a configuration object an app is created with.
type AppConfig struct {
// name of the app being created
Name string
// IP/port the app will bind to
ListenIP string
ListenPort int
// optional router to use
Router *mux.Router
// hostnames of the public and protected API entrypoints used for vulcan registration
PublicAPIHost string
ProtectedAPIHost string
ProtectedAPIURL string
// how to register the app's endpoint and handlers in vulcan
Registry registry.Registry
Interval time.Duration
// metrics service used for emitting the app's real-time metrics
Client metrics.Client
}
// Create a new app.
func NewApp() *App {
return NewAppWithConfig(AppConfig{})
}
// Create a new app with the provided configuration.
func NewAppWithConfig(config AppConfig) *App {
router := config.Router
if router == nil {
router = mux.NewRouter()
}
interval := config.Interval
if interval == 0 {
interval = defaultRegisterInterval
}
registration := ®istry.AppRegistration{Name: config.Name, Host: config.ListenIP, Port: config.ListenPort}
heartbeater := registry.NewHeartbeater(registration, config.Registry, interval)
return &App{
Config: config,
router: router,
heartbeater: heartbeater,
stats: newAppStats(config.Client),
}
}
// Register a handler function.
//
// If vulcan registration is enabled in the both app config and handler spec,
// the handler will be registered in the local etcd instance.
func (app *App) AddHandler(spec Spec) error {
var handler http.HandlerFunc
// make a handler depending on the function provided in the spec
if spec.RawHandler != nil {
handler = spec.RawHandler
} else if spec.Handler != nil {
handler = MakeHandler(app, spec.Handler, spec)
} else if spec.HandlerWithBody != nil {
handler = MakeHandlerWithBody(app, spec.HandlerWithBody, spec)
} else {
return fmt.Errorf("the spec does not provide a handler function: %v", spec)
}
for _, path := range spec.Paths {
// register a handler in the router
route := app.router.HandleFunc(path, handler).Methods(spec.Methods...)
if len(spec.Headers) != 0 {
route.Headers(spec.Headers...)
}
app.registerLocation(spec.Methods, path, spec.Scopes, spec.Middlewares)
}
return nil
}
// GetHandler returns HTTP compatible Handler interface.
func (app *App) GetHandler() http.Handler {
return app.router
}
// SetNotFoundHandler sets the handler for the case when URL can not be matched by the router.
func (app *App) SetNotFoundHandler(fn http.HandlerFunc) {
app.router.NotFoundHandler = fn
}
// IsPublicRequest determines whether the provided request came through the public HTTP endpoint.
func (app *App) IsPublicRequest(request *http.Request) bool {
return request.Host == app.Config.PublicAPIHost
}
// Start the app on the configured host/port.
//
// Supports graceful shutdown on 'kill' and 'int' signals.
func (app *App) Run() error {
http.Handle("/", app.router)
// toggle heartbeat on SIGUSR1
go func() {
app.heartbeater.Start()
heartbeatChan := make(chan os.Signal, 1)
signal.Notify(heartbeatChan, syscall.SIGUSR1)
for s := range heartbeatChan {
log.Infof("Received signal: %v, toggling heartbeat", s)
app.heartbeater.Toggle()
}
}()
// listen for a shutdown signal
go func() {
exitChan := make(chan os.Signal, 1)
signal.Notify(exitChan, os.Interrupt, os.Kill)
s := <-exitChan
log.Infof("Got shutdown signal: %v", s)
manners.Close()
}()
addr := fmt.Sprintf("%v:%v", app.Config.ListenIP, app.Config.ListenPort)
return manners.ListenAndServe(addr, nil)
}
// registerLocation is a helper for registering handlers in vulcan.
func (app *App) registerLocation(methods []string, path string, scopes []Scope, middlewares []middleware.Middleware) {
for _, scope := range scopes {
app.registerLocationForScope(methods, path, scope, middlewares)
}
}
// registerLocationForScope registers a location with a specified scope.
func (app *App) registerLocationForScope(methods []string, path string, scope Scope, middlewares []middleware.Middleware) {
host, err := app.apiHostForScope(scope)
if err != nil {
log.Errorf("Failed to register a location: %v", err)
return
}
app.registerLocationForHost(methods, path, host, middlewares)
}
// registerLocationForHost registers a location for a specified hostname.
func (app *App) registerLocationForHost(methods []string, path, host string, middlewares []middleware.Middleware) {
r := ®istry.HandlerRegistration{
Name: app.Config.Name,
Host: host,
Path: path,
Methods: methods,
Middlewares: middlewares,
}
app.Config.Registry.RegisterHandler(r)
log.Infof("Registered: %v", r)
}
// apiHostForScope is a helper that returns an appropriate API hostname for a provided scope.
func (app *App) apiHostForScope(scope Scope) (string, error) {
if scope == ScopePublic {
return app.Config.PublicAPIHost, nil
} else if scope == ScopeProtected {
return app.Config.ProtectedAPIHost, nil
} else {
return "", fmt.Errorf("unknown scope value: %v", scope)
}
}