I want CakePHP to stream a download to the browser. The content of the stream is served via an API.
So, CakePHP makes a request to that API, gets a response with a file and must stream this response to the browser.
This is what I got so far:
public function getDownload() {
// do other things
$http = new Client([
'headers' => [
'accept' =>'application/octet-stream'
]
]);
$response = $http->get($this->url,[]);
// first try
// $stream = new CallbackStream(function () use ($response) {
// return $response;
// });
// $response = $response->withBody($stream);
// second try
// $stream = new CallbackStream($http->get($this->url,[])->getData());
// $response = $response->withBody($stream);
return $response;
}
With this setup I can download small files. The reason I need a stream is, because the API could send files up to 10GB. My guess is, that with $http->get CakePHP stores the whole response in memory. Thats why I'm getting a memory exhausted error.
I know I'm lacking a bit of understanding here. Any help is appreciated :)
Finally I found a solution:
public function getDownload($url) {
$opts = array(
'http'=>array(
'method'=>"GET",
'header'=>"accept: application/octet-stream\r\n"
)
);
$context = stream_context_create($opts);
$response = new Response();
$file = fopen($url, 'r',false, $context);
$stream = new CallbackStream(function () use ($file) {
rewind($file);
fpassthru($file);
fclose($file);
});
$response = $response->withBody($stream);
return $response;
}
Related
I am trying to asynchronously download files with Guzzle 6, but the documentation seems vague and couldn't find any useful examples.
The thing I am not sure about is - how am I supposed to save the received data?
Currently I am doing it like this:
$successHandler = function (Response $response, $index) use ($files) {
$file = fopen($files[$index], 'a');
$handle = $response->getBody();
while (!$handle->eof()) {
fwrite($file, $handle->read(2048));
}
fclose($file);
};
Is this really asynchronous?
Since if we get into one callback and start looping, how can we get the data from the other ones at the same time?
Is there a more direct way to tell, when creating a Request, where should the response be stored? (or directly passing a stream for that).
The sink option should be your friend here:
$client->request('GET', '/stream/20', [
'sink' => '/path/to/file',
]);
For reference, see http://docs.guzzlephp.org/en/latest/request-options.html#sink.
use function GuzzleHttp\Psr7\stream_for;
use GuzzleHttp\RequestOptions;
use GuzzleHttp\Client;
$tmpFile = tempnam(sys_get_temp_dir(), uniqid(strftime('%G-%m-%d')));
$resource = fopen($tmpFile, 'w');
$stream = stream_for($resource);
$client = new Client();
$options = [
RequestOptions::SINK => $stream, // the body of a response
RequestOptions::CONNECT_TIMEOUT => 10.0, // request
RequestOptions::TIMEOUT => 60.0, // response
];
$response = $client->request('GET', 'https://github.com/robots.txt', $options);
$stream->close();
fclose($resource);
if ($response->getStatusCode() === 200) {
echo file_get_contents($tmpFile); // content
}
I'm trying to build a basic app where I can modify my own google calendar using Google's RESTful api, but I'm having trouble getting the oAuth token. I've chosen to do it using the Services Application oAuth flow - I don't want to have to constantly re-agree to letting my own app use my calendar, but if there's a better way to do this please let me know.
Every time I make the http request I get a Bad Request error. Any ideas/help?
Here's the code:
<?php
$payload = array(
"iss"=>"services client email",
"scope"=>"https://www.googleapis.com/auth/calendar",
"aud"=>"https://accounts.google.com/o/oauth2/token",
"iat"=>date("U"),
"exp"=>date("U")+3600
);
$key = "Simple API key";
$jwt = encode_header($payload,$key);
print_r(request_g_token($jwt));
function request_g_token($jwt)
{
$data = array(
'grant_type' => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
'assertion' => $jwt
);
$data = http_build_query($data);
$url = "https://accounts.google.com/o/oauth2/token";
//echo $data;
$opts = array('http' =>
array(
'protocol_version' => '1.1',
'method' => 'POST',
'header' => "Host: accounts.google.com\r\n" .
"Content-Type: application/x-www-form-urlencoded\r\n",
'content' => $data
)
);
$context = stream_context_create($opts);
return (file_get_contents($url, false, $context));
}
function urlsafeb64encode($input){
return str_replace('=','',strtr(base64_encode($input),'+/','-_'));
}
function encode_header($payload, $key, $algo = 'RS256'){
$header = array('typ' => 'JWT', 'alg' => $algo);
$segments = array();
$segments[] = urlsafeb64encode(json_encode($header));
$segments[] = urlsafeb64encode(json_encode($payload));
$signing_input = implode('.',$segments);
$sig = sign_encode($signing_input,$key);
$segments[]=urlsafeb64encode($sig);
return implode('.',$segments);
}
function sign_encode($msg, $key){
return hash_hmac('sha256', $msg, $key, true);
}
?>
Any help would be greatly appreciated
UPDATE
So I went through the service process again and realized I need to use a private key, which I'm now doing. My major question is whether or not to include the "private-key.p12" part of what I downloaded from google or not. I'm still receiving a Bad Request error unfortunately...
UPDATE 2
Realized I needed to pull the key from the pk12 file, and I did so with this code:
function getKey($file){
$p12cert = array();
$fd = fopen($file, 'r');
$p12buf = fread($fd,filesize($file));
fclose($fd);
if ( openssl_pkcs12_read($p12buf, $p12cert, 'notasecret') )
{
//worked
$temp = $p12cert['pkey'];
$temp = str_replace("-----BEGIN PRIVATE KEY-----","",$temp);
$temp = str_replace("-----END PRIVATE KEY-----","",$temp);
return $temp;
}
else
{
//failed
return "failed";
}
}
However, it's still giving me a bad request error and I think it's to do with the fact that the key comes back in multiple lines. Any ideas?
For anyone experiencing this issue, I've basically come to the conclusion that google hasn't interfaced calendar with their service accounts yet, and am using a refresh token to achieve similar results without having to log in all the time.
I am using the example function given in this post:
<?php
function do_post_request($url, $data, $optional_headers = null)
{
$params = array('http' => array(
'method' => 'POST',
'content' => $data
));
if ($optional_headers !== null) {
$params['http']['header'] = $optional_headers;
}
$ctx = stream_context_create($params);
$fp = #fopen($url, 'rb', false, $ctx);
if (!$fp) {
throw new Exception("Problem with $url, $php_errormsg");
}
$response = #stream_get_contents($fp);
if ($response === false) {
throw new Exception("Problem reading data from $url, $php_errormsg");
}
return $response;
}
?>
I also tried a similar approach using file_get_contents(), like this:
$options = array(
'http'=>array(
'method'=>"POST",
'header'=>
"Accept-language: en\r\n".
"Content-type: application/x-www-form-urlencoded\r\n",
'content'=>http_build_query(
array(
'arg1'=>'arg_data_1',
'oper'=>'get_data',
'arg2'=>'arg_data_2',
'id_number'=>'7862'
),'','&'
)
));
$context = stream_context_create($options);
$refno = file_get_contents('/path/to/script/script.php',false,$context);
var_dump($refno);
With both these scripts, the response from the server script is the same, and it is the TEXT of the script.php. The code of the server script is never begin executed, and the text content (the PHP code) of the script is being returned to the original script.
A little strange that it doesn't return all the text, but just certain pieces... I tried making a test script (test.php) that just contains:
<?php
echo '{"a":1,"b":2,"c":3,"d":4,"e":5}';
?>
but that doesn't return anything from the POST request, so I didn't include that. The script.php is a must longer script that does a lot of logic and MySQL queries then returns a JSON object.
The desired output will be to have the PHP code execute and return a JSON object (the way it works with ajax).
What am I doing wrong?
You are trying access script localy.
You must call it like any other external script like
$refno = file_get_contents('http://yourhost/path/to/script/script.php',false,$context);
I'm trying to use PHP to authenticate to a web service by posting to an authentication method with a .txt file containing my username and password formatted in JSON.
It's not working and I'm having a hard time figuring out why not.
Here's the code I'm trying to use. First there's a function I'm using to do the posting. Then I create a variable for my data file and another for my URL for the AUTH service.
<?php
function do_post_request($url, $data, $optional_headers = null)
{
$params = array('http' => array(
'method' => 'POST',
'content' => $data
));
if ($optional_headers !== null) {
$params['http']['header'] = $optional_headers;
}
$ctx = stream_context_create($params);
$fp = #fopen($url, 'rb', false, $ctx);
if (!$fp) {
throw new Exception("Problem with $url, $php_errormsg");
}
$response = #stream_get_contents($fp);
if ($response === false) {
throw new Exception("Problem reading data from $url, $php_errormsg");
}
return $response;
}
// Let's get logged in and get an authkey
$url = 'http://service.com/auth';
$data= 'creds.txt';
$authkey = do_post_request($url, $data);
print_r($authkey);
?>
The text of my creds.txt file:
{
"auth": {
"username": "myusername",
"password": "mypassword"
}
}
What am I doing wrong? I'm getting an invalid data error. Do I need to use a full URL to the text file? Is the textfile not formatted properly?
The docs for the service only say that I need:
"a JSON-formatted text file with your username and password"
$data= 'creds.txt'; should be like:
$data= file_get_contents('creds.txt');
I don't see you open the creds.txt anywhere, I believe you should send in jSON format.
Without disclosing "service.com" or more details about the website itself it'll be hard to figure out a solution since it can be a number of things but you're never passing the actual data (only the file path).
Try:
$authkey = do_post_request($url, file_get_contents($data));
i want to post an xml document to a url, using simple php code.
i have a javascript code but the javascript will not support cross domain, so i just want to do it with php.
does any one have a code for this to support me...
Take a look at SimpleXML: http://us2.php.net/simplexml
Handling HTTP messaging in PHP is quite straightforward using the PECL HTTP classes.
In your instance you want to issue an HTTP request (that's a client->server message). Thankfully the HttpRequest::setPostFiles simplifies the process of including file content in an HTTP request. Refer to the PHP manual page (previous link) for specifics.
Unfortunately the manual pages for the HTTP classes are a bit sparse on details and it's not fully clear what the arguments for HttpRequest::setPostFiles should be, but the following code should get you started:
$request = new HttpRequest(HttpMessage::HTTP_METH_POST);
$request->setPostFiles(array($file));
$response = $request->send(); // $response should be an HttpMessage object
The manual for HttpRequest::setPostFiles states that the single argument of this method is an array of files to post. This is unclear and may mean an array of local file names, an array of file handles or an array of file contents. It shouldn't take long to figure out which is correct!
Here's an example that uses streams and does not rely on PECL.
// Simulate server side
if (isset($_GET['req'])) {
echo htmlspecialchars($_POST['data']);
exit();
}
/**
* Found at: http://netevil.org/blog/2006/nov/http-post-from-php-without-curl
*/
function do_post_request($url, $data, $optional_headers = null)
{
$params = array('http' => array('method' => 'POST',
'content' => $data));
if ($optional_headers !== null) {
$params['http']['header'] = $optional_headers;
}
$ctx = stream_context_create($params);
$fp = #fopen($url, 'rb', false, $ctx);
if (!$fp) {
throw new Exception("Problem with $url, $php_errormsg");
}
$response = #stream_get_contents($fp);
if ($response === false) {
throw new Exception("Problem reading data from $url, $php_errormsg");
}
return $response;
}
// Example taken from: http://en.wikipedia.org/wiki/XML
// (Of course, this should be filled with content from an external file using
// file_get_contents() or something)
$xml_data = <<<EOF
<?xml version="1.0" encoding='ISO-8859-1'?>
<painting>
<img src="madonna.jpg" alt='Foligno Madonna, by Raphael'/>
<caption>This is Raphael's "Foligno" Madonna, painted
in <date>1511</date>-<date>1512</date>.</caption>
</painting>
EOF;
// Request is sent to self (same file) to keep all data
// for the example in one file
$ret = do_post_request(
'http://' . $_SERVER['SERVER_NAME'] . $_SERVER['SCRIPT_NAME'] . '?req',
'data=' . urlencode($xml_data));
echo $ret;