Soap server with attachments refactoring
This commit is contained in:
@ -50,11 +50,10 @@ class Curl
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @todo: do not use options as Array
|
||||
* @param array $options Options array from SoapClient constructor
|
||||
* @param int $followLocationMaxRedirects Redirection limit for Location header
|
||||
*/
|
||||
public function __construct(array $options = array(), $followLocationMaxRedirects = 10)
|
||||
public function __construct(array $options = [], $followLocationMaxRedirects = 10)
|
||||
{
|
||||
// set the default HTTP user agent
|
||||
if (!isset($options['user_agent'])) {
|
||||
@ -126,7 +125,7 @@ class Curl
|
||||
|
||||
/**
|
||||
* Execute HTTP request.
|
||||
* Returns true if request was successfull.
|
||||
* Returns true if request was successful.
|
||||
*
|
||||
* @param string $location HTTP location
|
||||
* @param string $request Request body
|
||||
|
@ -16,6 +16,7 @@ use BeSimple\SoapCommon\Helper;
|
||||
use BeSimple\SoapCommon\Mime\MultiPart as MimeMultiPart;
|
||||
use BeSimple\SoapCommon\Mime\Parser as MimeParser;
|
||||
use BeSimple\SoapCommon\Mime\Part as MimePart;
|
||||
use BeSimple\SoapCommon\Mime\Part;
|
||||
use BeSimple\SoapCommon\SoapRequest;
|
||||
use BeSimple\SoapCommon\SoapRequestFilter;
|
||||
use BeSimple\SoapCommon\SoapResponse;
|
||||
@ -31,31 +32,27 @@ class MimeFilter implements SoapRequestFilter, SoapResponseFilter
|
||||
public function filterRequest(SoapRequest $request, $attachmentType)
|
||||
{
|
||||
$attachmentsToSend = $request->getAttachments();
|
||||
|
||||
// build mime message if we have attachments
|
||||
if (count($attachmentsToSend) > 0) {
|
||||
$multipart = new MimeMultiPart();
|
||||
$multipart = new MimeMultiPart('Part_' . rand(10, 15) . '_' . uniqid() . '.' . uniqid());
|
||||
$soapPart = new MimePart($request->getContent(), 'text/xml', 'utf-8', MimePart::ENCODING_EIGHT_BIT);
|
||||
$soapVersion = $request->getVersion();
|
||||
// change content type headers for MTOM with SOAP 1.1
|
||||
|
||||
if ($soapVersion == SOAP_1_1 && $attachmentType & Helper::ATTACHMENTS_TYPE_MTOM) {
|
||||
$multipart->setHeader('Content-Type', 'type', 'application/xop+xml');
|
||||
$multipart->setHeader('Content-Type', 'start-info', 'text/xml');
|
||||
$soapPart->setHeader('Content-Type', 'application/xop+xml');
|
||||
$soapPart->setHeader('Content-Type', 'type', 'text/xml');
|
||||
}
|
||||
// change content type headers for SOAP 1.2
|
||||
elseif ($soapVersion == SOAP_1_2) {
|
||||
} elseif ($soapVersion == SOAP_1_2) {
|
||||
$multipart->setHeader('Content-Type', 'type', 'application/soap+xml');
|
||||
$soapPart->setHeader('Content-Type', 'application/soap+xml');
|
||||
}
|
||||
|
||||
$multipart->addPart($soapPart, true);
|
||||
foreach ($attachmentsToSend as $cid => $attachment) {
|
||||
$multipart->addPart($attachment, false);
|
||||
}
|
||||
$request->setContent($multipart->getMimeMessage());
|
||||
|
||||
// TODO
|
||||
$headers = $multipart->getHeadersForHttp();
|
||||
list(, $contentType) = explode(': ', $headers[0]);
|
||||
|
||||
@ -67,33 +64,26 @@ class MimeFilter implements SoapRequestFilter, SoapResponseFilter
|
||||
|
||||
public function filterResponse(SoapResponse $response, $attachmentType)
|
||||
{
|
||||
$attachmentsReceived = [];
|
||||
$multiPartMessage = MimeParser::parseMimeMessage(
|
||||
$response->getContent(),
|
||||
['Content-Type' => trim($response->getContentType())]
|
||||
);
|
||||
$soapPart = $multiPartMessage->getMainPart();
|
||||
$attachments = $multiPartMessage->getAttachments();
|
||||
|
||||
// check content type if it is a multipart mime message
|
||||
$responseContentType = $response->getContentType();
|
||||
if (false !== stripos($responseContentType, 'multipart/related')) {
|
||||
// parse mime message
|
||||
$headers = array(
|
||||
'Content-Type' => trim($responseContentType),
|
||||
);
|
||||
$multipart = MimeParser::parseMimeMessage($response->getContent(), $headers);
|
||||
// get soap payload and update SoapResponse object
|
||||
$soapPart = $multipart->getPart();
|
||||
// convert href -> myhref for external references as PHP throws exception in this case
|
||||
// http://svn.php.net/viewvc/php/php-src/branches/PHP_5_4/ext/soap/php_encoding.c?view=markup#l3436
|
||||
$content = preg_replace('/href=(?!#)/', 'myhref=', $soapPart->getContent());
|
||||
$response->setContent($content);
|
||||
$response->setContentType($soapPart->getHeader('Content-Type'));
|
||||
// store attachments
|
||||
$attachments = $multipart->getParts(false);
|
||||
foreach ($attachments as $cid => $attachment) {
|
||||
$attachmentsReceived[$cid] = $attachment;
|
||||
}
|
||||
}
|
||||
if (count($attachmentsReceived) > 0) {
|
||||
$response->setAttachments($attachmentsReceived);
|
||||
$response->setContent($this->sanitizePhpExceptionOnHrefs($soapPart));
|
||||
$response->setContentType($soapPart->getHeader('Content-Type'));
|
||||
if (count($attachments) > 0) {
|
||||
$response->setAttachments($attachments);
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
private function sanitizePhpExceptionOnHrefs(Part $soapPart)
|
||||
{
|
||||
// convert href -> myhref for external references as PHP throws exception in this case
|
||||
// http://svn.php.net/viewvc/php/php-src/branches/PHP_5_4/ext/soap/php_encoding.c?view=markup#l3436
|
||||
return preg_replace('/href=(?!#)/', 'myhref=', $soapPart->getContent());
|
||||
}
|
||||
}
|
||||
|
@ -12,9 +12,7 @@
|
||||
|
||||
namespace BeSimple\SoapClient;
|
||||
|
||||
use BeSimple\SoapCommon\Helper;
|
||||
use BeSimple\SoapCommon\Converter\MtomTypeConverter;
|
||||
use BeSimple\SoapCommon\Converter\SwaTypeConverter;
|
||||
use BeSimple\SoapCommon\SoapKernel;
|
||||
use BeSimple\SoapCommon\SoapOptions\SoapOptions;
|
||||
use BeSimple\SoapCommon\SoapRequest;
|
||||
use BeSimple\SoapCommon\SoapRequestFactory;
|
||||
@ -30,20 +28,10 @@ use BeSimple\SoapCommon\SoapRequestFactory;
|
||||
class SoapClient extends \SoapClient
|
||||
{
|
||||
protected $soapVersion;
|
||||
|
||||
/**
|
||||
* Tracing enabled?
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $tracingEnabled = false;
|
||||
|
||||
/**
|
||||
* cURL instance.
|
||||
*
|
||||
* @var \BeSimple\SoapClient\Curl
|
||||
*/
|
||||
protected $curl = null;
|
||||
protected $tracingEnabled;
|
||||
protected $soapClientOptions;
|
||||
protected $soapOptions;
|
||||
protected $curl;
|
||||
|
||||
/**
|
||||
* Last request headers.
|
||||
@ -73,13 +61,6 @@ class SoapClient extends \SoapClient
|
||||
*/
|
||||
private $lastResponse = '';
|
||||
|
||||
/**
|
||||
* Soap kernel.
|
||||
*
|
||||
* @var \BeSimple\SoapClient\SoapKernel
|
||||
*/
|
||||
protected $soapKernel = null;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
@ -88,44 +69,99 @@ class SoapClient extends \SoapClient
|
||||
*/
|
||||
public function __construct(SoapClientOptions $soapClientOptions, SoapOptions $soapOptions)
|
||||
{
|
||||
$this->soapKernel = new SoapKernel();
|
||||
$this->soapVersion = $soapOptions->getSoapVersion();
|
||||
$this->tracingEnabled = $soapClientOptions->getTrace();
|
||||
$this->soapClientOptions = $soapClientOptions;
|
||||
$this->soapOptions = $soapOptions;
|
||||
|
||||
// @todo: refactor SoapClient: do not use $options as array
|
||||
$options = $this->configureMime($soapOptions->toArray());
|
||||
|
||||
// @todo: refactor SoapClient: do not use $options as array
|
||||
// @todo: refactor SoapClient: do not use $options as array: refactor Curl
|
||||
$this->curl = new Curl($soapClientOptions->toArray());
|
||||
|
||||
// @todo: refactor SoapClient: do not use $options as array
|
||||
$wsdlFile = $this->loadWsdl($soapOptions->getWsdlFile(), $soapOptions->toArray());
|
||||
$wsdlFile = $this->loadWsdl(
|
||||
$soapOptions->getWsdlFile(),
|
||||
$soapOptions->getWsdlCacheType()
|
||||
);
|
||||
|
||||
parent::__construct($wsdlFile, $options);
|
||||
parent::__construct(
|
||||
$wsdlFile,
|
||||
$soapClientOptions->toArray() + $soapOptions->toArray()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom request method to be able to modify the SOAP messages.
|
||||
* $oneWay parameter is not used at the moment.
|
||||
*
|
||||
* @param string $request Request string
|
||||
* @param string $location Location
|
||||
* @param string $action SOAP action
|
||||
* @param int $version SOAP version
|
||||
* @param int $oneWay 0|1
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __doRequest($request, $location, $action, $version, $oneWay = 0)
|
||||
{
|
||||
$soapRequest = $this->createSoapRequest($location, $action, $version, $request);
|
||||
$soapResponse = $this->getSoapResponseFromRequest($soapRequest);
|
||||
|
||||
return $soapResponse->getContent();
|
||||
}
|
||||
|
||||
private function createSoapRequest($location, $action, $version, $request)
|
||||
{
|
||||
$soapRequest = SoapRequestFactory::create($location, $action, $version, $request);
|
||||
if ($this->soapOptions->hasAttachments()) {
|
||||
$soapKernel = new SoapKernel();
|
||||
$soapRequest = $soapKernel->filterRequest(
|
||||
$soapRequest,
|
||||
$this->getAttachmentFilters(),
|
||||
$this->soapOptions->getAttachmentType()
|
||||
);
|
||||
}
|
||||
|
||||
return $soapRequest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the currently registered request filters on the request, performs
|
||||
* the HTTP request and runs the response filters.
|
||||
*
|
||||
* @param SoapRequest $soapRequest SOAP request object
|
||||
*
|
||||
* @return SoapResponse
|
||||
*/
|
||||
private function getSoapResponseFromRequest(SoapRequest $soapRequest)
|
||||
{
|
||||
$soapResponse = $this->performHttpSoapRequest($soapRequest);
|
||||
if ($this->soapOptions->hasAttachments()) {
|
||||
$soapKernel = new SoapKernel();
|
||||
$soapKernel->filterResponse($soapResponse, $this->getAttachmentFilters(), $this->soapOptions->getAttachmentType());
|
||||
}
|
||||
|
||||
return $soapResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform HTTP request with cURL.
|
||||
*
|
||||
* @param SoapRequest $soapRequest SoapRequest object
|
||||
*
|
||||
* @return SoapResponse
|
||||
*/
|
||||
private function __doHttpRequest(SoapRequest $soapRequest)
|
||||
private function performHttpSoapRequest(SoapRequest $soapRequest)
|
||||
{
|
||||
// HTTP headers
|
||||
$soapVersion = $soapRequest->getVersion();
|
||||
$soapAction = $soapRequest->getAction();
|
||||
if (SOAP_1_1 == $soapVersion) {
|
||||
$headers = array(
|
||||
$headers = [
|
||||
'Content-Type:' . $soapRequest->getContentType(),
|
||||
'SOAPAction: "' . $soapAction . '"',
|
||||
);
|
||||
];
|
||||
} else {
|
||||
$headers = array(
|
||||
$headers = [
|
||||
'Content-Type:' . $soapRequest->getContentType() . '; action="' . $soapAction . '"',
|
||||
);
|
||||
];
|
||||
}
|
||||
|
||||
$location = $soapRequest->getLocation();
|
||||
@ -135,8 +171,7 @@ class SoapClient extends \SoapClient
|
||||
|
||||
$options = $this->filterRequestOptions($soapRequest);
|
||||
|
||||
// execute HTTP request with cURL
|
||||
$responseSuccessfull = $this->curl->exec(
|
||||
$responseSuccessful = $this->curl->exec(
|
||||
$location,
|
||||
$content,
|
||||
$headers,
|
||||
@ -149,7 +184,7 @@ class SoapClient extends \SoapClient
|
||||
$this->lastRequest = $soapRequest->getContent();
|
||||
}
|
||||
// in case of an error while making the http request throw a soapFault
|
||||
if ($responseSuccessfull === false) {
|
||||
if ($responseSuccessful === false) {
|
||||
// get error message from curl
|
||||
$faultstring = $this->curl->getErrorMessage();
|
||||
throw new \SoapFault('HTTP', $faultstring);
|
||||
@ -171,53 +206,6 @@ class SoapClient extends \SoapClient
|
||||
return $soapResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom request method to be able to modify the SOAP messages.
|
||||
* $oneWay parameter is not used at the moment.
|
||||
*
|
||||
* @todo: refactor SoapClient: refactoring starts from here
|
||||
* @param string $request Request string
|
||||
* @param string $location Location
|
||||
* @param string $action SOAP action
|
||||
* @param int $version SOAP version
|
||||
* @param int $oneWay 0|1
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __doRequest($request, $location, $action, $version, $oneWay = 0)
|
||||
{
|
||||
// wrap request data in SoapRequest object
|
||||
$soapRequest = SoapRequestFactory::create($location, $action, $version, $request);
|
||||
|
||||
// do actual SOAP request
|
||||
$soapResponse = $this->__doRequest2($soapRequest);
|
||||
|
||||
// return SOAP response to ext/soap
|
||||
return $soapResponse->getContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the currently registered request filters on the request, performs
|
||||
* the HTTP request and runs the response filters.
|
||||
*
|
||||
* @param SoapRequest $soapRequest SOAP request object
|
||||
*
|
||||
* @return SoapResponse
|
||||
*/
|
||||
protected function __doRequest2(SoapRequest $soapRequest)
|
||||
{
|
||||
// run SoapKernel on SoapRequest
|
||||
$this->soapKernel->filterRequest($soapRequest);
|
||||
|
||||
// perform HTTP request with cURL
|
||||
$soapResponse = $this->__doHttpRequest($soapRequest);
|
||||
|
||||
// run SoapKernel on SoapResponse
|
||||
$this->soapKernel->filterResponse($soapResponse);
|
||||
|
||||
return $soapResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters HTTP headers which will be sent
|
||||
*
|
||||
@ -240,7 +228,7 @@ class SoapClient extends \SoapClient
|
||||
*/
|
||||
protected function filterRequestOptions(SoapRequest $soapRequest)
|
||||
{
|
||||
return array();
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -284,81 +272,34 @@ class SoapClient extends \SoapClient
|
||||
}
|
||||
|
||||
/**
|
||||
* Get SoapKernel instance.
|
||||
*
|
||||
* @return \BeSimple\SoapClient\SoapKernel
|
||||
*/
|
||||
public function getSoapKernel()
|
||||
{
|
||||
return $this->soapKernel;
|
||||
}
|
||||
|
||||
private function configureMime(array $options)
|
||||
{
|
||||
// @todo: PBe: refactor same as SoapServer
|
||||
if (Helper::ATTACHMENTS_TYPE_BASE64 !== $options['attachment_type']) {
|
||||
// register mime filter in SoapKernel
|
||||
$mimeFilter = new MimeFilter($options['attachment_type']);
|
||||
$this->soapKernel->registerFilter($mimeFilter);
|
||||
// configure type converter
|
||||
if (Helper::ATTACHMENTS_TYPE_SWA === $options['attachment_type']) {
|
||||
$converter = new SwaTypeConverter();
|
||||
$converter->setKernel($this->soapKernel);
|
||||
} elseif (Helper::ATTACHMENTS_TYPE_MTOM === $options['attachment_type']) {
|
||||
$xmlMimeFilter = new XmlMimeFilter($options['attachment_type']);
|
||||
$this->soapKernel->registerFilter($xmlMimeFilter);
|
||||
$converter = new MtomTypeConverter();
|
||||
$converter->setKernel($this->soapKernel);
|
||||
}
|
||||
// configure typemap
|
||||
if (!isset($options['typemap'])) {
|
||||
$options['typemap'] = array();
|
||||
}
|
||||
$options['typemap'][] = array(
|
||||
'type_name' => $converter->getTypeName(),
|
||||
'type_ns' => $converter->getTypeNamespace(),
|
||||
'from_xml' => function($input) use ($converter) {
|
||||
return $converter->convertXmlToPhp($input);
|
||||
},
|
||||
'to_xml' => function($input) use ($converter) {
|
||||
return $converter->convertPhpToXml($input);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Downloads WSDL files with cURL. Uses all SoapClient options for
|
||||
* authentication. Uses the WSDL_CACHE_* constants and the 'soap.wsdl_*'
|
||||
* ini settings. Does only file caching as SoapClient only supports a file
|
||||
* name parameter.
|
||||
*
|
||||
* @param string $wsdl WSDL file
|
||||
* @param array(string=>mixed) $options Options array
|
||||
* @param string $wsdl
|
||||
* @param int $wsdlCache
|
||||
* @param bool $resolveRemoteIncludes
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function loadWsdl($wsdl, array $options)
|
||||
private function loadWsdl($wsdl, $wsdlCache, $resolveRemoteIncludes = true)
|
||||
{
|
||||
// option to resolve wsdl/xsd includes
|
||||
$resolveRemoteIncludes = true;
|
||||
if (isset($options['resolve_wsdl_remote_includes'])) {
|
||||
$resolveRemoteIncludes = $options['resolve_wsdl_remote_includes'];
|
||||
}
|
||||
// option to enable cache
|
||||
$wsdlCache = WSDL_CACHE_DISK;
|
||||
if (isset($options['cache_wsdl'])) {
|
||||
$wsdlCache = $options['cache_wsdl'];
|
||||
}
|
||||
$wsdlDownloader = new WsdlDownloader($this->curl, $resolveRemoteIncludes, $wsdlCache);
|
||||
try {
|
||||
$cacheFileName = $wsdlDownloader->download($wsdl);
|
||||
} catch (\RuntimeException $e) {
|
||||
throw new \SoapFault('WSDL', "SOAP-ERROR: Parsing WSDL: Couldn't load from '" . $wsdl . "' : failed to load external entity \"" . $wsdl . "\"");
|
||||
throw new \SoapFault('WSDL', "SOAP-ERROR: Parsing WSDL: Couldn't load from '" . $wsdl . "'");
|
||||
}
|
||||
|
||||
return $cacheFileName;
|
||||
}
|
||||
}
|
||||
|
||||
private function getAttachmentFilters()
|
||||
{
|
||||
$filters = [];
|
||||
if ($this->soapOptions->getAttachmentType() !== SoapOptions::SOAP_ATTACHMENTS_TYPE_BASE64) {
|
||||
$filters[] = new MimeFilter();
|
||||
}
|
||||
if ($this->soapOptions->getAttachmentType() === SoapOptions::SOAP_ATTACHMENTS_TYPE_MTOM) {
|
||||
$filters[] = new XmlMimeFilter();
|
||||
}
|
||||
|
||||
return $filters;
|
||||
}
|
||||
}
|
||||
|
@ -25,54 +25,22 @@ use BeSimple\SoapCommon\Helper;
|
||||
*/
|
||||
class WsdlDownloader
|
||||
{
|
||||
/**
|
||||
* Cache enabled.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $curl;
|
||||
protected $resolveRemoteIncludes = true;
|
||||
protected $cacheEnabled;
|
||||
|
||||
/**
|
||||
* Cache dir.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $cacheDir;
|
||||
|
||||
/**
|
||||
* Cache TTL.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $cacheTtl;
|
||||
|
||||
/**
|
||||
* cURL instance for downloads.
|
||||
*
|
||||
* @var unknown_type
|
||||
* @param Curl $curl
|
||||
* @param int $cacheWsdl = Cache::TYPE_NONE|Cache::WSDL_CACHE_DISK|Cache::WSDL_CACHE_BOTH|Cache::WSDL_CACHE_MEMORY
|
||||
* @param boolean $resolveRemoteIncludes
|
||||
*/
|
||||
protected $curl;
|
||||
|
||||
/**
|
||||
* Resolve WSDl/XSD includes.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $resolveRemoteIncludes = true;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param \BeSimple\SoapClient\Curl $curl Curl instance
|
||||
* @param boolean $resolveRemoteIncludes WSDL/XSD include enabled?
|
||||
* @param boolean $cacheWsdl Cache constant
|
||||
*/
|
||||
public function __construct(Curl $curl, $resolveRemoteIncludes = true, $cacheWsdl = Cache::TYPE_DISK)
|
||||
public function __construct(Curl $curl, $cacheWsdl, $resolveRemoteIncludes = true)
|
||||
{
|
||||
$this->curl = $curl;
|
||||
$this->resolveRemoteIncludes = (Boolean) $resolveRemoteIncludes;
|
||||
$this->curl = $curl;
|
||||
$this->resolveRemoteIncludes = $resolveRemoteIncludes;
|
||||
|
||||
// get current WSDL caching config
|
||||
$this->cacheEnabled = $cacheWsdl === Cache::TYPE_NONE ? Cache::DISABLED : Cache::ENABLED == Cache::isEnabled();
|
||||
$this->cacheDir = Cache::getDirectory();
|
||||
$this->cacheTtl = Cache::getLifetime();
|
||||
|
Reference in New Issue
Block a user