Keeping Magento satisfied behind Varnish

Working at Karlsson & Lord involves alot of new experiences and ways of solving technical problems for online retailers. This week I was fiddling around with a Magento module called Turpentine that helps out when serving your front end via Varnish.

Internal Redirects

The one thing which struck me when configuring my local environment to use Varnish in front of Nginx was that application redirects started acting wierd. Adding a product for comparision from a product view redirected me to the base URL, instead of back to the product view (referer). So I started digging into the core to see what actually going on.

app/code/core/Mage/Core/Controller/Varien/Action.php:773

The _getRefererUrl method looks for any referer URL in the request URI or from request headers. For some reason the $refererUrl did not pass as _isUrlInternal which makes it fall back to the base URL.

/**
 * Identify referer url via all accepted methods (HTTP_REFERER, regular or base64-encoded request param)
 *
 * @return string
 */
protected function _getRefererUrl()
{
    $refererUrl = $this->getRequest()->getServer('HTTP_REFERER');
    if ($url = $this->getRequest()->getParam(self::PARAM_NAME_REFERER_URL)) {
        $refererUrl = $url;
    }
    if ($url = $this->getRequest()->getParam(self::PARAM_NAME_BASE64_URL)) {
        $refererUrl = Mage::helper('core')->urlDecode($url);
    }
    if ($url = $this->getRequest()->getParam(self::PARAM_NAME_URL_ENCODED)) {
        $refererUrl = Mage::helper('core')->urlDecode($url);
    }

    $refererUrl = Mage::helper('core')->escapeUrl($refererUrl);

    if (!$this->_isUrlInternal($refererUrl)) {
        $refererUrl = Mage::app()->getStore()->getBaseUrl();
    }
    return $refererUrl;
}

app/code/core/Mage/Core/Controller/Varien/Action.php:799

Here Magento’s looking for the position of the (base URL) string "http://domain.com/" in (the referer URL) "http://domain.com:8080/any/url.html", which will never be found because of the port number Nginx was listening on (behind Varnish).

/**
 * Check url to be used as internal
 *
 * @param   string $url
 * @return  bool
 */
protected function _isUrlInternal($url)
{
    if (strpos($url, 'http') !== false) {
        /**
         * Url must start from base secure or base unsecure url
         */
        if ((strpos($url, Mage::app()->getStore()->getBaseUrl()) === 0)
            || (strpos($url, Mage::app()->getStore()->getBaseUrl(Mage_Core_Model_Store::URL_TYPE_LINK, true)) === 0)
        ) {
            return true;
        }
    }
    return false;
}

app/code/core/Mage/Core/Helper/Url.php:37

That port number is (for some odd reason) added when building the current URL in Magento.

/**
 * Retrieve current url
 *
 * @return string
 */
public function getCurrentUrl()
{
    $request = Mage::app()->getRequest();
    $port = $request->getServer('SERVER_PORT');
    if ($port) {
        $defaultPorts = array(
            Mage_Core_Controller_Request_Http::DEFAULT_HTTP_PORT,
            Mage_Core_Controller_Request_Http::DEFAULT_HTTPS_PORT
        );
        $port = (in_array($port, $defaultPorts)) ? '' : ':' . $port;
    }
    $url = $request->getScheme() . '://' . $request->getHttpHost() . $port . $request->getServer('REQUEST_URI');
    return $url;
}

Getting the redirects working again

Without altering the application, your webserver (Nginx) needs to be listening on port 80 (or 443) while still keeping Varnish in front of it. This can be accomplished by using iptables.