$response->getBody() is empty when testing Slim 3 routes with PHPUnit - php

I use the following method to dispatch a Slim app's route in my PHPUnit tests:
protected function dispatch($path, $method='GET', $data=array())
{
// Prepare a mock environment
$env = Environment::mock(array(
'REQUEST_URI' => $path,
'REQUEST_METHOD' => $method,
));
// Prepare request and response objects
$uri = Uri::createFromEnvironment($env);
$headers = Headers::createFromEnvironment($env);
$cookies = [];
$serverParams = $env->all();
$body = new RequestBody();
// create request, and set params
$req = new Request($method, $uri, $headers, $cookies, $serverParams, $body);
if (!empty($data))
$req = $req->withParsedBody($data);
$res = new Response();
$this->headers = $headers;
$this->request = $req;
$this->response = call_user_func_array($this->app, array($req, $res));
}
For example:
public function testGetProducts()
{
$this->dispatch('/products');
$this->assertEquals(200, $this->response->getStatusCode());
}
However, as much as things like the status code and header are in the response, (string) $response->getBody() is empty so I cannot check for the presence of elements in the HTML. When I run the same route in the browser, I get the expected HTML output. Also, if I echo $response->getBody(); exit; and then view the output in the browser, I see HTML body. Is there any reason, with my implementation, I'm not seeing this in the my tests? (in the CLI, so different environment I guess)

$response->getBody() will be empty as you've set up the parsed body. It's easier to put it into the RequestBody as a string in the same way as it would be set if it came in over the wire.
i.e something like this:
protected function dispatch($path, $method='GET', $data=array())
{
// Prepare a mock environment
$env = Environment::mock(array(
'REQUEST_URI' => $path,
'REQUEST_METHOD' => $method,
'CONTENT_TYPE' => 'application/json',
));
// Prepare request and response objects
$uri = Uri::createFromEnvironment($env);
$headers = Headers::createFromEnvironment($env);
$cookies = [];
$serverParams = $env->all();
$body = new RequestBody();
if (!empty($data)) {
$body->write(json_encode($data));
}
// create request, and set params
$req = new Request($method, $uri, $headers, $cookies, $serverParams, $body);
$res = new Response();
$this->headers = $headers;
$this->request = $req;
$this->response = call_user_func_array($this->app, array($req, $res));
}

Related

Getting the acess token from Guzzle Oauth authentication

I'm currently trying to develop a connection to the Reddit api through Oauth using Guzzle. I get to the point where I authenticate in Reddit, then I get to the authorization token, but I can't take the access token from the Guzzle response so I can set it as a cookie and using on subsequent requests. My current code looks like this:
public function __construct(){
if(isset($_COOKIE['reddit_token'])){
$token_info = explode(":", $_COOKIE['reddit_token']);
$this->token_type = $token_info[0];
$this->access_token = $token_info[1];
} else {
if (isset($_GET['code'])){
//capture code from auth
$code = $_GET["code"];
//construct POST object for access token fetch request
$postvals = sprintf("code=%s&redirect_uri=%s&grant_type=authorization_code",
$code,
redditConfig::$ENDPOINT_OAUTH_REDIRECT);
//get JSON access token object (with refresh_token parameter)
$token = self::runCurl(redditConfig::$ENDPOINT_OAUTH_TOKEN, $postvals, null, true);
//store token and type
if (isset($token->access_token)){
$this->access_token = $token->access_token;
$this->token_type = $token->token_type;
//set token cookie for later use
$cookie_time = 60 * 59 + time(); //seconds * minutes = 59 minutes (token expires in 1hr)
setcookie('reddit_token', "{$this->token_type}:{$this->access_token}", $cookie_time);
}
} else {
$state = rand();
$urlAuth = sprintf("%s?response_type=code&client_id=%s&redirect_uri=%s&scope=%s&state=%s",
redditConfig::$ENDPOINT_OAUTH_AUTHORIZE,
redditConfig::$CLIENT_ID,
redditConfig::$ENDPOINT_OAUTH_REDIRECT,
redditConfig::$SCOPES,
$state);
//forward user to PayPal auth page
header("Location: $urlAuth");
}
}
This is my authentication flow. The I have the runCurl method that is going to make the guzzle requests:
private function runCurl($url, $postVals = null, $headers = null, $auth = false){
$options = array(
'timeout' => 10,
'verify' => false,
'headers' => ['User-Agent' => 'testing/1.0']
);
$requestType = 'GET';
if ($postVals != null){
$options['body'] = $postVals;
$requestType = "POST";
}
if ($this->auth_mode == 'oauth'){
$options['headers'] = [
'User-Agent' => 'testing/1.0',
'Authorization' => "{$this->token_type} {$this->access_token}"];
}
if ($auth){
$options['auth'] = [redditConfig::$CLIENT_ID, redditConfig::$CLIENT_SECRET];
}
$client = new \GuzzleHttp\Client();
$response = $client->request($requestType, $url, $options);
$body = $response->getBody();
return $body;
}
The problem resides here, the getBody() method returns a stream, and if I use getBody()->getContents() I get a string, none of which can help me.
Any idea on how can I get the access token so I can finish the authentication process?
To answer the question itself - you just need to cast $body to string. It should be a json, so you will also need to json_decode it to use it as an object in the code above. So instead of return $body; in your runCurl, you need to do:
return json_decode((string)$body);
I would recommend to use the official client tho. The code in the question has some unrelated issues, which will make it costy to maintain.

Argument 3 passed to GuzzleHttp\Client::request() must be of the type array, string given

I'm experimenting with SammyK/LaravelFacebookSdk.
Trying to run this line from example:
$response = Facebook::get('/me?fields=id,name,email', 'user-access-token');
which in turn runs /var/www/vendor/facebook/php-sdk-v4/src/Facebook/HttpClients/FacebookGuzzleHttpClient.php line 61
public function send($url, $method, $body, array $headers, $timeOut)
{
$options = [
'headers' => $headers,
'body' => $body,
'timeout' => $timeOut,
'connect_timeout' => 10,
'verify' => __DIR__ . '/certs/DigiCertHighAssuranceEVRootCA.pem',
];
$request = $this->guzzleClient->createRequest($method, $url, $options);
try {
$rawResponse = $this->guzzleClient->send($request);
} catch (RequestException $e) {
$rawResponse = $e->getResponse();
if ($e->getPrevious() instanceof RingException || !$rawResponse instanceof ResponseInterface) {
throw new FacebookSDKException($e->getMessage(), $e->getCode());
}
}
$rawHeaders = $this->getHeadersAsString($rawResponse);
$rawBody = $rawResponse->getBody();
$httpStatusCode = $rawResponse->getStatusCode();
return new GraphRawResponse($rawHeaders, $rawBody, $httpStatusCode);
}
That calls /var/www/vendor/guzzlehttp/guzzle/src/Client.php line 87
public function __call($method, $args)
{
if (count($args) < 1) {
throw new \InvalidArgumentException('Magic request methods require a URI and optional options array');
}
$uri = $args[0];
$opts = isset($args[1]) ? $args[1] : [];
return substr($method, -5) === 'Async'
? $this->requestAsync(substr($method, 0, -5), $uri, $opts)
: $this->request($method, $uri, $opts);
}
This methond interprets input as array('method' => 'createRequest', 'uri' => 'GET'))
Changing index seems to correct the error (though other problems araise)
$uri = $args[1];
$opts = isset($args[2]) ? $args[2] : [];
But since it's a very bad practice to edit other packages, how should I correct this error?
I've get the same problem.
Changing indexes won't work for me, but I've found a workaround. Installing php-curl extension switches a whole workflow thru cURL, so the problem is vanished.
Due to Facebook SDK 5.x use guzzle version 5.
So downgrade the guzzle library will workaround
$ composer require guzzlehttp/guzzle:~5.0

Setting proxy in Goutte

I've tried using Guzzle's docs to set proxy but it's not working. The official Github page for Goutte is pretty dead so can't find anything there.
Anyone know how to set a proxy?
This is what I've tried:
$client = new Client();
$client->setHeader('User-Agent', $user_agent);
$crawler = $client->request('GET', $request, ['proxy' => $proxy]);
I have solved this problem =>
$url = 'https://api.myip.com';
$client = new \Goutte\Client;
$client->setClient(new \GuzzleHttp\Client(['proxy' => 'http://xx.xx.xx.xx:8080']));
$get_html = $client->request('GET', $url)->html();
var_dump($get_html);
You thinking rigth, but in Goutte\Client::doRequest(), when create Guzzle client
$guzzleRequest = $this->getClient()->createRequest(
$request->getMethod(),
$request->getUri(),
$headers,
$body
);
options are not passed when create request object. So, if you want to use a proxy, then override the class Goutte\Client, the method doRequest(), and replace this code on
$guzzleRequest = $this->getClient()->createRequest(
$request->getMethod(),
$request->getUri(),
$headers,
$body,
$request->getParameters()
);
Example overriding class:
<?php
namespace igancev\override;
class Client extends \Goutte\Client
{
protected function doRequest($request)
{
$headers = array();
foreach ($request->getServer() as $key => $val) {
$key = implode('-', array_map('ucfirst', explode('-', strtolower(str_replace(array('_', 'HTTP-'), array('-', ''), $key)))));
if (!isset($headers[$key])) {
$headers[$key] = $val;
}
}
$body = null;
if (!in_array($request->getMethod(), array('GET','HEAD'))) {
if (null !== $request->getContent()) {
$body = $request->getContent();
} else {
$body = $request->getParameters();
}
}
$guzzleRequest = $this->getClient()->createRequest(
$request->getMethod(),
$request->getUri(),
$headers,
$body,
$request->getParameters()
);
foreach ($this->headers as $name => $value) {
$guzzleRequest->setHeader($name, $value);
}
if ($this->auth !== null) {
$guzzleRequest->setAuth(
$this->auth['user'],
$this->auth['password'],
$this->auth['type']
);
}
foreach ($this->getCookieJar()->allRawValues($request->getUri()) as $name => $value) {
$guzzleRequest->addCookie($name, $value);
}
if ('POST' == $request->getMethod() || 'PUT' == $request->getMethod()) {
$this->addPostFiles($guzzleRequest, $request->getFiles());
}
$guzzleRequest->getParams()->set('redirect.disable', true);
$curlOptions = $guzzleRequest->getCurlOptions();
if (!$curlOptions->hasKey(CURLOPT_TIMEOUT)) {
$curlOptions->set(CURLOPT_TIMEOUT, 30);
}
// Let BrowserKit handle redirects
try {
$response = $guzzleRequest->send();
} catch (CurlException $e) {
if (!strpos($e->getMessage(), 'redirects')) {
throw $e;
}
$response = $e->getResponse();
} catch (BadResponseException $e) {
$response = $e->getResponse();
}
return $this->createResponse($response);
}
}
And try send request
$client = new \igancev\override\Client();
$proxy = 'http://149.56.85.17:8080'; // free proxy example
$crawler = $client->request('GET', $request, ['proxy' => $proxy]);
You can directly use in Goutte or Guzzle Request
$proxy = 'xx.xx.xx.xx:xxxx';
$goutte = new GoutteClient();
echo $goutte->request('GET', 'https://example.com/', ['proxy' => $proxy])->html();
Use Same method in Guzzle
$Guzzle = new Client();
$GuzzleResponse = $Guzzle->request('GET', 'https://example.com/', ['proxy' => $proxy]);
You can set a custom GuzzleClient and assign it to Goutte client.
When Guzzle makes the request through Goutte uses the default config. That config is passed in the Guzzle construct.
$guzzle = new \GuzzleHttp\Client(['proxy' => 'http://192.168.1.1:8080']);
$goutte = new \Goutte\Client();
$goutte->setClient($guzzle);
$crawler = $goutte->request($method, $url);
For recent versions use:
Goutte Client instance (which extends Symfony\Component\BrowserKit\HttpBrowser)
use Symfony\Component\HttpClient\HttpClient;
use Goutte\Client;
$client = new Client(HttpClient::create(['proxy' => 'http://xx.xx.xx.xx:80']));
...

How to POST via Reddit API (addcomment)

I've been able to successfully log a user in and return their details. The next step is to get them to post a comment via my app.
I tried modifying code from the reddit-php-sdk -- https://github.com/jcleblanc/reddit-php-sdk/blob/master/reddit.php -- but I can't get it to work.
My code is as follows:
function addComment($name, $text, $token){
$response = null;
if ($name && $text){
$urlComment = "https://ssl.reddit.com/api/comment";
$postData = sprintf("thing_id=%s&text=%s",
$name,
$text);
$response = runCurl($urlComment, $token, $postData);
}
return $response;
}
function runCurl($url, $token, $postVals = null, $headers = null, $auth = false){
$ch = curl_init($url);
$auth_mode = 'oauth';
$options = array(
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 10
);
$headers = array("Authorization: Bearer {$token}");
$options[CURLOPT_HEADER] = false;
$options[CURLINFO_HEADER_OUT] = false;
$options[CURLOPT_HTTPHEADER] = $headers;
if (!empty($_SERVER['HTTP_USER_AGENT'])){
$options[CURLOPT_USERAGENT] = $_SERVER['HTTP_USER_AGENT'];
}
if ($postVals != null){
$options[CURLOPT_POSTFIELDS] = $postVals;
$options[CURLOPT_CUSTOMREQUEST] = "POST";
}
curl_setopt_array($ch, $options);
$apiResponse = curl_exec($ch);
$response = json_decode($apiResponse);
//check if non-valid JSON is returned
if ($error = json_last_error()){
$response = $apiResponse;
}
curl_close($ch);
return $response;
}
$thing_id = 't2_'; // Not the actual thing id
$perma_id = '2daoej'; // Not the actual perma id
$name = $thing_id . $perma_id;
$text = "test text";
$reddit_access_token = $_SESSION['reddit_access_token'] // This is set after login
addComment($name, $text, $reddit_access_token);
The addComment function puts the comment together according to their API -- http://www.reddit.com/dev/api
addComment then calls runCurl to make the request. My guess is that the curl request is messed up because I'm not receiving any response whatsoever. I'm not getting any errors so I'm not sure what's going wrong. Any help would really be appreciated. Thanks!
If you are using your own oAuth solution, I would suggest using the SDK as I said in my comment, but extend it to overwrite the construct method.
class MyReddit extends reddit {
public function __construct()
{
//set API endpoint
$this->apiHost = ENDPOINT_OAUTH;
}
public function setAuthVars($accessToken, $tokenType)
{
$this->access_token = $accessToken;
$this->token_type = $tokenType;
//set auth mode for requests
$this->auth_mode = 'oauth';
}
}
You just need to make sure that you call setAuthVars before running any api calls.

Guzzle and Unable to parse response into JSON

I have a little php api client with this method:
private function send($endpoint)
{
$headers = array();
$body = $this->xmlSerialiser->convertToXML($this->getQueue());
try {
$response = json_decode(
$this->guzzleClient->post(
$endpoint,
$headers, $body
)
->send()
->json()
);
} catch (\Guzzle\Http\Exception\BadResponseException $e) {
$response = array('Error' => $e->getMessage());
}
return $response;
}
I'm always receiving
Unable to parse response body into JSON: 4 (500 Internal Server Error)
I already tried to know an example of server response and seems to be fine:
echo (string) $this->guzzleClient->post(
$endpoint,
$headers, $body
)
->send()->getBody();
and this is the result:
<Messages xmlns="http://www.example.com/xxx/3.0">
<GetAccountResponse RequestType="GetAccount">
<AccountId>xxxx-xxx-xxx-xxxx-xxxx</AccountId>
<Token>xxxxxxxxxxxxxx/t3VkEJXC7f6b6G4yPJSZ5QfT2hdSQXUmi0e8cndSYLK4N7mswRHifzwGHLUJYHM17iGL8s=</Token>
</GetAccountResponse>
I answered myself
the Guzzle documentation says: json method -> Parse the JSON response body and return an array
so in my case I need to switch the json mehod to xml (cos the response is an xml).
Finally this is the result:
private function send($endpoint)
{
$headers = array();
$body = $this->xmlSerialiser->convertToXML($this->getQueue());
try {
$response = (array)(
$this->guzzleClient->post(
$endpoint,
$headers, $body
)
->send()
->xml()
);
} catch (\Guzzle\Http\Exception\BadResponseException $e) {
$response = array('Error' => $e->getMessage());
}
return $response;
}
Use following code.
$response->getBody()->getContents()

Categories