I'm building a client app based on Guzzle. I'm getting stucked with cookie handling. I'm trying to implement it using Cookie plugin but I cannot get it to work. My client application is standard web application and it looks like it's working as long as I'm using the same guzzle object, but across requests it doesn't send the right cookies. I'm using FileCookieJar for storing cookies. How can I keep cookies across multiple guzzle objects?
// first request with login works fine
$cookiePlugin = new CookiePlugin(new FileCookieJar('/tmp/cookie-file'));
$client->addSubscriber($cookiePlugin);
$client->post('/login');
$client->get('/test/123.php?a=b');
// second request where I expect it working, but it's not...
$cookiePlugin = new CookiePlugin(new FileCookieJar('/tmp/cookie-file'));
$client->addSubscriber($cookiePlugin);
$client->get('/another-test/456');
You are creating a new instance of the CookiePlugin on the second request, you have to use the first one on the second (and subsequent) request as well.
$cookiePlugin = new CookiePlugin(new FileCookieJar('/tmp/cookie-file'));
//First Request
$client = new Guzzle\Http\Client();
$client->addSubscriber($cookiePlugin);
$client->post('/login');
$client->get('/test/first');
//Second Request, same client
// No need for $cookiePlugin = new CookiePlugin(...
$client->get('/test/second');
//Third Request, new client, same cookies
$client2 = new Guzzle\Http\Client();
$client2->addSubscriber($cookiePlugin); //uses same instance
$client2->get('/test/third');
$cookiePlugin = new CookiePlugin(new FileCookieJar($cookie_file_name));
// Add the cookie plugin to a client
$client = new Client($domain);
$client->addSubscriber($cookiePlugin);
// Send the request with no cookies and parse the returned cookies
$client->get($domain)->send();
// Send the request again, noticing that cookies are being sent
$request = $client->get($domain);
$request->send();
print_r ($request->getCookies());
Current answers will work if all requests are done in the same user request. But it won't work if the user first log in, then navigate through the site and query again later the "Domain".
Here is my solution (with ArrayCookieJar()):
Login
$cookiePlugin = new CookiePlugin(new ArrayCookieJar());
//First Request
$client = new Client($domain);
$client->addSubscriber($cookiePlugin);
$request = $client->post('/login');
$response = $request->send();
// Retrieve the cookie to save it somehow
$cookiesArray = $cookiePlugin->getCookieJar()->all($domain);
$cookie = $cookiesArray[0]->toArray();
// Save in session or cache of your app.
// In example laravel:
Cache::put('cookie', $cookie, 30);
Other request
// Create a new client object
$client = new Client($domain);
// Get the previously stored cookie
// Here example for laravel
$cookie = Cache::get('cookie');
// Create the new CookiePlugin object
$cookie = new Cookie($cookie);
$cookieJar = new ArrayCookieJar();
$cookieJar->add($cookie);
$cookiePlugin = new CookiePlugin($cookieJar);
$client->addSubscriber($cookiePlugin);
// Then you can do other query with these cookie
$request = $client->get('/getData');
$response = $request->send();
Related
I added some functionality where if you edit an item that somebody edited in the meantime, you go to an "edit confirm" page where you can select which changes you want to overwrite.
How it's done:
if ($request->isMethod('GET')) {
$session->set('overwriteDate', $language->getUpdatedAt()?->format('Y-m-d H:i:s'));
$session->set('language_referer', $request->headers->get('referer'));
}
$form = $this->createForm(LanguageFormType::class, $language);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
var_dump($language->getUpdatedAt());
var_dump($session->get('overwriteDate'));
if ($session->get('overwriteDate') !== $language->getUpdatedAt()?->format('Y-m-d H:i:s')) {
$session->set('overwriteItem', $language);
return $this->redirectToRoute('languages_edit_confirm', ['id' => $language->getId()]);
} else {
$baseEntityService->save($language);
return $this->redirect($session->get('language_referer'));
}
}
return $this->render('admin/language/edit.html.twig', [
'language' => $language,
'form' => $form->createView()
]);
If the overwriteDate in the session doesn't match the updatedAt from the object, it will redirect to the edit confirm page. This all works fine in the browser (tested it manually a lot, never had an issue).
However, now I try to write a functional test for this, and it's inconsistent. This is the begin of the test:
$crawler1 = $this->client->request('GET', '/admin/languages');
$crawler1 = $this->filterTable($crawler1, $originalEnglishName);
$crawler1 = $this->client->click($crawler1->filter('a.edit-language')->link());
$crawler2 = $this->client->request('GET', '/admin/languages');
$crawler2 = $this->filterTable($crawler2, $originalEnglishName);
$crawler2 = $this->client->click($crawler2->filter('a.edit-language')->link());
$form = $crawler1->selectButton('saveLanguage')->form();
$form['language_form[abbreviation]'] = $firstEditAbbreviation;
$form['language_form[englishName]'] = $firstEditEnglishName;
$form['language_form[name]'] = $firstEditName;
$form['language_form[flag]'] = $firstEditFlag;
$form2 = $crawler2->selectButton('saveLanguage')->form();
$form2['language_form[abbreviation]'] = $secondEditAbbreviation;
$form2['language_form[englishName]'] = $secondEditEnglishName;
$form2['language_form[name]'] = $secondEditName;
$form2['language_form[flag]'] = $secondEditFlag;
$this->client->submit($form);
$this->client->submit($form2);
$crawler2 = $this->client->followRedirect();
$tbody = $crawler2->filter('table#difference-table tbody')->first();
After this I try to assert some stuff from $tbody however sometimes the test works fine but sometimes it gives errors because it acts like a normal edit and redirects to the homepage and the data from $tbody I search on doesn't exist.
Edit:
I tried changing the session stuff to putting it in the form as hidden input, but this gives the same issues.
You'll need to preserve the session cookie between then requests in your test(s).
The Symfony HTTPClient does not do this by default.
The HTTP client provided by this component is stateless but handling cookies requires a stateful storage (because responses can update cookies and they must be used for subsequent requests). That's why this component doesn't handle cookies automatically.
You can either handle cookies yourself using the Cookie HTTP header or use the BrowserKit component which provides this feature and integrates seamlessly with the HttpClient component.
Example usage:
// Request using the client
$crawler = $client->request('GET', '/');
// Get the cookie Jar
$cookieJar = $client->getCookieJar();
// Get the history
$history = $client->getHistory();
// Get a cookie by name
$sessionCookie = $cookieJar->get('PHPSESSID');
// create cookies and add to cookie jar
$cookie = new Cookie('PHPSESSID', 'XYZ', strtotime('+1 day'));
$cookieJar = new CookieJar();
$cookieJar->set($cookie);
// create a client and set the cookies
$client = new Client([], null, $cookieJar);
How can I send a custom header with the ZEND_HTTP_CLIENT. I am trying to send a variable key with a certain value, that I will check later for authenticity
I've tried this
$client = new Zend_Http_Client('http://localhost/v3/files/');
$client->setHeaders('Content-type','multipart/form-data');
$client->setHeaders('key','XXXxXXXXXXXXXXXXXX');
$client->setParameterPost('document_id', $id);
$client->setParameterPost('type_id', $docType['type']);
$client->setParameterPost('file', $form->file);
$response = $client->request(Zend_Http_Client::POST);
and this
$client = new Zend_Http_Client('http://localhost/v3/files/');
$client->setHeaders(array(
'Content-type','multipart/form-data',
'key','XXXxXXXXXXXXXXXXXX'));
$client->setParameterPost('document_id', $id);
$client->setParameterPost('type_id', $docType['type']);
$client->setParameterPost('file', $form->file);
$response = $client->request(Zend_Http_Client::POST);
but it doesnt seem to work. It says key is not a valid type.
I want to send a custom header like this (similar to what happens when you set headers with the Postman client).
Is this possible?
Try with add a config param like this:
$client = new Zend_Http_Client('http://localhost/v3/files/', array('strict' => false));
I'm working on crawling web sites and there is no problem for parsing HTML with Goutte so far. But I need to retrieve JSON from a web site and because of the cookie management, I don't want to do this with file_get_contents() - that doesn't work.
I can do with pure cURL but in this case I just want to use Goutte and don't want to use any other library.
So is there any method that I can parse only text via Goutte or do I really have to do this with good old methods?
/* Sample Code */
$client = new Client();
$crawler = $client->request('foo');
$crawler = $crawler->filter('bar'); // of course not working
Thank you.
After very deep search inside Goutte libraries I found a way and I wanted to share. Because Goutte is really powerful library but there are so complicated documentation.
Parsing JSON via (Goutte > Guzzle)
Just get needed output page and store json into an array.
$client = new Client(); // Goutte Client
$request = $client->getClient()->createRequest('GET', 'http://***.json');
/* getClient() for taking Guzzle Client */
$response = $request->send(); // Send created request to server
$data = $response->json(); // Returns PHP Array
Parsing JSON with Cookies via (Goutte + Guzzle) - For authentication
Send request one of the page of the site (main page looks better) to get cookies and then use these cookies for authentication.
$client = new Client(); // Goutte Client
$crawler = $client->request("GET", "http://foo.bar");
/* Send request directly and get whole data. It includes cookies from server and
it automatically stored in Goutte Client object */
$request = $client->getClient()->createRequest('GET', 'http://foo.bar/baz.json');
/* getClient() for taking Guzzle Client */
$cookies = $client->getRequest()->getCookies();
foreach ($cookies as $key => $value) {
$request->addCookie($key, $value);
}
/* Get cookies from Goutte Client and add to cookies in Guzzle request */
$response = $request->send(); // Send created request to server
$data = $response->json(); // Returns PHP Array
I hope it helps. Because I almost spend 3 days to understand Gouttle and it's components.
I figured this out after several hours of search , simply do this :
$client = new Client(); // Goutte Client
$crawler = $client->request("GET", "http://foo.bar");
$jsonData = $crawler->text();
mithataydogmus' solution didn't work for me. I created a new class "BetterClient":
use Goutte\Client as GoutteClient;
class BetterClient extends GoutteClient
{
private $guzzleResponse;
public function getGuzzleResponse() {
return $this->guzzleResponse;
}
protected function createResponse($response)
{
$this->guzzleResponse = $response;
return parent::createResponse($response);
}
}
Usage:
$client = new BetterClient();
$request = $client->request('GET', $url);
$data = $client->getGuzzleResponse()->json();
I also could get JSON with:
$client->getResponse()->getContent()->getContents()
I'm trying to create a instagram app using the php library which is here(its not very long file, don't worry):
http://pastie.org/3391787
I'm trying to authorise the user by using OAUTH, which works perfectly, and the authorised(current user) can see his information(like Image, name etc...), but when i refresh the page it all goes and I'm left with blank data, heres the code -
// Instantiate the API handler object
$instagram = new Instagram($config);
$accessToken = $instagram->getAccessToken();
$_SESSION['InstagramAccessToken'] = $accessToken;
$instagram->setAccessToken($_SESSION['InstagramAccessToken']);
$userinfo = $instagram->getUser($_SESSION['InstagramAccessToken']);
// After getting the response, let's iterate the payload
$ures = json_decode($userinfo, true);
this code works fine the first time round, but when you refresh the page, the information is not being retrieved, and that could only mean one thing, that the session or the cookie is not set properly and thats why the getUser method is not retrieving the information,
$userinfo = $instagram->getUser($_SESSION['InstagramAccessToken']);
please can you shed some light into this problem, i have been trying for 4 hours!!
On each your request you overwrite current session data with nothing by this line:
$_SESSION['InstagramAccessToken'] = $accessToken;
Most likely this is how it should look like:
$instagram = new Instagram($config);
if (!empty($_SESSION['InstagramAccessToken'])) {
$accessToken = $instagram->getAccessToken();
$_SESSION['InstagramAccessToken'] = $accessToken;
} else {
$instagram->setAccessToken($_SESSION['InstagramAccessToken']);
}
$userinfo = $instagram->getUser($_SESSION['InstagramAccessToken']);
// After getting the response, let's iterate the payload
$ures = json_decode($userinfo, true);
Im using the following code to read to consumer_key and consumer_secret from config.php, pass it to twitter and retrieve some bits of information back from them.
What the script below attempts to do is 'cache' the request_token and request_secret. So in theory I should be able to reuse those details (all 4 of them to automatically tweet when required).
<?php
require_once('twitteroauth/twitteroauth.php');
require_once('config.php');
$consumer_key = CONSUMER_KEY;
$consumer_secret = CONSUMER_SECRET;
if (isset($_GET["register"]))
{
// If the "register" parameter is set we create a new TwitterOAuth object
// and request a token
/* Build TwitterOAuth object with client credentials. */
$oauth = new TwitterOAuth(CONSUMER_KEY, CONSUMER_SECRET);
$request = $oauth->getRequestToken();
$request_token = $request["oauth_token"];
$request_token_secret = $request["oauth_token_secret"];
// At this I store the two request tokens somewhere.
file_put_contents("request_token", $request_token);
file_put_contents("request_token_secret", $request_token_secret);
// Generate a request link and output it
$request_link = $oauth->getAuthorizeURL($request);
echo "Request here: " . $request_link . "";
die();
}
elseif (isset($_GET["validate"]))
{
// This is the validation part. I read the stored request
// tokens.
$request_token = file_get_contents("request_token");
$request_token_secret = file_get_contents("request_token_secret");
// Initiate a new TwitterOAuth object. This time we provide them with more details:
// The request token and the request token secret
$oauth = new TwitterOAuth($consumer_key, $consumer_secret,
$request_token, $request_token_secret);
// Ask Twitter for an access token (and an access token secret)
$request = $oauth->getAccessToken();
// There we go
$access_token = $request['oauth_token'];
$access_token_secret = $request['oauth_token_secret'];
// Now store the two tokens into another file (or database or whatever):
file_put_contents("access_token", $access_token);
file_put_contents("access_token_secret", $access_token_secret);
// Great! Now we've got the access tokens stored.
// Let's verify credentials and output the username.
// Note that this time we're passing TwitterOAuth the access tokens.
$oauth = new TwitterOAuth($consumer_key, $consumer_secret,
$access_token, $access_token_secret);
// Send an API request to verify credentials
$credentials = $oauth->oAuthRequest('https://twitter.com/account/verify_credentials.xml', 'GET', array());
// Parse the result (assuming you've got simplexml installed)
$credentials = simplexml_load_string($credentials);
var_dump($credentials);
// And finaly output some text
echo "Access token saved! Authorized as #" . $credentials->screen_name;
die();
}
?>
When i run /?verify&oauth_token=0000000000000000 - It works however trying to resuse the generated tokens etc... I get a 401
Here is the last bit of code where I attempt to reuse the details from Twitter combined with my consumer_key and consumer_secret and get the 401:
require_once('twitteroauth/twitteroauth.php');
require_once('config.php');
// Read the access tokens
$access_token = file_get_contents("access_token");
$access_token_secret = file_get_contents("access_token_secret");
// Initiate a TwitterOAuth using those access tokens
$oauth = new TwitterOAuth($consumer_key, $consumer_key_secret,
$access_token, $access_token_secret);
// Post an update to Twitter via your application:
$oauth->OAuthRequest('https://twitter.com/statuses/update.xml',
array('status' => "Hey! I'm posting via #OAuth!"), 'POST');
Not sure whats going wrong, are you able to cache the details or do i need to try something else?
You can't store the OAuth tokens in cache and to more than 1 request with it, as OAuth is there to help make the system secure, your "oauth_token" will contain some unique data, this token will only be able to make one call back to twitter, as soon as the call was made, that "oauth_token" is no longer valid, and the OAuth class should request a new "oauth_token", thus making sure that every call that was made is secure.
That is why you are getting an "401 unauthorized" error on the second time as the token is no longer valid.
twitter is still using OAuth v1 (v2 is still in the draft process even though facebook and google already implemented it in some parts)
The image below describes the flow of the OAuth authentication.
Hope it helps.
A while ago I used this to connect to twitter and send tweets, just note that it did make use of some Zend classes as the project was running on a zend server.
require_once 'Zend/Service/Twitter.php';
class Twitter {
protected $_username = '<your_twitter_username>';
protected $_token = '<your_twitter_access_token>';
protected $_secret = '<your_twitter_access_token_secret>';
protected $_twitter = NULL;
//class constructor
public function __construct() {
$this->getTwitter();
}
//singleton twitter object
protected function getTwitter() {
if (null === $this->_twitter) {
$accessToken = new Zend_Oauth_Token_Access;
$accessToken->setToken($this->_token)
->setTokenSecret($this->_secret);
$this->_twitter = new Zend_Service_Twitter(array(
'username' => $this->_username,
'accessToken' => $accessToken,
));
$response = $this->_twitter->account->verifyCredentials();
if ($response->isError()) {
throw new Zend_Exception('Provided credentials for Twitter log writer are wrong');
}
}
return $this->_twitter;
}
//send a status message to twitter
public function update( $tweet ) {
$this->getTwitter()->status->update($tweet);
}
}
In your second script, it looks like you aren't setting the Consumer Key and Consumer Secret when you create your TwitterOAuth instance.
// Initiate a TwitterOAuth using those access tokens
$oauth = new TwitterOAuth(CONSUMER_KEY, CONSUMER_SECRET, $access_token, $access_token_secret);