diff --git a/CHANGELOG.md b/CHANGELOG.md index 6863077..e9cbc42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] -Nothing so far +- Add new `Config.EventsAPIConfig.Middlewar` configuration and corresponding `WithMiddleware(…)` option. + This fixes #19 by allowing the user to inject a custom HTTP middleware for the + `EventsAPIServer`, e.g. in order to enable custom (health) endpoints. ## [v2.1.0] - 2020-07-25 - Add new `EventsAPIAdapter` function to support integrating with Slack via the diff --git a/events_api.go b/events_api.go index 5a38d17..94a1a81 100644 --- a/events_api.go +++ b/events_api.go @@ -68,9 +68,14 @@ func NewEventsAPIServer(ctx context.Context, listenAddr string, conf Config) (*E }, )) + var handler http.Handler = http.HandlerFunc(a.httpHandler) + if conf.EventsAPI.Middleware != nil { + handler = conf.EventsAPI.Middleware(handler) + } + a.http = &http.Server{ Addr: listenAddr, - Handler: http.HandlerFunc(a.httpHandler), + Handler: handler, ErrorLog: zap.NewStdLog(conf.Logger), TLSConfig: conf.EventsAPI.TLSConf, ReadTimeout: conf.EventsAPI.ReadTimeout, diff --git a/events_api_test.go b/events_api_test.go index 4dcdb72..b73cc25 100644 --- a/events_api_test.go +++ b/events_api_test.go @@ -4,9 +4,11 @@ import ( "bytes" "context" "encoding/json" + "fmt" "io" "net/http" "net/http/httptest" + "strings" "testing" "github.com/go-joe/joe" @@ -16,14 +18,21 @@ import ( "github.com/slack-go/slack/slackevents" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "go.uber.org/zap" "go.uber.org/zap/zaptest" + "go.uber.org/zap/zaptest/observer" ) -func newTestEventsAPIServer(t *testing.T) (_ *EventsAPIServer, finish func() (events []interface{})) { +func newTestEventsAPIServer(t *testing.T, optionalConf ...Config) (_ *EventsAPIServer, finish func() (events []interface{})) { + var conf Config + if len(optionalConf) > 0 { + conf = optionalConf[0] + } + ctx := context.Background() - conf := Config{ - Logger: zaptest.NewLogger(t), - Debug: true, + conf.Debug = true + if conf.Logger == nil { + conf.Logger = zaptest.NewLogger(t) } slackAPI := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -153,6 +162,35 @@ func TestEventsAPIServer_HandleReactionAddedEvent(t *testing.T) { assert.Equal(t, "+1", actual.Reaction.Shortcode) } +func TestEventsAPIServer_HTTPHandlerMiddleware(t *testing.T) { + middleware := func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + if req.URL.Path == "/health" { + _, _ = fmt.Fprint(w, "Everything is fine!") + return + } + + next.ServeHTTP(w, req) + }) + } + + obs, errorLogs := observer.New(zap.ErrorLevel) + s, _ := newTestEventsAPIServer(t, Config{ + Logger: zap.New(obs), + EventsAPI: EventsAPIConfig{ + Middleware: middleware, + }, + }) + + req := httptest.NewRequest("GET", "/health", strings.NewReader("Are you okay?")) + resp := httptest.NewRecorder() + + s.http.Handler.ServeHTTP(resp, req) + + assert.Equal(t, "Everything is fine!", resp.Body.String()) + assert.Empty(t, errorLogs.All()) +} + func toJSON(req interface{}) io.Reader { b := new(bytes.Buffer) err := json.NewEncoder(b).Encode(req) diff --git a/options.go b/options.go index f32d8f3..72b8b56 100644 --- a/options.go +++ b/options.go @@ -3,6 +3,7 @@ package slack import ( "crypto/tls" "errors" + "net/http" "time" "github.com/slack-go/slack" @@ -38,6 +39,7 @@ type Config struct { // EventsAPIConfig contains the configuration of an EventsAPIServer. type EventsAPIConfig struct { + Middleware func(next http.Handler) http.Handler ShutdownTimeout time.Duration ReadTimeout time.Duration WriteTimeout time.Duration @@ -167,3 +169,12 @@ func WithWriteTimeout(d time.Duration) Option { return nil } } + +// WithMiddleware is an option for the EventsAPIServer that allows the user to +// inject an HTTP middleware to the HTTP server. +func WithMiddleware(mw func(next http.Handler) http.Handler) Option { + return func(conf *Config) error { + conf.EventsAPI.Middleware = mw + return nil + } +} diff --git a/options_test.go b/options_test.go index 8ea0055..a83ff77 100644 --- a/options_test.go +++ b/options_test.go @@ -1,6 +1,7 @@ package slack import ( + "net/http" "testing" "github.com/go-joe/joe" @@ -85,3 +86,21 @@ func TestWithListenPassive(t *testing.T) { require.NoError(t, err) assert.Equal(t, true, conf.ListenPassive) } + +func TestWithMiddleware(t *testing.T) { + var ok bool + middleware := func(next http.Handler) http.Handler { + ok = true // proof this function executed + return next + } + + conf, err := newConf("my-secret-token", joeConf(t), []Option{ + WithMiddleware(middleware), + }) + + require.NoError(t, err) + require.NotNil(t, conf.EventsAPI.Middleware) + + conf.EventsAPI.Middleware(nil) + assert.True(t, ok) +}