/* ****************************************************************************
*
* httpRequestConnect -
*/
int httpRequestConnect(const std::string& host, unsigned short port)
{
    int                 fd;
    struct addrinfo     hints;
    struct addrinfo*    peer;
    char                port_str[10];

    LM_TRANSACTION_START("to", ip.c_str(), port, resource.c_str());

    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = 0;

    if (ipVersionUsed == IPV4)
    {
        hints.ai_family = AF_INET;
        LM_T(LmtIpVersion, ("Allow IPv4 only"));
    }
    else if (ipVersionUsed == IPV6)
    {
        hints.ai_family = AF_INET6;
        LM_T(LmtIpVersion, ("Allow  IPv6 only"));
    }
    else
    {
        hints.ai_family = AF_UNSPEC;
        LM_T(LmtIpVersion, ("Allow IPv4 or IPv6"));
    }

    snprintf(port_str, sizeof(port_str), "%d" , (int) port);

    if (getaddrinfo(host.c_str(), port_str, &hints, &peer) != 0)
    {
        LM_W(("Notification failure for %s:%d (getaddrinfo: %s)", host.c_str(), port, strerror(errno)));
        LM_TRANSACTION_END();
        return -1;
    }

    if ((fd = socket(peer->ai_family, peer->ai_socktype, peer->ai_protocol)) == -1)
    {
        LM_W(("Notification failure for %s:%d (socket: %s)", host.c_str(), port, strerror(errno)));
        LM_TRANSACTION_END();
        return -1;
    }

    if (connect(fd, peer->ai_addr, peer->ai_addrlen) == -1)
    {
        freeaddrinfo(peer);
        close(fd);
        LM_W(("Notification failure for %s:%d (connect: %s)", host.c_str(), port, strerror(errno)));
        LM_TRANSACTION_END();
        return -1;
    }

    freeaddrinfo(peer);
    LM_TRANSACTION_END();
    return fd;
}
Exemple #2
0
/* ****************************************************************************
*
* connectionTreat - 
*
* This is the MHD_AccessHandlerCallback function for MHD_start_daemon
* This function returns:
* o MHD_YES  if the connection was handled successfully
* o MHD_NO   if the socket must be closed due to a serious error
*
* - This function is called once when the headers are read and the ciP is created.
* - Then it is called for data payload and once all the payload an acknowledgement
*   must be done, setting *upload_data_size to ZERO.
* - The last call is made with *upload_data_size == 0 and now is when the connection
*   is open to send responses.
*
* Call 1: *con_cls == NULL
* Call 2: *con_cls != NULL  AND  *upload_data_size != 0
* Call 3: *con_cls != NULL  AND  *upload_data_size == 0
*/
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)
  {
    //
    // 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)
    {
      port = (mciP->client_addr->sa_data[0] << 8) + mciP->client_addr->sa_data[1];
      snprintf(ip, sizeof(ip), "%d.%d.%d.%d", mciP->client_addr->sa_data[2]  & 0xFF, mciP->client_addr->sa_data[3]  & 0xFF, mciP->client_addr->sa_data[4] & 0xFF, mciP->client_addr->sa_data[5] & 0xFF);
    }
    else
    {
      port = 0;
      snprintf(ip, sizeof(ip), "IP unknown");
    }



    //
    // ConnectionInfo
    //
    if ((ciP = new ConnectionInfo(url, method, version, connection)) == NULL)
    {
      LM_E(("Runtime Error (error allocating ConnectionInfo)"));
      return MHD_NO;
    }

    *con_cls = (void*) ciP; // Pointer to ConnectionInfo for subsequent calls
    ciP->port = port;
    ciP->ip   = ip;
    
    //
    // Transaction starts here
    //
    LM_TRANSACTION_START("from", ip, port, url);  // Incoming REST request starts



    //
    // URI parameters
    // 
    ciP->uriParam[URI_PARAM_NOTIFY_FORMAT]      = DEFAULT_PARAM_NOTIFY_FORMAT;
    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_GET_ARGUMENT_KIND, uriArgumentGet, ciP);
    if (ciP->httpStatusCode != SccOk)
    {
      LM_W(("Bad Input (error in URI parameters)"));
      restReply(ciP, ciP->answer);
      return MHD_YES;
    }
    LM_T(LmtUriParams, ("notifyFormat: '%s'", ciP->uriParam[URI_PARAM_NOTIFY_FORMAT].c_str()));

    MHD_get_connection_values(connection, MHD_HEADER_KIND, httpHeaderGet, &ciP->httpHeaders);
    
    char tenant[128];
    ciP->tenantFromHttpHeader = strToLower(tenant, ciP->httpHeaders.tenant.c_str(), sizeof(tenant));
    LM_T(LmtTenant, ("HTTP tenant: '%s'", ciP->httpHeaders.tenant.c_str()));
    ciP->outFormat            = wantedOutputSupported(ciP->httpHeaders.accept, &ciP->charset);
    if (ciP->outFormat == NOFORMAT)
      ciP->outFormat = XML; // XML is default output format

    ciP->servicePath = ciP->httpHeaders.servicePath;
    if (servicePathSplit(ciP) != 0)
    {
      LM_W(("Bad Input (error in ServicePath http-header)"));
      restReply(ciP, ciP->answer);
    }

    if (contentTypeCheck(ciP) != 0)
    {
      LM_W(("Bad Input (invalid mime-type in Content-Type http-header)"));
      restReply(ciP, ciP->answer);
    }
    else if (outFormatCheck(ciP) != 0)
    {
      LM_W(("Bad Input (invalid mime-type in Accept http-header)"));
      restReply(ciP, ciP->answer);
    }
    else
      ciP->inFormat = formatParse(ciP->httpHeaders.contentType, NULL);

    // Set default mime-type for notifications
    if (ciP->uriParam[URI_PARAM_NOTIFY_FORMAT] == "")
    {
      if (ciP->outFormat == XML)
        ciP->uriParam[URI_PARAM_NOTIFY_FORMAT] = "XML";
      else if (ciP->outFormat == JSON)
        ciP->uriParam[URI_PARAM_NOTIFY_FORMAT] = "JSON";
      else
        ciP->uriParam[URI_PARAM_NOTIFY_FORMAT] = "XML";

      LM_T(LmtUriParams, ("'default' value for notifyFormat (ciP->outFormat == %d)): '%s'", ciP->outFormat, ciP->uriParam[URI_PARAM_NOTIFY_FORMAT].c_str()));
    }

    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 (dataLen == ciP->httpHeaders.contentLength)
    {
      if (ciP->httpHeaders.contentLength <= PAYLOAD_MAX_SIZE)
      {
        if (ciP->httpHeaders.contentLength > STATIC_BUFFER_SIZE)
          ciP->payload = (char*) malloc(ciP->httpHeaders.contentLength + 1);
        else
          ciP->payload = static_buffer;

        ciP->payloadSize = dataLen;
        memcpy(ciP->payload, upload_data, dataLen);
        ciP->payload[dataLen] = 0;
      }
      else
      {
        char details[256];
        snprintf(details, sizeof(details), "payload size: %d, max size supported: %d", ciP->httpHeaders.contentLength, PAYLOAD_MAX_SIZE);

        ciP->answer         = restErrorReplyGet(ciP, ciP->outFormat, "", ciP->url, SccRequestEntityTooLarge, details);
        ciP->httpStatusCode = SccRequestEntityTooLarge;
      }

      // All payload received, acknowledge!
      *upload_data_size = 0;
    }
    else
      LM_T(LmtPartialPayload, ("Got %d of payload of %d bytes", dataLen, ciP->httpHeaders.contentLength));

    return MHD_YES;
  }


  // 3. Finally, serve the request (unless an error has occurred)
  if (((ciP->verb == POST) || (ciP->verb == PUT)) && (ciP->httpHeaders.contentLength == 0) && (strncasecmp(ciP->url.c_str(), "/log/", 5) != 0))
  {
    std::string errorMsg = restErrorReplyGet(ciP, ciP->outFormat, "", url, SccLengthRequired, "Zero/No Content-Length in PUT/POST request");
    ciP->httpStatusCode = SccLengthRequired;
    restReply(ciP, errorMsg);
  }
  else if (ciP->answer != "")
    restReply(ciP, ciP->answer);
  else
    serveFunction(ciP);

  return MHD_YES;
}
/* ****************************************************************************
*
* httpRequestSend -
*/
std::string 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&     content_type,
    const std::string&     content,
    bool                   useRush,
    bool                   waitForResponse,
    const std::string&     acceptFormat,
    long                   timeoutInMilliseconds
)
{
    char                       portAsString[16];
    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;
    CURL*                      curl;
    struct curl_context        cc;

    ++callNo;

    if (timeoutInMilliseconds == -1)
    {
        timeoutInMilliseconds = defaultTimeout;
    }

    LM_TRANSACTION_START("to", ip.c_str(), port, resource.c_str());

    // Preconditions check
    if (port == 0)
    {
        LM_E(("Runtime Error (port is ZERO)"));
        LM_TRANSACTION_END();
        return "error";
    }

    if (ip.empty())
    {
        LM_E(("Runtime Error (ip is empty)"));
        LM_TRANSACTION_END();
        return "error";
    }

    if (verb.empty())
    {
        LM_E(("Runtime Error (verb is empty)"));
        LM_TRANSACTION_END();
        return "error";
    }

    if (resource.empty())
    {
        LM_E(("Runtime Error (resource is empty)"));
        LM_TRANSACTION_END();
        return "error";
    }

    if ((content_type.empty()) && (!content.empty()))
    {
        LM_E(("Runtime Error (Content-Type is empty but there is actual content)"));
        LM_TRANSACTION_END();
        return "error";
    }

    if ((!content_type.empty()) && (content.empty()))
    {
        LM_E(("Runtime Error (Content-Type non-empty but there is no content)"));
        LM_TRANSACTION_END();
        return "error";
    }

    get_curl_context(ip, &cc);
    if ((curl = cc.curl) == NULL)
    {
        LM_E(("Runtime Error (could not init libcurl)"));
        LM_TRANSACTION_END();
        release_curl_context(&cc);
        return "error";
    }

    // 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[16];
        uint16_t     rushHeaderPort     = 0;
        std::string  rushHeaderIP       = "";
        std::string  headerRushHttp     = "";

        rushHeaderIP   = ip;
        rushHeaderPort = port;
        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), "%d", 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/xml, 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: "


    // 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));

        // Cleanup curl environment
        curl_slist_free_all(headers);
        release_curl_context(&cc);

        free(httpResponse->memory);
        delete httpResponse;

        LM_TRANSACTION_END();
        return "error";
    }

    // 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
    LM_T(LmtClientOutputPayload, ("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.
        //
        LM_W(("Notification failure for %s:%s (curl_easy_perform failed: %s)", ip.c_str(), portAsString, curl_easy_strerror(res)));
        result = "";
    }
    else
    {
        // The Response is here
        LM_I(("Notification Successfully Sent to %s", url.c_str()));
        result.assign(httpResponse->memory, httpResponse->size);
    }

    // Cleanup curl environment
    curl_slist_free_all(headers);
    release_curl_context(&cc);

    free(httpResponse->memory);
    delete httpResponse;

    LM_TRANSACTION_END();

    return result;
}