Salesforce/PHP — исходящие сообщения (SOAP) — проблема с ограничением памяти? DOMDocument::loadXML() Преждевременный конец данных в проблеме с тегом?

ОБНОВИТЬ:

ОК, я понял, похоже, у fread есть ограничение на размер файла, изменил это на

file_get_contents('php://input')

, но теперь, когда SF дает java.net.SocketTimeoutException: ошибка ожидания чтения и ничего на стороне PHP. Я также добавил set_time_limit(0); к скрипту PHP, который, если я правильно понимаю, выполняет скрипт столько времени, сколько потребуется. Есть предположения?

Кстати: я могу обработать до 25 (которые я тестировал), но не 100


Я использую Salesforce для отправки исходящих сообщений (через SOAP) на другой сервер. Сервер может обрабатывать около 8 сообщений за раз, но не будет отправлять обратно файл ACK, если запрос SOAP содержит более 8 сообщений. SF может отправлять до 100 исходящих сообщений в 1 запросе SOAP, и я думаю, что это вызывает проблему с памятью в PHP. Если я обрабатываю исходящие сообщения 1 за 1, все они проходят нормально, я даже могу обрабатывать 8 за раз без проблем. Но большие наборы не работают.

ОШИБКА в СФ:

org.xml.sax.SAXParseException: Premature end of file

Глядя в журналы ошибок HTTP, я вижу, что входящее сообщение SOAP выглядит обрезанным, из-за чего появляется предупреждение PHP о том, что:

DOMDocument::loadXML() ... Premature end of data in tag ...

Неустранимая ошибка PHP:

Call to a member function getAttribute() on a non-object

Это наводит меня на мысль, что у PHP проблемы с памятью, и он не может проанализировать входящее сообщение из-за его размера.

Я думал, что могу просто установить:

ini_set('memory_limit', '64M'); // This has done nothing to fix the problem

Но будет ли это правильным подходом? Есть ли способ, которым я мог бы установить это для динамического увеличения входящего запроса SOAP?

ОБНОВЛЕНИЕ: Добавление кода

 /**
 * To parse out incoming SOAP requests and insert the values into a database table
 * 
 * {@link http://www.mikesimonds.com/salesforce-php-tutorials/95-using-salesforce-outbound-soap-messages-php-2.html}
 */

// Might need more memory?
ini_set('memory_limit', '64M'); // So far this does nothing to help the bulk requests

/**
 * Set the document root path
 * @var $doc_root
 */
$doc_root = $_SERVER['DOCUMENT_ROOT'];


/**
 * This is needed for the $sObject object variable creation
 * found in phptoolkit-11_0 package available from SalesForce
 */
require_once(DOC_ROOT . SALESFORCE_DIRECTORY . SALESFORCE_PHP_TOOLKIT .'/soapclient/SforcePartnerClient.php'); 

/**
 * Reads SOAP incoming message from Salesforce/MAPS
 * @var incoming SOAP request
 */
$data = fopen('php://input','rb');

$headers = getallheaders();
$content_length = $headers['Content-Length'];
$buffer_length = 1000; // Do I need this buffer? 
$fread_length = $content_length + $buffer_length;

$content = fread($data,$fread_length);

/**
 * Parse values from soap string into DOM XML
 */
$dom = new DOMDocument();
$dom->loadXML($content);
$resultArray = parseNotification($dom);
$sObject = $resultArray["sObject"];

// Can remove this once I figure out the bug
$testing = false;

// Set $testing to true if you would like to see the incoming SOAP request from SF
if($testing) {
    // Make it look nice
    $dom->formatOutput = true;

    // Write message and values to a file
    $fh = fopen(LOG_FILE_PATH.'/'.LOG_FILE_NAME,'a');
    fwrite($fh,$dom->saveXML());
    $ret_val = fclose($fh);
}

/**
 * Checks if the SOAP request was parsed out,
 * the $sObject->ACK is set to a string value of true in
 * the parseNotification()
 * @var $sObject->ACK
 */
if($sObject->ACK == 'true') {
    respond('true');
} else {
    // This means something might be wrong
    mail(BAD_ACK_TO_EMAIL,BAD_ACK_EMAIL_SUBJECT,$content,BAD_ACK_EMAIL_HEADER_WITH_CC);
    respond('false');
}

if(WRITE_OUTPUT_TO_LOG_FILE) {
    // Clear variable
    $fields_string = "";

    /**
     * List common values of the SOAP request
     * @var $sObject
     */
    $fields_string .= "Organization Id: " . $sObject->OrganizationId . "\n";
    $fields_string .= "Action Id: " . $sObject->ActionId . "\n";
    //$fields_string .= "Session Id: " . $sObject->SessionId . "\n"; // Session Id is not being passed right now, don't need it
    $fields_string .= "Enterprise URL: " . $sObject->EnterpriseUrl . "\n";
    $fields_string .= "Partner URL: " . $sObject->PartnerUrl . "\n"; 

    /**
     * @todo: Still need to add the notification Id to an array or some sort
     */
    //$fields_string .= "Notification Id: " . $sObject->NotificationId . "\n"; 
    //$fields_string .= '<pre>' . print_r($sObject->NotificationId,true) . '</pre>';

    /**
     * now you have an array as $record and you can use the
     * data as you need to for updates or calls back to salesforce
     * whatever you need to do is here
     * @var $resultArray['MapsRecords']
     */
    foreach ($resultArray['MapsRecords'] as $record) {
        // Just prints the fields in the array
        $fields_string .= '<pre>' . print_r($record,true) . '</pre>';  
    }

    // Flag used to send ACK response
    $fields_string .= "\nACK Flag: " . $sObject->ACK;

    // $content_length
    $fields_string .= "\nContent Length (Outbound Message Size): " . $content_length;

    // Close Border to separate each request
    $fields_string .= "\n /*********************************************/ \n";

    // Write message and values to a file
    $fh = fopen(LOG_FILE_PATH.'/'.LOG_FILE_NAME,'a');
    fwrite($fh,$fields_string);
    $ret_val = fclose($fh); 
}

/**
 * Parse a Salesforce.com Outbound Message notification SOAP packet
 * into an array of notification parms and an sObject. 
 * @param   XML [$domDoc] SOAP request as XML
 * @return  object/array[ $result] typecast XML to object of arrays
 **/
function parseNotification($domDoc) {  
    // Parse Notification parameters into result array
    $result = array("OrganizationId" => "",
                    "ActionId" => "",
                    "SessionId" => "",
                    "EnterpriseUrl" => "",
                    "PartnerUrl" => "",
                    "sObject" => null,
                    "MapsRecords" => array());

    // Create sObject and fill fields provided in notification
    $sObjectNode = $domDoc->getElementsByTagName("sObject")->item(0);
    $sObjType = $sObjectNode->getAttribute("type");

    if(substr_count($sObjType,"sf:")) {
        $sObjType = substr($sObjType,3);
    }

    $result["sObject"] = new SObject($sObjType);
    $result["sObject"]->type = $sObjType;    
    $result["sObject"]->OrganizationId = $domDoc->getElementsByTagName("OrganizationId")->item(0)->textContent;
    $result["sObject"]->ActionId = $domDoc->getElementsByTagName("ActionId")->item(0)->textContent;
    $result["sObject"]->SessionId = $domDoc->getElementsByTagName("SessionId")->item(0)->textContent;
    $result["sObject"]->EnterpriseUrl = $domDoc->getElementsByTagName("EnterpriseUrl")->item(0)->textContent;
    $result["sObject"]->PartnerUrl = $domDoc->getElementsByTagName("PartnerUrl")->item(0)->textContent;

    /**
     * @todo: for multiple requests, need to add an array of Notification Id's
     *        might move this inside the loop or something
     *        might not need to do this as well
     */
    //$notificationId[] = $domDoc->getElementsByTagName("Id")->item(0)->textContent;
    //$result["sObject"]->NotificationId = $notificationId;

    $sObjectNodes = $domDoc->getElementsByTagNameNS('urn:sobject.BLAH.com','*');
    $result["sObject"]->fieldnames = array();
    $count = 0;
    $tempMapRecord = array();

    // Loop through each notification sObject
    foreach ($sObjectNodes as $node) {
        if ($node->localName == "Id") {
            if ($count > 0) {
                $result["MapsRecords"][] = $tempMapRecord;
                $tempMapRecord = array();                          
            }
            // @note: added the strip_tags() to strip out all HTML tags
            $tempMapRecord[$node->localName] = strip_tags($node->textContent);
        } else {
            // @note: added the strip_tags() to strip out all HTML tags
            $tempMapRecord[$node->localName] = strip_tags($node->textContent);
        }        
        $count++;

        // set flag for ACK
        $result["sObject"]->ACK = 'true';
    }
    // Finish last item
    $result["MapsRecords"][] = $tempMapRecord;

    return $result;
}

/**
 * ACK to SalesForce, True/False (Prints header)
 * @param object $tf
 * @return $ACK
 */
function respond($tf) {
    $ACK = <<<ACK
<?xml version = "1.0" encoding = "utf-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <soapenv:Body>
        <notifications xmlns="http://BLAH.com/outbound">
            <Ack>$tf</Ack>
        </notifications>
    </soapenv:Body>
</soapenv:Envelope>
ACK;

    print trim($ACK); 
}

Пример SOAP-запроса от Salesforce, в более крупный запрос будет добавлено несколько узлов уведомлений.

<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 <soapenv:Body>
 <notifications xmlns="http://BLAH.com/outbound">
  <OrganizationId>BLAH</OrganizationId>
  <ActionId>BLAH</ActionId>
  <SessionId xsi:nil="true"/>
  <EnterpriseUrl>https://BLAH.com/</EnterpriseUrl>
  <PartnerUrl>https://BLAH.com/</PartnerUrl>
  <Notification>
   <Id>BLAH</Id>
   <sObject xmlns:sf="urn:sobject.BLAH.com" xsi:type="sf:Case">
    <sf:Id>BLAH</sf:Id>
    <sf:CaseNumber>BLAH</sf:CaseNumber>
    <sf:Case_Owner_ID_hidden__c>BLAH</sf:Case_Owner_ID_hidden__c>
    <sf:CreatedDate>2010-03-17T12:11:33.000Z</sf:CreatedDate>
    <sf:LastModifiedDate>2010-03-17T15:21:29.000Z</sf:LastModifiedDate>
    <sf:OwnerId>BLAH</sf:OwnerId>
    <sf:Status>BLAH</sf:Status>
   </sObject>
  </Notification>
 </notifications>
 </soapenv:Body>
</soapenv:Envelope>

person Phill Pafford    schedule 17.03.2010    source источник
comment
Я думаю, что вы получите фатальную ошибку из-за нехватки памяти, если вы достигнете предела памяти. Я думал, что это может быть post_max_size, но я бы не ожидал, что данные будут обрезаны, это просто ничего не должно вам давать.   -  person Tom Haigh    schedule 17.03.2010
comment
В какой строке возникает ошибка PHP?   -  person Pekka    schedule 17.03.2010
comment
После обновления в PHP нет ошибки, это было вызвано ограничением размера файла fread. Теперь ошибка находится на стороне SF.   -  person Phill Pafford    schedule 17.03.2010


Ответы (1)


Проблема с памятью PHP скажет

PHP Fatal error: Out of memory (allocated 250871808)...

Скорее всего, это неправильно завершенные или усеченные данные, исходящие от платформы Salesforce — попробуйте отладить первую ошибку из SF.

РЕДАКТИРОВАТЬ:

Хорошо, похоже, вы собираете данные устаревшим способом. Попробуйте заменить fread() на stream_get_contents(), а также echo $content сразу после того, как получите его, чтобы проверить вывод.

person Andy    schedule 17.03.2010
comment
да, это то, что я сделал в обновлении, теперь оно работает, но возникают проблемы с Salesforce и подтверждением массового запроса. Я видел, как некоторые говорят, что вы можете отправить файл ACK для всего сообщения SOAP, но я могу заставить его работать, только если я делаю это для каждого сообщения. Я знаю, что это другой вопрос, но есть мысли? - person Phill Pafford; 17.03.2010