/* **************************************************************************** * * httpRequestSend - * * RETURN VALUES * httpRequestSend returns 0 on success and a negative number on failure: * -1: Invalid port * -2: Invalid IP * -3: Invalid verb * -4: Invalid resource * -5: No Content-Type BUT content present * -6: Content-Type present but there is no content * -7: Total outgoing message size is too big * -8: Unable to initialize libcurl * -9: Error making HTTP request * * [ error codes -1 to -7 comes from httpRequestSendWithCurl ] */ int httpRequestSend ( const std::string& _ip, unsigned short port, const std::string& protocol, const std::string& verb, const std::string& tenant, const std::string& servicePath, const std::string& xauthToken, const std::string& resource, const std::string& orig_content_type, const std::string& content, const std::string& fiwareCorrelation, const std::string& ngisv2AttrFormat, bool useRush, bool waitForResponse, std::string* outP, const std::string& acceptFormat, long timeoutInMilliseconds ) { struct curl_context cc; int response; get_curl_context(_ip, &cc); if (cc.curl == NULL) { release_curl_context(&cc); LM_E(("Runtime Error (could not init libcurl)")); lmTransactionEnd(); *outP = "error"; return -8; } response = httpRequestSendWithCurl(cc.curl, _ip, port, protocol, verb, tenant, servicePath, xauthToken, resource, orig_content_type, content, fiwareCorrelation, ngisv2AttrFormat, useRush, waitForResponse, outP, acceptFormat, timeoutInMilliseconds); release_curl_context(&cc); return response; }
/* **************************************************************************** * * httpRequestSendWithCurl - * * The waitForResponse arguments specifies if the method has to wait for response * before return. If this argument is false, the return string is "" * * NOTE * We are using a hybrid approach, consisting in a static thread-local buffer of a * small size that copes with most notifications to avoid expensive * calloc/free syscalls if the notification payload is not very large. * * RETURN VALUES * httpRequestSendWithCurl returns 0 on success and a negative number on failure: * -1: Invalid port * -2: Invalid IP * -3: Invalid verb * -4: Invalid resource * -5: No Content-Type BUT content present * -6: Content-Type present but there is no content * -7: Total outgoing message size is too big * -9: Error making HTTP request */ int httpRequestSendWithCurl ( CURL *curl, const std::string& _ip, unsigned short port, const std::string& protocol, const std::string& verb, const std::string& tenant, const std::string& servicePath, const std::string& xauthToken, const std::string& resource, const std::string& orig_content_type, const std::string& content, const std::string& fiwareCorrelation, const std::string& ngisv2AttrFormat, bool useRush, bool waitForResponse, std::string* outP, const std::string& acceptFormat, long timeoutInMilliseconds ) { char portAsString[STRING_SIZE_FOR_INT]; static unsigned long long callNo = 0; std::string result; std::string ip = _ip; struct curl_slist* headers = NULL; MemoryStruct* httpResponse = NULL; CURLcode res; int outgoingMsgSize = 0; std::string content_type(orig_content_type); ++callNo; // For content-type application/json we add charset=utf-8 if (orig_content_type == "application/json") { content_type += "; charset=utf-8"; } if (timeoutInMilliseconds == -1) { timeoutInMilliseconds = defaultTimeout; } lmTransactionStart("to", ip.c_str(), port, resource.c_str()); // Preconditions check if (port == 0) { LM_E(("Runtime Error (port is ZERO)")); lmTransactionEnd(); *outP = "error"; return -1; } if (ip.empty()) { LM_E(("Runtime Error (ip is empty)")); lmTransactionEnd(); *outP = "error"; return -2; } if (verb.empty()) { LM_E(("Runtime Error (verb is empty)")); lmTransactionEnd(); *outP = "error"; return -3; } if (resource.empty()) { LM_E(("Runtime Error (resource is empty)")); lmTransactionEnd(); *outP = "error"; return -4; } if ((content_type.empty()) && (!content.empty())) { LM_E(("Runtime Error (Content-Type is empty but there is actual content)")); lmTransactionEnd(); *outP = "error"; return -5; } if ((!content_type.empty()) && (content.empty())) { LM_E(("Runtime Error (Content-Type non-empty but there is no content)")); lmTransactionEnd(); *outP = "error"; return -6; } // Allocate to hold HTTP response httpResponse = new MemoryStruct; httpResponse->memory = (char*) malloc(1); // will grow as needed httpResponse->size = 0; // no data at this point // // Rush // Every call to httpRequestSend specifies whether RUSH should be used or not. // But, this depends also on how the broker was started, so here the 'useRush' // parameter is cancelled in case the broker was started without rush. // // If rush is to be used, the IP/port is stored in rushHeaderIP/rushHeaderPort and // after that, the host and port of rush is set as ip/port for the message, that is // now sent to rush instead of to its final destination. // Also, a few HTTP headers for rush must be setup. // if ((rushPort == 0) || (rushHost == "")) { useRush = false; } if (useRush) { char rushHeaderPortAsString[STRING_SIZE_FOR_INT]; uint16_t rushHeaderPort = port; std::string rushHeaderIP = ip; std::string headerRushHttp; ip = rushHost; port = rushPort; snprintf(rushHeaderPortAsString, sizeof(rushHeaderPortAsString), "%d", rushHeaderPort); headerRushHttp = "X-relayer-host: " + rushHeaderIP + ":" + rushHeaderPortAsString; LM_T(LmtHttpHeaders, ("HTTP-HEADERS: '%s'", headerRushHttp.c_str())); headers = curl_slist_append(headers, headerRushHttp.c_str()); outgoingMsgSize += headerRushHttp.size(); if (protocol == "https:") { headerRushHttp = "X-relayer-protocol: https"; LM_T(LmtHttpHeaders, ("HTTP-HEADERS: '%s'", headerRushHttp.c_str())); headers = curl_slist_append(headers, headerRushHttp.c_str()); outgoingMsgSize += headerRushHttp.size(); } } snprintf(portAsString, sizeof(portAsString), "%u", port); // ----- User Agent char cvBuf[CURL_VERSION_MAX_LENGTH]; char headerUserAgent[HTTP_HEADER_USER_AGENT_MAX_LENGTH]; snprintf(headerUserAgent, sizeof(headerUserAgent), "User-Agent: orion/%s libcurl/%s", versionGet(), curlVersionGet(cvBuf, sizeof(cvBuf))); LM_T(LmtHttpHeaders, ("HTTP-HEADERS: '%s'", headerUserAgent)); headers = curl_slist_append(headers, headerUserAgent); outgoingMsgSize += strlen(headerUserAgent) + 1; // ----- Host char headerHost[HTTP_HEADER_HOST_MAX_LENGTH]; snprintf(headerHost, sizeof(headerHost), "Host: %s:%d", ip.c_str(), (int) port); LM_T(LmtHttpHeaders, ("HTTP-HEADERS: '%s'", headerHost)); headers = curl_slist_append(headers, headerHost); outgoingMsgSize += strlen(headerHost) + 1; // ----- Tenant if (tenant != "") { headers = curl_slist_append(headers, ("fiware-service: " + tenant).c_str()); outgoingMsgSize += tenant.size() + 16; // "fiware-service: " } // ----- Service-Path if (servicePath != "") { headers = curl_slist_append(headers, ("Fiware-ServicePath: " + servicePath).c_str()); outgoingMsgSize += servicePath.size() + strlen("Fiware-ServicePath: "); } // ----- X-Auth-Token if (xauthToken != "") { headers = curl_slist_append(headers, ("X-Auth-Token: " + xauthToken).c_str()); outgoingMsgSize += xauthToken.size() + strlen("X-Auth-Token: "); } // ----- Accept std::string acceptedFormats = "application/json"; if (acceptFormat != "") { acceptedFormats = acceptFormat; } std::string acceptString = "Accept: " + acceptedFormats; headers = curl_slist_append(headers, acceptString.c_str()); outgoingMsgSize += acceptString.size(); // ----- Expect headers = curl_slist_append(headers, "Expect: "); outgoingMsgSize += 8; // from "Expect: " // ----- Content-length std::stringstream contentLengthStringStream; contentLengthStringStream << content.size(); std::string headerContentLength = "Content-length: " + contentLengthStringStream.str(); LM_T(LmtHttpHeaders, ("HTTP-HEADERS: '%s'", headerContentLength.c_str())); headers = curl_slist_append(headers, headerContentLength.c_str()); outgoingMsgSize += contentLengthStringStream.str().size() + 16; // from "Content-length: " outgoingMsgSize += content.size(); // ----- Content-type headers = curl_slist_append(headers, ("Content-type: " + content_type).c_str()); outgoingMsgSize += content_type.size() + 14; // from "Content-type: " // Fiware-Correlator std::string correlation = "Fiware-Correlator: " + fiwareCorrelation; headers = curl_slist_append(headers, correlation.c_str()); outgoingMsgSize += correlation.size(); // Notify Format if ((ngisv2AttrFormat != "") && (ngisv2AttrFormat != "JSON") && (ngisv2AttrFormat != "legacy")) { std::string nFormat = "X-Ngsiv2-AttrsFormat: " + ngisv2AttrFormat; headers = curl_slist_append(headers, nFormat.c_str()); outgoingMsgSize += nFormat.size(); } // Check if total outgoing message size is too big if (outgoingMsgSize > MAX_DYN_MSG_SIZE) { LM_E(("Runtime Error (HTTP request to send is too large: %d bytes)", outgoingMsgSize)); curl_slist_free_all(headers); free(httpResponse->memory); delete httpResponse; lmTransactionEnd(); *outP = "error"; return -7; } // Contents const char* payload = content.c_str(); curl_easy_setopt(curl, CURLOPT_POSTFIELDS, (u_int8_t*) payload); // Set up URL std::string url; if (isIPv6(ip)) url = "[" + ip + "]"; else url = ip; url = url + ":" + portAsString + (resource.at(0) == '/'? "" : "/") + resource; // Prepare CURL handle with obtained options curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, verb.c_str()); // Set HTTP verb curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); // Allow redirection (?) curl_easy_setopt(curl, CURLOPT_HEADER, 1); // Activate include the header in the body output curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); // Put headers in place curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &writeMemoryCallback); // Send data here curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void*) httpResponse); // Custom data for response handling // // There is a known problem in libcurl (see http://stackoverflow.com/questions/9191668/error-longjmp-causes-uninitialized-stack-frame) // which is solved using CURLOPT_NOSIGNAL. If we update some day from libcurl 7.19 (the one that comes with CentOS 6.x) to a newer version // (there are some reports about the bug is no longer in libcurl 7.32), using CURLOPT_NOSIGNAL could be not necessary and this be removed). // See issue #1016 for more details. // curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1); // // Timeout // // The parameter timeoutInMilliseconds holds the timeout time in milliseconds. // If the timeout time requested is 0, then no timeuot is used. // if (timeoutInMilliseconds != 0) { curl_easy_setopt(curl, CURLOPT_TIMEOUT_MS, timeoutInMilliseconds); } // Synchronous HTTP request // This was previously a LM_T trace, but we have "promoted" it to INFO due to it is needed to check logs in a .test case (case 000 notification_different_sizes.test) LM_I(("Sending message %lu to HTTP server: sending message of %d bytes to HTTP server", callNo, outgoingMsgSize)); res = curl_easy_perform(curl); if (res != CURLE_OK) { // // NOTE: This log line is used by the functional tests in cases/880_timeout_for_forward_and_notifications/ // So, this line should not be removed/altered, at least not without also modifying the functests. // alarmMgr.notificationError(url, "(curl_easy_perform failed: " + std::string(curl_easy_strerror(res)) + ")"); *outP = "notification failure"; } else { // The Response is here LM_I(("Notification Successfully Sent to %s", url.c_str())); outP->assign(httpResponse->memory, httpResponse->size); } // Cleanup curl environment curl_slist_free_all(headers); free(httpResponse->memory); delete httpResponse; lmTransactionEnd(); return res == CURLE_OK ? 0 : -9; }
/* **************************************************************************** * * requestCompleted - */ static void requestCompleted ( void* cls, MHD_Connection* connection, void** con_cls, MHD_RequestTerminationCode toe ) { ConnectionInfo* ciP = (ConnectionInfo*) *con_cls; struct timespec reqEndTime; if ((ciP->payload != NULL) && (ciP->payload != static_buffer)) { free(ciP->payload); } *con_cls = NULL; lmTransactionEnd(); // Incoming REST request ends if (timingStatistics) { clock_gettime(CLOCK_REALTIME, &reqEndTime); clock_difftime(&reqEndTime, &ciP->reqStartTime, &threadLastTimeStat.reqTime); } delete(ciP); // // Statistics // // Flush this requests timing measures onto a global var to be read by "GET /statistics". // Also, increment the accumulated measures. // if (timingStatistics) { timeStatSemTake(__FUNCTION__, "updating statistics"); memcpy(&lastTimeStat, &threadLastTimeStat, sizeof(lastTimeStat)); // // "Fix" mongoBackendTime // Substract times waiting at mongo driver operation (in mongo[Read|Write|Command]WaitTime counters) so mongoBackendTime // contains at the end the time passed in our logic, i.e. a kind of "self-time" for mongoBackend // clock_subtime(&threadLastTimeStat.mongoBackendTime, &threadLastTimeStat.mongoReadWaitTime); clock_subtime(&threadLastTimeStat.mongoBackendTime, &threadLastTimeStat.mongoWriteWaitTime); clock_subtime(&threadLastTimeStat.mongoBackendTime, &threadLastTimeStat.mongoCommandWaitTime); clock_addtime(&accTimeStat.jsonV1ParseTime, &threadLastTimeStat.jsonV1ParseTime); clock_addtime(&accTimeStat.jsonV2ParseTime, &threadLastTimeStat.jsonV2ParseTime); clock_addtime(&accTimeStat.mongoBackendTime, &threadLastTimeStat.mongoBackendTime); clock_addtime(&accTimeStat.mongoWriteWaitTime, &threadLastTimeStat.mongoWriteWaitTime); clock_addtime(&accTimeStat.mongoReadWaitTime, &threadLastTimeStat.mongoReadWaitTime); clock_addtime(&accTimeStat.mongoCommandWaitTime, &threadLastTimeStat.mongoCommandWaitTime); clock_addtime(&accTimeStat.renderTime, &threadLastTimeStat.renderTime); clock_addtime(&accTimeStat.reqTime, &threadLastTimeStat.reqTime); timeStatSemGive(__FUNCTION__, "updating statistics"); } }