// Do a request/task asynchronously func (service *Service) doAsync(task Task) <-chan Task { ch := make(chan Task, 1) go func() { defer func() { ch <- task }() log.Tracef("REST: sending: %+v", task.request) task.response, task.err = service.client.Do(task.request) if task.err != nil { log.Warnf("REST: failed to do %s %s request (error: %s)", task.request.Method, task.request.URL, task.err) return } log.Tracef("REST: got %s %s response: %+v", task.request.Method, task.request.URL, task.response) // read body defer task.response.Body.Close() task.body, task.err = ioutil.ReadAll(task.response.Body) if task.err != nil { log.Warnf("REST: failed to read %s %s response body (error: %s)", task.request.Method, task.request.URL, task.err) return } log.Debugf("REST: got %s %s body: %s", task.request.Method, task.request.URL, string(task.body)) }() return ch }
// GetServerInfo() function gets the main server's information. func (service *Service) GetServerInfo(timeout time.Duration) (info *core.ServerInfo, err error) { log.Tracef("REST: getting server info...") task, err := service.prepareGetServerInfo() if err != nil { log.Warnf("REST: failed to prepare /info task (error: %s)", err) return } select { case <-time.After(timeout): log.Warnf("REST: failed to wait %s for /info task", timeout) err = fmt.Errorf("timed out") case task = <-service.doAsync(task): info = &core.ServerInfo{} err = service.processGetServerInfo(task, info) if err != nil { log.Warnf("REST: failed to process /info task (error: %s)", err) return } } return }
// NewService creates new service. func NewService(baseUrl, accessKey string) (service *Service, err error) { log.Tracef("REST: creating service (url:%q)", baseUrl) service = &Service{accessKey: accessKey} // remove trailing slashes from URL for len(baseUrl) > 1 && strings.HasSuffix(baseUrl, "/") { baseUrl = baseUrl[0 : len(baseUrl)-1] } // parse URL service.baseUrl, err = url.Parse(baseUrl) if err != nil { log.Warnf("REST: failed to parse URL (error: %s)", err) return } // initialize HTTP client service.client = &http.Client{} // TODO: client.Transport // TODO: client.CookieJar // TODO: client.Timeout service.commandListeners = make(map[string]*core.CommandListener) service.notificationListeners = make(map[string]*core.NotificationListener) return }
// GetNetwork() function get the network data. func (service *Service) GetNetwork(networkId uint64, timeout time.Duration) (network *core.Network, err error) { log.Tracef("REST: getting network %d...", networkId) task, err := service.prepareGetNetwork(networkId) if err != nil { log.Warnf("REST: failed to prepare /network/get task (error: %s)", err) return } select { case <-time.After(timeout): log.Warnf("REST: failed to wait %s for /network/get task", timeout) err = fmt.Errorf("timed out") case task = <-service.doAsync(task): network = &core.Network{Id: networkId} err = service.processGetNetwork(task, network) if err != nil { log.Warnf("REST: failed to process /network/get task (error: %s)", err) return } } return }
// TX thread func (service *Service) doTX() { for { select { case task, ok := <-service.tx: if !ok || task == nil { log.Infof("WS: TX thread stopped") service.conn.Close() // TODO: send Close frame? return } body, err := task.Format() if err != nil { log.Warnf("WS: failed to format message (error: %s)", err) continue // TODO: return? } log.Tracef("WS: sending message: %s", string(body)) err = service.conn.WriteMessage(websocket.TextMessage, body) if err != nil { log.Warnf("WS: failed to send message (error: %s)", err) continue // TODO: return? } // case <-service.pingTimer.C: // if err := c.write(websocket.PingMessage, []byte{}); err != nil { // log.Warnf("WS: could not write ping message (error: %s)", err) // return // } } } }
// GetNotification() function get the notification data. func (service *Service) GetNotification(device *core.Device, notificationId uint64, timeout time.Duration) (notification *core.Notification, err error) { log.Tracef("REST: getting notification %q/%d...", device.Id, notificationId) task, err := service.prepareGetNotification(device, notificationId) if err != nil { log.Warnf("REST: failed to prepare /notification/get task (error: %s)", err) return } select { case <-time.After(timeout): log.Warnf("REST: failed to wait %s for /notification/get task", timeout) err = fmt.Errorf("timed out") case task = <-service.doAsync(task): notification = &core.Notification{Id: notificationId} err = service.processGetNotification(task, notification) if err != nil { log.Warnf("REST: failed to process /notification/get task (error: %s)", err) return } } return }
// GetDevice() function get the device data. func (service *Service) GetDevice(deviceId, deviceKey string, timeout time.Duration) (device *core.Device, err error) { log.Tracef("REST: getting device %q...", deviceId) task, err := service.prepareGetDevice(deviceId, deviceKey) if err != nil { log.Warnf("REST: failed to prepare /device/get task (error: %s)", err) return } select { case <-time.After(timeout): log.Warnf("REST: failed to wait %s for /device/get task", timeout) err = fmt.Errorf("timed out") case task = <-service.doAsync(task): device = &core.Device{Id: deviceId, Key: deviceKey} err = service.processGetDevice(task, device) if err != nil { log.Warnf("REST: failed to process /device/get task (error: %s)", err) return } } return }
// export main + introspectable DBus objects func exportDBusObject(bus *dbus.Conn, w *DBusWrapper) { bus.Export(w, ComDevicehiveCloudPath, ComDevicehiveCloudIface) // main service interface serviceInterface := introspect.Interface{ Name: ComDevicehiveCloudIface, Methods: introspect.Methods(w), Signals: []introspect.Signal{ { Name: "CommandReceived", Args: []introspect.Arg{ {"id", "t", "in"}, {"name", "s", "in"}, {"parameters", "s", "in"}, // JSON string }, }, }, } // main service node n := &introspect.Node{ Name: ComDevicehiveCloudPath, Interfaces: []introspect.Interface{ introspect.IntrospectData, prop.IntrospectData, serviceInterface}, } n_obj := introspect.NewIntrospectable(n) log.Tracef("%q introspectable: %s", ComDevicehiveCloudPath, n_obj) bus.Export(n_obj, ComDevicehiveCloudPath, "org.freedesktop.DBus.Introspectable") // root node root := &introspect.Node{ Children: []introspect.Node{ {Name: ComDevicehiveCloudPath}, }, } root_obj := introspect.NewIntrospectable(root) log.Tracef("%q introspectable: %s", "/", root_obj) bus.Export(root_obj, "/", "org.freedesktop.DBus.Introspectable") }
// NewService creates new Websocket /device service. func NewService(baseUrl, accessKey string) (service *Service, err error) { log.Tracef("WS: creating service (url:%q)", baseUrl) service = &Service{accessKey: accessKey} // remove trailing slashes from URL for len(baseUrl) > 1 && strings.HasSuffix(baseUrl, "/") { baseUrl = baseUrl[0 : len(baseUrl)-1] } // parse URL service.baseUrl, err = url.Parse(baseUrl) if err != nil { log.Warnf("WS: failed to parse URL (error: %s)", err) service = nil return } // connect to /device endpoint ws_url := fmt.Sprintf("%s/device", service.baseUrl) headers := http.Header{} headers.Add("Origin", "http://localhost/") if len(service.accessKey) != 0 { headers.Add("Authorization", "Bearer "+service.accessKey) } service.conn, _, err = websocket.DefaultDialer.Dial(ws_url, headers) if err != nil { log.Warnf("WS: failed to dial (error: %s)", err) service = nil return } // set of active tasks service.tasks = make(map[uint64]*Task) // command listeners service.commandListeners = make(map[string]*core.CommandListener) // create TX channel service.tx = make(chan *Task) // and start RX/TX threads go service.doRX() go service.doTX() return }
// RX thread func (service *Service) doRX() { for { _, body, err := service.conn.ReadMessage() if err != nil { log.Warnf("WS: failed to receive message (error: %s)", err) return } log.Tracef("WS: received message: %s", string(body)) // parse JSON var msg map[string]interface{} err = json.Unmarshal(body, &msg) if err != nil { log.Warnf("WS: failed to parse JSON (error: %s), ignored", err) continue } // log.Tracef("WS: parsed JSON %+v", msg) if v, ok := msg["requestId"]; ok { id := safeUint64(v) task := service.takeTask(id) if task != nil { task.dataRecved = msg task.done <- task continue } } if v, ok := msg["action"]; ok { action := safeString(v) service.handleAction(action, msg) continue } log.Warnf("WS: unexpected message: %s, ignored", string(body)) } }
// InsertNetwork() function inserts the network. func (service *Service) InsertNetwork(network *core.Network, timeout time.Duration) (err error) { log.Tracef("REST: inserting network %q...", network.Name) task, err := service.prepareInsertNetwork(network) if err != nil { log.Warnf("REST: failed to prepare /network/insert task (error: %s)", err) return } select { case <-time.After(timeout): log.Warnf("REST: failed to wait %s for /network/insert task", timeout) err = fmt.Errorf("timed out") case task = <-service.doAsync(task): err = service.processInsertNetwork(task, network) if err != nil { log.Warnf("REST: failed to process /network/insert task (error: %s)", err) return } } return }
// GetDeviceList() function get the device list. func (service *Service) GetDeviceList(take, skip int, timeout time.Duration) (devices []core.Device, err error) { log.Tracef("REST: getting device list (take:%d, skip:%d)...", take, skip) task, err := service.prepareGetDeviceList(take, skip) if err != nil { log.Warnf("REST: failed to prepare /device/list task (error: %s)", err) return } select { case <-time.After(timeout): log.Warnf("REST: failed to wait %s for /device/list task", timeout) err = fmt.Errorf("timed out") case task = <-service.doAsync(task): devices, err = service.processGetDeviceList(task) if err != nil { log.Warnf("REST: failed to process /device/list task (error: %s)", err) return } } return }
// GetNotification() function poll the notifications. func (service *Service) PollNotifications(device *core.Device, timestamp, names, waitTimeout string, timeout time.Duration) (notifications []core.Notification, err error) { log.Tracef("REST: polling notifications %q...", device.Id) task, err := service.preparePollNotification(device, timestamp, names, waitTimeout) if err != nil { log.Warnf("REST: failed to prepare /notification/poll task (error: %s)", err) return } select { case <-time.After(timeout): log.Warnf("REST: failed to wait %s for /notification/poll task", timeout) err = fmt.Errorf("timed out") case task = <-service.doAsync(task): notifications, err = service.processPollNotification(task) if err != nil { log.Warnf("REST: failed to process /notification/poll task (error: %s)", err) return } } return }
// InsertNotification() function inserts the device notification. func (service *Service) InsertNotification(device *core.Device, notification *core.Notification, timeout time.Duration) (err error) { log.Tracef("REST: inserting notification %q to %q...", notification.Name, device.Id) task, err := service.prepareInsertNotification(device, notification) if err != nil { log.Warnf("REST: failed to prepare /notification/insert task (error: %s)", err) return } select { case <-time.After(timeout): log.Warnf("REST: failed to wait %s for /notification/insert task", timeout) err = fmt.Errorf("timed out") case task = <-service.doAsync(task): err = service.processInsertNotification(task, notification) if err != nil { log.Warnf("REST: failed to process /notification/insert task (error: %s)", err) return } } return }
// RegisterDevice() function registers the device. func (service *Service) RegisterDevice(device *core.Device, timeout time.Duration) (err error) { log.Tracef("REST: registering device %q...", device.Id) task, err := service.prepareRegisterDevice(device) if err != nil { log.Warnf("REST: failed to prepare /device/register task (error: %s)", err) return } select { case <-time.After(timeout): log.Warnf("REST: failed to wait %s for /device/register task", timeout) err = fmt.Errorf("timed out") case task = <-service.doAsync(task): err = service.processRegisterDevice(task) if err != nil { log.Warnf("REST: failed to process /device/register task (error: %s)", err) return } } return }