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.
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 have a controller has an action that looks something like this:
/**
* #Route("/my_route_path", name="my_route_name")
*/
public function doSomethingAction(Request $request)
{
$myPath = $request->getScheme().'://'.$request->getHttpHost().''.$request->getBasePath();
$data = file_get_contents($myPath. '/data_folder/data.json');
return $this->render('#Entry/my_template.html.twig', array(
'data' => json_decode($data, true)
));
}
And I create a functional test for this controller like this:
/** #test */
public function doSomething_should_success()
{
$client = static::createClient();
$crawler = $client->request('GET', '/my_route_path');
$this->assertEquals(200, $client->getResponse()->getStatusCode());
}
But I can't run the functional test I still get : Failed asserting that 500 is identical to 200
So, after I checked the test.log file I find this error : file_get_contents(http://localhost/data_folder/data.json) : failed to open stream
As now the problem is comming from $request->getBasePath() because always contain empty string but the expected behaviour is return PATH_TO_MY_PROJECT_FOLDER\web in my case must return projects\web_apps\MY_PROJECT_FOLDER_NAME\web
So, the simplified question: why the request object always contain an empty basePath string in the unit test but it works very well on the browser.
The Request object helps you handle the request of a client, that is something like GET /my_route_path plus lots of headers and a server that is directed at.
The web server passes those information on to php and symfony, and symfony will turn this into a Request object. Symfony has usually one entry point, which is public/index.php (symfony 4) or web/app.php (symfony 3) which is assumed to be / or possibly /basePath/ (the basepath will be communicated by the web server and handled by Symfony).
Symfony will generate a Request object, where the basepath is essentially abstracted away, and whenever you generate a url (via Controller::generateUrl) the base path is taken into account. that's why the basepath is important for Requests.
This is actually described pretty well in the comments of the Request's functions:
getBasePath vs getPathInfo.
However, this only concerns the public facing URLs and doesn't have anything to do with how you structure your project and where that project is located, because that's completely irrelevant to the Request (separation of concerns and stuff).
So I guess, you are actually looking for the root directory of your project.
To find the location of your project dir, there is the very base version, where you directly use the PHP magic var __DIR__ which contains the directory the current script file is in, and you can navigate from there. since controllers are usually located such that their path is projectdir/src/Controller/TheController.php a __DIR__.'/../.. would give you the projectdir. However, that's not really clean. The better version:
Depending on the symfony version you're using, you should retrieve the project dir via the ParameterBagInterface (symfony 4)
function doSomethingAction(ParameterBagInterface $params) {
$projectDir = $params->get('kernel.project_dir');
}
or via the container (symfony 3) see also: new in symfony 3.3: A simpler way to get the project root directory
function doSomethingAction() {
$projectDir = $this->getParameter('kernel.project_dir');
}
In my case I had to inyect RequestStack $stackand access the main request, after that my "BasePath" has value. This is because I where in a subrequest and I had to access to the top level of the request.
This post helped me to understood: Symfony2 - get main request's current route in twig partial/subrequest
/**
* #Route("/myroute", name="myroute")
*/
public function myroute(RequestStack $stack)
{
$request = $stack->getMainRequest();
$route = $request->getPathInfo();
}
Working on a REST API for PDF processor using Mpdf(and tfox symfony bundle) on Symfony 3 Framework. I created two GET requests, one with no parameters for testing, and one with the parameter(URL of the HTML file) I want to read and then convert into PDF.
The Generic GET function:
/**
*
* #Rest\Get("/create")
*/
public function createPDFAction(){
$mpdfService = $this->get('tfox.mpdfport');
$html = "<h1> Hello </h1>";
$mpdf = $mpdfService->getMpdf();
$mpdf->WriteHTML($html);
$mpdf->Output();
exit;
}
The Second GET function with parameter:
/**
* #param $htmlSource
* #Rest\Get("/create/{htmlSource}")
*/
public function createPDFFromSourceAction($htmlSource){
$mpdfService = $this->get('tfox.mpdfport');
$html = file_get_contents($htmlSource);
$mpdf = $mpdfService->getMpdf();
$mpdf->WriteHTML($html);
$mpdf->Output();
exit;
}
The problem is, when I call the second function using browser or Postman the first function is always returned instead and I get the PDF with "Hello", if I remove the first GET function, I get error "no route found for GET/create"
I investigated:
The PDF URL is correct, I manually inserted it in first function and worked
No syntax error, I copied the same function without the parameters and worked
The Calls I do are:
http://localhost:8000/create This one works
http://localhost:8000/create?htmlSource=PATH-TO-FILE-LOCALLY This one doesnot work
If I put the PATH-TO-FILE-LOCALLY in function 1 manually it works fine
So I have 2 Questions:
Since I am new to REST and LAMP, should I use GET or others ? My goal is to read the HTML form that the user will fill into a variable and pass it to Mpdf which will convert it into PDF and return that PDF for viewing or download
Why only the first GET function is being read ?
Notes: I am developing on Linux, with PHPStorm, PHP 7, Symfony 3, localhost, the html file I am testing with is on my local machine
Side point: In case this is resolved, I am supposed to upload this to my clients server (which is Apache) - do you have any guides on how to do that and what should be the URLs changed to ?
Thank you all in advance
Updates:
I have changed the functionality to POST methods and it now works fine:
/**
* #Rest\Post("/mPDF/")
*/
public function createPDFAction(Request $request){
$source = $request->get('source');
if($source == ""){
return new View('No Data found', Response::HTTP_NO_CONTENT);
}
$mpdfService = $this->get('tfox.mpdfport');
$html = file_get_contents($source);
$mpdf = $mpdfService->getMpdf();
$mpdf->WriteHTML($html);
$mpdf->Output();
exit;
}
After publishing to Apache production server and some configuration tweaks the site is now live ! - but now I am facing a new issue which I will post a new question for with all the config info I have - basically POST method is returning {
"error": {
"code": 405,
"message": "Method Not Allowed"
}
}
http://localhost:8000/create?htmlSource=PATH-TO-FILE-LOCALLY
("/create/{htmlSource}")
These paths do not match.
First path consists of domain name, and route create, while second path has route "create" + slash + wildcard.
Query parameters are not defined within routing annotation. Instead, access them inside controller, using
public function createPDFFromSourceAction(Request $request)
{
$htmlSource = $request->query->get('htmlSource'); // query string parameter
$somethingElse = $request->request->get('somethingElse'); //POST request parameter
...
}
Symfony will pass Request object inside the controller for you.
As for your other question, GET requests are usually used for things that do not change the state of the application, and POST/PUT/PATCH/DELETE requests change the state. Since you are uploading something, use POST request.
For your 'side note' you should ask another question instead.
I'm experiencing a problem with my SOAP solution. Sometimes I get an error saying the following:
Function (functionA) is not a valid method for this service
Edit 8 months later
Although I could not find the cause of the problem I was able to work around it. Whenever I recieve an response from the API I check for the SoapFault and just send another identical request and use the answer that comes back the second time.(posted as an answer)
This occurs in calls from PHP like:
functionA() - expected response
functionA() - expected response
functionA() - SoapFault
functionA() - expected response
Same result is to be expected in all the above calls and the same parameters are used(if any). Since it's working fine for almost all calls I know that the function and the corresponding WSDL is there.
What I thougt were the problem was caching an old version which would not have that function. I tried disabling the caching with:
ini_set("soap.wsdl_cache_enabled", "0");
And makeing every call with added with a random dummy parameter as well as disabling it when I use Zend_SoapClient.
'cache_wsdl' = false
I hope someone could point me in any direction or have any direct suggestion on what could be the cause.
My code looks like:
public function __construct()
{
$wsdl = "http://catlovers.nl/index.php?wsdl&dummy=".rand(1000,9999);
$this->_client = new Zend_Soap_Client($wsdl, array(
'soapVersion' => SOAP_1_1,
'cache_wsdl' => false
));
$this->_client->setWsdlCache(false);
}
function __call($name, $arguments) // Calls are made this way
{
array_unshift($arguments, $this->_apiKey, $this->_user, $this->_password);
return call_user_func_array(array($this->_client, $name), $arguments);
}
public function getCat()
{
return ($this->__call('getCat',array()));
}
On "the other side" I have:
$server = new nusoap_server();
$server->wsdl->addComplexType('Cat', ....
$server->register( 'getCat', return Cat ...
function getCat($apikey, $email, $password)
{
$cat = $db->get("redCat");
return $cat;
}
First of all, try to call function using built-in SoapClient class and printing debug information:
$wsdl = "http://abcd.com/index.php?wsdl&dummy=".rand(1000,9999);
$soap = new SoapClient($wsdl, array(
'cache_wsdl' => WSDL_CACHE_NONE,
'trace' => true,
));
try {
var_dump($soap->functionA());
} catch ( Exception $ex ) {
var_dump($ex);
}
var_dump($soap->__getLastRequest());
var_dump($soap->__getLastRequestHeaders());
var_dump($soap->__getLastResponse());
var_dump($soap->__getLastResponseHeaders());
This way you'll know where is the problem. If everything is ok all the time, the problem is in Zend's class. If not, look what service responds. May be there is some server-side error or dummy generation with such id fails
I guess your problem is related to nusoap, because for many years I'm using PHP soap server/client and I never faced this problem. (but I always had strange problems with nusoap lib)
currently I'm using jool.nl web service helper which is very powerfull yet neat and object oriented library not only makes coding easier and cleaner but also provides you object oriented approach to web service designing. It also provides a nice web interface for your web service with documentation.
As this library uses internal PHP SOAP server I'm pretty sure you're problem will be disappear then.
I suggest you to give it a try and I'm sure if you make your first web service with this library you will never try something else.
I hope this helps you.
So the problem was still there after trying other solutions so I was never able to find underlying cause of the problem. On the other hand I found a way to work around the problem that has been working since I wrote it. This is how my call to the API looks like with user,password and key for authentication.
function __call($name, $arguments)
{
/* Using the stored data to ensure that the user is allowed to access */
/* ............... */
array_unshift($arguments, $this->_apiKey, $this->_user, $this->_password);
$call = call_user_func_array(array($this->_client, $name), $arguments);
if(isset($call->faultstring) && substr(trim($call->faultstring),0,7) == "Function")
{
$recall = call_user_func_array(array($this->_client, $name), $arguments);
return $recall;
}
else
return $call;
}
This is basicly: if it doesn't work the first time just try again.
I am trying to use a SOAP Client-Server in my computer and it doesn't look like it is going to work, I am getting this error Error Fetching Http Headers when I try to run my SOAP Client.
I have been looking and the solution that I have encountred is to increase the default_socket_timeout from 60 to 120 seconds and it doesn't work for me, also I have seen another solution that is putting the vhost in my apache KeepAlive Off and that didn't work.
The WSDL is working fine because I try to use it in another computer and it work.
I am running PHP Version 5.3.5-1ubuntu7.4 in Linux Mint using Zend Framework, I hope some of you can help me fix this thank you.
I'm sorry but I don't know what you are using to set up your SOAP service.....
If you can give more information about your SOAP service (poss Zend_Soap given the Zend Framework tag) etc that would be great.
Also, as a quick alternative, you say you've looked at the WSDL on another computer, perhaps try the application in an alternative environment to ensure it's not an environment issue.
May be a simple issue with your client-server code.
UPDATE: Ok so I realised the example I mentioned yesterday wasn't fully implemented so I've hacked something together quickly that you can try to see if it works in your environment.
The code is a mix of something I found here (an example of Zend_Soap_Server) and something from another SO question here (an example of a basic SOAP service test).
I've tested it at my end using ZF 1.11 and the example I'm outlining uses the default Application path you get with a new ZF project (e.g models are in directory application/models so the model shown is headed up Application_Model_Classname).
If it works, you can tweak accordingly....if it doesn't work we can try something else.
Start by creating a new SOAP controller and set the class up like this:
<?php
class SoapController extends Zend_Controller_Action
{
public function init()
{
ini_set("soap.wsdl_cache_enabled", "0"); //disable WSDL caching
$this->_helper->layout()->disableLayout(); //disable the layout
$this->_helper->viewRenderer->setNoRender(); //disable the view
}
public function indexAction ()
{
if (isset($_GET['wsdl'])) {
//return the WSDL
$this->handleWSDL();
} else {
//handle SOAP request
$this->handleSOAP();
}
}
private function handleWSDL ()
{
$strategy = new Zend_Soap_Wsdl_Strategy_AnyType();
$autodiscover = new Zend_Soap_AutoDiscover();
$autodiscover->setComplexTypeStrategy($strategy);
$autodiscover->setClass('Application_Model_SoapService');
$autodiscover->handle();
}
private function handleSOAP ()
{
$server = new Zend_Soap_Server(null,
array('uri' => "http://YOURDOMAIN/soap?wsdl"));
$server->setClass("Application_Model_SoapService");
$server->handle();
}
public function testAction()
{
$client = new Zend_Soap_Client("http://YOURDOMAIN/soap?wsdl");
try {
echo $client->testMethod('test');
} catch (Exception $e) {
echo $e;
}
}
}
In the class above, the WSDL is automatically generated using Zend_Soap_Autodiscover with a SoapService.php file at application/models/SoapService.php used as the template. Note the DocBock comments above each method in your target class are integral to this process.
Next create the SoapService.php file in the default models folder:
<?php
class Application_Model_SoapService
{
/**
* testMethod
*
* #param string $string
* #return string $testSuccess
*/
public function testMethod(string $string)
{
$testSuccess = 'Test successful, the message was: ' . $string;
return $testSuccess;
}
}
If all is working as it should be you can visit:
http://YOURDOMAIN/soap?wsdl
to see the WSDL and visit:
http://YOURDOMAIN/soap/test
to get a success message with the string you specified in the client request within the testAction() code in the SoapController class as part of the message.
Let me know if it's working or not and we can go from there.
I'll be able to have another look on Monday.