diff --git a/glide.lock b/glide.lock index a92875b..aa09448 100644 --- a/glide.lock +++ b/glide.lock @@ -1,20 +1,12 @@ -hash: 0a451841a1f9493d419d658fab8da6863ef5df0645ddea506a225865078c9dcb -updated: 2016-08-10T22:23:10.989237578-03:00 +hash: ee2de935e70f2a39cbfd2dd16add0736c698b32f5c02409d0c25844d883c91a1 +updated: 2016-08-22T22:55:07.279113421-03:00 imports: - name: github.com/golang/glog version: 23def4e6c14b4da8ac2ed8007337bc5eb5007998 - name: github.com/gorilla/websocket version: a69d25be2fe2923a97c2af6849b2f52426f68fc0 -- name: github.com/pusher/pusher-http-go - version: 2bba5f217f6f0f4f0c0a9bb11b945b206b32bec5 -- name: goji.io - version: e355964ac565b94cf0fc7f218346626529125086 +- name: github.com/pressly/chi + version: 12aad88c7d86de2affe686f855b6ed94a07cba9c subpackages: - - pat - - pattern - - internal -- name: golang.org/x/net - version: 075e191f18186a8ff2becaf64478e30f4545cdad - subpackages: - - context + - middleware testImports: [] diff --git a/glide.yaml b/glide.yaml index c34e069..92332d0 100644 --- a/glide.yaml +++ b/glide.yaml @@ -2,10 +2,6 @@ package: github.com/dimiro1/ipe import: - package: github.com/golang/glog - package: github.com/gorilla/websocket -- package: github.com/pusher/pusher-http-go -- package: goji.io - subpackages: - - pat -- package: golang.org/x/net - subpackages: - - context +- package: github.com/pressly/chi +excludeDirs: +- functional \ No newline at end of file diff --git a/ipe/handlers.go b/ipe/handlers.go index 717c4ba..93a74ff 100644 --- a/ipe/handlers.go +++ b/ipe/handlers.go @@ -12,12 +12,8 @@ import ( "sort" "strings" - goji "goji.io" - - "goji.io/pat" - log "github.com/golang/glog" - "golang.org/x/net/context" + "github.com/pressly/chi" "github.com/dimiro1/ipe/utils" ) @@ -51,79 +47,64 @@ func prepareQueryString(params url.Values) string { // * 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. // 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 { - return func(ctx context.Context, w http.ResponseWriter, r *http.Request) { - appID := pat.Param(ctx, "app_id") +func authenticationHandler(DB db) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + appID := chi.URLParam(r, "app_id") - app, err := DB.GetAppByAppID(appID) + app, err := DB.GetAppByAppID(appID) - if err != nil { - log.Error(err) - http.Error(w, "Not authorized", http.StatusUnauthorized) - return + if err != nil { + log.Error(err) + http.Error(w, "Not authorized", http.StatusUnauthorized) + return + } + + query := r.URL.Query() + + signature := query.Get("auth_signature") + query.Del("auth_signature") + + queryString := prepareQueryString(query) + + toSign := strings.ToUpper(r.Method) + "\n" + r.URL.Path + "\n" + queryString + + if utils.HashMAC([]byte(toSign), []byte(app.Secret)) == signature { + next.ServeHTTP(w, r) + } else { + log.Error("Not authorized") + http.Error(w, "Not authorized", http.StatusUnauthorized) + } } - query := r.URL.Query() - - signature := query.Get("auth_signature") - query.Del("auth_signature") - - queryString := prepareQueryString(query) - - toSign := strings.ToUpper(r.Method) + "\n" + r.URL.Path + "\n" + queryString - - if utils.HashMAC([]byte(toSign), []byte(app.Secret)) == signature { - next.ServeHTTPC(ctx, w, r) - } else { - log.Error("Not authorized") - http.Error(w, "Not authorized", http.StatusUnauthorized) - } + return http.HandlerFunc(fn) } } // Check if the application is disabled -func restCheckAppDisabledHandler(DB db, next goji.Handler) goji.HandlerFunc { - return func(ctx context.Context, w http.ResponseWriter, r *http.Request) { - appID := pat.Param(ctx, "app_id") +func checkAppDisabled(DB db) func(http.Handler) http.Handler { + return func(next http.Handler) http.Handler { + fn := func(w http.ResponseWriter, r *http.Request) { + appID := chi.URLParam(r, "app_id") - currentApp, err := DB.GetAppByAppID(appID) + currentApp, err := DB.GetAppByAppID(appID) - if err != nil { - http.Error(w, fmt.Sprintf("Could not found an app with app_id: %s", appID), http.StatusForbidden) - return - } - - if currentApp.ApplicationDisabled { - http.Error(w, "Application disabled", http.StatusForbidden) - return - } - - next.ServeHTTPC(ctx, w, r) - } -} - -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) + if err != nil { + http.Error(w, fmt.Sprintf("Could not found an app with app_id: %s", appID), http.StatusForbidden) return } - }() - next.ServeHTTPC(ctx, w, r) + + if currentApp.ApplicationDisabled { + http.Error(w, "Application disabled", http.StatusForbidden) + return + } + + next.ServeHTTP(w, r) + } + return http.HandlerFunc(fn) } } -// 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 } // ServeHTTPC An event consists of a name and data (typically JSON) which may be sent to all subscribers to a particular channel or channels. @@ -141,8 +122,8 @@ type postEventsHandler struct{ DB db } // Response is an empty JSON hash. // // POST /apps/{app_id}/events -func (h *postEventsHandler) ServeHTTPC(ctx context.Context, w http.ResponseWriter, r *http.Request) { - appID := pat.Param(ctx, "app_id") +func (h *postEventsHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + appID := chi.URLParam(r, "app_id") app, err := h.DB.GetAppByAppID(appID) @@ -187,10 +168,6 @@ func (h *postEventsHandler) ServeHTTPC(ctx context.Context, w http.ResponseWrite w.Write([]byte("{}")) } -func newGetChannelsHandler(DB db) goji.HandlerFunc { - return commonHandlers(DB, &getChannelsHandler{DB}) -} - type getChannelsHandler struct{ DB db } // 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 -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() - appID := pat.Param(ctx, "app_id") + appID := chi.URLParam(r, "app_id") filter := query.Get("filter_by_prefix") 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 } // Fetch info for one channel @@ -298,19 +271,19 @@ type getChannelHandler struct{ DB db } // } // // 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") query := r.URL.Query() - appID := pat.Param(ctx, "app_id") + appID := chi.URLParam(r, "app_id") app, err := h.DB.GetAppByAppID(appID) if err != nil { 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 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 } // Allowed only for presence-channels @@ -393,9 +362,9 @@ type getChannelUsersHandler struct{ DB db } // } // // GET /apps/{app_id}/channels/{channel_name}/users -func (h *getChannelUsersHandler) ServeHTTPC(ctx context.Context, w http.ResponseWriter, r *http.Request) { - appID := pat.Param(ctx, "app_id") - channelName := pat.Param(ctx, "channel_name") +func (h *getChannelUsersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + appID := chi.URLParam(r, "app_id") + channelName := chi.URLParam(r, "channel_name") isPresence := utils.IsPresenceChannel(channelName) diff --git a/ipe/handlers_test.go b/ipe/handlers_test.go index cb7ec23..971c941 100644 --- a/ipe/handlers_test.go +++ b/ipe/handlers_test.go @@ -1,15 +1,14 @@ package ipe import ( + "context" "encoding/json" "fmt" "net/http" "net/http/httptest" "testing" - "goji.io/pattern" - - "golang.org/x/net/context" + "github.com/pressly/chi" ) var ( @@ -41,14 +40,15 @@ func init() { func Test_getChannels_all(t *testing.T) { appID := testApp.AppID - ctx := context.Background() - ctx = context.WithValue(ctx, pattern.Variable("app_id"), appID) + rctx := chi.NewRouteContext() + rctx.URLParams.Add("app_id", appID) r, _ := http.NewRequest("GET", fmt.Sprintf("/apps/%s/channels", appID), nil) + r = r.WithContext(context.WithValue(context.Background(), chi.RouteCtxKey, rctx)) w := httptest.NewRecorder() handler := &getChannelsHandler{database} - handler.ServeHTTPC(ctx, w, r) + handler.ServeHTTP(w, r) if 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) { appID := testApp.AppID - ctx := context.Background() - ctx = context.WithValue(ctx, pattern.Variable("app_id"), appID) + rctx := chi.NewRouteContext() + rctx.URLParams.Add("app_id", appID) 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() handler := &getChannelsHandler{database} - handler.ServeHTTPC(ctx, w, r) + handler.ServeHTTP(w, r) if 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) { appID := testApp.AppID - ctx := context.Background() - ctx = context.WithValue(ctx, pattern.Variable("app_id"), appID) + rctx := chi.NewRouteContext() + 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 = r.WithContext(context.WithValue(context.Background(), chi.RouteCtxKey, rctx)) w := httptest.NewRecorder() handler := &getChannelsHandler{database} - handler.ServeHTTPC(ctx, w, r) + handler.ServeHTTP(w, r) if 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) { appID := testApp.AppID - ctx := context.Background() - ctx = context.WithValue(ctx, pattern.Variable("app_id"), appID) + rctx := chi.NewRouteContext() + 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 = r.WithContext(context.WithValue(context.Background(), chi.RouteCtxKey, rctx)) w := httptest.NewRecorder() handler := &getChannelsHandler{database} - handler.ServeHTTPC(ctx, w, r) + handler.ServeHTTP(w, r) if 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) { appID := testApp.AppID - ctx := context.Background() - ctx = context.WithValue(ctx, pattern.Variable("app_id"), appID) + rctx := chi.NewRouteContext() + rctx.URLParams.Add("app_id", appID) 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() handler := &getChannelsHandler{database} - handler.ServeHTTPC(ctx, w, r) + handler.ServeHTTP(w, r) if 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) { appID := testApp.AppID - ctx := context.Background() - ctx = context.WithValue(ctx, pattern.Variable("app_id"), appID) + rctx := chi.NewRouteContext() + rctx.URLParams.Add("app_id", appID) 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() handler := &getChannelsHandler{database} - handler.ServeHTTPC(ctx, w, r) + handler.ServeHTTP(w, r) if w.Code != http.StatusOK { t.Errorf("w.Code == %d, wants %d", w.Code, http.StatusOK) diff --git a/ipe/ipe.go b/ipe/ipe.go index 0ef9b7b..382aadf 100644 --- a/ipe/ipe.go +++ b/ipe/ipe.go @@ -11,11 +11,9 @@ import ( "os" "time" - "goji.io/pat" - - goji "goji.io" - log "github.com/golang/glog" + "github.com/pressly/chi" + "github.com/pressly/chi/middleware" ) // Start Parse the configuration file and starts the ipe server @@ -47,20 +45,27 @@ func Start(filename string) { db.AddApp(newAppFromConfig(a)) } - router := goji.NewMux() - router.HandleFuncC(pat.Post("/apps/:app_id/events"), newPostEventsHandler(db)) - router.HandleFuncC(pat.Get("/apps/:app_id/channels"), newGetChannelsHandler(db)) - router.HandleFuncC(pat.Get("/apps/:app_id/channels/:channel_name"), newGetChannelHandler(db)) - router.HandleFuncC(pat.Get("/apps/:app_id/channels/:channel_name/users"), newGetChannelUsersHandler(db)) - router.HandleC(pat.Get("/app/:key"), newWebsocketHandler(db)) + r := chi.NewRouter() + r.Use(middleware.Recoverer) + + r.Handle("/app/:key", &websocketHandler{db}) + r.Group(func(r chi.Router) { + r.Use(checkAppDisabled(db)) + r.Use(authenticationHandler(db)) + + r.Handle("/apps/:app_id/events", &postEventsHandler{db}) + r.Handle("/apps/:app_id/channels", &getChannelsHandler{db}) + r.Handle("/apps/:app_id/channels/:channel_name", &getChannelHandler{db}) + r.Handle("/apps/:app_id/channels/:channel_name/users", &getChannelUsersHandler{db}) + }) if conf.SSL { go func() { 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.Fatal(http.ListenAndServe(conf.Host, router)) + log.Fatal(http.ListenAndServe(conf.Host, r)) } diff --git a/ipe/websocket.go b/ipe/websocket.go index 8116f4c..c821b73 100644 --- a/ipe/websocket.go +++ b/ipe/websocket.go @@ -12,13 +12,9 @@ import ( "strconv" "strings" - goji "goji.io" - - "goji.io/pat" - log "github.com/golang/glog" "github.com/gorilla/websocket" - "golang.org/x/net/context" + "github.com/pressly/chi" "github.com/dimiro1/ipe/utils" ) @@ -245,16 +241,12 @@ func emitWSError(err error, conn *websocket.Conn) { } } -func newWebsocketHandler(DB db) goji.Handler { - return &websocketHandler{DB} -} - type websocketHandler struct { DB db } // 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) defer func() { if conn != nil { @@ -267,7 +259,7 @@ func (h *websocketHandler) ServeHTTPC(ctx context.Context, w http.ResponseWriter return } - appKey := pat.Param(ctx, "key") + appKey := chi.URLParam(r, "key") app, err := h.DB.GetAppByKey(appKey)