-
Notifications
You must be signed in to change notification settings - Fork 2
/
eventrouter.go
340 lines (306 loc) · 12 KB
/
eventrouter.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
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
// Package eventrouter provides a way to dispatch events from Slack.
package eventrouter
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"github.com/pkg/errors"
"github.com/slack-go/slack/slackevents"
"github.com/genkami/go-slack-event-router/appmention"
"github.com/genkami/go-slack-event-router/appratelimited"
routererrors "github.com/genkami/go-slack-event-router/errors"
"github.com/genkami/go-slack-event-router/internal/routerutils"
"github.com/genkami/go-slack-event-router/message"
"github.com/genkami/go-slack-event-router/reaction"
"github.com/genkami/go-slack-event-router/signature"
"github.com/genkami/go-slack-event-router/urlverification"
)
// Handler is a handler that processes events from Slack.
// Usually you don't need to use this directly. Instead, you might want to use event-specific handler types like `appmention.Handler`.
//
// Handlers may return `routererrors.NotInterested` (or its equivalents in the sense of `errors.Is`). In such case the Router falls back to other handlers.
//
// Handlers also may return `routererrors.HttpError` (or its equivalents in the sense of `errors.Is`). In such case the Router responds with corresponding HTTP status codes.
//
// If any other errors are returned, the Router responds with Internal Server Error.
type Handler interface {
HandleEventsAPIEvent(context.Context, *slackevents.EventsAPIEvent) error
}
type HandlerFunc func(context.Context, *slackevents.EventsAPIEvent) error
func (f HandlerFunc) HandleEventsAPIEvent(ctx context.Context, e *slackevents.EventsAPIEvent) error {
return f(ctx, e)
}
// Option configures the Router.
type Option interface {
apply(*Router)
}
type optionFunc func(*Router)
func (f optionFunc) apply(r *Router) {
f(r)
}
// InsecureSkipVerification skips verifying request signatures.
// This is useful to test your handlers, but do not use this in production environments.
func InsecureSkipVerification() Option {
return optionFunc(func(r *Router) {
r.skipVerification = true
})
}
// WithSigningSecret sets a signing token to verify requests from Slack.
//
// For more details, see https://api.slack.com/authentication/verifying-requests-from-slack.
func WithSigningSecret(token string) Option {
return optionFunc(func(r *Router) {
r.signingSecret = token
})
}
// If VerboseResponse is set, the Router shows error details when it fails to process requests.
func VerboseResponse() Option {
return optionFunc(func(r *Router) {
r.verboseResponse = true
})
}
// Router is an http.Handler that processes events from Slack via Events API.
//
// For more details, see https://api.slack.com/apis/connections/events-api.
type Router struct {
signingSecret string
skipVerification bool
verboseResponse bool
callbackHandlers map[string][]Handler
urlVerificationHandler urlverification.Handler
appRateLimitedHandler appratelimited.Handler
fallbackHandler Handler
httpHandler http.Handler
}
// New creates a new Router.
//
// At least one of WithSigningSecret() or InsecureSkipVerification() must be specified.
func New(options ...Option) (*Router, error) {
r := &Router{
callbackHandlers: make(map[string][]Handler),
urlVerificationHandler: urlverification.DefaultHandler,
appRateLimitedHandler: appratelimited.DefaultHandler,
}
for _, o := range options {
o.apply(r)
}
if r.signingSecret == "" && !r.skipVerification {
return nil, errors.New("WithSigningSecret must be set, or you can ignore this by setting InsecureSkipVerification")
}
if r.signingSecret != "" && r.skipVerification {
return nil, errors.New("both WithSigningSecret and InsecureSkipVerification are given")
}
r.httpHandler = http.HandlerFunc(r.serveHTTP)
if !r.skipVerification {
r.httpHandler = &signature.Middleware{
SigningSecret: r.signingSecret,
VerboseResponse: r.verboseResponse,
Handler: r.httpHandler,
}
}
return r, nil
}
// On registers a handler for a specific event type.
//
// If more than one handlers are registered, the first ones take precedence.
//
// Handlers may return `routererrors.NotInterested` (or its equivalents in the sense of `errors.Is`). In such case the Router falls back to other handlers.
//
// Handlers also may return `routererrors.HttpError` (or its equivalents in the sense of `errors.Is`). In such case the Router responds with corresponding HTTP status codes.
//
// If any other errors are returned, the Router responds with Internal Server Error.
//
// This can be useful if you have a general-purpose event handlers that can process arbitrary types of events,
// but, in the most cases it would be better option to use event-specfic `OnEVENT_NAME` methods instead.
func (r *Router) On(eventType string, h Handler) {
handlers, ok := r.callbackHandlers[eventType]
if !ok {
handlers = make([]Handler, 0)
}
handlers = append(handlers, h)
r.callbackHandlers[eventType] = handlers
}
// OnMessage registers a handler that processes `message` events.
//
// If more than one handlers are registered, the first ones take precedence.
//
// Predicates are used to distinguish whether a coming event should be processed by the given handler or not.
// The handler `h` will be called only when all of given Predicates are true.
func (r *Router) OnMessage(h message.Handler, preds ...message.Predicate) {
h = message.Build(h, preds...)
r.On(slackevents.Message, HandlerFunc(func(ctx context.Context, e *slackevents.EventsAPIEvent) error {
inner, ok := e.InnerEvent.Data.(*slackevents.MessageEvent)
if !ok {
return routererrors.HttpError(http.StatusBadRequest)
}
return h.HandleMessageEvent(ctx, inner)
}))
}
// OnAppMention registers a handler that processes `app_mention` events.
//
// If more than one handlers are registered, the first ones take precedence.
//
// Predicates are used to distinguish whether a coming event should be processed by the given handler or not.
// The handler `h` will be called only when all of given Predicates are true.
func (r *Router) OnAppMention(h appmention.Handler, preds ...appmention.Predicate) {
h = appmention.Build(h, preds...)
r.On(slackevents.AppMention, HandlerFunc(func(ctx context.Context, e *slackevents.EventsAPIEvent) error {
inner, ok := e.InnerEvent.Data.(*slackevents.AppMentionEvent)
if !ok {
return routererrors.HttpError(http.StatusBadRequest)
}
return h.HandleAppMentionEvent(ctx, inner)
}))
}
// OnReactionAdded registers a handler that processes `reaction_added` events.
//
// If more than one handlers are registered, the first ones take precedence.
//
// Predicates are used to distinguish whether a coming event should be processed by the given handler or not.
// The handler `h` will be called only when all of given Predicates are true.
func (r *Router) OnReactionAdded(h reaction.AddedHandler, preds ...reaction.Predicate) {
h = reaction.BuildAdded(h, preds...)
r.On(slackevents.ReactionAdded, HandlerFunc(func(ctx context.Context, e *slackevents.EventsAPIEvent) error {
inner, ok := e.InnerEvent.Data.(*slackevents.ReactionAddedEvent)
if !ok {
return routererrors.HttpError(http.StatusBadRequest)
}
return h.HandleReactionAddedEvent(ctx, inner)
}))
}
// OnReactionRemoved registers a handler that processes `reaction_removed` events.
//
// If more than one handlers are registered, the first ones take precedence.
//
// Predicates are used to distinguish whether a coming event should be processed by the given handler or not.
// The handler `h` will be called only when all of given Predicates are true.
func (r *Router) OnReactionRemoved(h reaction.RemovedHandler, preds ...reaction.Predicate) {
h = reaction.BuildRemoved(h, preds...)
r.On(slackevents.ReactionRemoved, HandlerFunc(func(ctx context.Context, e *slackevents.EventsAPIEvent) error {
inner, ok := e.InnerEvent.Data.(*slackevents.ReactionRemovedEvent)
if !ok {
return routererrors.HttpError(http.StatusBadRequest)
}
return h.HandleReactionRemovedEvent(ctx, inner)
}))
}
// SetURLVerificationHandler sets a handler to process `url_verification` events.
//
// If more than one handlers are registered, the last one will be used.
//
// If no handler is set explicitly, the Rotuer uses the default handler.
//
// For more details see https://api.slack.com/events/url_verification.
func (r *Router) SetURLVerificationHandler(h urlverification.Handler) {
r.urlVerificationHandler = h
}
// SetAppRateLimitedHandler sets a handler to process `app_rate_limited` events.
//
// If more than one handlers are registered, the last one will be used.
//
// If no handler is set explicitly, the Rotuer uses the default handler that simply ignores events of this type.
//
// For more details see https://api.slack.com/docs/rate-limits#rate-limits__events-api.
func (r *Router) SetAppRateLimitedHandler(h appratelimited.Handler) {
r.appRateLimitedHandler = h
}
// SetFallback sets a fallback handler that is called when none of the registered handlers matches to a coming event.
//
// If more than one handlers are registered, the last one will be used.
func (r *Router) SetFallback(h Handler) {
r.fallbackHandler = h
}
func (router *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
router.httpHandler.ServeHTTP(w, req)
}
func (router *Router) serveHTTP(w http.ResponseWriter, req *http.Request) {
body, err := ioutil.ReadAll(req.Body)
if err != nil {
router.respondWithError(w, err)
return
}
eventsAPIEvent, err := slackevents.ParseEvent(json.RawMessage(body), slackevents.OptionNoVerifyToken())
if err != nil {
router.respondWithError(
w,
errors.WithMessage(routererrors.HttpError(http.StatusBadRequest), err.Error()))
return
}
ctx := req.Context()
switch eventsAPIEvent.Type {
case slackevents.URLVerification:
router.handleURLVerification(ctx, w, &eventsAPIEvent)
case slackevents.CallbackEvent:
router.handleCallbackEvent(ctx, w, &eventsAPIEvent)
case slackevents.AppRateLimited:
// Surprisingly, ParseEvent can't deal with EventsAPIAppRateLimitedEvent correctly.
// So we should re-parse the entire body for now.
appRateLimited := slackevents.EventsAPIAppRateLimited{}
err := json.Unmarshal(body, &appRateLimited)
if err != nil {
router.respondWithError(
w,
errors.WithMessage(err, "failed to parse app_rate_limited event"))
}
router.handleAppRateLimited(ctx, w, &appRateLimited)
default:
router.respondWithError(
w,
errors.WithMessagef(routererrors.HttpError(http.StatusBadRequest),
"unknown event type: %s", eventsAPIEvent.Type))
}
}
func (r *Router) handleURLVerification(ctx context.Context, w http.ResponseWriter, e *slackevents.EventsAPIEvent) {
ev, ok := e.Data.(*slackevents.EventsAPIURLVerificationEvent)
if !ok {
r.respondWithError(w, fmt.Errorf("expected EventsAPIURLVerificationEvent but got %T", e.Data))
return
}
resp, err := r.urlVerificationHandler.HandleURLVerification(ctx, ev)
if err != nil {
r.respondWithError(w, err)
return
}
w.Header().Add("Content-Type", "application/json")
enc := json.NewEncoder(w)
_ = enc.Encode(resp)
}
func (r *Router) handleCallbackEvent(ctx context.Context, w http.ResponseWriter, e *slackevents.EventsAPIEvent) {
var err error = routererrors.NotInterested
handlers, ok := r.callbackHandlers[e.InnerEvent.Type]
if ok {
for _, h := range handlers {
err = h.HandleEventsAPIEvent(ctx, e)
if !errors.Is(err, routererrors.NotInterested) {
break
}
}
}
if errors.Is(err, routererrors.NotInterested) {
err = r.handleFallback(ctx, e)
}
if err != nil && !errors.Is(err, routererrors.NotInterested) {
r.respondWithError(w, err)
return
}
w.WriteHeader(http.StatusOK)
}
func (r *Router) handleAppRateLimited(ctx context.Context, w http.ResponseWriter, e *slackevents.EventsAPIAppRateLimited) {
err := r.appRateLimitedHandler.HandleAppRateLimited(ctx, e)
if err != nil {
r.respondWithError(w, err)
return
}
_, _ = w.Write([]byte("OK"))
}
func (r *Router) handleFallback(ctx context.Context, e *slackevents.EventsAPIEvent) error {
if r.fallbackHandler == nil {
return routererrors.NotInterested
}
return r.fallbackHandler.HandleEventsAPIEvent(ctx, e)
}
func (r *Router) respondWithError(w http.ResponseWriter, err error) {
routerutils.RespondWithError(w, err, r.verboseResponse)
}