+ Reply to Thread
Results 1 to 4 of 4
This is a discussion on DOMDocument::loadXML() ... Premature end of data in tag within the Salesforce Coding Discussions forums, part of the Salesforce category; I'm using Salesforce to send outbound messages (via SOAP) to another server.
  1. #1
    ppafford is offline Junior Member
    Join Date
    Sep 2009
    Posts
    21

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

    I'm using Salesforce to send outbound messages (via SOAP) to another server. The server can process about 8 messages at a time, but will not send back the ACK file if the SOAP request contains more than 8 messages. SF can send up to 100 outbound messages in 1 SOAP request and I think this is causing a memory issue with PHP. If I process the outbound messages 1 by 1 they all go through fine, I can even do 8 at a time with no issues. But larger sets are not working.

    ERROR in SF:

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

    Looking in the HTTP error logs I see that the incoming SOAP message looks to be getting cut of which throws a PHP warning stating:

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

    PHP Fatal error:

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

    My Code to parse incoming SOAP Requests
    PHP Code:
    /**
     * 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'];

    /**
     * The soap.ini.php file holds all the values for the Database connections
     * as well as some of the email alerts that you can test with
     */
    include_once($doc_root . '/sf/support.sf.ini.php');

    /**
     * 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.enterprise.soap.sforce.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://soap.sforce.com/2005/09/outbound">
                <Ack>$tf</Ack>
            </notifications>
        </soapenv:Body>
    </soapenv:Envelope>
    ACK;

        print trim($ACK); 
    }

  2. #2
    ppafford is offline Junior Member
    Join Date
    Sep 2009
    Posts
    21
    OK I figured it out, looks like fread has a filesize limitation, changed this to file_get_contents('php://input'), but now having SF give a java.net.SocketTimeoutException: Read timed out error and nothing on the PHP side. I have also added set_time_limit(0); to the PHP script which if I understand correctly execute the script for as long as it takes. Any thoughts?

    BTW: I can process up to 25 (that I've tested) but not 100

    Code:
    set_time_limit(0); // No time limit on execution
    $data = 'php://input';
    $content = file_get_contents($data);
    instead of
    Code:
    $data = fopen('php://input','rb');
    
    $headers = getallheaders();
    $content_length = $headers['Content-Length'];
    $buffer_length = 1000;
    $fread_length = $content_length + $buffer_length;
    
    $content = fread($data,$fread_length);

  3. #3
    mike's Avatar
    mike is offline Administrator
    Join Date
    May 2007
    Location
    Wylie, Texas
    Posts
    607
    Blog Entries
    16
    sorry for the delay... Try this to consume the file > How to setup outbound message and receive the data in Mysql.

    Also make sure that there is nothing in your output buffer when you send the response back to salesforce to clear the message out or you will get errors in the outbound Que...

  4. #4
    mike's Avatar
    mike is offline Administrator
    Join Date
    May 2007
    Location
    Wylie, Texas
    Posts
    607
    Blog Entries
    16
    oh holy crap this is an old post... sorry dude I thought this was new....

+ Reply to Thread

LinkBacks (?)

  1. Hits: 2
    04-03-2011, 03:40 PM

Tags for this Thread

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts

SEO by vBSEO 3.5.2