Tag: proxy

  • Request data through a proxy with GuzzleHttp – PSR-7 compliant

    Request data through a proxy with GuzzleHttp – PSR-7 compliant

    When a Magento shop – or any other PHP application – is required to request data from or transmit data to a remote server, it is often necessary to redirect the traffic through a Http-proxy server. In my case this was essential because the called endpoint allows only certain IP addresses to access the requested ressource. Since I am working from various locations, each having a different IP address, I had to find a way that certain Http-requests are routed through the company’s proxy server. This article is going to describe what a proxy actually does for us, why I cannot and should not use PHP’s environment variables and what the best solution is for scenarios where PSR-7 / PHP-Http in combination with GuzzleHttp might be.

    What about PSR-15?

    Good point! This text will discuss Http middleware components on client side with the example of adding proxy configurations to a request. The relatively new PSR-15 standard handles the server side and focuses mainly on manipulating the response which is sent back to the calling host. Nevertheless you will get a lot of useful information from the standard and the explanation.

    Why don’t you simply use the the HTTP_PROXY environment variable?

    Guzzle indeed accesses the PHP environment variable HTTP_PROXY and routes every request through the specified proxy, if this value is actually configured in php.ini or by using the setenv()-command. But for security reasons this feature is only available when the script is called from the shell (checked using method php_sapi_name()). More information on the vulnerability and the reason why GuzzleHttp declines to use the settings from environment variables can be found at httpoxy.org.

    GuzzleHttp offers a proxy-configuration within the request()-method – why not use this?

    Because it it not PSR-7 compliant! The HTTP message interface simply does not provide a method with name request that allows the developer to pass an options-array in which we could add our proxy-configuration. Instead the interface does only declare a method sendRequest that accepts an object of type \Psr\Http\Message\RequestInterface which also does not offer any proxy configuration.

    Middleware and Handlers

    Middleware components are a powerful – and PSR-7 compliant – way to manipulate Http requests before they’re sent to a remote server.

    In an example scenario where it would be required to send authentication data in each server call, it’d sure be exhausting to update the credentials for each method call all over your application’s code. Adding a middleware layer that automatically adds the necessary data to each request, before it is transmitted, will reduce the code lines to be updated to just a single one (maybe two).

    A middleware is simply a method that resides between the command for sending a request and the actual transmittion. It allows to add, remove or modify data, the target-URL or any other property of the \Psr\Http\Message\RequestInterface as well as additional configuration, that is important for data transfer.

    More information on the PSR-7 standard is given in our in-depth series.

    Create a middleware and add it to the HandlerStack

    The diagram below sketches on the top half the classical way of sending a request: The developer adds a a sendRequest command along with the connection and data settings hold by $request and passes it to a various transmittion mechanism, e.g. GuzzleHttp.

    In the bottom half of the diagram two sample middleware methods m1 and m2 are added to stack that resides between sending the request and the actual transmittion. Both methods are free to modify the request or the configuration hold by $options and are executed one after another, as described by the chain of responsibility, part of the infamous Gang of Four.

    The following implementation has proven to be stable but might be a bit more complex than what is actually necessary to create a middleware method. The reason for that is because of a better test-ability and more flexibility for future add-ons.

    Class ProxyMiddleware

    First step is to create a new class called ProxyMiddleware that is responsible to prepare the concrete middleware method for the handler stack, managed by GuzzleHttp. This class has static method, which holds the proxy settings (URL and port) and will return a closure that will be pushed onto the handler stack later.

    public static function getProxyMiddleware($serverUrl): callable
    {
        self::$proxyUrl = $serverUrl;
    ​
        return function (callable $handler) {
            return new ProxyMiddleware($handler);
        };
    }

    As soon as the closure is called during data transmittion a new instance of the ProxyMiddleware-class is created and invoked by the stack dispatcher. Therefore we need to implement the actual middleware-call inside of the magic __invoke-method:

    public function __invoke(RequestInterface $request, array $options)
    {
       $fn = $this->nextHandler;

       $options['proxy'] = [
           'http'  => self::$proxyUrl,
           'https' => self::$proxyUrl,
      ];

       return $fn($request, $options);
    }

    It’s that simple! We just add the proxy information to the $options-array and pass the new information onto the method described by $nextHandler. That was easy!

    Add our middleware to the handler stack

    After we have created the middleware handler, we need to tell GuzzleHttp to actually execute it after the sendRequest-command is executed. For this second step we need to instantiate a new HandlerStack-object using the class’ static create-method.

    $handlerStack = \GuzzleHttp\HandlerStack::create();
    $handlerStack->push(ProxyMiddleware::getProxyMiddleware('proxy_url'), 'middleware_id');

    $options['handler'] = $handlerStack;

    $httpClient = \Http\Adapter\Guzzle6\Client::createWithConfig($options);

    Now we simply push the previously described closure onto the stack together with an identifier-string. Allthough this ID is optional, it will be more than helpful for debugging your application, since Guzzle adds a couple of default middlewares to the stack and you would end up in a unmanageable mess.

    After we have created and configured the handler stack, we simply use the static createWithConfig-method that allows us to inject a options-array into the instantiation of a new Guzzle PSR-7 compliant Http client.

    From now on, all requests that are sent using this Http client will be transmitted with your middleware executed inbetween and having the proxy setting configured!

    Caveat: Unit Testing

    Testing your new middleware-class might be a little bit tricky. I just want you to know that closures, like the class member $nextHandler can be mocked using PHP’s pre-defined core class \stdClass and the magic __invoke method. In case you’re using PHPUnit, the following snippet might be helpful:

    $nextHandlerMock = $this->createPartialMock(\stdClass::class, ['__invoke']);

    More information

  • How to add alternative HTTP headers to Magento 2?

    If you have more than one frontend server running in your business, it’s needed to load balance the traffic between the nodes. In this case we have a new instance between the browser and the web-server.

    Often it’s a system like HAProxy or Varnish.

    Simple load balancer/proxy setup

    If the load balancer or proxy receives a request from a browser, it forwards it to backend server in the internal network. The IP address of the client is than added to a forward header which contains the IP address of a forward chain.

    Example:

    X-Forwarded-For: client, proxy1, proxy2

    In some situation i.e. for GEO-IP checks, you need the real IP address of a client. If you don’t configure Magento the remote address is always 127.0.0.1.

    That’s not what we want. We need the first part of the comma separated list of the IP chain. Magento offers us a mechanism to solve this issue.

    Remote Address

    The key to solve the issue is the class Magento\Framework\HTTP\PhpEnvironment\RemoteAddress which is provided by the Magento 2 framework.

    In Magento 2 it’s prohibited to call the $_SERVER['REMOTE_ADD'] directly. The RemoteAddress class is a wrapper to deal with the remote address.

    The correct way to get the remote address is this:
    use Magento\Framework\HTTP\PhpEnvironment\RemoteAddress;

    use Magento\Framework\HTTP\PhpEnvironment\RemoteAddress;
    
    class MyClass
    {
        /**
         * @var RemoteAddress
         */
        private $remoteAddress;
    
        public function __construct(RemoteAddress $remoteAddress)
        {
            $this->remoteAddress = $remoteAddress;
        }
    
        public function doSomething()
        {
            $ipAddressOfTheClient = $this->remoteAddress->getRemoteAddress();
        }
    }
    

    Configuration

    It’s possible to inject a list of alternative headers to the RemoteAddress class by Dependency Injection. This config isn’t related to any module. It’s a special config for a production server setup.

    It’s important to know that Magento 2 will load any „di.xml“ file from any subfolder of the „app/etc“ folder!

    With this information we can create a subfolder like „app/etc/myproject/di.xml“ with the following content:

    <?xml version="1.0"?>
    <config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    
        <type name="Magento\Framework\HTTP\PhpEnvironment\RemoteAddress">
            <arguments>
                <argument name="alternativeHeaders" xsi:type="array">
                    <item name="x-forwarded-for" xsi:type="string">HTTP_X_FORWARDED_FOR</item>
                </argument>
            </arguments>
        </type>
    </config>

     

    After that Magento will look into the given HTTP header „X-Forwarded-For“. The name of the header is normalized by PHP.
    X-Forwarded-For is available as $_SERVER['HTTP_XFORWARDED_FOR'].

    It’s possible to add more than one header to alternative header list.

    That’s it. Have fun with Magento 2. 🙂

  • Docker: Simplified container mapping for local development

    When you are working with docker on your local machine, you often have to map your local ports to different container and end up in a port-mapping-mess like this:

    • localhost:80 -> Local apache for native stuff
    • localhost:8080 -> Docker container with apache for testing
    • localhost:8100 -> Some sort of dockered WebApp
    • localhost:59924 -> “Yea, well … don’t know, lets check docker process-list …”

    To simplify this mess, we created a little proxy-script (+ environment setup) that will make your life much easier: https://github.com/netz98/docker-router-proxy

    The router proxy will add the ability to dispatch your request based on the container name. To archive this, it adds a special TLD which will be used to determine that we want to call an docker container.

    In our case (default) this will be .dock, but you can choose a different one if you like.

    After configuring the environment (as described within the project description on GitHub) you can simply call your container like this:

    http://my-service-container.dock

    this will be proxied to

    0.0.0.0:8999->80/tcp    my_service_container

    I’m using this script now for several days and it’s absolutely worth the time for setting up the environment. It’s very easy now to navigate through your services and projects just using your browser history.

    I’m very interested what you think about this approach and if you might have found other solutions for this?

    So, please leave a comment!