PHP PSR-7 Proxy

Nov 21, 2016 · By Darren Mothersele

Web Dev

Creating a proxy in PHP is easy thanks to the PSR-7 HTTP message interface definitions.

Different tools using these same interface definitions are interoperable, without any extra work. With PSR-7 it is easy to write a simple proxy. Like this one, that sits between a client and an API and performs some mapping and reformatting of the data.

If you’d rather read the code than the description go to this GitHub repo.

I use the Guzzle HTTP client to make API calls in my code. This works with HTTP message objects that conform to PSR-7.

Symfony HTTP Foundation (as used in Drupal) can work with PSR-7, but needs the PSR-7 Bridge library added. So, for simple projects, I prefer to use a standalone implementation of PSR-7 like Zend Diactoros.

On simple projects I tend to use PHP-DI to configure everything. So, a simple composer.json file for this project might look something like this:

{
    "require": {
        "php-di/php-di": "^5.4",
        "guzzlehttp/guzzle": "^6.2",
        "zendframework/zend-diactoros": "^1.3",
        "mtdowling/jmespath.php": "^2.3"
    },
    "autoload": {
        "psr-4": {
            "MyProxy\\": "src"
        }
    }
}

I’ve added JMESPath to perform data manipulation.

I put some standard bootstrap stuff into an App class. In the simplest of cases this just builds the DI container, and provides a run() method.

class App
{
    /** @var Container */
    protected $container;

    public function __construct()
    {
        $builder = new ContainerBuilder;
        $builder->addDefinitions(__DIR__.'/di-config.php');
        $this->container = $builder->build();
    }

    public function run(ServerRequestInterface $request, ResponseInterface $response)
    {
        $runner = $this->container->get(StackRunner::class);
        return $runner($request, $response);
    }
}

This uses the Container from PHP-DI, and makes use of the StackRunner class I talked about in a previous blog post

The StackRunner is given a stack of middleware to process requests. They are configured in the di-config.php file that is loaded into PHP-DI’s container. Here’s my example config for a very simple proxy that forwards requests to an API, then does some manipulation of the data it receives before sending the reply back to the client.

Here’s the DI config, simplified to show the important bits:

return [

    'target' => 'http://example.com',

    'middleware' => [
        ProxyMiddleware::class,
        FilterMiddleware::class
    ],

    StackRunner::class => object()
        ->constructorParameter('stack', get('middleware')),

    ProxyMiddleware::class => object()
        ->constructorParameter('target', get('target')),
];

In this example the middleware stack contains just two items. The first one proxies the request, the second one applies a filter to the response, before returning.

The actual implementation of ProxyMiddleware is very straightforward, because of the use of compatible PSR-7 http message objects.

class ProxyMiddleware
{
    /** @var Client */
    protected $client;

    protected $target;

    public function __construct(Client $client, $target)
    {
        $this->client = $client;
        $this->target = $target;
    }

    public function __invoke(ServerRequestInterface $request, ResponseInterface $response, callable $next)
    {
        $target = new Uri($this->target);

        /** @var Uri $uri */
        $uri = $request->getUri()
            ->withScheme($target->getScheme())
            ->withHost($target->getHost())
            ->withPort($target->getPort());

        $request = $request->withUri($uri);

        $response = $this->client->send($request);

        return $next($request, $response);
    }
}

Notice the ServerRequestInterface object it reused to send on to the API via the Guzzle HTTP client. I just reset the URI of the request and send it on. You might want to do more than this when manipulating the requests before passing them on, like removing/adding cookies.

The FilterMiddleware is just as simple…

class FilterMiddleware
{
    public function __invoke(ServerRequestInterface $request, ResponseInterface $response, $next)
    {
        $data = json_decode($response->getBody()->getContents(), true);
        
        // Do your manipulation of data here
        
        $response = new JsonResponse($data);
        return $next($request, $response);
    }
}

The point of this article is not to discuss the filtering and manipulating the data so I have omitted those bits of the code. I actually pass in an object to do the filtering, via the DI config. Which makes this middleware reusable.

The important point I wanted to highlight was just how easy it is to make a simple proxy using PSR-7 for compatibility.

Here is how it is used, by creating a Diactoros ServerRequestInterface object from the incoming PHP request, and using the SapiEmitter to output the response. Here’s my proxy.php:

require_once __DIR__.'/../vendor/autoload.php';

$request = ServerRequestFactory::fromGlobals();
$response = (new App)->run($request, new Response);
(new SapiEmitter)->emit($response);

You can run this with PHP’s internal web server, by calling php -S localhost:8080 proxy.php.

You can get the example code from this GitHub repo.

Darren's Photo

Darren Mothersele

Darren is a software developer who builds simple, creative, and independent technology. Read more »