Merge pull request #1 from aschamberger/master

Sync Server with Client and document builder methods
This commit is contained in:
Christian Kerl 2012-05-22 12:00:33 -07:00
commit 8dace85c50
8 changed files with 595 additions and 17 deletions

6
.gitignore vendored
View File

@ -1 +1,5 @@
/vendor
vendor
/phpunit.xml
.buildpath
.project
.settings

View File

@ -0,0 +1,138 @@
<?php
/*
* This file is part of the BeSimpleSoapClient.
*
* (c) Christian Kerl <christian-kerl@web.de>
* (c) Francis Besset <francis.besset@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace BeSimple\SoapServer;
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\SoapRequest;
use BeSimple\SoapCommon\SoapRequestFilter;
use BeSimple\SoapCommon\SoapResponse;
use BeSimple\SoapCommon\SoapResponseFilter;
/**
* MIME filter.
*
* @author Andreas Schamberger <mail@andreass.net>
*/
class MimeFilter implements SoapRequestFilter, SoapResponseFilter
{
/**
* Attachment type.
*
* @var int Helper::ATTACHMENTS_TYPE_SWA | Helper::ATTACHMENTS_TYPE_MTOM
*/
protected $attachmentType = Helper::ATTACHMENTS_TYPE_SWA;
/**
* Constructor.
*
* @param int $attachmentType Helper::ATTACHMENTS_TYPE_SWA | Helper::ATTACHMENTS_TYPE_MTOM
*/
public function __construct($attachmentType)
{
$this->attachmentType = $attachmentType;
}
/**
* Reset all properties to default values.
*/
public function resetFilter()
{
$this->attachmentType = Helper::ATTACHMENTS_TYPE_SWA;
}
/**
* Modify the given request XML.
*
* @param \BeSimple\SoapCommon\SoapRequest $request SOAP request
*
* @return void
*/
public function filterRequest(SoapRequest $request)
{
// array to store attachments
$attachmentsRecieved = array();
// check content type if it is a multipart mime message
$requestContentType = $request->getContentType();
if (false !== stripos($requestContentType, 'multipart/related')) {
// parse mime message
$headers = array(
'Content-Type' => trim($requestContentType),
);
$multipart = MimeParser::parseMimeMessage($request->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());
$request->setContent($content);
$request->setContentType($soapPart->getHeader('Content-Type'));
// store attachments
$attachments = $multipart->getParts(false);
foreach ($attachments as $cid => $attachment) {
$attachmentsRecieved[$cid] = $attachment;
}
}
// add attachments to response object
if (count($attachmentsRecieved) > 0) {
$request->setAttachments($attachmentsRecieved);
}
}
/**
* Modify the given response XML.
*
* @param \BeSimple\SoapCommon\SoapResponse $response SOAP response
*
* @return void
*/
public function filterResponse(SoapResponse $response)
{
// get attachments from request object
$attachmentsToSend = $response->getAttachments();
// build mime message if we have attachments
if (count($attachmentsToSend) > 0) {
$multipart = new MimeMultiPart();
$soapPart = new MimePart($response->getContent(), 'text/xml', 'utf-8', MimePart::ENCODING_EIGHT_BIT);
$soapVersion = $response->getVersion();
// change content type headers for MTOM with SOAP 1.1
if ($soapVersion == SOAP_1_1 && $this->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) {
$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);
}
$response->setContent($multipart->getMimeMessage());
// TODO
$headers = $multipart->getHeadersForHttp();
list($name, $contentType) = explode(': ', $headers[0]);
$response->setContentType($contentType);
}
}
}

View File

@ -0,0 +1,47 @@
<?php
/*
* This file is part of the BeSimpleSoapCommon.
*
* (c) Christian Kerl <christian-kerl@web.de>
* (c) Francis Besset <francis.besset@gmail.com>
* (c) Andreas Schamberger <mail@andreass.net>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace BeSimple\SoapServer;
use BeSimple\SoapCommon\SoapKernel as CommonSoapKernel;
use BeSimple\SoapCommon\SoapRequest;
use BeSimple\SoapCommon\SoapResponse;
/**
* SoapKernel for Server.
*
* @author Andreas Schamberger <mail@andreass.net>
*/
class SoapKernel extends CommonSoapKernel
{
/**
* {@inheritDoc}
*/
public function filterRequest(SoapRequest $request)
{
parent::filterRequest($request);
$this->attachments = $request->getAttachments();
}
/**
* {@inheritDoc}
*/
public function filterResponse(SoapResponse $response)
{
$response->setAttachments($this->attachments);
$this->attachments = array();
parent::filterResponse($response);
}
}

View File

@ -0,0 +1,71 @@
<?php
/*
* This file is part of the BeSimpleSoapClient.
*
* (c) Christian Kerl <christian-kerl@web.de>
* (c) Francis Besset <francis.besset@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace BeSimple\SoapServer;
use BeSimple\SoapCommon\SoapRequest as CommonSoapRequest;
use BeSimple\SoapCommon\SoapMessage;
/**
* SoapRequest class for SoapClient. Provides factory function for request object.
*
* @author Andreas Schamberger <mail@andreass.net>
*/
class SoapRequest extends CommonSoapRequest
{
/**
* Factory function for SoapRequest.
*
* @param string $content Content
* @param string $version SOAP version
*
* @return BeSimple\SoapClient\SoapRequest
*/
public static function create($content, $version)
{
$content = is_null($content) ? file_get_contents("php://input") : $content;
$location = self::getCurrentUrl();
$action = $_SERVER[SoapMessage::SOAP_ACTION_HEADER];
$contentType = $_SERVER[SoapMessage::CONTENT_TYPE_HEADER];
$request = new SoapRequest();
// $content is if unmodified from SoapClient not a php string type!
$request->setContent((string) $content);
$request->setLocation($location);
$request->setAction($action);
$request->setVersion($version);
$request->setContentType($contentType);
return $request;
}
/**
* Builds the current URL from the $_SERVER array.
*
* @return string
*/
public static function getCurrentUrl()
{
$url = '';
if (isset($_SERVER['HTTPS']) && (strtolower($_SERVER['HTTPS']) === 'on' || $_SERVER['HTTPS'] === '1')) {
$url .= 'https://';
} else {
$url .= 'http://';
}
$url .= isset( $_SERVER['SERVER_NAME'] ) ? $_SERVER['SERVER_NAME'] : '';
if (isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] != 80) {
$url .= ":{$_SERVER['SERVER_PORT']}";
}
$url .= isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : '';
return $url;
}
}

View File

@ -0,0 +1,62 @@
<?php
/*
* This file is part of the BeSimpleSoapClient.
*
* (c) Christian Kerl <christian-kerl@web.de>
* (c) Francis Besset <francis.besset@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace BeSimple\SoapServer;
use BeSimple\SoapCommon\SoapResponse as CommonSoapResponse;
use BeSimple\SoapCommon\SoapMessage;
/**
* SoapResponse class for SoapClient. Provides factory function for response object.
*
* @author Andreas Schamberger <mail@andreass.net>
*/
class SoapResponse extends CommonSoapResponse
{
/**
* Factory function for SoapResponse.
*
* @param string $content Content
* @param string $location Location
* @param string $action SOAP action
* @param string $version SOAP version
*
* @return BeSimple\SoapClient\SoapResponse
*/
public static function create($content, $location, $action, $version)
{
$response = new SoapResponse();
$response->setContent($content);
$response->setLocation($location);
$response->setAction($action);
$response->setVersion($version);
$contentType = SoapMessage::getContentTypeForVersion($version);
$response->setContentType($contentType);
return $response;
}
/**
* Send SOAP response to client.
*/
public function send()
{
// set Content-Type header
header('Content-Type: ' . $this->getContentType());
// get content to send
$response = $this->getContent();
// set Content-Length header
header('Content-Length: '. strlen($response));
// send response to client
echo $response;
}
}

View File

@ -12,18 +12,150 @@
namespace BeSimple\SoapServer;
use BeSimple\SoapCommon\Helper;
use BeSimple\SoapCommon\Converter\MtomTypeConverter;
use BeSimple\SoapCommon\Converter\SwaTypeConverter;
/**
* Extended SoapServer that allows adding filters for SwA, MTOM, ... .
*
* @author Andreas Schamberger <mail@andreass.net>
* @author Christian Kerl <christian-kerl@web.de>
*/
class SoapServer extends \SoapServer
{
/**
* Soap version.
*
* @var int
*/
protected $soapVersion = SOAP_1_1;
/**
* Soap kernel.
*
* @var \BeSimple\SoapServer\SoapKernel
*/
protected $soapKernel = null;
/**
* Constructor.
*
* @param string $wsdl WSDL file
* @param array(string=>mixed) $options Options array
*/
public function __construct($wsdl, array $options = array())
{
// store SOAP version
if (isset($options['soap_version'])) {
$this->soapVersion = $options['soap_version'];
}
// create soap kernel instance
$this->soapKernel = new SoapKernel();
// set up type converter and mime filter
$this->configureMime($options);
// we want the exceptions option to be set
$options['exceptions'] = true;
parent::__construct($wsdl, $options);
}
public function handle($soap_request = null)
/**
* Custom handle method to be able to modify the SOAP messages.
*
* @param string $request Request string
*/
public function handle($request = null)
{
parent::handle($soap_request);
// wrap request data in SoapRequest object
$soapRequest = SoapRequest::create($request, $this->soapVersion);
// handle actual SOAP request
$soapResponse = $this->handle2($soapRequest);
// send SOAP response to client
$soapResponse->send();
}
/**
* Runs the currently registered request filters on the request, calls the
* necessary functions (through the parent's class handle()) and runs the
* response filters.
*
* @param SoapRequest $soapRequest SOAP request object
*
* @return SoapResponse
*/
public function handle2(SoapRequest $soapRequest)
{
// run SoapKernel on SoapRequest
$this->soapKernel->filterRequest($soapRequest);
// call parent \SoapServer->handle() and buffer output
ob_start();
parent::handle($soapRequest->getContent());
$response = ob_get_clean();
// wrap response data in SoapResponse object
$soapResponse = SoapResponse::create(
$response,
$soapRequest->getLocation(),
$soapRequest->getAction(),
$soapRequest->getVersion()
);
// run SoapKernel on SoapResponse
$this->soapKernel->filterResponse($soapResponse);
return $soapResponse;
}
/**
* Get SoapKernel instance.
*
* @return \BeSimple\SoapServer\SoapKernel
*/
public function getSoapKernel()
{
return $this->soapKernel;
}
/**
* Configure filter and type converter for SwA/MTOM.
*
* @param array &$options SOAP constructor options array.
*
* @return void
*/
private function configureMime(array &$options)
{
if (isset($options['attachment_type']) && 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);
},
);
}
}
}

View File

@ -13,6 +13,7 @@
namespace BeSimple\SoapServer;
use BeSimple\SoapCommon\AbstractSoapBuilder;
use BeSimple\SoapCommon\Helper;
/**
* SoapServerBuilder provides a fluent interface to configure and create a SoapServer instance.
@ -28,13 +29,14 @@ class SoapServerBuilder extends AbstractSoapBuilder
protected $handlerObject;
/**
* @return SoapServerBuilder
* Create new instance with default options.
*
* @return \BeSimple\SoapServer\SoapServerBuilder
*/
static public function createWithDefaults()
{
return parent::createWithDefaults()
->withErrorReporting(false)
;
->withErrorReporting(false);
}
/**
@ -48,6 +50,11 @@ class SoapServerBuilder extends AbstractSoapBuilder
$this->withErrorReporting(false);
}
/**
* Finally returns a SoapClient instance.
*
* @return \BeSimple\SoapServer\SoapServer
*/
public function build()
{
$this->validateOptions();
@ -69,6 +76,13 @@ class SoapServerBuilder extends AbstractSoapBuilder
return $server;
}
/**
* Cofigures the SOAP actor.
*
* @param string $actor Actor name
*
* @return \BeSimple\SoapServer\SoapServerBuilder
*/
public function withActor($actor)
{
$this->options['actor'] = $actor;
@ -76,6 +90,11 @@ class SoapServerBuilder extends AbstractSoapBuilder
return $this;
}
/**
* Enables persistence.
*
* @return \BeSimple\SoapServer\SoapServerBuilder
*/
public function withPersistanceRequest()
{
$this->persistence = SOAP_PERSISTENCE_REQUEST;
@ -85,6 +104,8 @@ class SoapServerBuilder extends AbstractSoapBuilder
/**
* Enables the HTTP session. The handler object is persisted between multiple requests in a session.
*
* @return \BeSimple\SoapServer\SoapServerBuilder
*/
public function withPersistenceSession()
{
@ -96,7 +117,9 @@ class SoapServerBuilder extends AbstractSoapBuilder
/**
* Enables reporting of internal errors to clients. This should only be enabled in development environments.
*
* @param boolean $enable
* @param boolean $enable Enable error reporting
*
* @return \BeSimple\SoapServer\SoapServerBuilder
*/
public function withErrorReporting($enable = true)
{
@ -105,25 +128,48 @@ class SoapServerBuilder extends AbstractSoapBuilder
return $this;
}
/**
* SOAP attachment type Base64.
*
* @return \BeSimple\SoapServer\SoapServerBuilder
*/
public function withBase64Attachments()
{
return $this;
}
$this->options['attachment_type'] = Helper::ATTACHMENTS_TYPE_BASE64;
public function withSwaAttachments()
{
return $this;
}
public function withMtomAttachments()
{
return $this;
}
/**
* SOAP attachment type SwA.
*
* @return \BeSimple\SoapServer\SoapServerBuilder
*/
public function withSwaAttachments()
{
$this->options['attachment_type'] = Helper::ATTACHMENTS_TYPE_SWA;
return $this;
}
/**
* SOAP attachment type MTOM.
*
* @return \BeSimple\SoapServer\SoapServerBuilder
*/
public function withMtomAttachments()
{
$this->options['attachment_type'] = Helper::ATTACHMENTS_TYPE_MTOM;
return $this;
}
/**
* Configures the handler class or object.
*
* @param mixed $handler Can be either a class name or an object.
*
* @return SoapServerBuilder
* @return \BeSimple\SoapServer\SoapServerBuilder
*/
public function withHandler($handler)
{
@ -140,6 +186,9 @@ class SoapServerBuilder extends AbstractSoapBuilder
return $this;
}
/**
* Validate options.
*/
protected function validateOptions()
{
$this->validateWsdl();

View File

@ -0,0 +1,75 @@
<?php
/*
* This file is part of the BeSimpleSoapClient.
*
* (c) Christian Kerl <christian-kerl@web.de>
* (c) Francis Besset <francis.besset@gmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
namespace BeSimple\SoapServer;
use BeSimple\SoapCommon\FilterHelper;
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\SoapRequest;
use BeSimple\SoapCommon\SoapRequestFilter;
use BeSimple\SoapCommon\SoapResponse;
use BeSimple\SoapCommon\SoapResponseFilter;
/**
* XML MIME filter that fixes the namespace of xmime:contentType attribute.
*
* @author Andreas Schamberger <mail@andreass.net>
*/
class XmlMimeFilter implements SoapResponseFilter
{
/**
* Reset all properties to default values.
*/
public function resetFilter()
{
}
/**
* Modify the given response XML.
*
* @param \BeSimple\SoapCommon\SoapResponse $response SOAP request
*
* @return void
*/
public function filterResponse(SoapResponse $response)
{
// get \DOMDocument from SOAP request
$dom = $response->getContentDocument();
// create FilterHelper
$filterHelper = new FilterHelper($dom);
// add the neccessary namespaces
$filterHelper->addNamespace(Helper::PFX_XMLMIME, Helper::NS_XMLMIME);
// get xsd:base64binary elements
$xpath = new \DOMXPath($dom);
$xpath->registerNamespace('XOP', Helper::NS_XOP);
$query = '//XOP:Include/..';
$nodes = $xpath->query($query);
// exchange attributes
if ($nodes->length > 0) {
foreach ($nodes as $node) {
if ($node->hasAttribute('contentType')) {
$contentType = $node->getAttribute('contentType');
$node->removeAttribute('contentType');
$filterHelper->setAttribute($node, Helper::NS_XMLMIME, 'contentType', $contentType);
}
}
}
}
}