Replaces gorilla mux to goji and decied to remove vendor dependencies from git (#28)

* Replaces gorilla mux to goji

Removed the applicationContext stuff. Now every handler is a struct, and each one hold its dependencies.

* handler not handle

* Ignoring vendor dir

* Hide handlers complexity instantiation.

* Websocket handler does not need common handlers

* Use maps instead of for loops to search in memdb for apps (#29)

* Use maps instead of for loops to search in memdb for apps; use mutexes more efficiently by immediately unlocking when lock is no longer needed, not just at the end of the function via defer

* Fixed assignment to entry in nil map

* Minor impovements in source code

* Replaces gorilla mux to goji

Removed the applicationContext stuff. Now every handler is a struct, and each one hold its dependencies.

* handler not handle

* Ignoring vendor dir

* Hide handlers complexity instantiation.

* Websocket handler does not need common handlers

* renamed IdMutex to IDMutex to follow go convention
This commit is contained in:
Claudemiro
2016-08-11 21:06:05 -03:00
committed by GitHub
parent 7a4568e92d
commit 6225d8006f
76 changed files with 154 additions and 11925 deletions
-29
View File
@@ -1,29 +0,0 @@
// Copyright 2016 Claudemiro Alves Feitosa Neto. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package ipe
import "net/http"
type applicationContext struct {
DB db
}
// url params
type params map[string]string
func (p params) Get(key string) string {
return p[key]
}
// A contextHandler responds to an HTTP request with custom application context.
type contextHandler interface {
ServeWithContext(ctx *applicationContext, p params, w http.ResponseWriter, r *http.Request)
}
type contextHandlerFunc func(ctx *applicationContext, p params, w http.ResponseWriter, r *http.Request)
func (c contextHandlerFunc) ServeWithContext(ctx *applicationContext, p params, w http.ResponseWriter, r *http.Request) {
c(ctx, p, w, r)
}
+5 -5
View File
@@ -20,7 +20,7 @@ type db interface {
// memdb is an in memory implementation of db interface
type memdb struct {
IdMutex sync.Mutex
IDMutex sync.Mutex
KeyMutex sync.Mutex
AppsByAppID map[string]*app
AppsByKey map[string]*app
@@ -34,9 +34,9 @@ func newMemdb() *memdb {
}
func (db *memdb) AddApp(a *app) error {
db.IdMutex.Lock()
db.IDMutex.Lock()
db.AppsByAppID[a.AppID] = a
db.IdMutex.Unlock()
db.IDMutex.Unlock()
db.KeyMutex.Lock()
db.AppsByKey[a.Key] = a
@@ -46,9 +46,9 @@ func (db *memdb) AddApp(a *app) error {
// GetAppByAppID returns an App with by appID
func (db *memdb) GetAppByAppID(appID string) (*app, error) {
db.IdMutex.Lock()
db.IDMutex.Lock()
a, ok := db.AppsByAppID[appID]
db.IdMutex.Unlock()
db.IDMutex.Unlock()
if ok {
return a, nil
}
+71 -29
View File
@@ -12,7 +12,12 @@ import (
"sort"
"strings"
goji "goji.io"
"goji.io/pat"
log "github.com/golang/glog"
"golang.org/x/net/context"
"github.com/dimiro1/ipe/utils"
)
@@ -46,11 +51,11 @@ 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(ctx *applicationContext, h contextHandler) contextHandler {
return contextHandlerFunc(func(ctx *applicationContext, p params, w http.ResponseWriter, r *http.Request) {
appID := p.Get("app_id")
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")
app, err := ctx.DB.GetAppByAppID(appID)
app, err := DB.GetAppByAppID(appID)
if err != nil {
log.Error(err)
@@ -68,20 +73,20 @@ func restAuthenticationHandler(ctx *applicationContext, h contextHandler) contex
toSign := strings.ToUpper(r.Method) + "\n" + r.URL.Path + "\n" + queryString
if utils.HashMAC([]byte(toSign), []byte(app.Secret)) == signature {
h.ServeWithContext(ctx, p, w, r)
next.ServeHTTPC(ctx, w, r)
} else {
log.Error("Not authorized")
http.Error(w, "Not authorized", http.StatusUnauthorized)
}
})
}
}
// Check if the application is disabled
func restCheckAppDisabledHandler(ctx *applicationContext, h contextHandler) contextHandler {
return contextHandlerFunc(func(ctx *applicationContext, p params, w http.ResponseWriter, r *http.Request) {
appID := p.Get("app_id")
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")
currentApp, err := ctx.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)
@@ -93,16 +98,35 @@ func restCheckAppDisabledHandler(ctx *applicationContext, h contextHandler) cont
return
}
h.ServeWithContext(ctx, p, w, r)
})
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)
return
}
}()
next.ServeHTTPC(ctx, w, r)
}
}
// commonHandlers combine restCheckAppDisabledHandler and restAuthenticationHandler handlers
func commonHandlers(ctx *applicationContext, h contextHandlerFunc) contextHandler {
return restCheckAppDisabledHandler(ctx, restAuthenticationHandler(ctx, h))
func commonHandlers(DB db, next goji.Handler) goji.HandlerFunc {
return recoverHandler(restCheckAppDisabledHandler(DB, restAuthenticationHandler(DB, next)))
}
// An event consists of a name and data (typically JSON) which may be sent to all subscribers to a particular channel or channels.
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.
// This is conventionally known as triggering an event.
//
// The body should contain a Hash of parameters encoded as JSON where data parameter itself is JSON encoded.
@@ -117,10 +141,10 @@ func commonHandlers(ctx *applicationContext, h contextHandlerFunc) contextHandle
// Response is an empty JSON hash.
//
// POST /apps/{app_id}/events
func postEvents(ctx *applicationContext, p params, w http.ResponseWriter, r *http.Request) {
appID := p.Get("app_id")
func (h *postEventsHandler) ServeHTTPC(ctx context.Context, w http.ResponseWriter, r *http.Request) {
appID := pat.Param(ctx, "app_id")
app, err := ctx.DB.GetAppByAppID(appID)
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)
@@ -163,6 +187,12 @@ func postEvents(ctx *applicationContext, p params, w http.ResponseWriter, r *htt
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),
// and optionally one or more attributes for each channel.
//
@@ -182,10 +212,10 @@ func postEvents(ctx *applicationContext, p params, w http.ResponseWriter, r *htt
// }
//
// GET /apps/{app_id}/channels
func getChannels(ctx *applicationContext, p params, w http.ResponseWriter, r *http.Request) {
func (h *getChannelsHandler) ServeHTTPC(ctx context.Context, w http.ResponseWriter, r *http.Request) {
query := r.URL.Query()
appID := p.Get("app_id")
appID := pat.Param(ctx, "app_id")
filter := query.Get("filter_by_prefix")
info := query.Get("info")
@@ -206,7 +236,7 @@ func getChannels(ctx *applicationContext, p params, w http.ResponseWriter, r *ht
return
}
app, err := ctx.DB.GetAppByAppID(appID)
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)
@@ -252,6 +282,12 @@ func getChannels(ctx *applicationContext, p params, w http.ResponseWriter, r *ht
}
}
func newGetChannelHandler(DB db) goji.HandlerFunc {
return commonHandlers(DB, &getChannelHandler{DB})
}
type getChannelHandler struct{ DB db }
// Fetch info for one channel
//
// Example:
@@ -262,19 +298,19 @@ func getChannels(ctx *applicationContext, p params, w http.ResponseWriter, r *ht
// }
//
// GET /apps/{app_id}/channels/{channel_name}
func getChannel(ctx *applicationContext, p params, w http.ResponseWriter, r *http.Request) {
func (h *getChannelHandler) ServeHTTPC(ctx context.Context, w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json;charset=UTF-8")
query := r.URL.Query()
appID := p.Get("app_id")
app, err := ctx.DB.GetAppByAppID(appID)
appID := pat.Param(ctx, "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 := p.Get("channel_name")
channelName := pat.Param(ctx, "channel_name")
// Channel name could not be empty
if strings.TrimSpace(channelName) == "" {
@@ -340,6 +376,12 @@ func getChannel(ctx *applicationContext, p params, w http.ResponseWriter, r *htt
}
}
func newGetChannelUsersHandler(DB db) goji.HandlerFunc {
return commonHandlers(DB, &getChannelUsersHandler{DB})
}
type getChannelUsersHandler struct{ DB db }
// Allowed only for presence-channels
//
// Example:
@@ -351,9 +393,9 @@ func getChannel(ctx *applicationContext, p params, w http.ResponseWriter, r *htt
// }
//
// GET /apps/{app_id}/channels/{channel_name}/users
func getChannelUsers(ctx *applicationContext, p params, w http.ResponseWriter, r *http.Request) {
appID := p.Get("app_id")
channelName := p.Get("channel_name")
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")
isPresence := utils.IsPresenceChannel(channelName)
@@ -362,7 +404,7 @@ func getChannelUsers(ctx *applicationContext, p params, w http.ResponseWriter, r
return
}
app, err := ctx.DB.GetAppByAppID(appID)
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)
+31 -24
View File
@@ -6,11 +6,15 @@ import (
"net/http"
"net/http/httptest"
"testing"
"goji.io/pattern"
"golang.org/x/net/context"
)
var (
testApp *app
ctx *applicationContext
testApp *app
database db
)
func init() {
@@ -30,21 +34,21 @@ func init() {
db := newMemdb()
db.AddApp(testApp)
ctx = &applicationContext{DB: db}
database = db
}
// All Channels
func Test_getChannels_all(t *testing.T) {
appID := testApp.AppID
p := map[string]string{}
p["app_id"] = appID
ctx := context.Background()
ctx = context.WithValue(ctx, pattern.Variable("app_id"), appID)
r, _ := http.NewRequest("GET", fmt.Sprintf("/apps/%s/channels", appID), nil)
w := httptest.NewRecorder()
getChannels(ctx, params(p), w, r)
handler := &getChannelsHandler{database}
handler.ServeHTTPC(ctx, w, r)
if w.Code != http.StatusOK {
t.Errorf("w.Code == %d, wants %d", w.Code, http.StatusOK)
@@ -63,13 +67,14 @@ func Test_getChannels_all(t *testing.T) {
func Test_getChannels_filter_by_presence_prefix(t *testing.T) {
appID := testApp.AppID
p := map[string]string{}
p["app_id"] = appID
ctx := context.Background()
ctx = context.WithValue(ctx, pattern.Variable("app_id"), appID)
r, _ := http.NewRequest("GET", fmt.Sprintf("/apps/%s/channels?filter_by_prefix=presence-", appID), nil)
w := httptest.NewRecorder()
getChannels(ctx, params(p), w, r)
handler := &getChannelsHandler{database}
handler.ServeHTTPC(ctx, w, r)
if w.Code != http.StatusOK {
t.Errorf("w.Code == %d, wants %d", w.Code, http.StatusOK)
@@ -87,16 +92,16 @@ func Test_getChannels_filter_by_presence_prefix(t *testing.T) {
// Only presence channels and user_count
func Test_getChannels_filter_by_presence_prefix_and_user_count(t *testing.T) {
appID := testApp.AppID
p := map[string]string{}
p["app_id"] = appID
ctx := context.Background()
ctx = context.WithValue(ctx, pattern.Variable("app_id"), appID)
r, _ := http.NewRequest("GET", fmt.Sprintf("/apps/%s/channels?filter_by_prefix=presence-&info=user_count", appID), nil)
w := httptest.NewRecorder()
getChannels(ctx, params(p), w, r)
handler := &getChannelsHandler{database}
handler.ServeHTTPC(ctx, w, r)
if w.Code != http.StatusOK {
t.Errorf("w.Code == %d, wants %d", w.Code, http.StatusOK)
@@ -128,13 +133,14 @@ 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
p := map[string]string{}
p["app_id"] = appID
ctx := context.Background()
ctx = context.WithValue(ctx, pattern.Variable("app_id"), appID)
r, _ := http.NewRequest("GET", fmt.Sprintf("/apps/%s/channels?filter_by_prefix=private-&info=user_count", appID), nil)
w := httptest.NewRecorder()
getChannels(ctx, params(p), w, r)
handler := &getChannelsHandler{database}
handler.ServeHTTPC(ctx, w, r)
if w.Code != http.StatusBadRequest {
t.Errorf("w.Code == %d, wants %d", w.Code, http.StatusBadRequest)
@@ -144,13 +150,14 @@ 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
p := map[string]string{}
p["app_id"] = appID
ctx := context.Background()
ctx = context.WithValue(ctx, pattern.Variable("app_id"), appID)
r, _ := http.NewRequest("GET", fmt.Sprintf("/apps/%s/channels?filter_by_prefix=public-", appID), nil)
w := httptest.NewRecorder()
getChannels(ctx, params(p), w, r)
handler := &getChannelsHandler{database}
handler.ServeHTTPC(ctx, w, r)
if w.Code != http.StatusOK {
t.Errorf("w.Code == %d, wants %d", w.Code, http.StatusOK)
@@ -174,16 +181,16 @@ func Test_getChannels_filter_by_public_prefix(t *testing.T) {
}
func Test_getChannels_filter_by_private_prefix(t *testing.T) {
appID := testApp.AppID
p := map[string]string{}
p["app_id"] = appID
ctx := context.Background()
ctx = context.WithValue(ctx, pattern.Variable("app_id"), appID)
r, _ := http.NewRequest("GET", fmt.Sprintf("/apps/%s/channels?filter_by_prefix=private-", appID), nil)
w := httptest.NewRecorder()
getChannels(ctx, params(p), w, r)
handler := &getChannelsHandler{database}
handler.ServeHTTPC(ctx, w, r)
if w.Code != http.StatusOK {
t.Errorf("w.Code == %d, wants %d", w.Code, http.StatusOK)
+10 -15
View File
@@ -11,6 +11,10 @@ import (
"os"
"time"
"goji.io/pat"
goji "goji.io"
log "github.com/golang/glog"
)
@@ -41,21 +45,12 @@ func Start(filename string) {
db.AddApp(newAppFromConfig(a))
}
// Creating the global application context
ctx := &applicationContext{DB: db}
// The router
router := newRouter(ctx)
router.POST("/apps/{app_id}/events", commonHandlers(ctx, postEvents))
router.GET("/apps/{app_id}/channels", commonHandlers(ctx, getChannels))
router.GET("/apps/{app_id}/channels/{channel_name}", commonHandlers(ctx, getChannel))
router.GET("/apps/{app_id}/channels/{channel_name}/users", commonHandlers(ctx, getChannelUsers))
router.GET("/app/{key}", contextHandlerFunc(wsHandler))
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))
if conf.SSL {
go func() {
-42
View File
@@ -1,42 +0,0 @@
// Copyright 2014, 2016 Claudemiro Alves Feitosa Neto. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package ipe
import (
"net/http"
"github.com/gorilla/mux"
)
type router struct {
ctx *applicationContext
mux *mux.Router
routes map[string]contextHandler
}
func newRouter(ctx *applicationContext) *router {
return &router{
ctx: ctx,
mux: mux.NewRouter().StrictSlash(true),
}
}
func (a *router) GET(path string, handler contextHandler) {
a.Handle("GET", path, handler)
}
func (a *router) POST(path string, handler contextHandler) {
a.Handle("POST", path, handler)
}
func (a *router) Handle(method, path string, handler contextHandler) {
a.mux.Methods(method).Path(path).HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
handler.ServeWithContext(a.ctx, params(mux.Vars(r)), w, r)
})
}
func (a router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
a.mux.ServeHTTP(w, r)
}
+14 -3
View File
@@ -12,8 +12,13 @@ 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/dimiro1/ipe/utils"
)
@@ -206,8 +211,14 @@ func onMessage(conn *websocket.Conn, w http.ResponseWriter, r *http.Request, ses
} // For
}
func newWebsocketHandler(DB db) goji.Handler {
return &websocketHandler{DB}
}
type websocketHandler struct{ DB db }
// Websocket GET /app/{key}
func wsHandler(ctx *applicationContext, p params, w http.ResponseWriter, r *http.Request) {
func (h *websocketHandler) ServeHTTPC(ctx context.Context, w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
defer func() {
if conn != nil {
@@ -220,9 +231,9 @@ func wsHandler(ctx *applicationContext, p params, w http.ResponseWriter, r *http
return
}
appKey := p.Get("key")
appKey := pat.Param(ctx, "key")
app, err := ctx.DB.GetAppByKey(appKey)
app, err := h.DB.GetAppByKey(appKey)
if err != nil {
log.Error(err)