$app->get('/', function () {
// Initial page load.
include 'body-index.php';
return $response;
});
I have the code above on my /index.php. How would I then call and modify functions within body-index.php? As I'm learning MVCs and frameworks right now on my own I'd rather do it this way, rather then breaking out of Slim and do a get('/body-index.php', with the page code. Is this possible?
Thanks.
From Slim Framework documentation:
Most often, you’ll need to write to the PSR 7 Response object. You can write content to the StreamInterface instance with its write() method like this:
$body = $response->getBody();
$body->write('Hello');
You can also replace the PSR 7 Response object’s body with an entirely new StreamInterface instance. This is particularly useful when you want to pipe content from a remote destination (e.g. the filesystem or a remote API) into the HTTP response. You can replace the PSR 7 Response object’s body with its withBody(StreamInterface $body) method. Its argument MUST be an instance of \Psr\Http\Message\StreamInterface.
$newStream = new \GuzzleHttp\Psr7\LazyOpenStream('/path/to/file', 'r');
$newResponse = $oldResponse->withBody($newStream);
Source: Response - Slim Framework
Related
I have an application running on webserver A. I have a second application running on webserver B. Both webservers require a login. What I need to do is have a request to webserver A pass through to webserver B and return a file to the client without having the client login to Webserver B. (In other words, webserver B will be invisible to the client and I will take care of the auth credentials with my request to B from A). The code below is built on a laravel framework, but I don't believe the answer needs to be laravel specific.
The code works but it is only returning the HEAD information of the file to the calling client. Not the file itself.
Any help will be greatly appreciated!
Controller:
public function getAudioFile(Request $request)
{
//This is the id we are looking to pull
$uid = $request->uniqueid;
$audioServices = new AudioServices();
return $audioServices->getWavFile($uid);
}
Service:
public function getWavFile(String $uniqueId)
{
$client = new GuzzleHttp\Client(['verify' => false]);
return $client->request('GET', $this->connectString.$uniqueId, ['auth' => ['username', 'password']]);
}
As mentioned by bishop you can use sink option from Guzzle to stream the response of a Guzzle request.
You can pass that stream to a response from your controller. I'm not sure if Laravel has built-in stream support, but the underlying symfony httpfoundation components do. An example of it's usage can be found in this tutorial.
If you prefer not to use the sink option from Guzzle you can also use the response itself as that implements PSR-7 stream objects.
I played around with the PHP 7.2 runtime and HTTP trigger on Alibaba Cloud Function Compute. The basic example in the documentation is the following:
<? php
use RingCentral\Psr7\Response;
function handler($request, $context): Response{
/*
$body = $request->getBody()->getContents();
$queries = $request->getQueryParams();
$method = $request->getMethod();
$headers = $request->getHeaders();
$path = $request->getAttribute("path");
$requestURI = $request->getAttribute("requestURI");
$clientIP = $request->getAttribute("clientIP");
*/
return new Response(
200,
array(
"custom_header1" => "v1"
),
"hello world"
);
}
This works quite well. It's easy to get the query parameters from an URL. But the body content is only available in a whole string with
$request->getBody()->getContents();
Although the documentation says that the $request parameter follows the PSR-7 HTTP Message standard, it is not possible to use $request->getParsedBody() to deliver the values submitted by POST method. It didn't work as expected - the result remains empty.
The reason is the underlying technology. Alibaba Cloud Function Compute makes use of the event-driven React PHP library to handle the requests (you can check this by analyzing the $request object). So the $_POST array is empty and there is no "easy way to get POST data".
Luckily, Alibaba's Function Compute handler provides the body content by $request->getBody()->getContents(); as a string like
"bar=lala&foo=bar"
So a solution seems easiser than thought at the beginning, you can e.g. use PHP's own parse_str() function:
$data = [];
$body = $request->getBody()->getContents();
parse_str($body,$data);
If you place this snippet in the handler function, the POST variables are stored in the $data array and ready for further processing.
Hope that this helps somebody who asked the same questions than I. :-)
Kind regards,
Ralf
As you can see in the documentation you need to add a RequestBodyParserMiddleware as middleware to get a parsed PSR-7 request. It seems you didn't do that.
Also keep in mind that only the Content-Types: application/x-www-form-urlencoded and multipart/form-data are supported here. So make sure the client need to send these headers so the request can be parsed. If it's another Content-Type you need to use another middleware.
See: https://github.com/reactphp/http#requestbodyparsermiddleware for more information.
I hope this helps!
#legionth: I apologize that I didn't use the comment feature here, but my answer is too long. :-)
Thanks a lot for your comments - the usage of RequestBodyParserMiddleware is a great solution if you can control the server code. But in the context of Alibaba Cloud Function Compute service this seems not possible. I tried to find out more information about the invocation process - here are my results:
Function Compute makes use of the Docker image defined in https://github.com/aliyun/fc-docker/blob/master/php7.2/run/Dockerfile .
In the build process they download a PHP runtime environment from https://my-fc-testt.oss-cn-shanghai.aliyuncs.com/php7.2.tgz . (I didn't find this on GitHub, but the code is public downloadable.)
A shell script start_server.sh starts a PHP-CGI binary and runs a PHP script server.php.
In server.php a React\Http\Server is started by:
$server = new Server(function (ServerRequestInterface $request) {
[...]
});
[...]
$socket = new \React\Socket\Server(sprintf('0.0.0.0:%s', $port), $loop);
$server->listen($socket);
$loop->run();
As seen in the Function Compute documentation (& example of FC console), I can only use two functions:
/*
if you open the initializer feature, please implement the initializer function, as below:
*/
function initializer($context) {
}
and the handler function you can find in my first post.
Maybe Alibaba will extend the PHP runtime in future to make it possible to use a custom middleware, but currently I didn't find a way to do this.
Thanks again & kind regards,
Ralf
I am currently busy with a PSR-7 project with responses and requests.
Currently we are setting up an application in our index.php by doing something like:
$app = new Application();
$app->loadConfiguration(
'../config/global.yml',
);
// Should return the response?
$app->run((new ServerRequestFactory())->createServerRequestFromGlobals());
Here the run method also calls an emit method that is responsible for sending the headers and printing the body of the response.
The request and respons are now linked together in one call which makes it hard to test since you don't want to send the response with the headers straight to PHPUnit.
I have removed the emit call in the chain of the run method and added this to the index after the run method call:
// Send the response.
$app->send();
This way they are decoupled but the downside is I now have to hold a instance of my response in a response property inside my Application.php($app) class.
I want to move the response instance to the response class itself but my co-workers thinks a class should never hold an instance of itself. Yet when I look at frameworks this happens quite a lot. Is he right about this?
What arguments can I make to decouple my request and response besides easier testing?
I am pretty new to unit testing, one of the arguments I have already heard is that I should not test the full application anyways but rather separate components and therefore should not be worried about de-coupling the request and response.
I'm using Goutte to make a webscraper.
For development, I've saved a .html document I'd like to traverse (so i'm not constantly making requests to the website). Here's what I have so far:
use Goutte\Client;
$client = new Client();
$html=file_get_contents('test.html');
$crawler = $client->request(null,null,[],[],[],$html);
Which based of what I know should call request in Symfony\Component\BrowserKit, and pass in the raw body data. Here's the error message I'm getting:
PHP Fatal error: Uncaught exception 'GuzzleHttp\Exception\ConnectException' with message 'cURL error 7: Failed to connect to localhost port 80: Connection refused (see http://curl.haxx.se/libcurl/c/libcurl-errors.html)' in C:\Users\Ally\Sites\scrape\vendor\guzzlehttp\guzzle\src\Handler\CurlFactory.
If I were to just use DomCrawler, it's non-trivial to create a crawler using a string. (see: http://symfony.com/doc/current/components/dom_crawler.html). I'm just unsure about how to do the equivalent with Goutte.
Thanks in advance.
Tools you decided to use make real http connections and are not suitable for what you want to do. At least out of the box.
Option 1: Implement your own BrowserKit Client
All goutte does is it extends BrowserKit's Client. It implements http requests with Guzzle.
All you need to do to implement your own client, is to extend the Symfony\Component\BrowserKit\Client and provide the doRequest() method:
use Symfony\Component\BrowserKit\Client;
use Symfony\Component\BrowserKit\Request;
use Symfony\Component\BrowserKit\Response;
class FilesystemClient extends Client
{
/**
* #param object $request An origin request instance
*
* #return object An origin response instance
*/
protected function doRequest($request)
{
$file = $this->getFilePath($request->getUri());
if (!file_exists($file)) {
return new Response('Page not found', 404, []);
}
$content = file_get_contents($file);
return new Response($content, 200, []);
}
private function getFilePath($uri)
{
// convert an uri to a file path to your saved response
// could be something like this:
return preg_replace('#[^a-zA-Z_\-\.]#', '_', $uri).'.html';
}
}
$client = new FilesystemClient();
$client->request('GET', '/test');
Client's request() needs to accept real URIs, therefore you need to implement your own logic to convert it to a filesystem location.
Have a look at Goutte's Client for insipration.
Option 2: Implement a custom Guzzle handler
Since Goutte uses Guzzle, you could provide your own Guzzle handler that would load responses from files, instead of making real http requests. Have a look at the handlers and middleware doc.
If you're just after caching responses so you make less http requests, Guzzle provides support for this already.
Option 3: Use DomCrawler directly
new Crawler(file_get_contents('test.html'))
The only drawback is you'll loose some of convenience methods of the BrowserKit client, like click() or selectLink().
I have spent the past few days integrating Slim API to handle some PHP web services. The first few services utilized GET which was straight forward and had no problems. However, when trying to integrate some POST methods, I am receiving no response from the service. I have tried even just a simple echo to see if the service is being called. In every case, there is no return. Code below, some of the methods have been removed for clarity.
Any reason the POST method is unresponsive? Thanks! viv
$app->get('/login/:un/:pw/:type','login');
$app->get('/browseMO/:prm1/:prm2', 'browseMedia');
$app->get('/usersReviews/:userID','usersReviews');
$app->get('/pubsReviews/:userID','pubsReviews');
$app->get('/productReviews/:productID','getProductReviews');
$app->get('/productAvg/:productID','averageReviewsByOProduct');
$app->post('/userUpd','updateUserInfo');
$app->run();
function averageReviewsByOProduct($productID){
reviews::getAvgReviewByProduct($productID);
}
function browseMedia($param1, $param2){
browseMediaObjects::getMedia($param1, $param2);
}
function updateUserInfo(){
// $request = Slim::getInstance()->request();
// $body = $request->getBody();
echo "UPDATE CALLED"; // never reached
}
Try creating an anonymous function in the slim post declaration and see if the function gets call. If it doesn't, it's something with slim. If it does, it's something with your code.
$app->post('/userUpd',function() use ($app) {
echo 'Test';
});
If that doesn't work, make sure you are returning your data correctly. For instance the above returns data correctly for an AJAX call that expects a text string response.