diff --git a/src/BeSimple/SoapClient/SoapClient.php b/src/BeSimple/SoapClient/SoapClient.php index 95dd5a0..d4f9638 100644 --- a/src/BeSimple/SoapClient/SoapClient.php +++ b/src/BeSimple/SoapClient/SoapClient.php @@ -95,7 +95,7 @@ class SoapClient extends \SoapClient } catch (SoapFault $soapFault) { if (SoapFaultSourceGetter::isNativeSoapFault($soapFault)) { - $soapFault = $this->decorateNativeSoapFault($soapFault); + $soapFault = $this->decorateNativeSoapFaultWithSoapResponseTracingData($soapFault); } throw $soapFault; @@ -257,6 +257,23 @@ class SoapClient extends \SoapClient return $loadedWsdlFilePath; } + private function getHttpHeadersBySoapVersion(SoapRequest $soapRequest) + { + if ($soapRequest->getVersion() === SOAP_1_1) { + + return [ + 'Content-Type: ' . $soapRequest->getContentType(), + 'SOAPAction: "' . $soapRequest->getAction() . '"', + 'Connection: ' . ($this->soapOptions->isConnectionKeepAlive() ? 'Keep-Alive' : 'close'), + ]; + } + + return [ + 'Content-Type: ' . $soapRequest->getContentType() . '; action="' . $soapRequest->getAction() . '"', + 'Connection: ' . ($this->soapOptions->isConnectionKeepAlive() ? 'Keep-Alive' : 'close'), + ]; + } + private function getAttachmentFilters() { $filters = []; @@ -277,7 +294,6 @@ class SoapClient extends \SoapClient array $soapAttachments = [] ) { if ($this->soapClientOptions->getTrace() === true) { - return SoapResponseFactory::createWithTracingData( $soapRequest, $curlResponse->getResponseBody(), @@ -288,10 +304,8 @@ class SoapClient extends \SoapClient } return SoapResponseFactory::create( + $soapRequest, $curlResponse->getResponseBody(), - $soapRequest->getLocation(), - $soapRequest->getAction(), - $soapRequest->getVersion(), $curlResponse->getHttpResponseContentType(), $soapAttachments ); @@ -320,68 +334,43 @@ class SoapClient extends \SoapClient ); } - private function decorateNativeSoapFault(SoapFault $nativePhpSoapFault) + private function decorateNativeSoapFaultWithSoapResponseTracingData(SoapFault $nativePhpSoapFault) { - $soapResponse = $this->getSoapResponseFromStorage(); - if ($soapResponse instanceof SoapResponse) { - $soapFault = $this->throwSoapFaultByTracing( - SoapFaultPrefixEnum::PREFIX_PHP . '-' . $nativePhpSoapFault->getCode(), - $nativePhpSoapFault->getMessage(), - $this->getSoapResponseTracingDataFromNativeSoapFault( - $nativePhpSoapFault, - new SoapResponseTracingData( - 'Content-Type: '.$soapResponse->getRequest()->getContentType(), - $soapResponse->getRequest()->getContent(), - 'Content-Type: '.$soapResponse->getContentType(), - $soapResponse->getResponseContent() - ) - ) - ); - } else { - $soapFault = $this->throwSoapFaultByTracing( - $nativePhpSoapFault->faultcode, - $nativePhpSoapFault->getMessage(), - $this->getSoapResponseTracingDataFromNativeSoapFault( - $nativePhpSoapFault, - new SoapResponseTracingData( - null, - null, - null, - null - ) - ) - ); - } - - return $soapFault; + return $this->throwSoapFaultByTracing( + $nativePhpSoapFault->faultcode, + $nativePhpSoapFault->getMessage(), + $this->getSoapResponseTracingDataFromNativeSoapFaultOrStorage($nativePhpSoapFault) + ); } - private function getSoapResponseTracingDataFromNativeSoapFault( - SoapFault $nativePhpSoapFault, - SoapResponseTracingData $defaultSoapFaultTracingData - ) { + private function getSoapResponseTracingDataFromNativeSoapFaultOrStorage(SoapFault $nativePhpSoapFault) + { if ($nativePhpSoapFault instanceof SoapFaultWithTracingData) { - return $nativePhpSoapFault->getSoapResponseTracingData(); } - return $defaultSoapFaultTracingData; + return $this->getSoapResponseTracingDataFromRequestStorage(); } - private function getHttpHeadersBySoapVersion(SoapRequest $soapRequest) + private function getSoapResponseTracingDataFromRequestStorage() { - if ($soapRequest->getVersion() === SOAP_1_1) { + $lastResponseHeaders = $lastResponse = $lastRequestHeaders = $lastRequest = null; + $soapResponse = $this->getSoapResponseFromStorage(); + if ($soapResponse instanceof SoapResponse) { + $lastResponseHeaders = 'Content-Type: ' . $soapResponse->getContentType(); + $lastResponse = $soapResponse->getResponseContent(); - return [ - 'Content-Type: ' . $soapRequest->getContentType(), - 'SOAPAction: "' . $soapRequest->getAction() . '"', - 'Connection: ' . ($this->soapOptions->isConnectionKeepAlive() ? 'Keep-Alive' : 'close'), - ]; + if ($soapResponse->hasRequest() === true) { + $lastRequestHeaders = 'Content-Type: ' . $soapResponse->getRequest()->getContentType(); + $lastRequest = $soapResponse->getRequest()->getContent(); + } } - return [ - 'Content-Type: ' . $soapRequest->getContentType() . '; action="' . $soapRequest->getAction() . '"', - 'Connection: ' . ($this->soapOptions->isConnectionKeepAlive() ? 'Keep-Alive' : 'close'), - ]; + return new SoapResponseTracingData( + $lastRequestHeaders, + $lastRequest, + $lastResponseHeaders, + $lastResponse + ); } } diff --git a/src/BeSimple/SoapClient/SoapClientOptionsBuilder.php b/src/BeSimple/SoapClient/SoapClientOptionsBuilder.php index 444817c..6d54aa0 100644 --- a/src/BeSimple/SoapClient/SoapClientOptionsBuilder.php +++ b/src/BeSimple/SoapClient/SoapClientOptionsBuilder.php @@ -28,7 +28,7 @@ class SoapClientOptionsBuilder public static function createWithDefaults() { return new SoapClientOptions( - SoapClientOptions::SOAP_CLIENT_TRACE_OFF, + SoapClientOptions::SOAP_CLIENT_TRACE_ON, SoapClientOptions::SOAP_CLIENT_EXCEPTIONS_ON, CurlOptions::DEFAULT_USER_AGENT, SoapClientOptions::SOAP_CLIENT_COMPRESSION_NONE diff --git a/src/BeSimple/SoapClient/SoapResponse.php b/src/BeSimple/SoapClient/SoapResponse.php index 587ca88..34c8b5a 100644 --- a/src/BeSimple/SoapClient/SoapResponse.php +++ b/src/BeSimple/SoapClient/SoapResponse.php @@ -44,6 +44,11 @@ class SoapResponse extends CommonSoapResponse $this->tracingData = $tracingData; } + public function hasRequest() + { + return $this->request !== null; + } + public function setRequest(SoapRequest $request) { $this->request = $request; diff --git a/src/BeSimple/SoapClient/SoapResponseFactory.php b/src/BeSimple/SoapClient/SoapResponseFactory.php index 0c88c28..d4d954d 100644 --- a/src/BeSimple/SoapClient/SoapResponseFactory.php +++ b/src/BeSimple/SoapClient/SoapResponseFactory.php @@ -27,27 +27,24 @@ class SoapResponseFactory /** * Factory method for SoapClient\SoapResponse. * + * @param SoapRequest $soapRequest related request object * @param string $content Content - * @param string $location Location - * @param string $action SOAP action - * @param string $version SOAP version * @param string $contentType Content type header * @param SoapAttachment[] $attachments SOAP attachments * @return SoapResponse */ public static function create( + SoapRequest $soapRequest, $content, - $location, - $action, - $version, $contentType, array $attachments = [] ) { $response = new SoapResponse(); + $response->setRequest($soapRequest); $response->setContent($content); - $response->setLocation($location); - $response->setAction($action); - $response->setVersion($version); + $response->setLocation($soapRequest->getLocation()); + $response->setAction($soapRequest->getAction()); + $response->setVersion($soapRequest->getVersion()); $response->setContentType($contentType); if (count($attachments) > 0) { $response->setAttachments( @@ -82,9 +79,7 @@ class SoapResponseFactory $response->setAction($soapRequest->getAction()); $response->setVersion($soapRequest->getVersion()); $response->setContentType($contentType); - if ($tracingData !== null) { - $response->setTracingData($tracingData); - } + $response->setTracingData($tracingData); if (count($attachments) > 0) { $response->setAttachments( PartFactory::createAttachmentParts($attachments) diff --git a/tests/BeSimple/SoapServerAndSoapClientCommunicationSoapFaultsTest.php b/tests/BeSimple/SoapServerAndSoapClientCommunicationSoapFaultsTest.php index dacd307..effd4c6 100644 --- a/tests/BeSimple/SoapServerAndSoapClientCommunicationSoapFaultsTest.php +++ b/tests/BeSimple/SoapServerAndSoapClientCommunicationSoapFaultsTest.php @@ -2,9 +2,11 @@ namespace BeSimple; +use BeSimple\SoapClient\Curl\CurlOptions; use BeSimple\SoapClient\SoapClientBuilder; use BeSimple\SoapClient\SoapClientOptionsBuilder; use BeSimple\SoapClient\SoapFaultWithTracingData; +use BeSimple\SoapClient\SoapOptions\SoapClientOptions; use BeSimple\SoapCommon\ClassMap; use BeSimple\SoapCommon\SoapOptions\SoapOptions; use BeSimple\SoapCommon\SoapOptionsBuilder; @@ -34,6 +36,55 @@ class SoapServerAndSoapClientCommunicationSoapFaultsTest extends PHPUnit_Framewo pclose($this->localWebServerProcess); } + public function testSoapCallSwaWithLargeSwaResponseWithSoapFaultAndTracingOff() + { + $soapClient = $this->getSoapClientBuilder()->buildWithSoapHeader( + new SoapClientOptions( + SoapClientOptions::SOAP_CLIENT_TRACE_OFF, + SoapClientOptions::SOAP_CLIENT_EXCEPTIONS_ON, + CurlOptions::DEFAULT_USER_AGENT, + SoapClientOptions::SOAP_CLIENT_COMPRESSION_NONE, + SoapClientOptions::SOAP_CLIENT_AUTHENTICATION_NONE, + SoapClientOptions::SOAP_CLIENT_PROXY_NONE, + self::TEST_HTTP_URL.'/SwaSenderSoapFaultEndpoint.php' + ), + SoapOptionsBuilder::createSwaWithClassMap( + self::TEST_HTTP_URL.'/SwaSenderEndpoint.php?wsdl', + new ClassMap([ + 'GenerateTestRequest' => GenerateTestRequest::class, + ]), + SoapOptions::SOAP_CACHE_TYPE_NONE + ), + new SoapHeader('http://schema.testcase', 'SoapHeader', [ + 'user' => 'admin', + ]) + ); + + $this->setExpectedException(SoapFault::class); + + try { + $soapClient->soapCall('dummyServiceMethodWithOutgoingLargeSwa', []); + } catch (SoapFault $e) { + self::assertNotInstanceOf( + SoapFaultWithTracingData::class, + $e, + 'SoapClient must not return tracing data when SoapClientOptions::trace is off.' + ); + self::assertEquals( + '911', + $e->faultcode + ); + self::assertContains( + 'with HTTP response code 500 with Message: This is a dummy SoapFault. and Code: 911', + $e->getMessage() + ); + + throw $e; + } + + self::fail('Expected SoapFault was not thrown'); + } + public function testSoapCallSwaWithLargeSwaResponseWithSoapFault() { $soapClient = $this->getSoapClientBuilder()->buildWithSoapHeader( @@ -59,7 +110,8 @@ class SoapServerAndSoapClientCommunicationSoapFaultsTest extends PHPUnit_Framewo } catch (SoapFault $e) { self::assertInstanceOf( SoapFaultWithTracingData::class, - $e + $e, + 'SoapClient must return tracing data when SoapClientOptions::trace is on.' ); /** @var SoapFaultWithTracingData $e */ self::assertEquals( diff --git a/tests/BeSimple/SoapServerAndSoapClientCommunicationTest.php b/tests/BeSimple/SoapServerAndSoapClientCommunicationTest.php index ee55c01..566ddf6 100644 --- a/tests/BeSimple/SoapServerAndSoapClientCommunicationTest.php +++ b/tests/BeSimple/SoapServerAndSoapClientCommunicationTest.php @@ -3,12 +3,15 @@ namespace BeSimple; use BeSimple\SoapBundle\Soap\SoapAttachment; +use BeSimple\SoapClient\Curl\CurlOptions; use BeSimple\SoapClient\SoapClientBuilder; use BeSimple\SoapClient\SoapClientOptionsBuilder; use BeSimple\SoapClient\SoapFaultWithTracingData; +use BeSimple\SoapClient\SoapOptions\SoapClientOptions; use BeSimple\SoapCommon\ClassMap; use BeSimple\SoapCommon\SoapOptions\SoapOptions; use BeSimple\SoapCommon\SoapOptionsBuilder; +use BeSimple\SoapCommon\SoapRequest; use BeSimple\SoapServer\SoapServerBuilder; use BeSimple\SoapServer\SoapServerOptionsBuilder; use Fixtures\DummyService; @@ -108,6 +111,65 @@ class SoapServerAndSoapClientCommunicationTest extends PHPUnit_Framework_TestCas self::assertContains('', $soapResponse->getResponseContent()); self::assertTrue($soapResponse->hasAttachments(), 'Response should contain attachments'); self::assertCount(3, $attachments); + self::assertInstanceOf( + SoapRequest::class, + $soapResponse->getRequest(), + 'SoapResponse::request must be SoapRequest for SoapClient calls with enabled tracing' + ); + + file_put_contents(self::CACHE_DIR . '/multipart-message-soap-client-response.xml', $soapResponse->getContent()); + foreach ($soapResponse->getAttachments() as $attachment) { + $fileName = preg_replace('/\<|\>/', '', $attachment->getContentId()); + file_put_contents(self::CACHE_DIR . DIRECTORY_SEPARATOR . 'attachment-client-response-' . $fileName, $attachment->getContent()); + + self::assertRegExp('/filename\.(docx|html|txt)/', $fileName); + } + + self::assertEquals( + filesize(self::LARGE_SWA_FILE), + filesize(self::CACHE_DIR.'/attachment-client-response-filename.docx'), + 'File cannot differ after transport from SoapClient to SoapServer' + ); + } + + public function testSoapCallSwaWithLargeSwaResponseAndTracingOff() + { + $soapClient = $this->getSoapBuilder()->buildWithSoapHeader( + new SoapClientOptions( + SoapClientOptions::SOAP_CLIENT_TRACE_OFF, + SoapClientOptions::SOAP_CLIENT_EXCEPTIONS_ON, + CurlOptions::DEFAULT_USER_AGENT, + SoapClientOptions::SOAP_CLIENT_COMPRESSION_NONE, + SoapClientOptions::SOAP_CLIENT_AUTHENTICATION_NONE, + SoapClientOptions::SOAP_CLIENT_PROXY_NONE, + self::TEST_HTTP_URL.'/SwaSenderEndpoint.php' + ), + SoapOptionsBuilder::createSwaWithClassMap( + self::TEST_HTTP_URL.'/SwaSenderEndpoint.php?wsdl', + new ClassMap([ + 'GenerateTestRequest' => GenerateTestRequest::class, + ]), + SoapOptions::SOAP_CACHE_TYPE_NONE + ), + new SoapHeader('http://schema.testcase', 'SoapHeader', [ + 'user' => 'admin', + ]) + ); + + $request = new DummyServiceMethodWithOutgoingLargeSwaRequest(); + $request->dummyAttribute = 1; + + $soapResponse = $soapClient->soapCall('dummyServiceMethodWithOutgoingLargeSwa', [$request]); + $attachments = $soapResponse->getAttachments(); + + self::assertContains('', $soapResponse->getResponseContent()); + self::assertTrue($soapResponse->hasAttachments(), 'Response should contain attachments'); + self::assertCount(3, $attachments); + self::assertInstanceOf( + SoapRequest::class, + $soapResponse->getRequest(), + 'SoapResponse::request must be SoapRequest for SoapClient calls with disabled tracing' + ); file_put_contents(self::CACHE_DIR . '/multipart-message-soap-client-response.xml', $soapResponse->getContent()); foreach ($soapResponse->getAttachments() as $attachment) { @@ -164,6 +226,7 @@ class SoapServerAndSoapClientCommunicationTest extends PHPUnit_Framework_TestCas self::assertContains('', $soapResponse->getResponseContent()); self::assertTrue($soapResponse->getRequest()->hasAttachments(), 'Response MUST contain attachments'); self::assertFalse($soapResponse->hasAttachments(), 'Response MUST NOT contain attachments'); + self::assertInstanceOf(SoapRequest::class, $soapResponse->getRequest()); foreach ($soapResponse->getRequest()->getAttachments() as $attachment) { file_put_contents(self::CACHE_DIR . '/attachment-client-request-'.trim($attachment->getContentId(), '<>'), $attachment->getContent());