diff --git a/core/lib/Drupal/Core/EventSubscriber/ExceptionJsonSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/ExceptionJsonSubscriber.php index d80384c6a8..76cc55ee3a 100644 --- a/core/lib/Drupal/Core/EventSubscriber/ExceptionJsonSubscriber.php +++ b/core/lib/Drupal/Core/EventSubscriber/ExceptionJsonSubscriber.php @@ -35,7 +35,7 @@ protected static function getPriority() { public function on4xx(GetResponseForExceptionEvent $event) { /** @var \Symfony\Component\HttpKernel\Exception\HttpExceptionInterface $exception */ $exception = $event->getException(); - $response = new JsonResponse(['message' => $event->getException()->getMessage()], $exception->getStatusCode()); + $response = new JsonResponse(['message' => $event->getException()->getMessage()], $exception->getStatusCode(), $exception->getHeaders()); $event->setResponse($response); } diff --git a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php index e154a75249..1224b45d94 100644 --- a/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php +++ b/core/modules/rest/tests/src/Functional/EntityResource/EntityResourceTestBase.php @@ -671,6 +671,7 @@ public function testPatch() { $response = $this->request('PATCH', $url, $request_options); if ($has_canonical_url) { $this->assertSame(405, $response->getStatusCode()); + $this->assertSame(['GET, POST, HEAD'], $response->getHeader('Allow')); $this->assertSame(['text/html; charset=UTF-8'], $response->getHeader('Content-Type')); } else { @@ -683,6 +684,7 @@ public function testPatch() { // DX: 405 when resource not provisioned. $response = $this->request('PATCH', $url, $request_options); + $this->assertSame(['GET, POST, HEAD'], $response->getHeader('Allow')); $this->assertResourceErrorResponse(405, 'No route found for "PATCH ' . str_replace($this->baseUrl, '', $this->getUrl()->setAbsolute()->toString()) . '": Method Not Allowed (Allow: GET, POST, HEAD)', $response); @@ -870,6 +872,7 @@ public function testDelete() { $response = $this->request('DELETE', $url, $request_options); if ($has_canonical_url) { $this->assertSame(405, $response->getStatusCode()); + $this->assertSame(['GET, POST, HEAD'], $response->getHeader('Allow')); $this->assertSame(['text/html; charset=UTF-8'], $response->getHeader('Content-Type')); } else { @@ -882,6 +885,7 @@ public function testDelete() { // DX: 405 when resource not provisioned. $response = $this->request('DELETE', $url, $request_options); + $this->assertSame(['GET, POST, HEAD'], $response->getHeader('Allow')); $this->assertResourceErrorResponse(405, 'No route found for "DELETE ' . str_replace($this->baseUrl, '', $this->getUrl()->setAbsolute()->toString()) . '": Method Not Allowed (Allow: GET, POST, HEAD)', $response); diff --git a/core/modules/serialization/src/EventSubscriber/DefaultExceptionSubscriber.php b/core/modules/serialization/src/EventSubscriber/DefaultExceptionSubscriber.php index 0d1171eeca..a1e7bad2f8 100644 --- a/core/modules/serialization/src/EventSubscriber/DefaultExceptionSubscriber.php +++ b/core/modules/serialization/src/EventSubscriber/DefaultExceptionSubscriber.php @@ -64,12 +64,17 @@ protected static function getPriority() { public function on4xx(GetResponseForExceptionEvent $event) { /** @var \Symfony\Component\HttpKernel\Exception\HttpExceptionInterface $exception */ $exception = $event->getException(); + $request = $event->getRequest(); - $format = $event->getRequest()->getRequestFormat(); + $format = $request->getRequestFormat(); $content = ['message' => $event->getException()->getMessage()]; $encoded_content = $this->serializer->serialize($content, $format); + $headers = $exception->getHeaders(); - $response = new Response($encoded_content, $exception->getStatusCode()); + // Add the MIME type from the request to send back in the header. + $headers['Content-Type'] = $request->getMimeType($format); + + $response = new Response($encoded_content, $exception->getStatusCode(), $headers); $event->setResponse($response); } diff --git a/core/modules/serialization/tests/src/Unit/EventSubscriber/DefaultExceptionSubscriberTest.php b/core/modules/serialization/tests/src/Unit/EventSubscriber/DefaultExceptionSubscriberTest.php new file mode 100644 index 0000000000..e20f6f7fe2 --- /dev/null +++ b/core/modules/serialization/tests/src/Unit/EventSubscriber/DefaultExceptionSubscriberTest.php @@ -0,0 +1,42 @@ +prophesize(HttpKernelInterface::class); + $request = Request::create('/test'); + $request->setRequestFormat('json'); + + $e = new MethodNotAllowedHttpException(['POST', 'PUT'], 'test message'); + $event = new GetResponseForExceptionEvent($kernel->reveal(), $request, 'GET', $e); + $subscriber = new DefaultExceptionSubscriber(new Serializer([], [new JsonEncoder()]), []); + $subscriber->on4xx($event); + $response = $event->getResponse(); + + $this->assertInstanceOf(Response::class, $response); + $this->assertEquals('{"message":"test message"}', $response->getContent()); + $this->assertEquals(405, $response->getStatusCode()); + $this->assertEquals('POST, PUT', $response->headers->get('Allow')); + $this->assertEquals('application/json', $response->headers->get('Content-Type')); + } + +} diff --git a/core/tests/Drupal/Tests/Core/EventSubscriber/DefaultExceptionSubscriberTest.php b/core/tests/Drupal/Tests/Core/EventSubscriber/DefaultExceptionSubscriberTest.php index 541d13636b..6582500ee6 100644 --- a/core/tests/Drupal/Tests/Core/EventSubscriber/DefaultExceptionSubscriberTest.php +++ b/core/tests/Drupal/Tests/Core/EventSubscriber/DefaultExceptionSubscriberTest.php @@ -23,10 +23,9 @@ class DefaultExceptionSubscriberTest extends UnitTestCase { public function testOnExceptionWithUnknownFormat() { $config_factory = $this->getConfigFactoryStub(); - // Format 'bananas' requested, yet only 'json' allowed. $kernel = $this->prophesize(HttpKernelInterface::class); $request = Request::create('/test?_format=bananas'); - $e = new MethodNotAllowedHttpException(['json'], 'test message'); + $e = new MethodNotAllowedHttpException(['POST', 'PUT'], 'test message'); $event = new GetResponseForExceptionEvent($kernel->reveal(), $request, 'GET', $e); $subscriber = new DefaultExceptionSubscriber($config_factory); $subscriber->onException($event); @@ -35,6 +34,9 @@ public function testOnExceptionWithUnknownFormat() { $this->assertInstanceOf(Response::class, $response); $this->assertEquals('test message', $response->getContent()); $this->assertEquals(405, $response->getStatusCode()); + $this->assertEquals('POST, PUT', $response->headers->get('Allow')); + // Also check that that text/plain content type was added. + $this->assertEquals('text/plain', $response->headers->get('Content-Type')); } } diff --git a/core/tests/Drupal/Tests/Core/EventSubscriber/ExceptionJsonSubscriberTest.php b/core/tests/Drupal/Tests/Core/EventSubscriber/ExceptionJsonSubscriberTest.php new file mode 100644 index 0000000000..86c6d86bfd --- /dev/null +++ b/core/tests/Drupal/Tests/Core/EventSubscriber/ExceptionJsonSubscriberTest.php @@ -0,0 +1,38 @@ +prophesize(HttpKernelInterface::class); + $request = Request::create('/test'); + $e = new MethodNotAllowedHttpException(['POST', 'PUT'], 'test message'); + $event = new GetResponseForExceptionEvent($kernel->reveal(), $request, 'GET', $e); + $subscriber = new ExceptionJsonSubscriber(); + $subscriber->on4xx($event); + $response = $event->getResponse(); + + $this->assertInstanceOf(JsonResponse::class, $response); + $this->assertEquals('{"message":"test message"}', $response->getContent()); + $this->assertEquals(405, $response->getStatusCode()); + $this->assertEquals('POST, PUT', $response->headers->get('Allow')); + $this->assertEquals('application/json', $response->headers->get('Content-Type')); + } + +}