symfony icon indicating copy to clipboard operation
symfony copied to clipboard

[WebProfilerBundle] Temporarily remove `XDEBUG_*` cookie while performing XHR

Open adrolter opened this issue 1 year ago • 3 comments

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!

adrolter avatar Dec 08 '23 17:12 adrolter

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!

adrolter avatar Dec 09 '23 17:12 adrolter

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.

adrolter avatar Dec 09 '23 18:12 adrolter

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.

adrolter avatar Dec 09 '23 23:12 adrolter

@nicolas-grekas I wish, but no, XHR in modern browsers does not support manually specifying the Cookie header for security reasons.

adrolter avatar Dec 27 '23 22:12 adrolter

As I see it, the only viable solutions to this issue are:

  1. 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.

  2. Requiring users to somehow configure their webserver to rewrite requests to the /_wdt/ and /_profiler/ paths, removing the XDEBUG_* 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 the Cookie 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.

adrolter avatar Dec 28 '23 01:12 adrolter

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?

adrolter avatar Dec 28 '23 15:12 adrolter

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:

nicolas-grekas avatar Dec 29 '23 15:12 nicolas-grekas

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.

adrolter avatar Dec 29 '23 22:12 adrolter

| 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 avatar Dec 31 '23 11:12 derickr

@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.

adrolter avatar Dec 31 '23 19:12 adrolter

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).

image

adrolter avatar Dec 31 '23 21:12 adrolter

What about XDEBUG_SESSION_STOP_NO_EXEC?

nicolas-grekas avatar Jan 02 '24 09:01 nicolas-grekas

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.

derickr avatar Jan 02 '24 09:01 derickr

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 the XDEBUG_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:

derickr avatar Jan 10 '24 11:01 derickr

An XDEBUG_IGNORE param would be awesome! Thank you very much for offering to help grease the wheels on this.

adrolter avatar Jan 10 '24 19:01 adrolter

https://bugs.xdebug.org/view.php?id=2239

derickr avatar Jan 16 '24 15:01 derickr

Thanks @derickr Let's update this PR by anticipating the new option?

nicolas-grekas avatar Jan 23 '24 13:01 nicolas-grekas

Just finishing it by writing the tests.

derickr avatar Jan 23 '24 14:01 derickr

I have merged this into Xdebug's master now, in case you want to try it (https://github.com/xdebug/xdebug/commit/5630bdbf094dc287e8ee1f623736f810d7321575)

derickr avatar Jan 23 '24 18:01 derickr

@adrolter let's resume this PR? :pray:

nicolas-grekas avatar Feb 09 '24 10:02 nicolas-grekas

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.

adrolter avatar Feb 10 '24 17:02 adrolter

Thank you @adrolter.

fabpot avatar Mar 17 '24 07:03 fabpot