Salesforce/PHP - 아웃바운드 메시지(SOAP) - 메모리 제한 문제가 있습니까?DOMDocument::loadXML() 태그 문제에서 데이터가 너무 일찍 끝났습니까?
-
20-09-2019 - |
문제
업데이트:
알겠습니다. fread에 파일 크기 제한이 있는 것 같습니다. 이것을 다음으로 변경했습니다.
file_get_contents('php://input')
, 그러나 이제 SF가 있으면 java.net.SocketTimeoutException이 발생합니다.읽기 시간 초과 오류가 발생했으며 PHP 측에는 아무것도 없습니다.또한 set_time_limit(0)도 추가했습니다.내가 올바르게 이해한다면 스크립트를 실행하는 데 걸리는 시간 동안 PHP 스크립트를 실행하십시오.이견있는 사람?
지금:테스트한 결과 최대 25개까지 처리할 수 있지만 100개는 처리할 수 없습니다.
저는 Salesforce를 사용하여 SOAP를 통해 아웃바운드 메시지를 다른 서버로 보내고 있습니다.서버는 한 번에 약 8개의 메시지를 처리할 수 있지만 SOAP 요청에 8개가 넘는 메시지가 포함되어 있으면 ACK 파일을 다시 보내지 않습니다.SF는 하나의 SOAP 요청으로 최대 100개의 아웃바운드 메시지를 보낼 수 있으며 이것이 PHP에서 메모리 문제를 일으키는 것 같습니다.아웃바운드 메시지를 1개씩 처리하면 모두 문제 없이 처리되며 한 번에 8개까지 문제 없이 처리할 수 있습니다.그러나 더 큰 세트는 작동하지 않습니다.
SF 오류:
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);
}
Salesforce의 SOAP 요청 예에는 더 큰 요청에 여러 개의 알림 노드가 추가됩니다.
<?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>
해결책
PHP 메모리 문제는 다음과 같습니다.
PHP Fatal error: Out of memory (allocated 250871808)...
이는 Salesforce 플랫폼에서 발생한 데이터가 잘못 종료되거나 잘릴 가능성이 높습니다. SF에서 첫 번째 오류를 디버깅해 보세요.
편집하다:
좋습니다. 구식 방식으로 데이터를 수집하고 있는 것 같습니다.교체해 보세요 fread()
~와 함께 stream_get_contents()
, 그리고 또한 echo $content
그것을 얻은 직후에 출력을 확인하십시오.