RequestHandler.php 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. <?php
  2. namespace Drupal\rest;
  3. use Drupal\Core\Cache\CacheableResponseInterface;
  4. use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
  5. use Drupal\Core\Entity\EntityStorageInterface;
  6. use Drupal\Core\Routing\RouteMatchInterface;
  7. use Symfony\Component\DependencyInjection\ContainerAwareInterface;
  8. use Symfony\Component\DependencyInjection\ContainerAwareTrait;
  9. use Symfony\Component\DependencyInjection\ContainerInterface;
  10. use Symfony\Component\HttpFoundation\Request;
  11. use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
  12. use Symfony\Component\HttpKernel\Exception\UnprocessableEntityHttpException;
  13. use Symfony\Component\Serializer\Exception\UnexpectedValueException;
  14. use Symfony\Component\Serializer\Exception\InvalidArgumentException;
  15. /**
  16. * Acts as intermediate request forwarder for resource plugins.
  17. *
  18. * @see \Drupal\rest\EventSubscriber\ResourceResponseSubscriber
  19. */
  20. class RequestHandler implements ContainerAwareInterface, ContainerInjectionInterface {
  21. use ContainerAwareTrait;
  22. /**
  23. * The resource configuration storage.
  24. *
  25. * @var \Drupal\Core\Entity\EntityStorageInterface
  26. */
  27. protected $resourceStorage;
  28. /**
  29. * Creates a new RequestHandler instance.
  30. *
  31. * @param \Drupal\Core\Entity\EntityStorageInterface $entity_storage
  32. * The resource configuration storage.
  33. */
  34. public function __construct(EntityStorageInterface $entity_storage) {
  35. $this->resourceStorage = $entity_storage;
  36. }
  37. /**
  38. * {@inheritdoc}
  39. */
  40. public static function create(ContainerInterface $container) {
  41. return new static($container->get('entity_type.manager')->getStorage('rest_resource_config'));
  42. }
  43. /**
  44. * Handles a web API request.
  45. *
  46. * @param \Drupal\Core\Routing\RouteMatchInterface $route_match
  47. * The route match.
  48. * @param \Symfony\Component\HttpFoundation\Request $request
  49. * The HTTP request object.
  50. *
  51. * @return \Symfony\Component\HttpFoundation\Response
  52. * The response object.
  53. */
  54. public function handle(RouteMatchInterface $route_match, Request $request) {
  55. // Symfony is built to transparently map HEAD requests to a GET request. In
  56. // the case of the REST module's RequestHandler though, we essentially have
  57. // our own light-weight routing system on top of the Drupal/symfony routing
  58. // system. So, we have to respect the decision that the routing system made:
  59. // we look not at the request method, but at the route's method. All REST
  60. // routes are guaranteed to have _method set.
  61. // Response::prepare() will transform it to a HEAD response at the very last
  62. // moment.
  63. // @see https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4
  64. // @see \Symfony\Component\Routing\Matcher\UrlMatcher::matchCollection()
  65. // @see \Symfony\Component\HttpFoundation\Response::prepare()
  66. $method = strtolower($route_match->getRouteObject()->getMethods()[0]);
  67. assert(count($route_match->getRouteObject()->getMethods()) === 1);
  68. $resource_config_id = $route_match->getRouteObject()->getDefault('_rest_resource_config');
  69. /** @var \Drupal\rest\RestResourceConfigInterface $resource_config */
  70. $resource_config = $this->resourceStorage->load($resource_config_id);
  71. $resource = $resource_config->getResourcePlugin();
  72. // Deserialize incoming data if available.
  73. /** @var \Symfony\Component\Serializer\SerializerInterface $serializer */
  74. $serializer = $this->container->get('serializer');
  75. $received = $request->getContent();
  76. $unserialized = NULL;
  77. if (!empty($received)) {
  78. $format = $request->getContentType();
  79. $definition = $resource->getPluginDefinition();
  80. // First decode the request data. We can then determine if the
  81. // serialized data was malformed.
  82. try {
  83. $unserialized = $serializer->decode($received, $format, ['request_method' => $method]);
  84. }
  85. catch (UnexpectedValueException $e) {
  86. // If an exception was thrown at this stage, there was a problem
  87. // decoding the data. Throw a 400 http exception.
  88. throw new BadRequestHttpException($e->getMessage());
  89. }
  90. // Then attempt to denormalize if there is a serialization class.
  91. if (!empty($definition['serialization_class'])) {
  92. try {
  93. $unserialized = $serializer->denormalize($unserialized, $definition['serialization_class'], $format, ['request_method' => $method]);
  94. }
  95. // These two serialization exception types mean there was a problem
  96. // with the structure of the decoded data and it's not valid.
  97. catch (UnexpectedValueException $e) {
  98. throw new UnprocessableEntityHttpException($e->getMessage());
  99. }
  100. catch (InvalidArgumentException $e) {
  101. throw new UnprocessableEntityHttpException($e->getMessage());
  102. }
  103. }
  104. }
  105. // Determine the request parameters that should be passed to the resource
  106. // plugin.
  107. $route_parameters = $route_match->getParameters();
  108. $parameters = [];
  109. // Filter out all internal parameters starting with "_".
  110. foreach ($route_parameters as $key => $parameter) {
  111. if ($key{0} !== '_') {
  112. $parameters[] = $parameter;
  113. }
  114. }
  115. // Invoke the operation on the resource plugin.
  116. $response = call_user_func_array([$resource, $method], array_merge($parameters, [$unserialized, $request]));
  117. if ($response instanceof CacheableResponseInterface) {
  118. // Add rest config's cache tags.
  119. $response->addCacheableDependency($resource_config);
  120. }
  121. return $response;
  122. }
  123. }