/* **************************************************************************** * * 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; }
static int connectionTreat ( void* cls, MHD_Connection* connection, const char* url, const char* method, const char* version, const char* upload_data, size_t* upload_data_size, void** con_cls ) { ConnectionInfo* ciP = (ConnectionInfo*) *con_cls; size_t dataLen = *upload_data_size; // 1. First call - setup ConnectionInfo and get/check HTTP headers if (ciP == NULL) { // // First thing to do on a new connection, set correlator to N/A. // After reading HTTP headers, the correlator id either changes due to encountering a // Fiware-Correlator HTTP Header, or, if no HTTP header with Fiware-Correlator is found, // a new correlator is generated. // correlatorIdSet("N/A"); // // IP Address and port of caller // char ip[32]; unsigned short port = 0; const union MHD_ConnectionInfo* mciP = MHD_get_connection_info(connection, MHD_CONNECTION_INFO_CLIENT_ADDRESS); if (mciP != NULL) { struct sockaddr* addr = (struct sockaddr*) mciP->client_addr; port = (addr->sa_data[0] << 8) + addr->sa_data[1]; snprintf(ip, sizeof(ip), "%d.%d.%d.%d", addr->sa_data[2] & 0xFF, addr->sa_data[3] & 0xFF, addr->sa_data[4] & 0xFF, addr->sa_data[5] & 0xFF); snprintf(clientIp, sizeof(clientIp), "%s", ip); } else { port = 0; snprintf(ip, sizeof(ip), "IP unknown"); } // // Reset time measuring? // if (timingStatistics) { memset(&threadLastTimeStat, 0, sizeof(threadLastTimeStat)); } // // ConnectionInfo // // FIXME P1: ConnectionInfo could be a thread variable (like the static_buffer), // as long as it is properly cleaned up between calls. // We would save the call to new/free for each and every request. // Once we *really* look to scratch some efficiency, this change should be made. // // Also, is ciP->ip really used? // if ((ciP = new ConnectionInfo(url, method, version, connection)) == NULL) { LM_E(("Runtime Error (error allocating ConnectionInfo)")); return MHD_NO; } if (timingStatistics) { clock_gettime(CLOCK_REALTIME, &ciP->reqStartTime); } LM_T(LmtRequest, ("")); // WARNING: This log message below is crucial for the correct function of the Behave tests - CANNOT BE REMOVED LM_T(LmtRequest, ("--------------------- Serving request %s %s -----------------", method, url)); *con_cls = (void*) ciP; // Pointer to ConnectionInfo for subsequent calls ciP->port = port; ciP->ip = ip; ciP->callNo = reqNo; ++reqNo; // // URI parameters // // FIXME P1: We might not want to do all these assignments, they are not used in all requests ... // Once we *really* look to scratch some efficiency, this change should be made. // ciP->uriParam[URI_PARAM_PAGINATION_OFFSET] = DEFAULT_PAGINATION_OFFSET; ciP->uriParam[URI_PARAM_PAGINATION_LIMIT] = DEFAULT_PAGINATION_LIMIT; ciP->uriParam[URI_PARAM_PAGINATION_DETAILS] = DEFAULT_PAGINATION_DETAILS; MHD_get_connection_values(connection, MHD_HEADER_KIND, httpHeaderGet, &ciP->httpHeaders); char correlator[CORRELATOR_ID_SIZE + 1]; if (ciP->httpHeaders.correlator == "") { correlatorGenerate(correlator); ciP->httpHeaders.correlator = correlator; } correlatorIdSet(ciP->httpHeaders.correlator.c_str()); ciP->httpHeader.push_back("Fiware-Correlator"); ciP->httpHeaderValue.push_back(ciP->httpHeaders.correlator); // // Transaction starts here // lmTransactionStart("from", ip, port, url); // Incoming REST request starts /* X-Forwared-For (used by a potential proxy on top of Orion) overrides ip */ if (ciP->httpHeaders.xforwardedFor == "") { lmTransactionSetFrom(ip); } else { lmTransactionSetFrom(ciP->httpHeaders.xforwardedFor.c_str()); } ciP->apiVersion = apiVersionGet(ciP->url.c_str()); char tenant[SERVICE_NAME_MAX_LEN + 1]; ciP->tenantFromHttpHeader = strToLower(tenant, ciP->httpHeaders.tenant.c_str(), sizeof(tenant)); ciP->outMimeType = wantedOutputSupported(ciP->apiVersion, ciP->httpHeaders.accept, &ciP->charset); if (ciP->outMimeType == NOMIMETYPE) { ciP->outMimeType = JSON; // JSON is default output mimeType } MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, uriArgumentGet, ciP); return MHD_YES; } // // 2. Data gathering calls // // 2-1. Data gathering calls, just wait // 2-2. Last data gathering call, acknowledge the receipt of data // if (dataLen != 0) { // // If the HTTP header says the request is bigger than our PAYLOAD_MAX_SIZE, // just silently "eat" the entire message // if (ciP->httpHeaders.contentLength > PAYLOAD_MAX_SIZE) { *upload_data_size = 0; return MHD_YES; } // // First call with payload - use the thread variable "static_buffer" if possible, // otherwise allocate a bigger buffer // // FIXME P1: This could be done in "Part I" instead, saving an "if" for each "Part II" call // Once we *really* look to scratch some efficiency, this change should be made. // if (ciP->payloadSize == 0) // First call with payload { if (ciP->httpHeaders.contentLength > STATIC_BUFFER_SIZE) { ciP->payload = (char*) malloc(ciP->httpHeaders.contentLength + 1); } else { ciP->payload = static_buffer; } } // Copy the chunk LM_T(LmtPartialPayload, ("Got %d of payload of %d bytes", dataLen, ciP->httpHeaders.contentLength)); memcpy(&ciP->payload[ciP->payloadSize], upload_data, dataLen); // Add to the size of the accumulated read buffer ciP->payloadSize += *upload_data_size; // Zero-terminate the payload ciP->payload[ciP->payloadSize] = 0; // Acknowledge the data and return *upload_data_size = 0; return MHD_YES; } // // 3. Finally, serve the request (unless an error has occurred) // // URL and headers checks are delayed to the "third" MHD call, as no // errors can be sent before all the request has been read // if (urlCheck(ciP, ciP->url) == false) { alarmMgr.badInput(clientIp, "error in URI path"); restReply(ciP, ciP->answer); } ciP->servicePath = ciP->httpHeaders.servicePath; lmTransactionSetSubservice(ciP->servicePath.c_str()); if (servicePathSplit(ciP) != 0) { alarmMgr.badInput(clientIp, "error in ServicePath http-header"); restReply(ciP, ciP->answer); } if (contentTypeCheck(ciP) != 0) { alarmMgr.badInput(clientIp, "invalid mime-type in Content-Type http-header"); restReply(ciP, ciP->answer); } else if (outMimeTypeCheck(ciP) != 0) { alarmMgr.badInput(clientIp, "invalid mime-type in Accept http-header"); restReply(ciP, ciP->answer); } else { ciP->inMimeType = mimeTypeParse(ciP->httpHeaders.contentType, NULL); } if (ciP->httpStatusCode != SccOk) { alarmMgr.badInput(clientIp, "error in URI parameters"); restReply(ciP, ciP->answer); return MHD_YES; } // // Here, if the incoming request was too big, return error about it // if (ciP->httpHeaders.contentLength > PAYLOAD_MAX_SIZE) { char details[256]; snprintf(details, sizeof(details), "payload size: %d, max size supported: %d", ciP->httpHeaders.contentLength, PAYLOAD_MAX_SIZE); alarmMgr.badInput(clientIp, details); ciP->answer = restErrorReplyGet(ciP, "", ciP->url, SccRequestEntityTooLarge, details); ciP->httpStatusCode = SccRequestEntityTooLarge; } // // Requests of verb POST, PUT or PATCH are considered erroneous if no payload is present - with two exceptions. // // - Old log requests (URL contains '/log/') // - New log requests (URL is exactly '/admin/log') // if (((ciP->verb == POST) || (ciP->verb == PUT) || (ciP->verb == PATCH )) && (ciP->httpHeaders.contentLength == 0) && ((strncasecmp(ciP->url.c_str(), "/log/", 5) != 0) && (strncasecmp(ciP->url.c_str(), "/admin/log", 10) != 0))) { std::string errorMsg = restErrorReplyGet(ciP, "", url, SccContentLengthRequired, "Zero/No Content-Length in PUT/POST/PATCH request"); ciP->httpStatusCode = SccContentLengthRequired; restReply(ciP, errorMsg); alarmMgr.badInput(clientIp, errorMsg); } else if (ciP->answer != "") { alarmMgr.badInput(clientIp, ciP->answer); restReply(ciP, ciP->answer); } else { serveFunction(ciP); } return MHD_YES; }