Problem/Motivation

This issue was discovered while testing a rest service and the site was in offline (maintenance) mode.
If the site is in maintenance mode and if a 3rd party tries to access a service with "_format" query string mentioned, the response returned is not based on "_format" query string.

Steps to Replicate

  1. Setup a node that should be accessed by Web services.
  2. Put the site in maintenance mode.
  3. With a REST client try to access http://drupal8.dev/node/node-id?_format=hal_json
  4. Expected: JSON response should be displayed with maintenance message but returns the full theme with the maintenance message.

Proposed resolution

\Drupal\Core\EventSubscriber\MaintenanceModeSubscriber should be aware of the requested response format.

Remaining tasks

  1. Write a patch.
  2. Add tests.

User interface changes

TBD

API changes

TBD

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

aneek created an issue. See original summary.

aneek’s picture

Issue summary: View changes
dawehner’s picture

Yeah I guess we need to adapt \Drupal\Core\EventSubscriber\MaintenanceModeSubscriber::onKernelRequestMaintenance to support other formats as well.

aneek’s picture

@dawehner,
I'll have a try then.

aneek’s picture

Assigned: Unassigned » aneek
Wim Leers’s picture

Component: rest.module » base system
  1. \Drupal\Core\EventSubscriber\MaintenanceModeSubscriber doesn't live in the REST module
  2. \Drupal\Core\EventSubscriber\MaintenanceModeSubscriber needs to be have better even when not using the REST module, but just using other formats

Therefore, moving to base system.

aneek’s picture

@Wim Leers, I believe, the logic can be to get the supplied "_format" in \Drupal\Core\EventSubscriber\MaintenanceModeSubscriber::onKernelRequestMaintenance and then call Serialization to respond back. But how to add serializer in MaintenanceModeSubscriber?

Wim Leers’s picture

Hmm… interesting. That would not be possible, because that subscriber lives in core itself, but the serializer service lives in the serialization module in core. If we'd indeed have to go that way… then the only way to do this would be to override the MaintenanceModeSubscriber in the serialization module, which feels very very inappropriate.

Not sure yet how to solve this. Perhaps @dawehner or others have ideas.

aneek’s picture

Yes @WimLeers, that is the issue. Even if serialization module is not enabled then also the site should give proper json/xml response. But currently this will not happen. I tried with http://symfony.com/doc/current/components/serializer.html but was not working. May be I missed something. I'll give it another try!!

But surely lets hear from others as well. I'll try to ping @dawehner tomorrow.

aneek’s picture

Issue summary: View changes
dawehner’s picture

Well, the idea world would be to return a MaintenanceMode domain object and then convert that to its appropriate response, but I think that kind of ship has sailed?
Maybe we want a dedicated listener for HTML and one for JSON?

aneek’s picture

Adding a quick fix patch. It's just works as I tested manually. Needs review and hopefully a better fix!!. But we have to start from somewhere :-)

@dawehner & @WimLeers please suggest your thoughts on this.

aneek’s picture

Status: Active » Needs review
Issue tags: +serialization
Wim Leers’s picture

I think #11 makes a lot of sense. Why do you think that wouldn't work anymore?

#12 is a step forward for core from an end user POV, but it's still going to be broken as soon as sites install modules that add support for more serialization formats. We should solve the generic problem, which is what #11 describes.

aneek’s picture

@WimLeers, that makes sense. Yes, the fix is a basic one and if other modules provide support for different types of serialization (like HATEOAS) then this might break.

Another idea is to return the MaintenanceMode domain object and convert it via Normalizer (maybe. ObjectNormalizer). Then expose the converted value to available serializations.

But how to? - Any ideas?

dawehner’s picture

Why do you think that wouldn't work anymore?

Well, sadly we kinda dropped using that approach a while ago for Drupal itself, but sure, maybe reintroducing that here makes sense. On the other hand, for maintenance mode, we might want to return as early as possible, so that code would not work.

+++ b/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php
@@ -97,16 +100,39 @@ public function __construct(MaintenanceModeInterface $maintenance_mode, ConfigFa
+              $json_encoder = new JsonEncoder();
+              $content = $json_encoder->encode(['message' => $content], 'json');
...
+            case 'xml':
+              $xml_encoder = new XmlEncoder();
+              $content = $xml_encoder->encode(['message' => $content], 'json');
+              break;
+          }

IMHO we don't really need a full serializer object for something we could just use JsonResponse

aneek’s picture

@dawehner, I believe, the serializer objects are required since we also support XML as a valid format. Well, for JSON JsonResponse will do the job but for XML I think XmlEncoder is required.

Version: 8.0.x-dev » 8.1.x-dev

Drupal 8.0.6 was released on April 6 and is the final bugfix release for the Drupal 8.0.x series. Drupal 8.0.x will not receive any further development aside from security fixes. Drupal 8.1.0-rc1 is now available and sites should prepare to update to 8.1.0.

Bug reports should be targeted against the 8.1.x-dev branch from now on, and new development or disruptive changes should be targeted against the 8.2.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

aneek’s picture

Assigned: aneek » Unassigned
Wim Leers’s picture

Component: base system » rest.module

Moving to rest.module for better visibility.

dawehner’s picture

Mh, it would be cool to override this service from within the serializer module, so it can use whatever format exists out there, by levering the serializer service.

aneek’s picture

Status: Needs review » Needs work
Wim Leers’s picture

Title: The response is not proper when site is in maintenance mode and "_format" is specified » While in maintenance mode, REST routes respond with HTML instead of XML/JSON/…
Assigned: Unassigned » damiankloip
Issue tags: +Needs tests
FileSize
5.98 KB

Mh, it would be cool to override this service from within the serializer module, so it can use whatever format exists out there, by levering the serializer service.

Agreed. So, basically: any module that adds another serialization format is responsible for overriding the maintenance mode subscriber to provide maintenance mode support for that particular format.

Here's a patch that does that, mirrored after \Drupal\Core\EventSubscriber\MaintenanceModeSubscriber. Manual testing shows it works. Now this still needs test coverage.

Assigning to damiankloip, because this is actually changing serialization module, which he is the maintainer of.

dawehner’s picture

Just a general question, is is okay to initialize the serializer service now on every request?

Wim Leers’s picture

Wow, that's a great question!

The answer is of course: probably not. But… how to then fix this? Should we make serializer a lazy service?

dawehner’s picture

An alternative approach as talked with @damiankloip would be the following:

Throw a Maintenance mode exception.
Provide a exception handler which deals with HTML
Provide a exception handler for serializer.

klausi’s picture

Note that REST services do not have to respond in the format the client requests, see the HTTP spec. The important thing a client must check is the status code: and that will be 500 while the site is in maintenance mode. Go fix your client to check status codes and do not rely on the response body when the status is 500.

Wim Leers’s picture

But… quoting yourself in #11:

Well, the idea world would be to return a MaintenanceMode domain object and then convert that to its appropriate response, but I think that kind of ship has sailed?

Why would #26 be okay? It'd be breaking BC in some sense. Although perhaps this is not considered an actual/official API.

dawehner’s picture

But… quoting yourself in #11:

There are of course always alternative ways to implement things. For me conceptually, at this moment in time and space, maintenance mode is not the normal flow of things, but I agree we should avoid throwing exceptions if possible.

Why would #26 be okay? It'd be breaking BC in some sense. Although perhaps this is not considered an actual/official API.

Well, event subscribers are certainly not an API, or are they?

Wim Leers’s picture

Go fix your client to check status codes and do not rely on the response body when the status is 500.

Does this mean this is a won't fix?

This answer sounds eminently sensible to me.

dawehner’s picture

IMHO its still odd that we return only HTML but I agree, clients have been always able to workaround it.

dawehner’s picture

The question is whether clients should show it somehow that the sites is in maintenance mode or just guess based upon the status code.

Wim Leers’s picture

The question is whether clients should show it somehow that the sites is in maintenance mode or just guess based upon the status code.

Indeed. But … would they be able to parse <response>The site is in maintenance mode…</response> in a sane way in case of XML? They'd be expecting a completely differently structured response anyway.

503 == "service unavailable" == maintenance mode, just without the friendly message. In case of REST, you need to provide your own friendly messages anyway.

So the more I think about it, the more klausi's point makes sense.

aneek’s picture

@WimLeers, I do get the point that you and @klausi trying to establish. And this is also true that a 3rd party should always check for the status code first then parse the response body. But is it a good approach to deliver a HTTP 503 with a HTML, CSS & JS based maintenance page when the 3rd party client requests for a xml or json response?
Also, yes 3rd party might not be expecting a <response>The site is in maintenance mode…</response> message as how could they know when the API server which they are using to consume services goes down. Correct!! But again, parsing the body while they get a 503 response. A simple XML with one parent or a JSON object is really easy to parse rather than a whole HTML content.

Let me know your views on this regards.

Thanks!!

Wim Leers’s picture

#34: I think it's fair to expect a REST API consumer to simply ignore the response body in case of a HTTP 5xx response. I think that if we do this, we don't do what I did in #23, where I put the site maintenance message in the response body. We just send an empty body: the 503 alone is sufficient.

dawehner’s picture

So yeah what about providing a really simple implementation for now: For HTML return the HTML page, for everything else return 'text/plain' with just the message?

Wim Leers’s picture

#36: I think that sounds eminently sensible, especially when considering #27: Note that REST services do not have to respond in the format the client requests, see the HTTP spec.

dawehner’s picture

#37 Yeah exactly, this was my idea. Sadly "Giraffe" is not valid JSON. This would have been even better.

Wim Leers’s picture

Status: Needs work » Needs review
FileSize
1.91 KB
6.74 KB

So, something like this then.

Status: Needs review » Needs work

The last submitted patch, 39: rest_maintenance_mode-2653318-39.patch, failed testing.

Wim Leers’s picture

00:00:24.253 PHP Parse error:  syntax error, unexpected ';' in ./core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php on line 103

I suck.

Wim Leers’s picture

Status: Needs work » Needs review
FileSize
1.91 KB
938 bytes
dawehner’s picture

+++ b/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php
@@ -90,10 +91,21 @@ public function __construct(MaintenanceModeInterface $maintenance_mode, ConfigFa
+        $message = SafeMarkup::format($this->config->get('system.maintenance')->get('message'), array(
+          '@site' => $this->config->get('system.site')->get('name'),
+        ));

Could we move

        $content = Xss::filterAdmin(SafeMarkup::format($this->config->get('system.maintenance')->get('message'), array(
          '@site' => $this->config->get('system.site')->get('name'),
        )));

into its own method?

Wim Leers’s picture

-        $content = Xss::filterAdmin(SafeMarkup::format($this->config->get('system.maintenance')->get('message'), array(
-          '@site' => $this->config->get('system.site')->get('name'),
-        )));
-        $response = $this->bareHtmlPageRenderer->renderBarePage(['#markup' => $content], $this->t('Site under maintenance'), 'maintenance_page');
+        $response = $this->bareHtmlPageRenderer->renderBarePage(['#markup' => $this->getSiteMaintenanceMessage()], $this->t('Site under maintenance'), 'maintenance_page');

I removed Xss::filterAdmin() … because #markup gets that done automatically :) (See \Drupal\Core\Render\Renderer::ensureMarkupIsSafe().)

dawehner’s picture

This looks like a great solution for the problem. Now some simple non HTML tests would be nice.

Wim Leers’s picture

Status: Needs review » Needs work

The last submitted patch, 46: rest_maintenance_mode-2653318-46-TEST_ONLY_FAIL.patch, failed testing.

Wim Leers’s picture

Status: Needs work » Needs review

I should've uploaded the FAIL patch first. My bad.

dawehner’s picture

Status: Needs review » Reviewed & tested by the community

Cool

aneek’s picture

Thanks @dawehner & @WimLeers for contributing time in this issue. Manually tested and is working as expected.

Btw, a different thing that I've found,

If I put the site in maintenance mode, any pages or rest export made using views still is visible. The steps to replicate this issue is:

  1. Create a new site with https://simplytest.me/
  2. Create some contents
  3. Create a views page like this paste: http://hastebin.com/owudiqoxeb.sm
  4. The permission should be "View published content"
  5. Put the service in maintenance and try to access the views page (for me it was https://drf5q.ply.st/articles)
  6. All the content listing are shown for any user has permission to view published contents and the "Anonymous" user can see it too. The same happens for the rest export as well. But you can't access the nodes while clicking on the node titles. Which is correct

Do you guys think this is a valid case? I believe this is an issue and a major one :)

Wim Leers’s picture

#50: that would be a separate problem. Please create a nee issue for that.

aneek’s picture

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php
@@ -90,18 +91,23 @@ public function __construct(MaintenanceModeInterface $maintenance_mode, ConfigFa
+      if ($request->getRequestFormat() !== 'html') {
+        $response = new Response($this->getSiteMaintenanceMessage(), 503, array('Content-Type' => 'text/plain'));
+        $event->setResponse($response);
+        return;
+      }
+
       if (!$this->maintenanceMode->exempt($this->account)) {

Why's this outside the exempt check? I think it should be in it.

Wim Leers’s picture

Eh… you're absolutely right.

aneek’s picture

aneek’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 55: rest_maintenance_mode-2653318-55.patch, failed testing.

aneek’s picture

aneek’s picture

Status: Needs work » Needs review
Wim Leers’s picture

Status: Needs review » Needs work

It's confusing that two separate comments are now joined together.

aneek’s picture

@WimLeers: I am not too sure that I got your view point here. But, I think the last patch #15 from #2732309: REST views ignore the maintenance mode setting is for this ticket. So just applying that patch here.

Wim Leers’s picture

aneek: I'm saying the changes you made in #55 are correct, but there should have been a blank line in between the comments.

aneek’s picture

@WimLeers: ahh.... :-)

aneek’s picture

aneek’s picture

Status: Needs work » Needs review

The last submitted patch, 58: rest_maintenance_mode-2653318-58.patch, failed testing.

Wim Leers’s picture

Status: Needs review » Reviewed & tested by the community

@aneek addressed the sole remark alexpott had in #53 over comments #54-#66. Restoring @dawehner's RTBC status from #49.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 64: rest_maintenance_mode-2653318-64.patch, failed testing.

dawehner’s picture

Status: Needs work » Reviewed & tested by the community

Looks exactly like my patch :) And yes, I posted to the wrong issue ...

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/modules/rest/src/Tests/Views/StyleSerializerTest.php
@@ -180,6 +180,13 @@ public function testSerializerResponses() {
+
+    // Set the site to maintenance mode.
+    $this->container->get('state')->set('system.maintenance_mode', TRUE);
+    \Drupal::service('page_cache_kill_switch')->trigger();
+    $this->drupalGetWithFormat('test/serialize/entity', 'json');
+    // Verify that the endpoint is unavailable for anonymous users.
+    $this->assertResponse(503);

This is failing and I think, but I'm not sure, might be part of another patch.

Wim Leers’s picture

True, see #50 + #52. #58 brought the fix to this issue. It feels like it's within scope, because it's again about REST responses + maintenance mode. However, if you really want, we could split that off to #2732309: REST views ignore the maintenance mode setting.

And it definitely used to pass :( Queued for re-testing.

The last submitted patch, 64: rest_maintenance_mode-2653318-64.patch, failed testing.

Wim Leers’s picture

Failed again. I wonder what changed in REST views code or elsewhere that is causing this to now fail?

dawehner’s picture

Status: Needs work » Needs review
FileSize
5.1 KB
961 bytes

This particular subset of the test never worked, see #55 and following.

What happens is the following:

    // PageCache miss
    $expected = $serializer->serialize($entities, 'json');
    $actual_json = $this->drupalGetWithFormat('test/serialize/entity', 'json');
    $this->assertIdentical($actual_json, $expected, 'The expected JSON output was found.');
    $expected = $serializer->serialize($entities, 'xml');
    // PageCache is set.

    // PageCache hit
    $this->drupalGetWithFormat('test/serialize/entity', 'json');

Questions:

  1. Is it okay to invalidate the caches in the test?
  2. Why are we not invalidated any cache in \Drupal\system\Form\SiteMaintenanceModeForm::submitForm?
  3. Why does the page cache not have a dedicated cache tag, so it can be cleared for itself?
Wim Leers’s picture

Why are we not invalidated any cache in \Drupal\system\Form\SiteMaintenanceModeForm::submitForm?

#2453351: Maintenance mode message ends up in page cache, served endlessly ensured that nothing ends up in page cache while maintenance mode is on.

But it looks like maintenance mode is designed to NOT prevent page cache from working completely; it seems it's designed to still let cached pages for anonymous users (either in Page Cache or in Varnish). That actually makes sense: if anonymous responses are cached in Page Cache/Varnish… which means that end users can continue to see sensible output… then why would we force them to see a maintenance message?

Why does the page cache not have a dedicated cache tag, so it can be cleared for itself?

Because it must be treated the same way as any other reverse proxy, such as Varnish.


So, as far as I'm concerned, the problem is that the additions to StyleSerializerTest are happening after PageCache has been filled. It should test PageCache behavior before PageCache has been filled, because once filled, it's intentional that users get to see the cached responses.

alexpott’s picture

+1 to #75 - yes we should show users cached pages if we have them.

Wim Leers’s picture

Status: Needs review » Needs work

So then #74 is wrong. Discussed with @dawehner: let's move that test into a separate test method, so that it runs in a unique environment, i.e. with a cold page cache.

marthinal’s picture

Status: Needs work » Needs review
FileSize
1.73 KB
5.39 KB

Adding a new method.

Wim Leers’s picture

Status: Needs review » Reviewed & tested by the community
alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed f692cd0 and pushed to 8.1.x and 8.2.x. Thanks!

I don't think MaintenanceModeSubscriber should be considered API therefore adding a protected method to solve a bug is fine in a patch release.

diff --git a/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php
index 37d7575..57d43ec 100644
--- a/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/MaintenanceModeSubscriber.php
@@ -3,7 +3,6 @@
 namespace Drupal\Core\EventSubscriber;
 
 use Drupal\Component\Utility\SafeMarkup;
-use Drupal\Component\Utility\Xss;
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Render\BareHtmlPageRendererInterface;
 use Drupal\Core\Routing\RouteMatch;

Remove unused use on commit.

  • alexpott committed 4b6d125 on 8.2.x
    Issue #2653318 by Wim Leers, aneek, marthinal, dawehner: While in...

  • alexpott committed f692cd0 on 8.1.x
    Issue #2653318 by Wim Leers, aneek, marthinal, dawehner: While in...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.