Since D8 the drupal_get_http_header() does not return any headers. This can be reproduced on 404 pages where a Status of '404 Not Found' should be set.

Example code:

function foo_page_alter(&$page) {

  // Get page status code for visibility filtering.
  $status = drupal_get_http_header('Status');
  print_r($status);

  $trackable_status_codes = array(
    '403 Forbidden',
    '404 Not Found',
  );

}

Bug blocks Google Analytics #1948588: Google Analytics 8 upgrade.

Comments

hass’s picture

Issue summary: View changes

a

hass’s picture

Issue summary: View changes

a

benjy’s picture

I can confirm the issue.

I had a bit of a look and it seems to me that maybe there is still work to be done in bootstrap.inc in relation the drupal_http* functions. For example the headers are sent using: Symfony\Component\HttpFoundation::sendHeaders() and not drupal_send_http_headers() which is why they're not available to drupal_get_http_header().

I notice from stepping through the code that you can subscribe to the kernel.terminate event and get the event which has a response object which contains the headers that were sent.

I'd need some direction at this point to get any further towards a solution.

hass’s picture

Priority: Normal » Major

https://drupal.org/node/2023537 looks not helpful at all.

Crell’s picture

Issue tags: +WSCCI, +symfony, +D8 cacheability

drupal_get_http_header() should be treated as deprecated. All HTTP information is being centralized on the Request/Response objects. If anything, the work here would be to add a @deprecated tag to drupal-get_http_header(), or simply eliminate it entirely (preferable).

hass’s picture

Can you share a solution for the issue I filed, please? deprecating this do not solve the issue at all and is a showstopper for google analytics module.

Crell’s picture

I can't tell from a 155 KB patch what you're missing. :-) Can you clarify what it is you're trying to accomplish? (High business level.)

hass’s picture

I must be able to execute a function that returns the status code of the current page that is rendered e.g if it's a 404 file not found or 403 access denied page or not (200). If 404 we add different tracker code to the page.

Crell’s picture

... So you need to inject one bit of HTML into the header for a 200, and a different bit for 404? Hm. Not sure there. I will ping the SCOTCH folks, as Sam may have a thought about it. We're trying to eliminate hook_page_alter, too.

hass’s picture

Thanks a lot for your help.

See http://drupalcode.org/project/google_analytics.git/blob/refs/heads/8.x-2... , please. I need the status in several conditions. Also see http://drupalcode.org/project/google_analytics.git/blob/refs/heads/8.x-2... is where the 404 tracker is generated. It does not add any extra stuff to header.

Dave Reid’s picture

I also have this use case in contrib. Would be interesting to find out what the solution is.

sdboyer’s picture

soooo...there are really two levels at which this discussion can be had - one is just the "get it done," and the other is the "understand it, and get it done right." and, there's the way it works in core, vs. how it works in SCOTCH/princess. after a bit of stepping through (omg response exception handling is HILARIOUS - not), i think i have most of these answers on hand...but this is spitball pseudocode.

there are two essential issues here: a) how do i know what the response code is for the current request and b) how do i inject html onto the page output. and the "good samaritan" issue is c) how do i do these things without totally breaking the ability to externally predict what's on the page - though as long as there's no user-specific information that's generated, being a good samaritan is largely a non-issue here.

first, the dirty approach for core.

to figure out what the response code is going to be, you need to go peering through the information that gets placed onto the Request object. this is because of {giant, unnecessary explanation of how response exception handling & subrequesting works}. with how core works right now, the safe place to do that is in an event listener on the KernelEvents::CONTROLLER event. that code should something like this:

class FigureOutTheStatusCode implements EventSubscriberInterface {
  public function onKernelController(FilterControllerEvent $event) {
    $request = $event->getRequest();
    if ($request->attributes->get('exception') instanceof FlattenException) {
      $code = $request->attributes->get('exception')->getStatusCode();
      if (in_array($code, array(403, 404)) {
        set_some_global_with_status_code_for_retrieval_during_hook_page_alter($code);
      }
    }
  }
}

that'll set a global var for you that you can grab later, during hook_page_alter(), to switch on. now, it'd be ideal to avoid hook_page_alter() entirely, but unfortunately you don't have much choice...in core. you could experiment with something that implements a KernelEvents::RESPONSE listener that does some string injection, but with the way HtmlPageController works right now, i think that would only work for the non-200 case, not the normal case.

in SCOTCH/princess, however, this works differently - most notably, because there is no hook_page_alter(). the rule there is, if you want to generate any kind of output (including adding an asset like a javascript file, or javascript settings) you HAVE to be a block. so, you implement some blocks. GA could either implement a few different blocks - a block for a 403, a block for a 404, or just a single block that varies its output based on configuration - and then attach instances of these blocks to the displays that are used specifically for the respective type of responses. for the redirect case, it's basically the same, except that you'd only attach your block to the 404 display. this'll be pretty easy to do programatically - you grab the display used for each of these purposes back, call a method to add your block, save the display, and you're done. like, done-done - no black magic detection of the status code from an exception buried on response object generated by a subrequest that's come out of a catch statement to handle when the original request threw one. yikes. we'll take care of getting that display where it needs to be.

hass’s picture

This sounds a bit complex to me. I've seen this event driven model, but it looks a wrong way to me. Why is there no variable or array that contains all headers? We have a lot of modules that look for headers other contrib modules have set and need evaluate them in conditions. Maybe I need to alter headers from module A in module B. The same way I could get the status code than. It's not really an event to read out a header and I'm not only talking about status code here.

I do no understand these display stuff... Guess i need an example what you are talking about. I'd really like to do it right... Not only - get it done.

I'm using hook_page_alter() only for the reason that it is in a processing model nearly the very last running process before the output is generated. This makes sure all other modules are done and have added required headers or altered the ga code before it is send to the client.

Crell’s picture

Sam, would there be any sense in encapsulating the head tag contents in their own object somehow before we render the page? Then we could have some alter-like behavior just for those...

sdboyer’s picture

This sounds a bit complex to me. I've seen this event driven model, but it looks a wrong way to me. Why is there no variable or array that contains all headers?

because globals are terrible, and having them and relying on them is the best way of chaining our code to the ground, when it wants to fly free...!

if you want access to the sort of globalish data you're looking for, you have to access it at the right time, in the right way. respecting that encapsulation lets us do magical things elsewhere. so, you implement an event - which is roughly like a hook, though does take a bit more setup.

Sam, would there be any sense in encapsulating the head tag contents in their own object somehow before we render the page? Then we could have some alter-like behavior just for those...

sure. that's basically what an HtmlFragment is. so if not that, then a render array.

we don't really have access to that right now because the page element - what drupal_render_page() uses - has #theme_wrapper => 'html'. so you can't really directly access the stuff that's output through the outer html template - you have to manipulate it indirectly via the handful of globals that are read by the rendering process.

though, the HTML head tag is orthogonal to the issue of the HTTP response code header.

hass’s picture

I'm still asking me how I can get/alter all headers set by a bunch of other modules in Google Analytic. I cannot read other module maintainers brain and have no clue if they may add headers conditionally or not. I need a Getter function that returns all headers.

Adding Contributed project blocker as I will not create a release if this is not solved.

hass’s picture

Can someone take a look into this code and help me in getting it running, please? Not sure if this is what sdboyer tried to explain.

Fatal error: Using $this when not in object context in \drupal8\modules\google_analytics\lib\Drupal\google_analytics\EventSubscriber\GoogleAnalyticsHttpStatusCode.php on line 33

[google_analytics.module]

$status = \Drupal\google_analytics\EventSubscriber\GoogleAnalyticsHttpStatusCode::getHttpStatusCode();

[GoogleAnalyticsHttpStatusCode.php]

/**
 * @file
 * Contains \Drupal\google_analytics\EventSubscriber\GoogleAnalyticsHttpStatusCode.
 */

namespace Drupal\google_analytics\EventSubscriber;

//use Symfony\Component\HttpKernel\KernelEvents;
//use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

class GoogleAnalyticsHttpStatusCode implements EventSubscriberInterface {

  protected $status = 0;

  public function __construct($code) {
    $this->status = $code;
  }

  /**
   * Implements \Drupal\google_analytics\EventSubscriber\GoogleAnalyticsHttpStatusCode::setHttpStatusCode().
   */
  public function setHttpStatusCode($code) {
    $this->status = $code;
  }

  /**
   * Implements \Drupal\google_analytics\EventSubscriber\GoogleAnalyticsHttpStatusCode::getHttpStatusCode().
   */
  public function getHttpStatusCode() {
    return $this->status;
  }

  public function onKernelController(FilterControllerEvent $event) {
    $request = $event->getRequest();
    if ($request->attributes->get('exception') instanceof FlattenException) {
      $code = $request->attributes->get('exception')->getStatusCode();
      $this->setHttpStatusCode($code);
    }
  }

  /**
   * Registers the methods in this class that should be listeners.
   *
   * @return array
   *   An array of event listener definitions.
   */
  static function getSubscribedEvents() {
    // Set a high value to start as late as possible.
    $events[KernelEvents::CONTROLLER][] = array('onKernelController', 100);
    return $events;
  }
}
Crell’s picture

The changes brought by #2068471: Normalize Controller/View-listener behavior with a Page object will address this issue; once that goes in you can use a view listener to inject meta tags into the page after the page is rendered, with knowledge of what the status code is.

hass’s picture

Thanks a lot for the update. Just for info - I do not like to add any meta tags... I'm in hook_page_alter() and add js code to #attached array.

hass’s picture

Issue summary: View changes

a

Crell’s picture

#2068471: Normalize Controller/View-listener behavior with a Page object is in now, so the status code is available on the HtmlPage object in a view listener. That's where you should detect it and then set the meta tags you need.

The API for setting the metatags on the page object got removed, but hopefully it will be back later. For now, you're stuck with the now-deprecated drupal_add_html_head(). Sorry. :-/

hass’s picture

I need the status code and maybe other *http* headers in hook_page_alter(). Not sure if I can run $page->getStatusCode() in hook_page_alter() now. Again this was all times about low level HTTP headers and nevers about meta or any other high level code. Gettinh http headers is still not possible :-(

$status = drupal_get_http_header('Status');

Crell’s picture

HTTP headers don't exist until the Response object, which doesn't exist until after we're done with the HtmlPage object. Also, at this point hook_page_alter should be viewed as vestigial and non-functional. I don't even know what's going through it at this point, at what level of granularity, and it's almost certain to change again soon. Really, don't use it.

What HTTP header do you need for modifying the HTML? I'm fairly certain whatever goal you're trying to get at can be done, just in a different way than before.

Dave Reid’s picture

I'm wondering if Redirect's ability to add a local task on 404 pages to create a new redirect has now officially been hosed. It sounds like it.

pwolanin’s picture

I think we are planning to leave hook_menu_local_tasks and hook_menu_local_tasks_alter (or at least the latter) in the API so that modules can still dynamically add tabs to any page.

hass’s picture

I only need the http status code that has been set by system module or any other custom 404 error handler module around and is going to be send to the client. Before the page is send to the vlient I'm adding special 404 javascript code/404 page tracker to the page content.

However we must be able to find a way to allow add/get headers anywhere. This is currently broken.

Crell’s picture

However we must be able to find a way to allow add/get headers anywhere.

No. The "do anything anywhere" model is what was most broken in earlier versions of Drupal. Providing structure around that is the most important improvement of Drupal 8.

For what you describe, you can now add a view listener and check for an HtmlPage object. It has the status code on it (there's a method). If it's 404 (or whatever), you can concat additional Javascript to the end of the page (inside the body tag) by just appending to the content of the object. $page->setContent($page->getContent() . $your_stuff).

If you want to add metatags or links there, that was the goal but that got pulled out. I need to open a new issue to resolve that. For the moment, you still have to use the deprecated drupal_add_*() functions there. Working on that...

Dave Reid’s picture

@pwolanin: Better question is when hose hooks run, do we know the status code is available? It sounds like in this issue it is later since drupal_get_http_header('Status') doesn't work for hook_page_alter(), then it likely won't work for hook_menu_local_tasks_alter().

catch’s picture

A similar feature to the Redirect one is hiding blocks on 404 pages (which we no longer do in core but presumably there's a contrib that does).

@Crell this isn't about 'doing anything anywhere', it's about having the response code available as context during the page rendering process. Which is completely reasonable since we have to know if we're on a 404/403 page before we can build the page array anyway.

https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Controlle...

If we make the default 404/403 controller a route instead of hard-coded render array inside that method, then there should be a couple of ways to do this:

1. Check the $request object for _exception_statuscode or make that available as a new public property.

2. Check if the current route is equal to \Drupal::config('system.site')->get('page.404'));

It looks like you could already do that check in hook_page_alter() (or any other place during rendering) if you also checked for the default markup, which ought to be enough of a workaround to unblock the porting.

PS. This is why I thought the method in the other issue is scope creep and asked for it to be removed, it's answering the wrong question.

hass’s picture

Something inside the $request object is what I'm looking for from first day. Variant 2 may not work well as it may not work module in-depended with other contrib 404 modules (untested) if this will be overridden.

I'm getting a bit nervous as we are getting closer to release... we must get this in and I'm not experienced with $response stuff and cannot really help... :-(

hass’s picture

Issue summary: View changes

How can we get the issues resolved?

Crell’s picture

hass: #2218117: Bring back metatag support for the HtmlPage object

HtmlPage already has the status code on it. Once we bring back the ability to add meta tags/links/etc. on it, you can register a view listener that checks the status code and adds whatever is appropriate.

olli’s picture

#2245767: Allow blocks to be configured to show/hide on 200/403/404 response pages and the system help block use $request->attributes->get('exception'). Would something like this:

if ($exception = $request->attributes->get('exception')) {
  debug($exception->getStatusCode());
}

work for your module?

catch’s picture

@Crell that issue doesn't solve the actual issue here, which ought to be very fixable.

The response type is known before the 404/403 controller gets executed - otherwise we'd not be able to execute the controller. So the information should be available somewhere.

hass’s picture

The response type is known before the 404/403 controller gets executed - otherwise we'd not be able to execute the controller. So the information should be available somewhere.

@catch: What Olli has noted in #30 looks promising to me. I can try this, but it sounds like you have something different in mind? Can you share your thoughts in details, please? Should I not try using what Olli noted? How can we get the code done you have in mind? :-)

catch’s picture

@hass I already replied in #26 with two options, looks like #30 is a better version of one of them.

hass’s picture

@olli: Sorry for asking stupid questions, but how can I get $request? $response = $this->container->get('http_kernel')->handle($subrequest, HttpKernelInterface::SUB_REQUEST); has $this and $subrequest... no idea how to get these variables in hook_page_alter().

olli’s picture

That is a good question. With current hook_page_alter(&$page), can't see any other way than $request = \Drupal::request().

hass’s picture

How can I get the status code if there is no exception? e.g. 200, 201, 301, 302... or all all status codes that are not of 200 - exceptions?

hass’s picture

Title: 404 pages: drupal_get_http_header('Status') returns no status code at all » 403/404 pages: drupal_get_http_header('Status') returns no status code at all
Issue tags: -Contributed project blocker

I'm removing Contributed project blocker for now as the broken drupal_get_http_header() does not affect Google Analytic any longer. I've committed http://drupalcode.org/project/google_analytics.git/commit/de04e6d if someone is interested in the code changes I've done. Thanks to Olli!

I leave the case open as the root cause drupal_get_http_header() is still unfixed and I strongly believe the return of this function is something that some module really need. If there is any other way to get all headers we need to document the D7 -> D8 upgrade path at least.

Wim Leers’s picture

We also still have this in MenuTree:

    if ($route_name = $request->attributes->get(RouteObjectInterface::ROUTE_NAME)) {
      // @todo https://drupal.org/node/2068471 is adding support so we can tell
      // if this is called on a 404/403 page.
      $system_path = $request->attributes->get('_system_path');
      $page_not_403 = 1;
    }

But that should now refer to this issue probably.

pwolanin’s picture

It seems perhaps that we can use this?

$page_is_403 = $request->attributes->get('_exception_statuscode') == 403;
ianthomas_uk’s picture

Crell’s picture

Status: Active » Closed (won't fix)
Related issues: +#2467753: Remove @deprecated _drupal_add_http_header() and friends.

The functions in question here are all going away, so this is no longer relevant.