Problem/Motivation
SerializableHttpException
was introduced in #2818221: [BUGFIX] Restore serialization with HttpException inline responses. This caused the JSON API module to not use Symfony's HttpException
exception class hierarchy. Which is unlike all the rest of Drupal, which does use that class hierarchy.
That issue blamed #2778627: [BUGFIX] Check for individual entity access in listings, which is wrong too. The real root cause of this problem is the fact that \Drupal\jsonapi\ResourceResponse
contains $this->responseData
, which can be an object that contains a whole bunch of things that get serialized when the response gets cached. So when a response is cached by page_cache
or dynamic_page_cache
, things are serialized that are not meant to be serialized. SerializableHttpException
fixed the symptom, not the root cause. IOW: the real root cause is the same as this one for REST: #2807501: ResourceResponse::$responseData is serialized, but is never used: unserializing it on every Page Cache hit is a huge performance penalty.
Furthermore, everything in the Drupal\jsonapi\Error
namespace would no longer be necessary.
Proposed resolution
This patch does two things:
- Removes
SerializedHttpException
- Adds this:
public function __sleep() { $this->responseData = NULL; return array_keys(get_object_vars($this)); }
Point 2 will be improved further in #2855693: Remove \Drupal\jsonapi\Controller\RequestHandler::renderJsonApiResponse(), add \Drupal\jsonapi\EventSubscriber\ResourceResponseSubscriber.
Remaining tasks
None.
User interface changes
None.
API changes
None.
Data model changes
None.
Comment | File | Size | Author |
---|---|---|---|
#29 | 2829977-29.patch | 37.06 KB | Wim Leers |
|
Comments
Comment #2
Wim LeersComment #4
Wim LeersThis is not the proper solution.
The proper solution basically requires duplicating what [#2807501 did. But that's very wasteful.
OTOH, reusing the exact same solution means reusing
ResourceResponseInterface
and therest.resource_response.subscriber
service.What this issue is pointing out, is that that service and interface (plus default implementation) are useful outside of the scope of the REST module. It seems we should move all of that to
core/lib/Drupal/Core/Serialization
? GraphQL should be able to benefit from this too.Thoughts?
Comment #5
Wim LeersHEAD changed 9 hours ago, when #2821312-20: [BUGFIX] Catch non HTTP exceptions landed. That's the third commit to that issue. The patch in #2 removes all the complexity.
Rebased, so no interdiff.
Comment #7
Wim LeersAnd now with updated unit test expectations.
Comment #9
Wim LeersJust one failing test now — I'll leave that to e0ipso.
Comment #10
e0ipsoComment #11
e0ipsoI believe that the underlying issue that has not yet been fixed in core is: #2818525: Generate error responses for HttpException by using a Normalizer instead of a KernelEvents::EXCEPTION event subscriber
I see that the response delivered for missing credentials is no longer in JSON format but plain text, which is making the functional tests to fail.
Maybe this discussion is still relevant for this issue: https://www.drupal.org/node/2810293#comment-11725965
Comment #12
e0ipsoComment #13
Wim Leers#11: that sounds like #2805281: ?_format=hal_json error responses are application/json, yet should be application/hal+json.
Comment #14
Wim LeersAFAICT, all code in
Drupal\jsonapi\Error
is related. That should also not be necessary. (If I'm mistaken and it's not related, please create a separate issue for that.)Comment #15
Wim Leers#2805281: ?_format=hal_json error responses are application/json, yet should be application/hal+json landed!
Comment #16
e0ipsoPer Wim's comment, this is no longer blocked.
Comment #17
dagmarA lot of stuff changed here. This was hard.
Comment #19
dagmarRemoving the
DefaultExceptionSubscriber
requires update some code of theJsonApiFunctionalTest
. I'm working on this.Comment #20
dagmarActually after some work I figure out the fail we are seeing in #17 indicates the solution provided by
__sleep
doesn't really work. The error response don't expose the error meesage in the body. This was present in #7 too.So probably we can just get rid of the SerializableHttpException and keep the DefaultExeptionSubscriber.
Comment #21
Wim LeersI've been working on this today. Big update on the way.
Comment #22
Wim LeersFirst line in the IS:
Once #2841027: JSON API format should only be used on JSON API routes, but can currently also be used for REST resources is fixed, this statement will be incorrect. Because the Serialization and REST modules will no longer be able to handle the JSON API format, because that format is not meant to be used generically: it's only meant to be used on the JSON API routes!
Also, we'll need a custom exception subscriber anyway, for the changes made in #2821045: [BUGFIX] Conform to the error spec.
Therefore this is also no longer true:
IS almost completely rewritten.
Comment #23
Wim LeersFirst, let's rebase this patch.
(This patch is rolled on top of #2841027: JSON API format should only be used on JSON API routes, but can currently also be used for REST resources.)
Comment #24
Wim Leers#20:
This indeed allows me to create a patch that passes all tests.
Patch attached that's hopefully green :)
See interdiff: this does exactly what @dagmar wrote, plus some additional changes to code that was added after @dagmar worked on it.
Comment #25
Wim LeersBut does this patch also make sense?
The current patch does:
SerializableHttpException
ResourceResponse::__sleep()
(to prevent that we attempt to cache and hence serialize the entities stored in a JSON API response object)Point 1 is fine.
But point 2 is a problem. We had the same problem in the REST module, and we fixed that in #2807501: ResourceResponse::$responseData is serialized, but is never used: unserializing it on every Page Cache hit is a huge performance penalty. We want the exact same solution here. Then there is no need for a
__sleep()
method. Yes, that means we're duplicating code from the REST module. But better a duplication than an incorrect abstraction. If it turns out that it's the right abstraction, we can move the code for this over to the Serialization module. So that still leaves open the door for future simplification. For now, what matters is that we follow the exact same pattern.In other words: we should revert the changes made by #2818221: [BUGFIX] Restore serialization with HttpException inline responses and instead introducing the same solution that is used in core's
rest
module: #2807501: ResourceResponse::$responseData is serialized, but is never used: unserializing it on every Page Cache hit is a huge performance penalty. Opened #2855693: Remove \Drupal\jsonapi\Controller\RequestHandler::renderJsonApiResponse(), add \Drupal\jsonapi\EventSubscriber\ResourceResponseSubscriber for that, because tacking that onto this issue will make this issue/patch very very difficult to review.Comment #26
Wim LeersA bit of clean-up.
Comment #27
Wim LeersThat still leaves
in the IS.
Well, simplifying that seems out of scope for this issue. That class was introduced in #2738269-15: [FEATURE] Implement expected error format for JSON API. But it turns out that the simplifications introduced in this patch allow me to simply remove that altogether!
This also allows #2855693: Remove \Drupal\jsonapi\Controller\RequestHandler::renderJsonApiResponse(), add \Drupal\jsonapi\EventSubscriber\ResourceResponseSubscriber to be simpler!
Comment #28
Wim LeersUpdating issue title. Adding
tag. I think this is so important for maintainability that this deserves to be major. This issue brings us to a place where JSON API does NOT have any special error/exception handling anymore. It does have normalizers for exceptions, but that's it.The IS and patch now also clearly state that
ResourceResponse::__sleep()
is only a temporary work-around, and it will be removed in #2855693: Remove \Drupal\jsonapi\Controller\RequestHandler::renderJsonApiResponse(), add \Drupal\jsonapi\EventSubscriber\ResourceResponseSubscriber.Comment #29
Wim LeersThis made me think about ensuring that this normalizer is only applied to the JSON API format. Turns out it's not! It's doing it for any format!
So, effectively, the current
\Drupal\jsonapi\Normalizer\HttpExceptionNormalizer
is exactly the code that one would propose for #2818525: Generate error responses for HttpException by using a Normalizer instead of a KernelEvents::EXCEPTION event subscriber.However… that's not without problems too, because:
and
All those keys are JSON API-specific: they comply with http://jsonapi.org/format/#error-objects.
So, really, this normalizer is highly specific to the JSON API format. And that also means that doing #2818525: Generate error responses for HttpException by using a Normalizer instead of a KernelEvents::EXCEPTION event subscriber is fairly pointless, because JSON API is the only module that would benefit from that, but it'd still have to A) provide a custom
HttpExceptionNormalizer
, B) still have to provide a custom exception subscriber, because it needs to pass['data_wrapper' => 'errors']
as the serialization context when calling the serializer.So, made
\Drupal\jsonapi\Normalizer\HttpExceptionNormalizer
only support the'api_json'
format.Comment #30
e0ipsoI still need to do a better review on this.
We should add https://gist.github.com/e0ipso/efcc4e96ca2aed58e32948e4f70c2460 for documentation on how to filter.
We are losing the ability to serialize PHP errors. Before, a syntax error would be turned into a JSON API response. Without the error handler we cannot really do that. I'm not sure how useful is that feature, I'm open for discussion.
Comment #31
dawehnerI'd argue that at least on production this really doesn't matter. You don't want to serialize PHP errors and expose them to the public. For dev sites IMHO not rendering PHP exceptions not within a nice JSONAPI output is totally acceptable. You don't want to handle the output anymore in your Client JS, I assume, but rather just look at the PHP error for example via postman.
Comment #32
Wim Leers#2841027: JSON API format should only be used on JSON API routes, but can currently also be used for REST resources landed, this is unblocked now!
Comment #33
Wim Leers#30:
BadRequestHttpException
etc instances withSerializableHttpException
.#31:
I could not agree more. If there's a PHP error, serializing it into an otherwise valid JSON API response is not at all helpful. The client can't fix it, because it's a server-side error. It's a hard failure. Making the client still kinda work is not helpful. In fact, I'd argue it's harmful… because it might result in the client pretending everything's okay, even though the server response cannot be trusted, precisely because it's a PHP error.
Comment #34
e0ipsoOne of my few virtues is that I know how to listen for arguments, and change my opinion.
This is a very good point.
This is another very good point.
I will be committing this soon.
Comment #35
Wim LeersYAY!
This really is quite a big simplification. Also in diff stats:
… but more so in abstraction complexity/concept complexity. JSON API now no longer has these two concepts that make it much more difficult to understand and hence maintain.
I think you have more virtues ;) But yes, that's definitely an important one in the world of software!
Comment #37
e0ipsoFixed! Thanks all, you rock!
Also, I made an edit to the README.md on commit that you wouldn't believe.
Comment #38
Wim LeersHehe, is it the linking to https://gist.github.com/e0ipso/efcc4e96ca2aed58e32948e4f70c2460? :)
Comment #39
e0ipsoHahaha! I forgot about that click bait. Yes, it's that link :-)