void P44VdcHost::configApiRequestHandler(JsonCommPtr aJsonComm, ErrorPtr aError, JsonObjectPtr aJsonObject) { ErrorPtr err; // when coming from mg44, requests have the following form // - for GET requests like http://localhost:8080/api/json/myuri?foo=bar&this=that // {"method":"GET","uri":"myuri","uri_params":{"foo":"bar","this":"that"}} // - for POST requests like // curl "http://localhost:8080/api/json/myuri?foo=bar&this=that" --data-ascii "{ \"content\":\"data\", \"important\":false }" // {"method":"POST","uri":"myuri","uri_params":{"foo":"bar","this":"that"},"data":{"content":"data","important":false}} // curl "http://localhost:8080/api/json/myuri" --data-ascii "{ \"content\":\"data\", \"important\":false }" // {"method":"POST","uri":"myuri","data":{"content":"data","important":false}} // processing: // - a JSON request must be either specified in the URL or in the POST data, not both // - if POST data ("data" member in the incoming request) is present, "uri_params" is ignored // - "uri" selects one of possibly multiple APIs if (Error::isOK(aError)) { // not JSON level error, try to process LOG(LOG_DEBUG, "cfg -> vdcd (JSON) request received: %s", aJsonObject->c_strValue()); // find out which one is our actual JSON request // - try POST data first JsonObjectPtr request = aJsonObject->get("data"); if (!request) { // no POST data, try uri_params request = aJsonObject->get("uri_params"); } if (!request) { // empty query, that's an error aError = Error::err<P44VdcError>(415, "empty request"); } else { // have the request processed string apiselector; JsonObjectPtr uri = aJsonObject->get("uri"); if (uri) apiselector = uri->stringValue(); // dispatch according to API if (apiselector=="vdc") { // process request that basically is a vdc API request, but as simple webbish JSON, not as JSON-RPC 2.0 // and without the need to start a vdc session // Notes: // - if dSUID is specified invalid or empty, the vdc host itself is addressed. // - use x-p44-vdcs and x-p44-devices properties to find dsuids aError = processVdcRequest(aJsonComm, request); } else if (apiselector=="p44") { // process p44 specific requests aError = processP44Request(aJsonComm, request); } else { // unknown API selector aError = Error::err<P44VdcError>(400, "invalid URI, unknown API"); } } } // if error or explicit OK, send response now. Otherwise, request processing will create and send the response if (aError) { sendCfgApiResponse(aJsonComm, JsonObjectPtr(), aError); } }
void HueApiOperation::processAnswer(JsonObjectPtr aJsonResponse, ErrorPtr aError) { error = aError; if (Error::isOK(error)) { // pre-process response in case of non-GET if (method!=httpMethodGET) { // Expected: // [{"error":{"type":xxx,"address":"yyy","description":"zzz"}}] // or // [{"success": { "xxx": "xxxxxxxx" }] int errCode = HueCommErrorInvalidResponse; string errMessage = "invalid response"; for (int i=0; i<aJsonResponse->arrayLength(); i++) { JsonObjectPtr responseItem = aJsonResponse->arrayGet(i); responseItem->resetKeyIteration(); JsonObjectPtr responseParams; string statusToken; if (responseItem->nextKeyValue(statusToken, responseParams)) { if (statusToken=="success" && responseParams) { // apparently successful, return entire response // Note: use getSuccessItem() to get success details data = aJsonResponse; errCode = HueCommErrorOK; // ok break; } else if (statusToken=="error" && responseParams) { // make Error object out of it JsonObjectPtr e = responseParams->get("type"); if (e) errCode = e->int32Value(); e = responseParams->get("description"); if (e) errMessage = e->stringValue(); break; } } } // for if (errCode!=HueCommErrorOK) { error = ErrorPtr(new HueCommError(errCode, errMessage)); } } else { // GET, just return entire data data = aJsonResponse; } } // done completed = true; // have queue reprocessed hueComm.processOperations(); }
void handleCreateUserAnswer(JsonObjectPtr aJsonResponse, ErrorPtr aError) { if (Error::isOK(aError)) { FOCUSLOG("Received success answer:\n%s", aJsonResponse->json_c_str()); JsonObjectPtr s = HueComm::getSuccessItem(aJsonResponse); // apparently successful, extract user name if (s) { JsonObjectPtr u = s->get("username"); if (u) { hueComm.userName = u->stringValue(); hueComm.uuid = currentAuthCandidate->first; hueComm.baseURL = currentAuthCandidate->second; hueComm.apiReady = true; // can use API now FOCUSLOG("hue Bridge %s @ %s: successfully registered as user %s", hueComm.uuid.c_str(), hueComm.baseURL.c_str(), hueComm.userName.c_str()); // successfully registered with hue bridge, let caller know callback(ErrorPtr()); // done! keepAlive.reset(); // will delete object if nobody else keeps it return; } } } else { LOG(LOG_ERR, "hue Bridge: Error creating user: %s", aError->description().c_str()); } // try next ++currentAuthCandidate; processCurrentAuthCandidate(); // process next, if any }
JsonObjectPtr HueComm::getSuccessItem(JsonObjectPtr aResult, int aIndex) { if (aResult && aIndex<aResult->arrayLength()) { JsonObjectPtr responseItem = aResult->arrayGet(aIndex); JsonObjectPtr successItem; if (responseItem && responseItem->get("success", successItem)) { return successItem; } } return JsonObjectPtr(); }
void NetatmoDevice::updateData(JsonObjectPtr aJson) { if (aJson) { if (auto swVersionJson = aJson->get("firmware")) { swVersion->setStringValue(swVersionJson->stringValue()); } if (auto dashBoardData = aJson->get("dashboard_data")) { if (auto tempJson = dashBoardData->get("Temperature")) { sensorTemperature->updateSensorValue(tempJson->doubleValue()); } if (auto humidityJson = dashBoardData->get("Humidity")) { sensorHumidity->updateSensorValue(humidityJson->doubleValue()); } if (auto tempTrendJson = dashBoardData->get("temp_trend")) { auto statusTrend = getStatusTrend(tempTrendJson->stringValue()); if (statusTrend < StatusTrend::_num){ if (statusTempTrend->value()->setInt32Value(static_cast<int>(statusTrend))){ statusTempTrend->push(); } } } if (auto timestampJson = dashBoardData->get("time_utc")){ measurementAbsloluteTimestamp = timestampJson->int64Value(); auto secondsToday = getSecondsFromMidnight(measurementAbsloluteTimestamp); measurementTimestamp->setInt32Value(secondsToday.count()); } } } bool wasPresent = isPresent; isPresent = getElapsedHoursFromLastMeasure(measurementAbsloluteTimestamp).count() < LAST_MEASUREMENT_ELAPSED_HOURS_MAX; // if last measurement was measured longer than 12 hrs ago, set device to inactive if (wasPresent != isPresent && !isPresent) reportVanished(); }
void NetatmoDevice::setIdentificationData(JsonObjectPtr aJson) { if (aJson) { if (auto typeJson = aJson->get("type")) { netatmoType = typeJson->stringValue(); } if (auto idJson = aJson->get("_id")) { netatmoId = idJson->stringValue(); } if (auto nameJson = aJson->get("module_name")) { netatmoName = nameJson->stringValue(); initializeName(netatmoName); } if (auto fwJson = aJson->get("firmware")) { netatmoFw = fwJson->stringValue(); } } }
// access to plan44 extras that are not part of the vdc API ErrorPtr P44VdcHost::processP44Request(JsonCommPtr aJsonComm, JsonObjectPtr aRequest) { ErrorPtr err; JsonObjectPtr m = aRequest->get("method"); if (!m) { err = Error::err<P44VdcError>(400, "missing 'method'"); } else { string method = m->stringValue(); if (method=="learn") { // check proximity check disabling bool disableProximity = false; JsonObjectPtr o = aRequest->get("disableProximityCheck"); if (o) { disableProximity = o->boolValue(); } // get timeout o = aRequest->get("seconds"); int seconds = 30; // default to 30 if (o) seconds = o->int32Value(); if (seconds==0) { // end learning prematurely stopLearning(); MainLoop::currentMainLoop().cancelExecutionTicket(learnIdentifyTicket); // - close still running learn request if (learnIdentifyRequest) { learnIdentifyRequest->closeConnection(); learnIdentifyRequest.reset(); } // - confirm abort with no result sendCfgApiResponse(aJsonComm, JsonObjectPtr(), ErrorPtr()); } else { // start learning learnIdentifyRequest = aJsonComm; // remember so we can cancel it when we receive a separate cancel request startLearning(boost::bind(&P44VdcHost::learnHandler, this, aJsonComm, _1, _2), disableProximity); learnIdentifyTicket = MainLoop::currentMainLoop().executeOnce(boost::bind(&P44VdcHost::learnHandler, this, aJsonComm, false, Error::err<P44VdcError>(408, "learn timeout")), seconds*Second); } } else if (method=="identify") { // get timeout JsonObjectPtr o = aRequest->get("seconds"); int seconds = 30; // default to 30 if (o) seconds = o->int32Value(); if (seconds==0) { // end reporting user activity setUserActionMonitor(NULL); MainLoop::currentMainLoop().cancelExecutionTicket(learnIdentifyTicket); // - close still running identify request if (learnIdentifyRequest) { learnIdentifyRequest->closeConnection(); learnIdentifyRequest.reset(); } // - confirm abort with no result sendCfgApiResponse(aJsonComm, JsonObjectPtr(), ErrorPtr()); } else { // wait for next user activity learnIdentifyRequest = aJsonComm; // remember so we can cancel it when we receive a separate cancel request setUserActionMonitor(boost::bind(&P44VdcHost::identifyHandler, this, aJsonComm, _1)); learnIdentifyTicket = MainLoop::currentMainLoop().executeOnce(boost::bind(&P44VdcHost::identifyHandler, this, aJsonComm, DevicePtr()), seconds*Second); } } else if (method=="logLevel") { // get or set logging level for vdcd JsonObjectPtr o = aRequest->get("value"); if (o) { // set new value first int newLevel = o->int32Value(); int oldLevel = LOGLEVEL; SETLOGLEVEL(newLevel); LOG(LOG_WARNING, "\n\n========== changed log level from %d to %d ===============", oldLevel, newLevel); } // anyway: return current value sendCfgApiResponse(aJsonComm, JsonObject::newInt32(LOGLEVEL), ErrorPtr()); } else { err = Error::err<P44VdcError>(400, "unknown method"); } } return err; }
// access to vdc API methods and notifications via web requests ErrorPtr P44VdcHost::processVdcRequest(JsonCommPtr aJsonComm, JsonObjectPtr aRequest) { ErrorPtr err; string cmd; bool isMethod = false; // get method/notification and params JsonObjectPtr m = aRequest->get("method"); if (m) { // is a method call, expects answer isMethod = true; } else { // not method, may be notification m = aRequest->get("notification"); } if (!m) { err = Error::err<P44VdcError>(400, "invalid request, must specify 'method' or 'notification'"); } else { // get method/notification name cmd = m->stringValue(); // get params // Note: the "method" or "notification" param will also be in the params, but should not cause any problem ApiValuePtr params = JsonApiValue::newValueFromJson(aRequest); P44JsonApiRequestPtr request = P44JsonApiRequestPtr(new P44JsonApiRequest(aJsonComm)); if (Error::isOK(err)) { // operation method if (isMethod) { // create request // check for old-style name/index and generate basic query (1 or 2 levels) ApiValuePtr query = params->newObject(); ApiValuePtr name = params->get("name"); if (name) { ApiValuePtr index = params->get("index"); ApiValuePtr subquery = params->newNull(); if (index) { // subquery subquery->setType(apivalue_object); subquery->add(index->stringValue(), subquery->newNull()); } string nm = trimWhiteSpace(name->stringValue()); // to allow a single space for deep recursing wildcard query->add(nm, subquery); params->add("query", query); } // have method handled err = handleMethodForParams(request, cmd, params); // Note: if method returns NULL, it has sent or will send results itself. // Otherwise, even if Error is ErrorOK we must send a generic response } else { // handle notification err = handleNotificationForParams(request->connection(), cmd, params); // Notifications are always immediately confirmed, so make sure there's an explicit ErrorOK if (!err) { err = ErrorPtr(new Error(Error::OK)); } } } } // returning NULL means caller should not do anything more // returning an Error object (even ErrorOK) means caller should return status return err; }
void JsonRpcComm::gotJson(ErrorPtr aError, JsonObjectPtr aJsonObject) { JsonRpcCommPtr keepMeAlive(this); // make sure this object lives until routine terminates ErrorPtr respErr; bool safeError = false; // set when reporting error is safe (i.e. not error possibly generated by malformed error, to prevent error loops) JsonObjectPtr idObj; const char *idString = NULL; if (Error::isOK(aError)) { // received proper JSON, now check JSON-RPC specifics FOCUSLOG("Received JSON message:\n %s\n", aJsonObject->c_strValue()); if (aJsonObject->isType(json_type_array)) { respErr = ErrorPtr(new JsonRpcError(JSONRPC_INVALID_REQUEST, "Invalid Request - batch mode not supported by this implementation")); } else if (!aJsonObject->isType(json_type_object)) { respErr = ErrorPtr(new JsonRpcError(JSONRPC_INVALID_REQUEST, "Invalid Request - request must be JSON object")); } else { // check request object fields const char *method = NULL; JsonObjectPtr o = aJsonObject->get("jsonrpc"); if (!o) respErr = ErrorPtr(new JsonRpcError(JSONRPC_INVALID_REQUEST, "Invalid Request - missing 'jsonrpc'")); else if (o->stringValue()!="2.0") respErr = ErrorPtr(new JsonRpcError(JSONRPC_INVALID_REQUEST, "Invalid Request - wrong version in 'jsonrpc'")); else { // get ID param (must be present for all messages except notification) idObj = aJsonObject->get("id"); if (idObj) idString = idObj->c_strValue(); JsonObjectPtr paramsObj = aJsonObject->get("params"); // JSON-RPC version is correct, check other params method = aJsonObject->getCString("method"); if (method) { // this is a request (responses don't have the method member) safeError = idObj!=NULL; // reporting error is safe if this is a method call. Other errors are reported only when reportAllErrors is set if (*method==0) respErr = ErrorPtr(new JsonRpcError(JSONRPC_INVALID_REQUEST, "Invalid Request - empty 'method'")); else { // looks like a valid method or notification call if (!jsonRequestHandler) { // no handler -> method cannot be executed respErr = ErrorPtr(new JsonRpcError(JSONRPC_METHOD_NOT_FOUND, "Method not found")); } else { if (paramsObj && !paramsObj->isType(json_type_array) && !paramsObj->isType(json_type_object)) { // invalid param object respErr = ErrorPtr(new JsonRpcError(JSONRPC_INVALID_REQUEST, "Invalid Request - 'params' must be object or array")); } else { // call handler to execute method or notification jsonRequestHandler(method, idString, paramsObj); } } } } else { // this is a response (requests always have a method member) // - check if result or error JsonObjectPtr respObj; if (!aJsonObject->get("result", respObj)) { // must be error, need further decoding respObj = aJsonObject->get("error"); if (!respObj) respErr = ErrorPtr(new JsonRpcError(JSONRPC_INTERNAL_ERROR, "Internal JSON-RPC error - response with neither 'result' nor 'error'")); else { // dissect error object ErrorCode errCode = JSONRPC_INTERNAL_ERROR; // Internal RPC error const char *errMsg = "malformed Error response"; // - try to get error code JsonObjectPtr o = respObj->get("code"); if (o) errCode = o->int32Value(); // - try to get error message o = respObj->get("message"); if (o) errMsg = o->c_strValue(); // compose error object from this respErr = ErrorPtr(new JsonRpcError(errCode, errMsg)); // also get optional data element respObj = respObj->get("data"); } } // Now we have either result or error.data in respObj, and respErr is Ok or contains the error code + message if (!idObj) { // errors without ID cannot be associated with calls made earlier, so just log the error LOG(LOG_WARNING,"JSON-RPC 2.0 warning: Received response with no or NULL 'id' that cannot be dispatched:\n %s\n", aJsonObject->c_strValue()); } else { // dispatch by ID uint32_t requestId = idObj->int32Value(); PendingAnswerMap::iterator pos = pendingAnswers.find(requestId); if (pos==pendingAnswers.end()) { // errors without ID cannot be associated with calls made earlier, so just log the error LOG(LOG_WARNING,"JSON-RPC 2.0 error: Received response with unknown 'id'=%d : %s\n", requestId, aJsonObject->c_strValue()); } else { // found callback JsonRpcResponseCB cb = pos->second; pendingAnswers.erase(pos); // erase cb(requestId, respErr, respObj); // call } respErr.reset(); // handled } } } } } else { // no proper JSON received, create error response if (aError->isDomain(JsonError::domain())) { // some kind of parsing error respErr = ErrorPtr(new JsonRpcError(JSONRPC_PARSE_ERROR, aError->description())); } else { // some other type of server error respErr = ErrorPtr(new JsonRpcError(JSONRPC_SERVER_ERROR, aError->description())); } } // auto-generate error response for internally created errors if (!Error::isOK(respErr)) { if (safeError || reportAllErrors) sendError(idString, respErr); else LOG(LOG_WARNING,"Received data that generated error which can't be sent back: Code=%d, Message='%s'\n", respErr->getErrorCode(), respErr->description().c_str()); } }