Send multiple requests through a middleware in Guzzle - php

Is there any way I can send multiple requests with guzzle using a middleware and according to certain parameters?
To be more specific I need:
class MyMiddleware
{
/**
* #param callable $handler
* #return \Closure
*/
public function __invoke(callable $handler)
{
return function (RequestInterface $request, array $options) use ($handler) {
if(!isset($options['token']){
//request the token from a url
}
//after the token is aquired proceed with the original request
return $handler($request, $options);
};
}
}

Is this similar to what you are inquiring about? I have done similar to it.
$handler = GuzzleHttp\HandlerStack::create();
$client = new GuzzleHttp\Client([
'handler' = $handler,
// other options
])
$handler->push(GuzzleHttp\Middleware::mapRequest(
function (Psr\Http\Message\RequestInterface $request) use ($client) {
if ('POST' !== $request->getMethod()) {
return $request;
}
if($RequestIsOkAsIs) {
return $request;
}
$response = $client->get($someUrl, $requestOptions);
// Play with the response to modify the body
$newBody = 'results of stuff';
return new GuzzleHttp\Psr7\Request(
$request->getMethod(),
$request->getUri(),
$request->getHeaders(),
$newBody,
$request->getProtocolVersion()
);
}
);

Related

Can't get POST parameters in controller

I'm experimenting with Symfony and I am trying to make a POST request. But I can't seem to get the request's POST parameters inside my controller.
/**
* #Route("/messages/comment/post/", methods={"POST"}, name="postComment")
*/
public function postComment($messageId, $comment)
{
$statuscode = 200;
$response = null;
try {
$response = $this->messageModel->postComment($messageId, $comment);
} catch (\PDOException $exception) {
var_dump($exception);
$statuscode = 500;
}
return new JsonResponse($response, $statuscode);
}
How do I define parameters for this?
To get POST parameters inside a controller you should use:
use Symfony\Component\HttpFoundation\Request;
....
/**
* #Route("/messages/comment/post/", methods={"POST"}, name="postComment")
*/
public function postComment(Request $request){
$messageId = $request->request->get('messageId');
$comment = $request->request->get('comment');
...
}
Documentation here

Pass array from controller to middleware Slim 3 PHP

I'm trying to pass an array with data to middleware and format it based on Accept HTTP header.
The controller gets the data from db and should pass it to the response object. Response object write() method accepts only strings:
public function getData(Request $request, Response $response): Response {
return $response->write($this->getUsers());
# This line of code should be fixed
}
The middleware should get the response and format it correctly:
public function __invoke(Request $request, Response $response, callable $next) {
$response = $next($request, $response);
$body = $response->getBody();
switch ($request->getHeader('Accept')) {
case 'application/json':
return $response->withJson($body);
break;
case 'application/xml':
# Building an XML with the data
$newResponse = new \Slim\Http\Response();
return $newResponse->write($xml)->withHeader('Content-type', 'application/xml');
break;
case 'text/html':
# Building a HTML list with the data
$newResponse = new \Slim\Http\Response();
return $newResponse->write($list)->withHeader('Content-type', 'text/html;charset=utf-8');
break;
}
}
I have a few routes behaves similarly:
$app->get('/api/users', 'UsersController:getUsers')->add($formatDataMiddleware);
$app->get('/api/products', 'UsersController:getProducts')->add($formatDataMiddleware);
By using middlewares I can add such functionality in a declarative way, keeping my controller thin.
How can I pass the original data array to response and implementing this pattern?
The Response-Object doesn't provide this functionallity nor has some extensions to do that. So you need to adjust the Response-Class
class MyResponse extends \Slim\Http\Response {
private $data;
public function getData() {
return $this->data;
}
public function withData($data) {
$clone = clone $this;
$clone->data = $data;
return $clone;
}
}
Then you need to add the new Response to the Container
$container = $app->getContainer();
$container['response'] = function($container) { // this stuff is the default from slim
$headers = new Headers(['Content-Type' => 'text/html; charset=UTF-8']);
$response = new MyResponse(200, $headers); // <-- adjust that to the new class
return $response->withProtocolVersion($container->get('settings')['httpVersion']);
}
Now change the Response type to MyResponse and use the withData method
public function getData(Request $request, \MyResponse $response): Response {
return $response->withData($this->getUsers());
}
At the end you can use the getData method and use its value and process it inside the middleware.
public function __invoke(Request $request, \MyResponse $response, callable $next) {
$response = $next($request, $response);
$data = $response->getData();
// [..]
}
That would be the answer to your question. A better solution in my opinion, would be a helper class which does what your middleware does, then you could something like this:
public function getData(Request $request, Response $response): Response {
$data = $this->getUsers();
return $this->helper->formatOutput($request, $response, $data);
}
For that there is already a lib for: rka-content-type-renderer

ZF2 - Set cookie in dispatch listener

I would like to set a cookie in a listener, if the query parameter "source" is set. I tried the following but the cookie does not exist.
How can I set the cookie correct?
class DispatchListener extends AbstractListenerAggregate {
public function attach(EventManagerInterface $eventManager) {
$this->listeners[] = $eventManager->getSharedManager()->attach(
'Zend\Stdlib\DispatchableInterface',
MvcEvent::EVENT_DISPATCH,
array($this, 'setCookie'),
-80
);
}
/**
* #var \Zend\Stdlib\RequestInterface
*/
protected $request;
/**
* #param \Zend\Stdlib\RequestInterface $request
*/
public function __construct(RequestInterface $request) {
$this->request = $request;
}
public function setCookie(EventInterface $event) {
if ($source = $this->request->getQuery('source')) {
$this->request->setCookies([
'source' => $source
]);
}
}
}
--------------------------------- UPDATE -----------------------------------
class Module implements ConfigProviderInterface, BootstrapListenerInterface {
public function onBootstrap(EventInterface $event) {
$target = $event->getTarget();
$serviceManager = $target->getServiceManager();
$eventManager = $target->getEventManager();
$eventManager->attach($serviceManager->get('Application\Listener\Dispatch'));
}
}
Seems like there are a few issues with your code, which we can rule out first.
You haven't provided code for how you are attaching your listener
Not sure why you are setting the cookie on the request, do you want to do this on the response?
Attaching the event listener:
public function onBootstrap(MvcEvent $e)
{
$eventManager = $e->getApplication()->getEventManager();
$app = $e->getApplication();
$em = $app->getEventManager();
// Attach event to attach listener after routing when query will be populated
$em->attach(MvcEvent::EVENT_ROUTE, function($e) use ($eventManager) {
$request = $e->getRequest();
// attach our listener
$eventManager->attach(new DispatchListener($request));
});
}
Updated setCookie method:
public function setCookie(EventInterface $event) {
if ($source = $this->request->getQuery('source')) {
$this->request->setCookies([
'source' => $source
]);
$request = $this->request;
$cookieData = $request->getCookie('someCookie', 'default');
var_dump($cookieData);
}
}
The var_dump prints the following:
object(Zend\Http\Header\Cookie)[274]
protected 'encodeValue' => boolean true
private 'storage' (ArrayObject) =>
array (size=1)
'source' => string 'test' (length=4)
Do you want a cookie in your request object or do you want to create a cookie for the response. You probably want to create a cookie and set it on the response object.
Check for example this answer.
public function setCookie(EventInterface $event) {
if ($source = $this->request->getQuery('source')) {
$cookie = new \Zend\Http\Header\SetCookie('source', $source);
$headers = $this->getResponse()->getHeaders();
$headers->addHeader($cookie);
}
}

How do I profile Guzzle 6 requests?

I'm trying to profile the requests made to an API server from a PHP client using Guzzle (v 6).
In the Guzzle 5.3 there is this complete and before event handling.
class GuzzleProfiler implements SubscriberInterface
{
public function getEvents()
{
return [
'before' => ['onBefore'],
'complete' => ['onComplete']
];
}
public function onBefore(BeforeEvent $event, $name)
{
start_profiling();
}
public function onComplete(CompleteEvent $event, $name)
{
end_profiling();
}
}
But how do I do this in v6?
Just found it using Middleware. Here's the code.
class Profiler {
/**
* #return callable
*/
public static function profile() {
return function(callable $handler) {
return function(\Psr\Http\Message\RequestInterface $request, array $options) use ($handler) {
start_profiling();
return $handler($request, $options)->then(function(\Psr\Http\Message\ResponseInterface $response) use ($token) {
end_profiling();
return $response;
});
};
};
}
}
And then attach the profiler like this.
$stack = \GuzzleHttp\HandlerStack::create();
$stack->push(Profiler::profile());
$client = new \GuzzleHttp\Client([
'handler' => $stack
]);

zf2 api response event catched by bjyauthorize

Hi can someone help me to prevent bjyauthorize to catch my api event error raised?
bjyauthorize redirect non logged user to login form as added to config. But since my api are allowed for all roles even for guest i just want it to return Json error message catched by ApiProblemListener
ApplicationRest\Module.php
class Module implements
ConfigProviderInterface,
AutoloaderProviderInterface
{
public function onBootstrap(MvcEvent $e)
{
$app = $e->getApplication();
$sm = $app->getServiceManager();
$events = $app->getEventManager();
$listener = $sm->get('ApplicationRest\ApiAuthenticationListener');
$events->getSharedManager()->attach('ApplicationRest\Controller', 'dispatch', $listener, 500);
$events->attach('render', array($this, 'onRender'), 100);
$events->attach($sm->get('ApplicationRest\ApiProblemListener'));
}
/**
* Listener for the render event
* Attaches a rendering/response strategy to the View.
*
* #param \Zend\Mvc\MvcEvent $e
*/
public function onRender($e)
{
$result = $e->getResult();
if (!$result instanceof RestfulJsonModel) {
return;
}
//var_dump(123);exit();
$app = $e->getTarget();
$services = $app->getServiceManager();
$view = $services->get('View');
$restfulJsonStrategy = $services->get('ApplicationRest\RestfulJsonStrategy');
$events = $view->getEventManager();
// register at high priority, to "beat" normal json strategy registered
// via view manager
$events->attach($restfulJsonStrategy, 500);
}
}
Have many modules and i am really thinking to move away my apiModule "ApplicationRest" to another project but don't really want to update model and service each time i make some updates on main project.
Any suggestions would welcome!
Thanks for your time!
EDIT: Provided more HeaderAuthentication class
class HeaderAuthentication implements AdapterInterface
{
const AUTHORIZATION_HEADER = 'Authorization';
const CRYPTO = 'sha256';
protected $request;
protected $repository;
public function __construct(RequestInterface $request, UserRepository $repository)
{
$this->request = $request;
$this->repository = $repository;
}
/**
* Authorization: Key={key} Timestamp={timestamp} Signature={signature}
* #return Result
*/
public function authenticate()
{
$request = $this->getRequest();
if (!$request instanceof Request) {
return;
}
$headers = $request->getHeaders();
// Check Authorization header presence
if (!$headers->has(static::AUTHORIZATION_HEADER)) {
return new Result(Result::FAILURE, null, array(
'Authorization header missing'
));
}
$authorization = $headers->get(static::AUTHORIZATION_HEADER)->getFieldValue();
// Validate public key
$publicKey = $this->extractPublicKey($authorization);
$user = $this->getUserRepository()
->findOneByApiSecret($publicKey);
if (null === $user) {
$code = Result::FAILURE_IDENTITY_NOT_FOUND;
return new Result($code, null, array(
'User not found based on public key'
));
}
// Validate signature
$signature = $this->extractSignature($authorization);
/*$hmac = $this->getHmac($request, $user);
if ($signature !== $hmac) {
$code = Result::FAILURE_CREDENTIAL_INVALID;
return new Result($code, null, array(
'Signature does not match'
));
}*/
return new Result(Result::SUCCESS, $user);
}
}
ApiAuthenticationListener
class ApiAuthenticationListener
{
protected $adapter;
public function __construct(HeaderAuthentication $adapter)
{
$this->adapter = $adapter;
}
public function __invoke(MvcEvent $event)
{
$result = $this->adapter->authenticate();
if (!$result->isValid()) {
$response = $event->getResponse();
// Set some response content
$response->setStatusCode(401);
return $response;
}
// All is OK
$event->setParam('user', $result->getIdentity());
}
}
I'm guessing you configured guards on your route. You need to tell BJYAuthorize, through your module config, that this controller or route shouldn't be protected.
'bjyauthorize' => [
'default_role' => 'guest',
...
'guards' => [
'BjyAuthorize\Guard\Controller' => [
// system tools
['controller' => 'Application\Controller\Api', 'roles' => [] ],
['controller' => 'error', 'roles' => []],
],
],
],
I cut out the nitty gritty that's app specific, but this type of thing is quickly solved. I had a similar need for CLI routes to be unprotected by what is otherwise, http auth.

Categories