HTTP-headers simple question

A simple question: is there already a proper way to set a header for a specific output mime type?

Sometimes the view should output json code or plain text instead of html. So I’m searching for sth. like


Yii::app()->resonse('json', array('pragma'=>'private', 'expires' => time() + 30)

or equivalent.

Maybe there’s already a solution and I didn’t find it?! Right now im using header(‘Content-type:…’); all the time - but json is always the same and as I learned the last few years I shouldn’t repeat shouldn’t repeat shouldn’t repeat shouldn’t repeat myself.

Another approach of other frameworks is a simple convention via "suffix":




…

  - views

     - output.html.php

     - output.json.php



In this case you could differ in the controller wether it’s an ajax request, you’d choose the json variant of the view, if it’s a simple Get request, you’d choose the html variant.

Then simply choose the matching variant:


$this->render('output', Yii::app()->isAjaxRequest ? 'json' : 'html');

If there’s no solution for this - this thread may be better located under “feature requests”.

I recall qiang saying this won’t be in the framework (auto-sending of http-headers). Sorry if I’m wrong.

For now you can create custom method or auto-detection of response type in a base-controller. For example you could define the response type in a url-rule and then auto-set it via base controller.

I would recommend finding a framework that handles responses based on requests, such as CakePHP, and then just grab the code and implement as an extension (or just drop into the Vendors folder, whichever)

I wonder, which different file mime types you need to send via PHP. Only can think of a few: CSS, XML, Javascript, maybe also PDF and Excel. The rest of files will be sent directly from the webserver which automatically sets the correct Content-Type anyway. For PDF headers are mostly generated by the PDF library you use (like FPDF). Same e.g. for Excel files.

So why not add some simple methods in your base controller that can create that bunch of headers for you? Like sendMimeHeader($type)? If you need to render content from files, add some wrappers around these methods that also perform rendering, like renderMimePartial($type,$view,$params,…).

I’m not sure about the filename format you proposed. The ending .php will confuse lots of editor’s syntax highlighting.

Ok, seems there’s nothing - so DIY :)

In my opinion it’s elementary to controll the headers. So why not automatically send out an expires HTTP header if the controller filter is set to “cache the whole site for 1 day”? There’s no need for the browser to request this site within the next 24 hours again from your server - it’ll be be delievered right from the servers’ cache and it’s already in the browser’s cache. This would save a lot of requests!

In nearly every project there’s the time where a json, rss, excel, pdf, or any kind of media file runs through an PHP controller. You may just want to count the downloads, restrict the access to authorized groups or

slightly modify it when it’s downloaded (e.g. unique watermark or serial no.).

I’d rather abandon the zii ajax gimmicks than something elementary than headers.

To cut a long story short:

I’ve written an extension which parses a mime.types file (caches it) and provides some predefined “named scopes” like “nocache”, “public”, “expires”=>500. The name of the static function defines the mime type.

So you just type:




    mtHeader::json(array('expires'=>500));

    $this->renderPartial('_detail', array('items' => $items);



or




    mtHeader::jpg('private');

    return readfile('protected/path/image.jpg');



or




    increaseCountFor($file); // some statistic stuff

    mtHeader::xls(array('public', 'expires'=>3600));

    return readfile('protected/path/company-q4.xls');



This saves me a lot of time and lines. So the next few days I’ll upload this extension - even if I’m the only one who needs sth. like this ;)

Nice, post back here plz when you release that :wink:

As it isn’t a real Yii-extension I’ll provide it this way. It’s a simple static php class to easily set the HTTP headers as needed.

How to use it:

[list=1]

[*]Simply throw the attached mtHeader.php file into your extensions folder

[*]Fetch a fresh mime.types declaraion file e.g. from http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types

[*]Drop it also into your extensions folder

[*]Use it in your controller right before the output starts ($this->render(), $this->renderText(), or just readfile()).

[/list]

A few examples:




// Setting a plain text header:

mtHeader::txt();


// Setting this png to private:

mtHeader::png('private');


// Telling the browser, to cache it 1 hour

mtHeader::png(array('expires'=>3600));


// Telling the browser not to cache

mtHeader::png('nocache');


// Setting individual headers

mtHeader::png(array('GreetingsTo' => 'my mom'));


// Force download with given file name "setup.exe" 1kb size

mtHeader::exe(array('download' => array('setup.exe', 1024)));



Inlucded scopes are:

  • nocache

  • length [size in bytes]

  • download [file name and optional file size in bytes]

  • public

  • private

You also can combine scopes using an array as argument list. Even some combinations don’t make sense as e.g.


mtheader::js(array('public', 'private'));

Feel free to add more scopes or improve this script. It’s be nice to publish this here too.

Ohh … nearly forgotten: This only works under php5.3 . You may also find a way to make it work in lower versions. As I’m using 5.3 and just wanted to share this little helper, I won’t develop it to be compatible with other versions

Hi Mintao!

Could you write more detailed instruction for installing and using your class?

You wrote that the class is not classic Yii extension, so does it need to be registered in main.php config file?

Please be more clearer in your 3. instruction "Drop it also into your extensions folder". Is this mean to copy the mime.types file to extension folder?

I’m receiving:




PHP Error


Description


include(mtHeader.php) [<a href='function.include'>function.include</a>]: failed to open stream: No such file or directory


Source File


C:\xampp\htdocs\yii\framework\YiiBase.php(341)


00329:      * @param string class name

00330:      * @return boolean whether the class has been loaded successfully

00331:      */

00332:     public static function autoload($className)

00333:     {

00334:         // use include so that the error PHP file may appear

00335:         if(isset(self::$_coreClasses[$className]))

00336:             include(YII_PATH.self::$_coreClasses[$className]);

00337:         else if(isset(self::$_classes[$className]))

00338:             include(self::$_classes[$className]);

00339:         else

00340:         {

00341:             include($className.'.php');

00342:             return class_exists($className,false) || interface_exists($className,false);

00343:         }

00344:         return true;

00345:     }

00346: 

00347:     /**

00348:      * Writes a trace message.

00349:      * This method will only log a message when the application is in debug mode.

00350:      * @param string message to be logged

00351:      * @param string category of the message

00352:      * @see log

00353:      */



Thanks again!

You should add in your controller (or config/main) the inclusion of the class:




Yii::import('ext.mtHeader.php');




I’m trying to modify headers so that, when a user submits a form, then clicks the Back button on their browser, they can still see the data they entered on the form.

So, I downloaded your extension (thanks!), enabled file caching, and am now calling a function from my controller, just before calling render():




mtHeader::html("private, post-check=0, pre-check=0");



In firebug, I view the headers, which include these:

[html]

Expires: Thu, 19 Nov 1981

Cache-Control: no-store, no-cache, must-revalidate, post-check=0

Pragma: no-cache

[/html]

However, the same behavior persists. I.e., when a user submits a form, then clicks the Back button on their browser, all of the form fields are empty.

Can someone tell me the proper way to call MtHeader functions in order to preserve the user’s form data?

Thanks!

Emily

Its been a while until I last used this extension, but I’ll put it on github so everyone can fork it. I’ll check this issue and post the reply in a few hours :)

Hey, thanks for looking into this.

My remark above was incorrect. This is a case where I want the browser to do it’s default behavior–which is to CACHE. Adding the headers below, manually, to my main layout file achieves this in Firefox (but not MSIE):




header("Content-Type: text/html; charset: UTF-8"); 

header("Cache-Control: private_no_expire");



However, I’d prefer not to call header() directly. I’d rather use your extension. Because, obviously, I don’t want ALL pages to be cached.

:slight_smile:

I’ve created a little github repo and extended the examples. So it’s maybe more usefull for you:

http://github.com/mintao/yii-ext-mtHeader

You also can set more than one header with the extension. But I’m not sure what you tried to achieve. To prevent caching I’d suppose to use

mtHeader::html(‘nocache’); which is a “scope” or alias for a bunch of headers sent to the browser.

Otherwise - if you WANT the browser to cache - I’d recommend mtHeader::html(‘expires’); This sets a 300 second caching time (can be modified).

Feel free to fork my extension, make it better, remove features and add some bugs ;)

Cheers,

Florian Fackler

Thanks for doing that so quickly, this is a wonderful extension. I do indeed wish to have caching ON in this particular case.

In my controller, in the action being called, I tried


mtHeader::html('expires');

but it didn’t work. I.e., in Firebug, when I view headers, I see:




Expires: 01 Dec 2010 01:29:33    # (over 8 hours in the future, not 300 sec.)

Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0

Am I putting the call to mtHeader::html(…) in the wrong place? Or, is there any other requirement for using mtHeader?

Emily

Okay, I made a bit more progress. In order to get the browser to CACHE the page, though, I need the "Expires" header to go away.

Here’s what I do, just before the render() function in my controller:




mtHeader::html(array(    

   'Cache-Control'=>'private_no_expire', 

   'Pragma'=>'', 

   'Expires'=>'Sat, 01 Jan 2011 00:00:00 GMT',

));



In Firebug, I now see these headers:




Expires : Wed, 01 Dec 2010 02:01:02 GMT  (my local time, in GMT) 

Cache-Control: private_no_expire



The only header that’s incorrect (and which is causing the browser NOT to cache the page), is the Expires header. How to set it?

TIA…!

:slight_smile:

Florian,

I still haven’t figured out how to control the “Expires” header. See above. I want the browser to CACHE a given page, so that the user can use the Back button and still have form inputs populated. Ideas?

Thanks!

:-*

Emily

Seems like you’ve found a bug. I’ve fixed it. Now firebug shows the correct header info. Just give it one more try and download it (or fork it) from github: https://github.com/mintao/yii-ext-mtHeader

One more thing:

If your expires header was 8 hours ahead I’d check your server time - seems like it’s not configured correctly. Just output a <?php echo date(‘H:i:s’); ?>

Florian,

I downloaded mtHeader.php and it now sets Expires, thanks. So now the issue isn’t with mtHeader, which works perfectly, but rather how to OVERRIDE Yii’s default behavior of disabling browser caching.

I.e., the headers shown below cause Firefox to CACHE, but they fail to forse MSIE to cache when using the Yii framework.

This is what I included in my controller, just before calling render()




$expires = 60*60*24*14;   // seconds * minutes * hours * days = 14 days

mtHeader::html(array('Cache-Control'=>'public',

		     'Pragma'=>'public',

		     'Expires'=>$expires,

		     'Last-Modified'=>'Wed, 1 Dec 2010 08:08:08 GMT'

		 ));



In Firebug, I see these headers:




Expires : Fri, 17 Dec 2010 01:49:00 GMT   ## Correct. 14 days in the future

Cache-Control : max-age=1209600, public, s=maxage=1209600

Pragma : public

Last Modified : Wed, 1 Dec 2010 08:08:08 GMT   ## Correct



Can anyone out there tell me how to force MSIE to cache?

TIA,

Emily

I figured out how to set browser’s caching on, but was unable to use Florian’s wonderful extension because I have to run this code on a server running PHP 5.2. >:(

The code shown below works with the latest MSIE and FireFox. Place these lines just BEFORE any call to render():




$expires = 300;  // sec

header("Content-Type: text/html; charset: UTF-8");

header("Cache-Control: max-age={$expires}, public, s-maxage={$expires}");

header("Pragma: ");

header('Expires: ' . gmdate('D, d M Y H:i:s', time() + $expires) . ' GMT');



Thanks to everyone for the help!

:D

Hello everybody,

I’m happy to see that so many of you are hear and work on Yii php framework,

Now with your permission i send mtHeader.php with support in php versions earlier < 5.3

But everybody should enter static name mtHeader::(json) in first parameter of method h,

It means you can write this now:

[color="#2E8B57"]mtHeader::h(‘json’, expiration options come here);[/color]

in php 5.3 for example you can use:

mtHeader::json(expiration options come here);