func (am RestAccessManager) AccessAllowed(accessType AccessType, userId string, path guble.Path) bool { u, _ := url.Parse(string(am)) q := u.Query() if accessType == READ { q.Set("type", "read") } else { q.Set("type", "write") } q.Set("userId", userId) q.Set("path", string(path)) resp, err := http.DefaultClient.Get(u.String()) if err != nil { guble.Warn("RestAccessManager: %v", err) return false } defer resp.Body.Close() responseBody, err := ioutil.ReadAll(resp.Body) if err != nil || resp.StatusCode != 200 { guble.Info("error getting permission", err) guble.Debug("error getting permission", responseBody) return false } guble.Debug("RestAccessManager: %v, %v, %v, %v", accessType, userId, path, string(responseBody)) return "true" == string(responseBody) }
func (rec *Receiver) receiveFromSubscription() { for { select { case msgAndRoute, ok := <-rec.route.C: if !ok { guble.Debug("messageSouce closed the channel returning from subscription", rec.applicationId) return } if guble.DebugEnabled() { guble.Debug("deliver message to applicationId=%v: %v", rec.applicationId, msgAndRoute.Message.MetadataLine()) } if msgAndRoute.Message.Id > rec.lastSendId { rec.lastSendId = msgAndRoute.Message.Id rec.sendChannel <- msgAndRoute.Message.Bytes() } else { guble.Debug("dropping message %v, because it was already sent to client", msgAndRoute.Message.Id) } case <-rec.cancelChannel: rec.shouldStop = true rec.messageSouce.Unsubscribe(rec.route) rec.route = nil rec.sendOK(guble.SUCCESS_CANCELED, string(rec.path)) return } } }
func (srv *WSHandler) checkAccess(raw []byte) bool { guble.Debug("raw message: %v", string(raw)) if raw[0] == byte('/') { path := getPathFromRawMessage(raw) guble.Debug("Received msg %v %v", srv.userId, path) return len(path) == 0 || srv.accessManager.AccessAllowed(READ, srv.userId, path) } return true }
// Registers the supplied module on this service. // This method checks the module for the following interfaces and // does the expected tegistrations: // Stopable: notify when the service stops // Endpoint: Register the handler function of the Endpoint in the http service at prefix // SetRouter: Provide the router // SetMessageEntry: Provide the message entry // // If the module does not have a HandlerFunc, the prefix parameter is ignored func (service *Service) Register(module interface{}) { name := reflect.TypeOf(module).String() switch m := module.(type) { case Stopable: guble.Info("register %v as StopListener", name) service.AddStopListener(m) } switch m := module.(type) { case Startable: guble.Info("register %v as StartListener", name) service.AddStartListener(m) } switch m := module.(type) { case Endpoint: guble.Info("register %v as Endpoint to %v", name, m.GetPrefix()) service.AddHandler(m.GetPrefix(), m) } // do the injections ... switch m := module.(type) { case SetKVStore: guble.Debug("inject KVStore to %v", name) m.SetKVStore(service.kvStore) } switch m := module.(type) { case SetMessageStore: guble.Debug("inject MessageStore to %v", name) m.SetMessageStore(service.messageStore) } switch m := module.(type) { case SetRouter: guble.Debug("inject Router to %v", name) m.SetRouter(service.router) } switch m := module.(type) { case SetMessageEntry: guble.Debug("inject MessageEntry to %v", name) m.SetMessageEntry(service.messageSink) } switch m := module.(type) { case SetAccessManager: guble.Debug("inject AccessManager to %v", name) m.SetAccessManager(service.accessManager) } }
func (gcmConnector *GCMConnector) sendMessageToGCM(msg server.MsgAndRoute) { gcmId := msg.Route.ApplicationId payload := gcmConnector.parseMessageToMap(msg.Message) var messageToGcm = gcm.NewMessage(payload, gcmId) guble.Info("sending message to %v ...", gcmId) result, err := gcmConnector.sender.Send(messageToGcm, 5) if err != nil { guble.Err("error sending message to cgmid=%v: %v", gcmId, err.Error()) return } errorJson := result.Results[0].Error if errorJson != "" { gcmConnector.handleJsonError(errorJson, gcmId, msg.Route) } else { guble.Debug("delivered message to gcm cgmid=%v: %v", gcmId, errorJson) } //we only send to one receiver, so we know that we can replace the old id with the first registration id (=canonical id) if result.CanonicalIDs != 0 { gcmConnector.replaceSubscriptionWithCanonicalID(msg.Route, result.Results[0].RegistrationID) } }
func (gcmConnector *GCMConnector) broadcastMessage(msg server.MsgAndRoute) { topic := msg.Message.Path payload := gcmConnector.parseMessageToMap(msg.Message) guble.Info("broadcasting message with topic %v ...", string(topic)) subscriptions := gcmConnector.kvStore.Iterate(GCM_REGISTRATIONS_SCHEMA, "") count := 0 for { select { case entry, ok := <-subscriptions: if !ok { guble.Info("send message to %v receivers", count) return } gcmId := entry[0] //TODO collect 1000 gcmIds and send them in one request! broadcastMessage := gcm.NewMessage(payload, gcmId) go func() { //TODO error handling of response! _, err := gcmConnector.sender.Send(broadcastMessage, 3) guble.Debug("sent broadcast message to gcmId=%v", gcmId) if err != nil { guble.Err("error sending broadcast message to cgmid=%v: %v", gcmId, err.Error()) } }() count++ } } }
func (router *PubSubRouter) Go() *PubSubRouter { go func() { for { func() { defer guble.PanicLogger() select { case message := <-router.messageIn: router.handleMessage(message) runtime.Gosched() case subscriber := <-router.subscribeChan: router.subscribe(subscriber.route) subscriber.doneNotify <- true case unsubscriber := <-router.unsubscribeChan: router.unsubscribe(unsubscriber.route) unsubscriber.doneNotify <- true case <-router.stop: router.closeAllRoutes() guble.Debug("stopping message router") break } }() } }() return router }
func (gcmConnector *GCMConnector) parseMessageToMap(msg *guble.Message) map[string]interface{} { payload := map[string]interface{}{} if msg.Body[0] == '{' { json.Unmarshal(msg.Body, &payload) } else { payload["message"] = msg.BodyAsString() } guble.Debug("parsed message is: %v", payload) return payload }
func (gcmConnector *GCMConnector) handleJsonError(jsonError string, gcmId string, route *server.Route) { if jsonError == "NotRegistered" { guble.Debug("remove not registered cgm registration cgmid=%v", gcmId) gcmConnector.removeSubscription(route, gcmId) } else if jsonError == "InvalidRegistration" { guble.Err("the cgmid=%v is not registered. %v", gcmId, jsonError) } else { guble.Err("unexpected error while sending to cgm cgmid=%v: %v", gcmId, jsonError) } }
func (srv *WSHandler) sendLoop() { for { select { case raw := <-srv.sendChannel: if guble.DebugEnabled() { if len(raw) < 80 { guble.Debug("send to client (userId=%v, applicationId=%v, totalSize=%v): %v", srv.userId, srv.applicationId, len(raw), string(raw)) } else { guble.Debug("send to client (userId=%v, applicationId=%v, totalSize=%v): %v...", srv.userId, srv.applicationId, len(raw), string(raw[0:79])) } } if err := srv.clientConn.Send(raw); err != nil { guble.Info("applicationId=%v closed the connection", srv.applicationId) srv.cleanAndClose() break } } } }
// Opens the database file. // If the directory does not exist, it will be created. func (kvStore *SqliteKVStore) Open() error { directoryPath := filepath.Dir(kvStore.filename) if err := ensureWriteableDirectory(directoryPath); err != nil { guble.Err("error db directory not writeable %q: %q", kvStore.filename, err) return err } guble.Info("opening sqldb %v", kvStore.filename) gormdb, err := gorm.Open("sqlite3", kvStore.filename) if err != nil { guble.Err("error opening sqlite3 db %q: %q", kvStore.filename, err) return err } if err := gormdb.DB().Ping(); err != nil { guble.Err("error pinging database %q: %q", kvStore.filename, err.Error()) } else { guble.Debug("can ping database %q", kvStore.filename) } //gormdb.LogMode(true) gormdb.DB().SetMaxIdleConns(2) gormdb.DB().SetMaxOpenConns(5) gormdb.SingularTable(true) if err := gormdb.AutoMigrate(&kvEntry{}).Error; err != nil { guble.Err("error in schema migration: %q", err) return err } else { guble.Debug("ensured db schema") } if !kvStore.syncOnWrite { guble.Info("setting db: PRAGMA synchronous = OFF") if err := gormdb.Exec("PRAGMA synchronous = OFF").Error; err != nil { guble.Err("error setting PRAGMA synchronous = OFF: %v", err) return err } } kvStore.db = &gormdb return nil }
func messagePartitionReader(name string, a *assert.Assertions, store *MessagePartition, n int, done chan bool) { lastReadMessage := 0 for lastReadMessage < n { msgC := make(chan MessageAndId) errorC := make(chan error) guble.Debug("[%v] start fetching at: %v", name, lastReadMessage+1) store.Fetch(FetchRequest{ Partition: "myMessages", StartId: uint64(lastReadMessage + 1), Direction: 1, Count: math.MaxInt32, MessageC: msgC, ErrorCallback: errorC, StartCallback: make(chan int, 1), }) fetch: for { select { case msgAndId, open := <-msgC: if !open { guble.Debug("[%v] stop fetching at %v", name, lastReadMessage) break fetch } a.Equal(lastReadMessage+1, int(msgAndId.Id)) lastReadMessage = int(msgAndId.Id) case err := <-errorC: a.Fail("received error", err.Error()) <-done return } } } guble.Debug("[%v] ready, got %v", name, lastReadMessage) done <- true }
func (rec *Receiver) fetch() error { var err error fetch := store.FetchRequest{ Partition: rec.path.Partition(), MessageC: make(chan store.MessageAndId, 3), ErrorCallback: make(chan error), StartCallback: make(chan int), Prefix: []byte(rec.path), Count: rec.maxCount, } if rec.startId >= 0 { fetch.Direction = 1 fetch.StartId = uint64(rec.startId) if rec.maxCount == 0 { fetch.Count = math.MaxInt32 } } else { fetch.Direction = -1 if fetch.StartId, err = rec.messageStore.MaxMessageId(rec.path.Partition()); err != nil { return err } if rec.maxCount == 0 { fetch.Count = -1 * int(rec.startId) } } rec.messageStore.Fetch(fetch) for { select { case numberOfResults := <-fetch.StartCallback: rec.sendOK(guble.SUCCESS_FETCH_START, fmt.Sprintf("%v %v", rec.path, numberOfResults)) case msgAndId, open := <-fetch.MessageC: if !open { rec.sendOK(guble.SUCCESS_FETCH_END, string(rec.path)) return nil } guble.Debug("replay send %v, %v", msgAndId.Id, string(msgAndId.Message)) rec.lastSendId = msgAndId.Id rec.sendChannel <- msgAndId.Message case err := <-fetch.ErrorCallback: return err case <-rec.cancelChannel: rec.shouldStop = true rec.sendOK(guble.SUCCESS_CANCELED, string(rec.path)) // TODO implement cancellation in message store return nil } } }
func (router *PubSubRouter) HandleMessage(message *guble.Message) error { guble.Debug("Route.HandleMessage: %v %v", message.PublisherUserId, message.Path) if !router.accessManager.AccessAllowed(WRITE, message.PublisherUserId, message.Path) { return errors.New("User not allowed to post message to topic.") } if float32(len(router.messageIn))/float32(cap(router.messageIn)) > 0.9 { guble.Warn("router.messageIn channel very full: current=%v, max=%v\n", len(router.messageIn), cap(router.messageIn)) time.Sleep(time.Millisecond) } router.messageIn <- message return nil }
func (service *Service) Start() error { el := guble.NewErrorList("Errors occured while startup the service: ") for _, startable := range service.startListener { name := reflect.TypeOf(startable).String() guble.Debug("starting module %v", name) if err := startable.Start(); err != nil { guble.Err("error on startup module %v", name) el.Add(err) } } return el.ErrorOrNil() }
// Add a route to the subscribers. // If there is already a route with same Application Id and Path, it will be replaced. func (router *PubSubRouter) Subscribe(r *Route) (*Route, error) { guble.Debug("subscribe %v, %v, %v", router.accessManager, r.UserId, r.Path) accessAllowed := router.accessManager.AccessAllowed(READ, r.UserId, r.Path) if !accessAllowed { return r, errors.New("not allowed") } req := SubscriptionRequest{ route: r, doneNotify: make(chan bool), } router.subscribeChan <- req <-req.doneNotify return r, nil }
func (c *Client) readLoop() error { for { if _, msg, err := c.ws.ReadMessage(); err != nil { c.Connected = false if c.shouldStop() { return nil } else { guble.Err("read error: %v", err.Error()) c.errors <- clientErrorMessage(err.Error()) return err } } else { guble.Debug("raw> %s", msg) c.handleIncommoingMessage(msg) } } }
func (gcmConnector *GCMConnector) loadSubscriptions() { subscriptions := gcmConnector.kvStore.Iterate(GCM_REGISTRATIONS_SCHEMA, "") count := 0 for { select { case entry, ok := <-subscriptions: if !ok { guble.Info("renewed %v gcm subscriptions", count) return } gcmId := entry[0] splitedValue := strings.SplitN(entry[1], ":", 2) userid := splitedValue[0] topic := splitedValue[1] guble.Debug("renew gcm subscription: user=%v, topic=%v, gcmid=%v", userid, topic, gcmId) route := server.NewRoute(topic, gcmConnector.channelFromRouter, gcmId, userid) gcmConnector.router.Subscribe(route) count++ } } }
func (srv *WSHandler) handleSendCmd(cmd *guble.Cmd) { guble.Debug("sending %v", string(cmd.Bytes())) if len(cmd.Arg) == 0 { srv.sendError(guble.ERROR_BAD_REQUEST, "send command requires a path argument, but non given") return } args := strings.SplitN(cmd.Arg, " ", 2) msg := &guble.Message{ Path: guble.Path(args[0]), PublisherApplicationId: srv.applicationId, PublisherUserId: srv.userId, HeaderJson: cmd.HeaderJson, Body: cmd.Body, } if len(args) == 2 { msg.PublisherMessageId = args[1] } srv.messageSink.HandleMessage(msg) srv.sendOK(guble.SUCCESS_SEND, msg.PublisherMessageId) }