symfony
symfony copied to clipboard
[WebProfilerBundle] Temporarily remove `XDEBUG_*` cookie while performing XHR
Q | A |
---|---|
Branch? | 7.1 |
Bug fix? | no |
New feature? | yes |
Deprecations? | no |
Issues | N/A |
License | MIT |
When using an Xdebug browser extension with Symfony, toolbar requests are also processed by Xdebug because the browser extension has set an XDEBUG_*
cookie on the page. This leads to superfluous breakpoint triggering when debugging at lower levels of the codebase, as well as a performance hit with no gain for every toolbar request, junk profiler/trace output blobs, etc.
The only sane way to prevent this behavior seems to be for the cookie to never be sent, as Xdebug has no mechanism for ignoring requests to certain URIs, and detecting this condition at the webserver and rewriting the request before passing it off to PHP is also non-trivial.
This PR detects the XDEBUG_*
cookie and removes it before conducting XHR, then re-sets it after the fact.
Thanks!
FYI I decided to implement the "beforeunload" handler and rework this a bit, so I've converted to it to a draft for the moment. I'll submit a revision and switch it back shortly. Thanks!
Interrupted XHRs due to page reloads/redirects/etc. are now handled by a beforeunload
listener, and trace/profile modes are now also supported (as well as any other future mode, as the patch now uses a regex to match). I also refactored the code to be localized to one function so that it's easier to follow.
After more testing, I discovered that reinstating the cookie after the response was received (in a readystatechange
callback) doesn't work, as taking longer than ~150ms to put the cookie back confuses the browser extensions. I reworked it again to reinstate the cookie after the request is sent, but this was also not so straightforward, because even though the documentation says that the request is sent by the time XMLHttpRequest.send()
returns, it's a filthy lie. 😭
What does seem to work very reliably, at least in Chrome, is reinstating the cookie on the next iteration of the browser's event loop. setTimeout(..., 0)
was reliable over hundreds of iterations of testing the functionality in Chrome. But alas, Firefox was a slightly different story. ~5% of the time, Firefox hasn't sent the request for an extra iteration or two of the event loop. Setting the timeout delay to a reasonable value of 50ms seems to satisfy all parties on both Firefox and Chrome, but Safari remains untested.
@nicolas-grekas I wish, but no, XHR in modern browsers does not support manually specifying the Cookie
header for security reasons.
As I see it, the only viable solutions to this issue are:
-
What I've implemented here, which admittedly is very much a hack and last resort. From my experience using it heavily for the last few weeks it is a very reliable, well-functioning hack (within the specific environments I've tested it in, of course)...but it is a hack, nonetheless.
-
Requiring users to somehow configure their webserver to rewrite requests to the
/_wdt/
and/_profiler/
paths, removing theXDEBUG_*
cookie in the process. Is this even possible with most common (Apache, Nginx, Caddy, ?) webservers? I guess if your profiler paths don't require authentication then you could more-or-less just drop theCookie
header altogether...
I wish there was a more elegant avenue because I quite dislike both of the given options for different reasons, but I haven't found one yet.
Actually, what if instead of managing the Xdebug cookie via some extra browser extension, the Symfony toolbar could manage it instead through a new UI element with similar functionality to the browser extensions? This is probably still prone to side-effects when there are concurrent requests in different tabs or caused by XHR in the user's app, for example, but maybe it's a step in the right direction?
Another idea: since xdebug is sensitive to $_GET
also, what would happen if we set those settings in the query string? Can this make xdebug ignore the cookie? Can you please check? :pray:
Impossible, unfortunately...at least AFAICT. All of the GET parameters that Xdebug respects (XDEBUG_TRIGGER
, XDEBUG_PROFILE
, XDEBUG_TRACE
, XDEBUG_SESSION
, and XDEBUG_CONFIG
) enable its functionality – none of them can disable it, because they are all "triggers" which selectively enable Xdebug in one or more modes for a single request (when xdebug.start_with_request = trigger
).
The only way to prevent triggering Xdebug when making an HTTP request and xdebug.start_with_request
is trigger
, is to never send one of these triggers via GET data, POST data, or the Cookie header to begin with.
The XDEBUG_MODE
environment variable can be set to off
to disable Xdebug...I exploit this in my dev environment to prevent the container from being rebuilt through Xdebug and taking an eternity to do so (using a lean console script that just initializes the kernel and exits, exec
-d from inside Kernel::initializeContainer()
which then immediately returns a recursive call to itself), but I can't think of a way to make use of that mechanism (the XDEBUG_MODE
environment variable) from the context of an HTTP client.
| Symfony toolbar could manage it instead through a new UI element with similar functionality to the browser extensions?
Please don't do that. That makes the support on my side even harder, as now there is another way to trigger Xdebug.
All of the GET parameters that Xdebug respects (XDEBUG_TRIGGER, XDEBUG_PROFILE, XDEBUG_TRACE, XDEBUG_SESSION, and XDEBUG_CONFIG) enable its functionality
Does that also include passing XDEBUG_SESSION_STOP
as GET parameter? I can't check right now, but I think that ought to work.
@derickr If that works, I won't complain, but it is undocumented. I'll report back with my findings – thanks!
That makes the support on my side even harder, as now there is another way to trigger Xdebug.
I hadn't considered this aspect. I assumed that support for the feature would fall on the Symfony project, but that was probably naive.
XDEBUG_SESSION_STOP
does not seem to have any effect. I do think I vaguely remember its existence years ago, but I'm still ending up on a breakpoint even with it set when I try it now (v3.2.2
).
What about XDEBUG_SESSION_STOP_NO_EXEC
?
No, XDEBUG_STOP_SESSION_NO_EXEC
will halt execution of the script. This is something I am likely to remove. I'll investigate the XDEBUG_STOP_SESSION
option in a bit. My todo list after the holidays is .... long, so it might take a while.
I have had a look now, and all that XDEBUG_SESSION_STOP
does is to unset the XDEBUG_SESSION
cookie. It does not prevent the debugger from activating.
I see two options:
- Change
XDEBUG_SESSION_STOP
to also inhibit the debugger from triggering - Add a new
XDEBUG_IGNORE
GET/POST/COOKIE
option that ignores just the current request, but does not mess with theXDEBUG_SESSION
system
I think I slightly prefer the latter, as I have the intention of phasing out the whole setting cookies by Xdebug. Most people either use an IDE to trigger a request, or a browser extension which always sends the cookie, no matter what Xdebug does itself with the cookies. :cookie:
An XDEBUG_IGNORE
param would be awesome! Thank you very much for offering to help grease the wheels on this.
https://bugs.xdebug.org/view.php?id=2239
Thanks @derickr Let's update this PR by anticipating the new option?
Just finishing it by writing the tests.
I have merged this into Xdebug's master now, in case you want to try it (https://github.com/xdebug/xdebug/commit/5630bdbf094dc287e8ee1f623736f810d7321575)
@adrolter let's resume this PR? :pray:
Reworked to use thew new XDEBUG_IGNORE
option.
@derickr Thank you very much for paving the way for this feature; it's very much appreciated! ~~One thing I did notice in my testing is that – as opposed to my previous implementation where I prevented the cookie from being sent – this still seems to incur all the same CPU/time overhead of a normal Xdebug enabled request, but just doesn't stop at breakpoints. Is this expected, and related to what you were alluding to by saying that it would not mess with the XDEBUG_SESSION
system? I just want to make sure I understand correctly how it is intended to work. Thanks again!~~ Edit: Nevermind, I don't know what I was talking about; it behaves the same.
Thank you @adrolter.