Title says it all.

Correct example

JsonResponse + 403 prints

{}

Where AjaxResponse fails

AjaxResponse + 403 prints

A fatal error occurred: 

, but should print

[]

There is zero test coverage for this AFAICT.

Steps to reproduce

  1. Create a node.
  2. Go to the front page.
  3. Convince yourself that the quickedit/attachments route is working by looking at the XHR requests when you look at the front page.
  4. Edit quickedit.routing.yml, change this
    quickedit_attachments:
      pattern: '/quickedit/attachments'
      defaults:
        _controller: '\Drupal\quickedit\QuickEditController::attachments'
      requirements:
        _permission: 'access in-place editing'
    

    to:

    quickedit_attachments:
      pattern: '/quickedit/attachments'
      defaults:
        _controller: '\Drupal\quickedit\QuickEditController::attachments'
      options:
        _access_mode: 'ALL'
      requirements:
        _permission: 'access in-place editing'
        _access: 'FALSE'
    
  5. Clear all caches.
  6. Now again look at the XHR requests on the front page, you will see the reported problem.

Related issues

#2055937: Introduce error handling to in-place editing; if an AJAX request to commit changes fails, the user cannot recover
#77245: Provide a common API for displaying JavaScript messages

Support from Acquia helps fund testing for Drupal Acquia logo

Comments

Wim Leers’s picture

Issue summary: View changes

Updated issue summary.

Wim Leers’s picture

Issue summary: View changes

Updated issue summary.

Wim Leers’s picture

Issue summary: View changes

Added steps to reproduce.

Wim Leers’s picture

Note that this it not necessarily a bug in AjaxResponse, it could also be a bug in the routing system.

I don't know enough about the routing system to pinpoint it further (without learning all of the routing system).

Wim Leers’s picture

Issue summary: View changes

Updated issue summary.

Crell’s picture

"A fatal error occurred" sounds like an actual error happened somewhere and it's being badly reported. That suggests an issue in the error handling system to me.

Wim Leers’s picture

#2: true. To that end, #1158322: Add backtrace to all errors could be highly helpful.

Crell’s picture

I know Symfony and Silex have very good debug output in case of uncaught exceptions. Symfony also has a nice debug toolbar. We should perhaps look into both of them and see if there's code we can borrow, or at least concepts.

Wim Leers’s picture

#4: that'd be AWESOME :)

Berdir’s picture

Drupal\Core\Controller\ExceptionController

  public function execute(FlattenException $exception, Request $request) {
    $method = 'on' . $exception->getStatusCode() . $this->negotiation->getContentType($request);

    if (method_exists($this, $method)) {
      return $this->$method($exception, $request);
    }

    return new Response('A fatal error occurred: ' . $exception->getMessage(), $exception->getStatusCode(), $exception->getHeaders());
  }

The method it's looking for is "on403drupal_ajax", which is a rather weird method name :) Maybe link that to Json on that level? We do expect json, right?

That whole thing seems quite un-extendable? What would have have to do if I want to return XML, or actually, request XML and get a 403 or 404?

Wim Leers’s picture

Great analysis — thanks so much, Berdir! :)

Damien Tournoud’s picture

So, how is Drupal\Core\EventSubscriber\ViewSubscriber supposed to work? It has similar code:

      $method = 'on' . $this->negotiation->getContentType($request);

      if (method_exists($this, $method)) {
        $event->setResponse($this->$method($event));
      }
      else {
        $event->setResponse(new Response('Not Acceptable', 406));
      }

... but defines a onAjax() but no ondrupal_ajax(). I suppose we are not actually using the correct mimetype yet?

I echo Berdir's sentiment: we are missing a proper rendering layer. Symfony Full Stack doesn't have a proper rendering layer either, but at least it can guess templates based on the format of the incoming request (as implemented in Sensio\Bundle\FrameworkExtraBundle\EventListener\TemplateListener.

Also, is there any reason we are not using the proper Request::getRequestFormat() / Request::setRequestFormat() instead of hardwiring our content negotiation service everywhere?

Crell’s picture

Damien: We have an open issue with no code yet to standardize on getRequestFormat() properly. You're correct that we should be.

Also, we're discussing in the WSCCI channel as we speak about how ViewSubscriber needs to die a painful death. :-) It was never intended to live this long; refactoring HtmlPageController and ViewSubscriber (which are quite muddled right now) is something I've been thinking about the past few days. ExceptionController is equally hackish. It's out of scope for this issue to rewrite, but I want to try rewriting them.

Damien Tournoud’s picture

@Crell: Is there any specific reason why we are wiring all this in "wrapping" controllers, instead of using the KernelEvents::VIEW event like Symfony full stack does (and like ViewSubscriber currently does)?

Also, the default Symfony\Component\HttpKernel\EventListener\ExceptionListener handles exceptions as sub-requests to a special controller, which makes a ton of sense to me too. But of course, that requires separating rendering from the controllers, and brings us back to the previous point.

Crell’s picture

Damien: HtmlPageController was written with the expectation that it would get replaced by SCOTCH, but that's not happened. ViewSubscriber was written 7 months earlier with the expectation that it would get replaced as well, by SCOTCH or otherwise. Refactoring both of those so that we're using the view event properly, with a classed object, is exactly what I want to do. That's tangentially related to this issue but would likely overlap with it. (I'm waiting for the title issue to get in so that I'm not editing the exact same lines at the same time.)

Crell’s picture

Issue summary: View changes

added related issues

Berdir’s picture

Issue summary: View changes

What's the state here? This is still a problem and makes debugging ajax requests very painfull. It's not just a problem for 404/403 but also real errors where right now, ajax actions just silently fail.

Crell’s picture

I believe #2323759: Modularize kernel exception handling will allow us to address this issue.

dawehner’s picture

Status: Active » Postponed

So

haydeniv’s picture

Status: Postponed » Active
Crell’s picture

Can we get a failing test here to confirm it's still an issue and wasn't fixed implicitly as part of the exception refactor? If it's still an issue we need a test anyway but that should make it much easier to fix.

webchick’s picture

Status: Active » Postponed (maintainer needs more info)
Wim Leers’s picture

Status: Postponed (maintainer needs more info) » Needs review
FileSize
4.16 KB

There you go.

(Still a fatal error.)

Wim Leers’s picture

Issue summary: View changes

Updated the STR in the IS.

Status: Needs review » Needs work

The last submitted patch, 18: 2063303-18.patch, failed testing.

dawehner’s picture

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

Let's see whether this works. On top of that I wonder whether we this will cause issues in ie9, see #2339491: Ajax requests on forms with files fail on IE 9

Wim Leers’s picture

Woot! Can't wait to see the test results :)

dawehner’s picture

At least one of the failures of the quick test ones, will be green.

Status: Needs review » Needs work

The last submitted patch, 21: ajax_response-2063303-21.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
8.81 KB
5.02 KB

Meh. Not sure whether {} or [] should be the prefered way when there is no text. It seems to be that symfony decided to go with the first one, for some reason? Now this
patch uses "[]" but we could easily switch.

Status: Needs review » Needs work

The last submitted patch, 25: ajax_response-2063303-25.patch, failed testing.

Crell’s picture

Remember, it's a JSON object. A bare [] isn't valid JSON. A bare {} is a valid JSON null object.

dawehner’s picture

Status: Needs work » Needs review
FileSize
8.61 KB
6.62 KB

Maybe now.

Status: Needs review » Needs work

The last submitted patch, 28: ajax_response-2063303-28.patch, failed testing.

Wim Leers’s picture

Hah, it used to be [], but I guess that's because the old AJAX system was also wrong :)

Only test failures remaining in a single test! :)

dawehner’s picture

Status: Needs work » Needs review
FileSize
9.03 KB
1.66 KB

There we go.

damiankloip’s picture

FileSize
9.08 KB
3.38 KB

This patch is looking good to me. Just one thing IMO, we should return {"error": "Some fatal error message here"} instead of just ["Some fatal error message here"]. This keeps things more consistent but also allows you to get this with response.error and not response[0]

Crell’s picture

Status: Needs review » Reviewed & tested by the community
+++ b/core/lib/Drupal/Core/EventSubscriber/DefaultExceptionSubscriber.php
@@ -171,15 +172,12 @@ protected function onJson(GetResponseForExceptionEvent $event) {
-    // @todo We would prefer to use JsonResponse here, but existing code and
-    // tests are not prepared for parsing a JSON response when there are quotes
-    // or other values that would cause escaping issues. Instead, return a
-    // plain string and mark it as such.
-    $response = new Response($message, Response::HTTP_INTERNAL_SERVER_ERROR, [
-      'Content-type' => 'text/plain'
-    ]);

And we even get to fix that thing, too. Nice!

Wim Leers’s picture

Hurray!

RTBC+1

catch’s picture

Status: Reviewed & tested by the community » Fixed

Committed/pushed to 8.0.x, thanks!

  • catch committed 7fa6684 on 8.0.x
    Issue #2063303 by dawehner, damiankloip, Wim Leers: Fixed A Drupal 8...

Status: Fixed » Closed (fixed)

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