I'm using Laravel 5.3 and I have this line under schedule() function in App\Console\Kernel.php.
$schedule->call('App\Http\Controllers\Controller#fetchXmlRpcResult')->everyMinute();
The function is sort of tedious but the jist of it: get the timestamp of the last record found in the database, create a new XML-RPC request asking for new records with start_date of the 'last found timestamp in the DB', decode the XML-RPC result and insert into the DB.
public static function fetchXmlRpcResult($user, $password, $account_id, $date)
{
$client = new Client('https://example.com/xmlapi/');
$client->setSSLVerifyPeer(false);
$client->setSSLVerifyHost(2);
$client->setCredentials($user, $password, CURLAUTH_DIGEST);
$parameters = [removed]
$request = new Request('getAccountData', $parameters);
$response = $client->send($request);
// removing the 401 HTTP Unauthorized header
$xml = (substr($response->raw_data, strpos($response->raw_data, "\r\n\r\n")+4));
// removing the OK HTTP header
$xml2 = (substr($xml, strpos($xml, "\r\n\r\n")+4));
$accountCDRs = xmlrpc_decode($xml2);
return $accountInfo;
}
When I run php artisan schedule:run in the console, I'm prompted with this error:
Running scheduled command: App\Http\Controllers\Controller#fetchXmlRpcResult
XML-RPC: PhpXmlRpc\Helper\Http::parseResponseHeaders: HTTP error, got response: HTTP/1.1 401 Unauthorized
[Symfony\Component\Debug\Exception\FatalThrowableError]
Call to undefined function App\Helpers\xmlrpc_decode()
The controller uses the file in App\Helpers\XmlHandler.php and in that file I use the following classes:
use PhpXmlRpc\Value;
use PhpXmlRpc\Request;
use PhpXmlRpc\Client;
Could the HTTP response be throwing it out? I tried executing the same function via the browser (aka putting the function in the route.php under http://example.com/update) and it worked perfectly fine.
You should try upgrading to the very latest version of the phpxmlrpc library.
It has fixed a bug that prevented it to be correctly used for making calls that use basic/digest auth using curl.
You could then remove the lines where you try to do manually the removal of the http headers from the response and the extra parsing of the response, and just use $response->value()
Related
Update
This seems to relate in some way to the reading of the stream when outputting. The function used by Slim to output the body looks like this, where $body implements StreamInterface and $this->responseChunkSize is 4096:
$amountToRead = $body->getSize();
while ($amountToRead > 0 && !$body->eof()) {
$length = min($this->responseChunkSize, $amountToRead);
$data = $body->read($length);
echo $data;
$amountToRead -= strlen($data);
if (connection_status() !== CONNECTION_NORMAL) {
break;
}
}
It appears the $body->eof() call (which is just a wrapper for PHP's feof() function) is returning true even though the full file has not been read. Not sure why that would be though. I also verified that this does not occur if I just do an fopen() on the file and create a Stream from it, then run the same code. It only happens when the stream is the product of the external REST API call via Guzzle.
Original Post
I have a service built using Slim (v4.4) that calls an external REST API using Guzzle (v6.5.3) that returns a file. This is running in Windows, web server is IIS/FastCGI (I know, unusual). PHP version is 7.3.10. The call from Slim to the external REST API retrieves the file just fine, but when my app calls the service, some files get corrupted, seems some data gets lost based on what I see in the file size. The call from the service to the external REST API is fairly simple:
$file_response = $guzzleClient->request('GET', "{$base_url}/docs/{$file_id}", [
'headers' => [
'Authorization' => "token {$token}"
]
]);
The above call works fine and returns the file correctly, I can either display it to screen or use the 'sink' option in Guzzle to save to a file, it works fine. But when I try to call the service that wraps that call, it fails. I tried a couple things. Firstly, I was just returning the response as is since it conforms to the interface required anyway. My Slim route looks like this:
$app->group('/files', function (Group $group) {
$group->get('/{file_id}', GetFileAction::class);
});
The GetFileAction class has a method like this:
public function __invoke(Request $request, Response $response, $args): Response {
...Guzzle request returning $file_response here...
return $file_response;
}
My app is also using Guzzle to call the service, the call looks like this:
$guzzleClient->request(
'GET',
"{$base_url}/files/{$file_id}",
[
'auth' => [$username, $password],
'sink' => $file_path
]
);
I wondered if returning the Guzzle response in Slim might be causing some unexpected result, so I tried returning this in the service instead:
return $response->withBody(new \Slim\Psr7\Stream($file_response->getBody()->detach()));
Same result. Obviously if somebody who has run into this exact same problem can help out it would be great, but if not some pointers on how I could try to debug the handling of the streams would likely be helpful.
I've confirmed this is linked to a weird issue with the feof() function returning true even though it hasn't read the full file. The solution I came up with involved creating a different Response Emitter than the default Slim 4 one (mostly the same) and overwrite the emitBody function so it does not rely on feof(). I did so like this:
$length = min($this->responseChunkSizeCopy, $amountToRead);
while ($amountToRead > 0 && ($data = $body->read($length)) !== false) {
echo $data;
$amountToRead -= $length;
$length = min($this->responseChunkSizeCopy, $amountToRead);
if (connection_status() !== CONNECTION_NORMAL) {
break;
}
}
So far this has worked well based on my testing. I have no idea why feof() is not working as expected and didn't really find anything that seemed to specifically address it. Maybe it's a Windows specific thing, and since PHP is less common on Windows it's not a common occurrence. But leaving this solution here in case it can help someone.
I'm trying to achieve a similar goal—using Slim to proxy and forward incoming requests to another service via a Guzzle client—and encountered a similar problem when returning the Guzzle response.
In my case the problem was that the other service was incorrectly returning a Transfer-Encoding: chunked header in the response.
Your mileage may vary, but the solution was to replace this with a correct Content-Length header in the returned response:
return $response
->withoutHeader('Transfer-Encoding')
->withHeader('Content-Length', $response->getBody()->getSize());
I don't know how to explain it but, I'm gonna give it a try.
This problem concerns 2 servers, a local and a hosting server. Both servers are running the same PHP version which is 7.0 [with almost same configurations]. And 2 controller actions. And the problem comes from $app->run($input, $out); from the codes below.
I have in my controller that action:
/**
* #Route("/testJson")
*/
public function testJsonAction() {
$app = new \Symfony\Bundle\FrameworkBundle\Console\Application($this->get("kernel"));
$app->setAutoExit(false);
$opt = array("command" =>
"doctrine:generate:entity",
"--entity" => "GuervylEditorBundle:TestOnline",
"--fields" => "kl:string");
$input = new \Symfony\Component\Console\Input\ArrayInput($opt);
$out = new \Symfony\Component\Console\Output\BufferedOutput();
$app->run($input, $out);
$out->fetch();
return new JsonResponse(\json_encode(["a" => "b", "c" => "d"]));
}
Calling this action from the local and hosting server returns "{\u0022a\u0022:\u0022b\u0022,\u0022c\u0022:\u0022d\u0022}" and with Content-Type
application/json which is great, it's the expected result.
Now, here comes the problem:
That almost same code above, I set it inside another class, I call it from another controller action, which passes through 4 methods from different classes to call the method that has the code above [callCommand]
This is the method that implement the code:
public function callCommand($cmd, $opt, &$mykernel = null) {
if ($mykernel == NULL) {
$mykernel = new myKernel("dev", false, __DIR__ . "/../Resources/template_2.8/app");
}
$app = new \Symfony\Bundle\FrameworkBundle\Console\Application($mykernel);
$app->setAutoExit(false);
$opt = array("command" => $cmd) + $opt;
$input = new \Symfony\Component\Console\Input\ArrayInput($opt);
$out = new \Symfony\Component\Console\Output\BufferedOutput();
$app->run($input, $out);
}
From that other controller action, I also return a json content at the end. I can't show the code because it's too big.
When I call that controller action from my localhost, I get the JSON content and Content-Type: application/json which is fine.
But calling it from the hosting server I get extra texts like:
Entity generation
created ./src/Guervyl/EditorBundle/Entity/TestCase.php
> Generating entity class src/Guervyl/EditorBundle/Entity/TestCase.php: OK!
> Generating repository class src/Guervyl/EditorBundle/Repository/TestCaseRepository.php: OK!
Everything is OK! Now get to work :).
Which is the output texts from the console when calling $app->run($input, $out);. After that I get the HTTP header that I set then the json content. And also the content-type is application/x-httpd-php5.
That error only happens on a specific hosting server. I tested other hosting server the code works like on my local server.
My question is why am I getting the error on that specific hosting? Is there something I can change from the PHP.ini to fix it? Because I really need to host my website on that hosting because it offers me great features that I need but the others don't or they are too expensive.
Well, After I debugged the code I noticed that error happened because I did not set the --no-interaction option. So without that option, Symfony was waiting for input when no fields are specified for an Entity.
I'm attempting to write a symfony 2 command which can basically read in a number of routes (either through a yml file or through arguments) and can go and get the response for each of these pages so I can report back whether they came back as 200/404/502 etc..
The routes are relative so would be routes such as '/' and '/news'.
Can't seem to work out how to send these requests through to get a real response, I can use Request::create() to create a request, but this doesn't seem to work how I want it to.
Do I have to go through the Kernel even though its a command? Any help would be appreciated.
What I have so far:
$request = Request::create('/news', 'GET');
$response = new Response();
$response->prepare($request);
$res = $response->send();
var_dump($res->getContent());
This comes back with an empty string all the time.
Also tried the following:
$client = new Client(
new HttpKernel(new EventDispatcher(), new ControllerResolver())
);
$client->request('GET', '/news');
var_dump($client->getResponse());
Which comes back with a route is wrongly configured error
Thanks
Kevin
According to your comment:
Basically im testing the code base to ensure its all set up ok before I release it to the public, so I want to simulate a client request to the code and ensure I get a 200 response back, if a 404 came back then I know there is a problem so to pause the release.
This is easy to do with a test:
<?php
// tests/AppBundle/Controller/PostControllerTest.php
namespace Tests\AppBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class PostControllerTest extends WebTestCase
{
public function testNews()
{
$client = static::createClient();
$crawler = $client->request('GET', '/news');
// Assert a specific 200 status code
$this->assertEquals(
200, // or Symfony\Component\HttpFoundation\Response::HTTP_OK
$client->getResponse()->getStatusCode()
);
}
}
You also need to install PHPUnit:
composer require --dev "phpunit/phpunit=5.4.*"
Then you can launch the tests:
php ./vendor/bin/phpunit -c app/phpunit.xml.dist
You'll have a result like this:
PHPUnit 5.4.6 by Sebastian Bergmann and contributors.
..................... 21 / 21 (100%)
Time: 5.29 seconds, Memory: 58.00MB
OK (21 tests, 149 assertions)
The best way to do it is using an http client to which transfer the responsibility to create a request object, perform the request, and return a response.
One of the most used is the guzzle http client (http://docs.guzzlephp.org/en/latest/)
So in your command you can get the router from the container, generate the url for that, and perform the request with guzzle client.
EDIT after comments:
To reach that goal to perform request without actually have a server running, you can use \Symfony\Bundle\FrameworkBundle\Client object that uses the kernel to perform request-response ( you have of course to inject the current kernel of your command). Pretty much as the WebTestCase class does.
http://api.symfony.com/2.8/Symfony/Bundle/FrameworkBundle/Client.html
Hope this will help ! (I will update with some code later)
I'm trying to test the Google Login in my Laravel App.
When I try to reach Google Login page I get a 404.
My idea is that in TestCase.php, I have a variable:
protected $baseUrl = 'http://laravel.dev';
So, it could make a conflict.... well the thing is I don't know how to do it, or to fix it!
Here is my code:
$this->visit('/auth/login')
->click('google');
dump(Request::url()); // https://accounts.google.com/o/oauth2/auth
$this->dump(); --> Gives me a 404 page
Any idea is welcome !
The answer is NO. The framework doesn't support it. If you dig a little bit you will found when you test with "visit", the request was not actually send out. In the call method of class MakesHttpRequests, it initialize a laravel http kernel and put the request thru it. No http request was actually issued.
$kernel = $this->app->make('Illuminate\Contracts\Http\Kernel');
$this->currentUri = $this->prepareUrlForRequest($uri);
$this->resetPageContext();
$request = Request::create(
$this->currentUri, $method, $parameters,
$cookies, $files, array_replace($this->serverVariables, $server), $content
);
$response = $kernel->handle($request);
The only way you can try is to mock this part.
I have just built a restful API with Slim Framework. For error conditions I simply respond with appropriate error codes for each error case and called with $app->halt, for example:
$app->halt(403, "Unauthorized");
But when I curled my API with -v and when I viewed headers in Firefox with HTTPFox I am always seeing error code 500. Anyone else notice this? Is there something I'm missing?
I ran into this same issue myself recently because I had forgotten to instantiate the $app variable within my function.
If you are not explicitly stating for your function to use($app), try adding the following line before $app-halt(403, 'Unauthorized') in order to see the desired error code:
$app = Slim::getInstance();
It is not allowed to call halt() method outside of the route callback.
You should use like this;
$app->get('/method/', function () {
//logical controls
//do something
//or
$app->halt();
});
There is a difference between halt() and setStatus().
With halt(), you will stop the current script execution and render a response according to the HTTP status code and message you choose to send. You can do it anywhere in your app with this code :
$app = \Slim\Slim::getInstance(); //if you don't have access to $app
$statusCode = 403;
$body = 'Unauthorized';
$app->halt($statusCode, $body);
//App will stop immediately
With setStatus() or $this->response->status(); you will only change the HTTP status code you are sending but your app will continue to execute like normally and won't stop. Its only changing the header that Slim will send to your client at then end of the route execution.
$app = \Slim\Slim::getInstance(); //if you don't have access to $app
$statusCode = 403;
$app->response->setStatus(400);
//App will continue normally