I want to make a real time application with CI.
So I write some code in controller (CI)
Here's my code:
$this->output->set_content_type('text/event-stream');
$this->output->set_header('Cache-Control: no-cache');
$time = date('r');
$output="data: The server time is: {$time}\n\n";
flush();
But, I get this error:
EventSource's response has a MIME type ("text/html") that is not
"text/event-stream". Aborting the connection.
Ideas?
You have to set the content type:
$this->output->set_content_type('text/plain', 'UTF-8');
$this->output->set_header('Cache-Control: no-store, no-cache, must-revalidate')
Please read the manual
For Codeigniter 3, you have to change:
set_output => _display
$time = date('r');
$output="data: The server time is: {$time}\n\n";
$this->output->set_content_type('text/event-stream')->_display($output);
$this->output->set_header('Cache-Control: no-cache');
flush();
see example
from codeigniter docs:
https://ellislab.com/codeigniter/user-guide/libraries/output.html
$this->output->set_content_type();
Permits you to set the mime-type of your page so you can serve JSON data, JPEG's, XML, etc easily.
$this->output->set_content_type('application/json')->set_output(json_encode(array('foo' => 'bar')));
$this->output->set_content_type('jpeg') // You could also use ".jpeg" which will have the full stop removed before looking in config/mimes.php
->set_output(file_get_contents('files/something.jpg'));
Important: Make sure any non-mime string you pass to this method exists in config/mimes.php or it will have no effect.
Second Option
Go to your application/config/mimes.php
add 'txt' => 'text/event-stream',
to $mimes array.
EDIT
change your code to:
$time = date('r');
$output="data: The server time is: {$time}\n\n";
$this->output->set_content_type('text/event-stream')->set_output($output);
$this->output->set_header('Cache-Control: no-cache');
flush();
Related
I am trying to cache static content, I want this content is have a lifetime of one hour and the content is public, it is the same for everyone.
I have the following code in my controller:
$response = new Response();
$response->setPublic();
$response->setMaxAge(3600);
$response->setSharedMaxAge(3600);
if ($response->isNotModified($request)) {
return $response;
}
return $this->render(
'ThemesBundle:Ad:content.html.twig',
array('context' => $context, 'block' => $block),
$response
);
But the isNotModified() function always returns false.
PS: I am using Symfony 2.0.22
You made a mistake, $response->isNotModified($request) is used only when using cache validation with a ETag or a Last-Modified test!
Here, you want to use expiration methods (with Cache-Control or Expires).
So just remove theses lines :
if ($response->isNotModified($request)) {
return $response;
}
$response->setMaxAge(3600); (and setSharedMaxAge) alone will do the job, you don't need to test anything, the framework (or client navigator) will do it for you.
The same response will be served during 3600 second without passing by the action. After 3600 seconds, the user will pass by the action anew and it will be cached for 3600 seconds, etc.
In addition, you can use #Cache annotation which simplify the read ;)
Let's consider a scenario like below:
User selects filter button which create a AJAX call to a symfony2 controller and return the result in JSON format.
User select some other links and system will redirect him to the page
User select browser Back button.
User will see JSON response, but he should see the original page.
My controller look like below :
/**
*
*
* #Route("/ajax", name="ajax_route" , options={"expose"=true})
* #Template()
*/
public function someAction()
{
$request = $this->getRequest();
$json = array( );
if($request->isXmlHttpRequest())
{
$res = json_encode($json);
return new Response($res , 200 , array( 'Content-Type' => 'application/json' ));
}
return array( );
}
In other words, if user press back button the if($request->isXmlHttpRequest()) returns true which is not the result I am looking for. Is it a normal behavior or what ?
Symfony\Component\HttpFoundation\Request::isXmlHttpRequest() is a simply utility-method that checks whether HTTP request came up with X-Requested-With header with value XMLHttpRequest. So it's as reliable as X-Requested-With header is.
However, this is not really important. The important thing to notice is the fact that when the user clicks on the back button the browser does not send a new HTTP request to the server. It just restores the page from its internal memory/cache.
I understand that this is an old question, but the same issue just caught me so I figured I'd write up an answer anyway.
In most scenarios, you can invalidate the back button cache by attaching an onUnload handler to the window, like so:
window.addEventListener('unload',function(){});
or, if you prefer jQuery:
$(window).unload(function(){});
but, since your AJAX response is in JSON, that's obviously not possible since you can't include script fragments. In this case, I think the best idea is to set the cache-control: no-store header so the browser wont attempt to cache the result.
You can do that in the OP's case with Symfony2 using:
return new Response($res , 200 , array(
'Content-Type' => 'application/json',
'Cache-Control' => 'no-store',
));
or for more general PHP:
header('Cache-Control: no-store');
There's a caveat here in that it may degrade your performance quite a bit, depending on your app's structure, in which case your best bet would probably be to just use a different URL for your AJAX call. Sucks, I know.
You can find some documentation on the bfcache here, which may be more helpful in different cases.
The browser caches the response using just the url and the request method (GET, POST, etc) as the key.
If you want the browser to recognize additional variations, you can tell it to do so by setting the Vary header in your response. So in your case, you want to tell the browser that the response from the server will vary depending on whether or not the 'X-Requested-With' header was set in the request.
Here's how to do it:
$response = new Response();
$response->setVary("X-Requested-With"); // <=========== Set the Vary header
if($request->isXmlHttpRequest()) {
//...
}
return $response;
Note: that you want to set the Vary header on both versions of the response (this is why I've set it outside of the if statement).
I'm trying to send a file to the user using xsendfile within the code igniter framework.
It is all installed correctly, my problem is that it only seems to work from the route, even though every page comes from index.php anyway.
This is my function:
function _output_file($name, $root_location, $public_location = FALSE)
{
if (function_exists('apache_get_modules') && in_array('mod_xsendfile', apache_get_modules())) {
header ('Content-Description: File Transfer');
header ('Content-Type: application/octet-stream');
if (strstr($_SERVER["HTTP_USER_AGENT"], "MSIE") != FALSE) {
header ('Content-Disposition: attachment; filename='.urlencode($name));
} else {
header ('Content-Disposition: attachment; filename="'.$name.'"');
}
//86400 is one day
header ('Expires: '.gmdate('D, d M Y H:i:s', (TIME_NOW + 86400)));
header ('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header ('Pragma: public');
header ('X-Sendfile: '.$root_location);
exit;
} else {
redirect(site_url($public_location));
}
}
If I place this at the top of my index.php and load the root it works fine, but if I try to access it from domain.com/controller/function it returns a 404 error.
It is definitely using the index.php file since if I replace the function call with die("test"); this displays to the screen.
I believe it's something to do with what permissions xsendfile has to access the file, but since it's working from the root index.php I would have thought it would have complete permissions, presumably it's based on what the request url is, which I find strange.
So.... does anyone have any suggestions as to how I can get xsendfile to work through codeigniter, from a url such as "domain.com/files/get/12"?
XSendFile has some specific security-related path quirks... and depending on your server configuration, these issues can sometimes occur over HTTPS even when HTTP seems to be working correctly.
If you run into mysterious 404s while using mod_xsendfile and you can confirm that the files being served really do exist, then you probably need to configure XSendFilePath in your Apache confs.
Add the following to the appropriate conf (httpd.conf, ssl.conf, httpd-ssl.conf, etc) and/or inside the appropriate VirtualHost declaration (if using vhosts)...
XSendFilePath /Absolute/Path/To/Your/Working/Directory/
Note: You cannot add this to an .htaccess file. It must be placed into an Apache conf.
Generally, xsendfile will try to figure out the working directory automatically, but sometimes it can't. This directive tells it explicitly what directory (or directories) should be accessible through xsendfile. Chances are that a mysterious 404 means that your directory isn't passing the whitelist check for some reason. This will fix that.
And don't forget to reboot apache after you change the config.
Prefixing a method name with an underscore makes it inaccessible through the URL.
From the documentation:
Private Functions
In some cases you may want certain functions hidden from public access. To make a function private, simply add an underscore as the name prefix and it will not be served via a URL request. For example, if you were to have a function like this:
private function _utility()
{
// some code
}
Trying to access it via the URL, like this, will not work: example.com/index.php/blog/_utility/
It seems this answer never got a reponse, in the end I just created a file in my root called "getfile.php", it's not perfect but it gets the job done for now, here it is for anyone that may find it useful.
<?php
define('BASEPATH', 'just done to stop direct access being disallowed');
function show_getfile_error()
{
echo 'You do not have permission to download this file, if you think this is a mistake please get in contact.';
exit;
}
include('applications/config/database.php');
$mysqli = new mysqli($db['default']['hostname'], $db['default']['username'], $db['default']['password'], $db['default']['database']);
if(!preg_match('%^[0-9]+$%', $_GET['key']))
{
show_getfile_error();
}
else
{
$query = mysqli_query($mysqli, 'SELECT * FROM getfiles WHERE getfile_key = '.(int)$_GET['key']);
$result = mysqli_fetch_array($query, MYSQLI_ASSOC);
if(!$result || $result['getfile_ip'] != $_SERVER['REMOTE_ADDR'])
{
show_getfile_error();
}
header ('Content-Description: File Transfer');
header ('Content-Type: application/octet-stream');
if (strstr($_SERVER["HTTP_USER_AGENT"], "MSIE") != FALSE) {
header ('Content-Disposition: attachment; filename='.urlencode($result['getfile_name']));
} else {
header ('Content-Disposition: attachment; filename="'.$result['getfile_name'].'"');
}
//86400 is one day
header ('Expires: '.gmdate('D, d M Y H:i:s', (TIME_NOW + 86400)));
header ('Cache-Control: must-revalidate, post-check=0, pre-check=0');
header ('Pragma: public');
header ('X-Sendfile: '.$result['getfile_location']);
}
?>
I have come across the 404 error today. If the path you pass to the header contains any components that lie outside of the root folder that index.php is in, then you get a 404. Make sure the path is relative to index.php, not an absolute path.
Ok, so I'm having difficulty with the PHPWord class which can be found: http://phpword.codeplex.com
The weird thing about it is when I use this same code in the "example" file it works fine in generating. When I don't force a download using headers the file will open up just fine. Using this code though with the headers is causing it when it downloads to throw errors in the word file saying the file is corrupt and can't be opened, but then it opens up just fine.
public function export()
{
// Load PHPWORD Library
$this->load->library('PHPWord');
$sec = $this->phpword->createSection($secStyle);
$header = $sec->createHeader();
$header->addWatermark('images/CC_watermark.png', array('marginTop'=>1015, 'marginLeft'=>-80));
$resultSelected = $this->input->post('cbox');
foreach($resultSelected as $row)
{
$sec->addText($row);
echo $row."<br>";
}
$fileName = "Plan_Generate_".date('Ymd').".docx";
// Force Download
$filePath = $fileName;
$fileName = basename($filePath);
// $fileSize = filesize($filePath);
// Output headers.
header("Cache-Control: private");
header("Content-Type: application/stream");
// header("Content-Length: ".$fileSize);
header("Content-Disposition: attachment; filename=".$fileName);
// Output file.
// readfile ($filePath);
// exit();
// Save File
// $fileName = "files/SomethingNew_".date("Ymd").".docx";
$objWriter = PHPWord_IOFactory::createWriter($this->phpword, 'Word2007');
$objWriter->save('php://output');
}
This is the code i'm using when trying to generate the file. The trouble I'm having is it's throwing an error when it tries to force download. Thanks for any help! Ask questions if you don't fully understand the question.
Update:
Here's the image of the error's I'm receiving. Thanks for the quick responses and I'm actually going to try the Codeigniters way of doing it Tomm. morning.
You should be setting headers via CodeIgniters functions, that is what could be causing the issue for you:
CI Output Class
$this->output->set_header();
Permits you to manually set server headers, which the output class will send for you when outputting the final rendered display. Example:
$this->output->set_header("HTTP/1.0 200 OK");
$this->output->set_header("HTTP/1.1 200 OK");
$this->output->set_header('Last-Modified: '.gmdate('D, d M Y H:i:s', $last_update).' GMT');
$this->output->set_header("Cache-Control: no-store, no-cache, must-revalidate");
$this->output->set_header("Cache-Control: post-check=0, pre-check=0");
$this->output->set_header("Pragma: no-cache");
My assumption is you are trying to do update headers the PHP way, but you are playing by CI rules for output.
This was fixed by using CI's $this->download->force_download().
And by passing the data through ob_start(), ob_get_contents(), and ob_end_clean() for people needing help with it.
Are you sure you don't want
header("Content-Type: application/octet-stream");
instead of just stream? What is the actual error and is it word or the browser or php that is throwing the error?
Also, CodeIgniter has a download helper to send files... You may want to try that.
http://codeigniter.com/user_guide/helpers/download_helper.html
I was wondering if there was a way for a controller to, instead of returning a string, or a view, return an image (be it JPG, PNG etc). For example, instead of ending with a $this->load->view('folder/special_view.php), I'd like to do something like $this->load->image('images/gorilla.png'), and have it so if my user were to go to that controller they would see an image as if they'd gone to a normal .png or jpeg. Can I set the headers so it expects a different MIME? Example code of this would be fantastic.
It would take forever for me to explain why I need this, but it involves bringing a premade CMS into codeigniter, and having it need certian things to be true. Thank you so much!
sure you can, use this instead of $this->load->view()
$filename="/path/to/file.jpg"; //<-- specify the image file
if(file_exists($filename)){
$mime = mime_content_type($filename); //<-- detect file type
header('Content-Length: '.filesize($filename)); //<-- sends filesize header
header("Content-Type: $mime"); //<-- send mime-type header
header('Content-Disposition: inline; filename="'.$filename.'";'); //<-- sends filename header
readfile($filename); //<--reads and outputs the file onto the output buffer
exit(); // or die()
}
This is not intended as One-upmanship, but pǝlɐɥʞ's suggestion is a pure PHP implementation that is not all that re-usable. You wanted to use the syntax $this->load->image('images/gorilla.png') so here is how you can.
Create /application/libraries/MY_Loader.php
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
/**
* Loader Class
*
* Loads views and files
*
* #package CodeIgniter
* #subpackage Libraries
* #author Phil Sturgeon
* #category Loader
* #link http://codeigniter.com/user_guide/libraries/loader.html
*/
class MY_Loader extends CI_Loader {
function image($file_path, $mime_type_or_return = 'image/png')
{
$this->helper('file');
$image_content = read_file($file_path);
// Image was not found
if($image_content === FALSE)
{
show_error('Image "'.$file_path.'" could not be found.');
return FALSE;
}
// Return the image or output it?
if($mime_type_or_return === TRUE)
{
return $image_content;
}
header('Content-Length: '.strlen($image_content)); // sends filesize header
header('Content-Type: '.$mime_type_or_return); // send mime-type header
header('Content-Disposition: inline; filename="'.basename($file_path).'";'); // sends filename header
exit($image_content); // reads and outputs the file onto the output buffer
}
There are a few ways you can use this:
Basic output (default is jpeg)
$this->load->image('/path/to/images/gorilla.png');
Send mime-type to use other image types
$this->load->image('/path/to/images/gorilla.jpg', 'image/jpeg');
Return the image
$image = $this->load->image('/path/to/images/gorilla.php', TRUE);
Just like $this->load->view, the 3rd parameter being set to TRUE means it will return instead of directly outputting.
Hope this helps :-)
An easier way with automatic mime-type.
$this->load->helper('file');
$image_path = '/path/to/image/file';
$this->output->set_content_type(get_mime_by_extension($image_path));
$this->output->set_output(file_get_contents($image_path));
About the Phil's code:
In CodeIgniter 2.0, today, there are a one change that have to be made in order to make it work:
The library has to be in /application/core/MY_Loader.php
I like to remark a small typo about the library's explanation:
There is a mistake in the header "Basic output (default is jpeg)" because in fact the default is .png
Another solutions to the problem are:
I've made a small code to make it work with the core codeIgniter libraries:
$this->output->set_header("Content-Type: image/png");
$this->load->file('../images/example.png');
Or using the Image Manipulation Library
$config['image_library'] = "GD2";
$config['source_image'] = "../images/example.png";
$config['maintain_ratio'] = TRUE;
$config['dynamic_output'] = TRUE;
$this->load->library('image_lib', $config);
$image = $this->image_lib->resize();
In both cases you get the same image you get from the source but in the output.
But for me, I liked more the extension to the core library :-)
Thank you very much Phil.
This method works even if you have $config['compress_output'] set to TRUE
$filename="/path/to/file.jpg"; //<-- specify the image file
if(file_exists($filename)){
header('Content-Length: '.filesize($filename])); //<-- sends filesize header
header('Content-Type: image/jpg'); //<-- send mime-type header
header('Content-Disposition: inline; filename="'.$filename.'";'); //<-- sends filename header
$jpg = file_get_contents($filename);
$this->output->set_output($jpg);
}
If it fits your use case, simply redirecting to it is just fine. For example, tracking using images would be like:
// Do your logic here
redirect($image_path); // Or PHP's header location function
No need to change headers. Your use case may not fit this, but someone might find this useful ^_^
I would do a
$computedImage = 'path/to/the/image.ext';
$this->load->helper('file');
$this->output->set_content_type(get_mime_by_extension($computedImage))->set_output(file_get_contents($computedImage));
If this helps. Cheers!