3

I want to show an existing page(node) normally but return a HTTP404/410 etc instead of HTTP200.

How can I do it? Is there any simple way to do it?

6
  • 2
    Why would you want to serve an incorrect status code?
    – Clive
    Apr 19, 2014 at 16:47
  • When a page is penalized due to harmful backlinks from external sites, we can delete the page(404) to nullify this penalty. But it'd be odd to remove the Home page itself. That's why I need to use this.
    – AgA
    Apr 20, 2014 at 5:22
  • 2
    This isn't really Drupal related and I'm not even sure if it's possible. Surely a web browser sees a 404 and that's what it displays to the user, regardless of other data sent with the request.
    – leexonline
    Apr 23, 2014 at 5:31
  • 2
    @leexonline actually server is allowed to post own user readable explanation of the error, and browser should render it. But there is a difference between should and will. AgA, care to explain what are you trying to do?
    – Mołot
    Apr 23, 2014 at 12:52
  • 1
    @Mołot I stand corrected: This status code is commonly used when the server does not wish to reveal exactly why the request has been refused, or when no other response is applicable.. Straight from the horse's mouth.
    – Clive
    Apr 23, 2014 at 13:25

2 Answers 2

17
+50

Drupal's built in mechanisms

for delivering 404 pages are useless to you. We can read:

Page callback functions wanting to report a "page not found" message should return MENU_NOT_FOUND instead of calling drupal_not_found(). However, functions that are invoked in contexts where that return value might not bubble up to menu_execute_active_handler() should call drupal_not_found().

Of course if page callback will return MENU_NOT_FOUND, it will not return any meaningful data. And while calling drupal_not_found() does not always prevent data delivery, comments under function clearly shows that it causes double display of error message more often than any other result - not what you want, either.

Drupal generic header functions

may help you. Especially drupal_add_http_header(). Let me emphasize one thing in it's description:

$value: The HTTP header value; if equal to FALSE, the specified header is unset. If $name is 'Status', this is expected to be a status code followed by a reason phrase, e.g. "404 Not Found".

So you want to set status to 404:

drupal_add_http_header('Status', '404 Not Found');

It should be enough, after all that's what drupal_deliver_html_page() is doing when you use higher-level 404 calls from previous part of my answer.

Also, you might be interested in logging it like other 404 errors:

 watchdog('page not found', check_plain($_GET['q']), NULL, WATCHDOG_WARNING);

Where to place it?

It depends on what you need to decide if page should be 404 or not. If all you need is the actual data in page, you can go for hook_page_delivery_callback_alter():

This hook is intended for altering the delivery callback based on information unrelated to the path of the page accessed. For example, it can be used to set the delivery callback based on a HTTP request header (as shown in the code sample). To specify a delivery callback based on path information, use hook_menu() or hook_menu_alter().

emphasis mine.

Let's assume that you need it for nodes

and this means you need a node id. NID is part of the path, so, following suggestion quoted above, you want to use hook_menu_alter().

Let's take a look on how node's menu looks like. The part we want to alter is:

  $items['node/%node'] = array(
    'title callback' => 'node_page_title',
    'title arguments' => array(1),

    // The page callback also invokes drupal_set_title() in case
    // the menu router's title is overridden by a menu link.
    'page callback' => 'node_page_view',
    'page arguments' => array(1),
    'access callback' => 'node_access',
    'access arguments' => array('view', 1),
  );

What you need to do is to alter it to provide your own callback. In that callback:

  1. Perform any logic needed
  2. Set header as you need
  3. Log to watchdog if you want to
  4. Call original callback and return it's result. Render cascade does not explicitly set HTTP 200 later, so your status header should be delivered unaltered. Unless some modules will alter it, of course, but that's something we cannot know.

Last notes

thanks to Ayesh K and MPD:

  • Sending header might now work in any of the preprocess hooks or just about any tpl.php file. The fact that you could does not mean you should, especially not in tpl files, as it may interfere with caching, but it's worth to remember you are not limited to the ways I described above. For example hook_page_alter() can be a safe choice, too.

  • Make sure the page size is more than 512 bytes, otherwise some browsers (Howdy, IE) will ignore it.

  • Keep in mind that by RFC 2616

    User agents SHOULD display any included entity to the user.

    As you may notice, this RFC uses MUST in some cases and this is not one of them, so if browser will refuse to display your page after you send 404, it's not exactly an error. Browsers are not forbidden to ignore your HTML when you send 404.

4
  • Nice answer. Just to add my 200 OK cents, note that sending header might now work in any of the preprocess hooks or just about any tpl.php file. You will definitely need to write a module, and invoke an earlier hook to send the headers. Also, make sure the page size is more than 512 bytes, otherwise browsers (Howdy, IE) will ignore it.
    – AKS
    Apr 23, 2014 at 15:11
  • @AyeshK updated with information from your comment.
    – Mołot
    Apr 24, 2014 at 6:15
  • Good answer. Pert sure I did this for someone, and decided that hook_page_alter() was the best place to consolidate this. Late enough to make sure you are actually going to serve up a page, but before templating starts.
    – mpdonadio
    Apr 26, 2014 at 1:01
  • @MPD page delivery callback is where Drupal itself is doing it (via drupal_deliver_html_page(), so I played on the safe side advising to do it there, too. Or where it's alter's api docs said to do it.
    – Mołot
    Apr 26, 2014 at 10:21
4

Use drupal_add_http_header()

Put this inside of the node via PHP filter (which is usually a bad idea).

drupal_add_http_header('Status', '404 Not Found');

OR Put this inside of a hook_init call.

// Get the current active node object if present.
$node = menu_get_object();
if (!empty($node) && !empty($node->nid) && $node->nid == 86) {
  drupal_add_http_header('Status', '404 Not Found');
}
4
  • 1
    -1 from me for 1) suggesting php filter, I think it's usually bad idea, and 2) not using Drupal functions from this file to add headers.
    – Mołot
    Apr 23, 2014 at 12:55
  • Bad questions don't get a lot of thought from me. Upvoted yours, really good answer
    – mikeytown2
    Apr 23, 2014 at 15:29
  • "Bad questions don't get a lot of thought from me." ;) +1
    – AKS
    Apr 23, 2014 at 16:39
  • Thanks for updating your answer, possibly we just saved one innocent soul ;) I agree this question is not really great question with a lot of research, but I think it might be useful for community to have this written in one place.
    – Mołot
    Apr 24, 2014 at 6:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.