[SoapBundle] Enhanced exception management with Symfony2

This commit is contained in:
Francis Besset 2013-11-13 15:02:17 +01:00
parent 10705aa9e2
commit 6a6661012e
8 changed files with 188 additions and 9 deletions

View File

@ -25,6 +25,7 @@
"ext-curl": "*", "ext-curl": "*",
"ass/xmlsecurity": "~1.0", "ass/xmlsecurity": "~1.0",
"symfony/framework-bundle": "~2.0", "symfony/framework-bundle": "~2.0",
"symfony/twig-bundle": "~2.0",
"zendframework/zend-mail": "2.1.*", "zendframework/zend-mail": "2.1.*",
"zendframework/zend-mime": "2.1.*", "zendframework/zend-mime": "2.1.*",
"zendframework/zend-soap": "2.1.*" "zendframework/zend-soap": "2.1.*"

View File

@ -12,14 +12,18 @@
namespace BeSimple\SoapBundle\Controller; namespace BeSimple\SoapBundle\Controller;
use BeSimple\SoapBundle\Handler\ExceptionHandler;
use BeSimple\SoapBundle\Soap\SoapRequest; use BeSimple\SoapBundle\Soap\SoapRequest;
use BeSimple\SoapBundle\Soap\SoapResponse; use BeSimple\SoapBundle\Soap\SoapResponse;
use BeSimple\SoapServer\Exception as SoapException; use BeSimple\SoapServer\Exception as SoapException;
use Symfony\Component\DependencyInjection\ContainerAware; use Symfony\Component\DependencyInjection\ContainerAware;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\FlattenException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\HttpKernelInterface; use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\Log\DebugLoggerInterface;
/** /**
* @author Christian Kerl <christian-kerl@web.de> * @author Christian Kerl <christian-kerl@web.de>
@ -100,6 +104,46 @@ class SoapWebServiceController extends ContainerAware
return $response; return $response;
} }
/**
* Converts an Exception to a SoapFault Response.
*
* @param Request $request The request
* @param FlattenException $exception A FlattenException instance
* @param DebugLoggerInterface $logger A DebugLoggerInterface instance
*
* @return Response
*
* @throws \LogicException When the request query parameter "_besimple_soap_webservice" does not exist
*/
public function exceptionAction(Request $request, FlattenException $exception, DebugLoggerInterface $logger = null)
{
if (!$webservice = $request->query->get('_besimple_soap_webservice')) {
throw new \LogicException(sprintf('The parameter "%s" is required in Request::$query parameter bag to generate the SoapFault.', '_besimple_soap_webservice'), null, $e);
}
$view = 'TwigBundle:Exception:'.($this->container->get('kernel')->isDebug() ? 'exception' : 'error').'.txt.twig';
$code = $exception->getStatusCode();
$details = $this->container->get('templating')->render($view, array(
'status_code' => $code,
'status_text' => isset(Response::$statusTexts[$code]) ? Response::$statusTexts[$code] : '',
'exception' => $exception,
'logger' => $logger,
));
$server = $this
->container
->get(sprintf('besimple.soap.context.%s', $webservice))
->getServerBuilder()
->withHandler(new ExceptionHandler($exception, $details))
->build()
;
ob_start();
$server->handle($request->getContent());
return new Response(ob_get_clean());
}
/** /**
* This method gets called once for every SOAP header the \SoapServer received * This method gets called once for every SOAP header the \SoapServer received
* and afterwards once for the called SOAP operation. * and afterwards once for the called SOAP operation.
@ -125,13 +169,7 @@ class SoapWebServiceController extends ContainerAware
); );
// forward to controller // forward to controller
try {
$response = $this->container->get('http_kernel')->handle($this->soapRequest, HttpKernelInterface::SUB_REQUEST, false); $response = $this->container->get('http_kernel')->handle($this->soapRequest, HttpKernelInterface::SUB_REQUEST, false);
} catch (\Exception $e) {
$this->soapResponse = new Response(null, 500);
throw $e instanceof \SoapFault || $this->container->getParameter('kernel.debug') ? $e : new SoapException\ReceiverSoapFault($e->getMessage());
}
$this->setResponse($response); $this->setResponse($response);

View File

@ -63,6 +63,8 @@ class BeSimpleSoapExtension extends Extension
$serviceConfig['name'] = $name; $serviceConfig['name'] = $name;
$this->createWebServiceContext($serviceConfig, $container); $this->createWebServiceContext($serviceConfig, $container);
} }
$container->setParameter('besimple.soap.exception_listener.controller', $config['exception_controller']);
} }
private function registerCacheConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader) private function registerCacheConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader)

View File

@ -40,6 +40,12 @@ class Configuration
$this->addServicesSection($rootNode); $this->addServicesSection($rootNode);
$this->addWsdlDumperSection($rootNode); $this->addWsdlDumperSection($rootNode);
$rootNode
->children()
->scalarNode('exception_controller')->defaultValue('BeSimpleSoapBundle:SoapWebService:exception')->end()
->end()
;
return $treeBuilder->buildTree(); return $treeBuilder->buildTree();
} }

View File

@ -0,0 +1,79 @@
<?php
/*
* This file is part of the BeSimpleSoapBundle.
*
* (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\SoapBundle\EventListener;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\EventListener\ExceptionListener;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpKernel\KernelEvents;
/**
* @author Francis Besset <francis.besset@gmail.com>
*/
class SoapExceptionListener extends ExceptionListener
{
/**
* @var ContainerInterface
*/
protected $container;
/**
* To avoid conflict between , the logger param is not typed:
* The parent class needs and instance of `Psr\Log\LoggerInterface` from Symfony 2.2,
* before logger is an instance of `Symfony\Component\HttpKernel\Log\LoggerInterface`.
*
* @param ContainerInterface $container A ContainerInterface instance
* @param string $controller The controller name to call
* @param LoggerInterface $logger A logger instance
*/
public function __construct(ContainerInterface $container, $controller, $logger)
{
parent::__construct($controller, $logger);
$this->container = $container;
}
public function onKernelException(GetResponseForExceptionEvent $event)
{
if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
return;
}
$request = $event->getRequest();
if ('soap' !== $request->getRequestFormat()) {
return;
}
$attributes = $request->attributes;
if (!$webservice = $attributes->get('webservice')) {
return;
}
if (!$this->container->has(sprintf('besimple.soap.context.%s', $webservice))) {
return;
}
// hack to retrieve the current WebService name in the controller
$request->query->set('_besimple_soap_webservice', $webservice);
parent::onKernelException($event);
}
public static function getSubscribedEvents()
{
return array(
KernelEvents::EXCEPTION => array('onKernelException', -64),
);
}
}

View File

@ -0,0 +1,43 @@
<?php
/*
* This file is part of the BeSimpleSoapBundle.
*
* (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\SoapBundle\Handler;
use BeSimple\SoapServer\Exception\ReceiverSoapFault;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\FlattenException;
/**
* @author Francis Besset <francis.besset@gmail.com>
*/
class ExceptionHandler
{
protected $exception;
protected $details;
public function __construct(FlattenException $exception, $details = null)
{
$this->exception = $exception;
$this->details = $details;
}
public function __call($method, $arguments)
{
$code = $this->exception->getStatusCode();
throw new ReceiverSoapFault(
isset(Response::$statusTexts[$code]) ? Response::$statusTexts[$code] : '',
null,
$this->details
);
}
}

View File

@ -6,6 +6,7 @@
<parameters> <parameters>
<parameter key="besimple.soap.response.class">BeSimple\SoapBundle\Soap\SoapResponse</parameter> <parameter key="besimple.soap.response.class">BeSimple\SoapBundle\Soap\SoapResponse</parameter>
<parameter key="besimple.soap.response.listener.class">BeSimple\SoapBundle\EventListener\SoapResponseListener</parameter> <parameter key="besimple.soap.response.listener.class">BeSimple\SoapBundle\EventListener\SoapResponseListener</parameter>
<parameter key="besimple.soap.exception_listener.class">BeSimple\SoapBundle\EventListener\SoapExceptionListener</parameter>
<parameter key="besimple.soap.context.class">BeSimple\SoapBundle\WebServiceContext</parameter> <parameter key="besimple.soap.context.class">BeSimple\SoapBundle\WebServiceContext</parameter>
<parameter key="besimple.soap.binder.request_header.rpcliteral.class">BeSimple\SoapBundle\ServiceBinding\RpcLiteralRequestHeaderMessageBinder</parameter> <parameter key="besimple.soap.binder.request_header.rpcliteral.class">BeSimple\SoapBundle\ServiceBinding\RpcLiteralRequestHeaderMessageBinder</parameter>
<parameter key="besimple.soap.binder.request.rpcliteral.class">BeSimple\SoapBundle\ServiceBinding\RpcLiteralRequestMessageBinder</parameter> <parameter key="besimple.soap.binder.request.rpcliteral.class">BeSimple\SoapBundle\ServiceBinding\RpcLiteralRequestMessageBinder</parameter>
@ -25,6 +26,14 @@
<argument type="service" id="besimple.soap.response" /> <argument type="service" id="besimple.soap.response" />
</service> </service>
<service id="besimple.soap.exception_listener" class="%besimple.soap.exception_listener.class%">
<tag name="kernel.event_subscriber" />
<tag name="monolog.logger" channel="request" />
<argument type="service" id="service_container" />
<argument>%besimple.soap.exception_listener.controller%</argument>
<argument type="service" id="logger" on-invalid="null" />
</service>
<service id="besimple.soap.context.rpcliteral" class="%besimple.soap.context.class%" abstract="true"> <service id="besimple.soap.context.rpcliteral" class="%besimple.soap.context.class%" abstract="true">
<argument type="service" id="besimple.soap.definition.loader" /> <argument type="service" id="besimple.soap.definition.loader" />
<argument type="service" id="besimple.soap.converter.collection" /> <argument type="service" id="besimple.soap.converter.collection" />

View File

@ -25,10 +25,11 @@
"besimple/soap-common": "0.2.*", "besimple/soap-common": "0.2.*",
"besimple/soap-wsdl": "0.2.*", "besimple/soap-wsdl": "0.2.*",
"ass/xmlsecurity": "~1.0", "ass/xmlsecurity": "~1.0",
"symfony/framework-bundle": "~2.0",
"symfony/twig-bundle": "~2.0",
"zendframework/zend-mail": "2.1.*", "zendframework/zend-mail": "2.1.*",
"zendframework/zend-mime": "2.1.*", "zendframework/zend-mime": "2.1.*",
"zendframework/zend-soap": "2.1.*", "zendframework/zend-soap": "2.1.*"
"symfony/framework-bundle": "~2.0"
}, },
"suggest": { "suggest": {
"besimple/soap-client": "0.2.*", "besimple/soap-client": "0.2.*",