Categories
Tutorials

Duplicate Content-Type header on Magento 1 API responses

Getting HTTP 500 errors with the message “duplicate header ‘Content-Type'” when using Magento 1 XmlRpc API? Here is how to fix them.

Last Updated on 2020-12-10 by aeno

Getting HTTP 500 errors with the message “duplicate header ‘Content-Type'” when using Magento 1 XmlRpc API? Here is how to fix them.

Beside the traditional way of using scripts to control Magento (for importers or fulfillment interfaces), you can also do so using Magento’s API. When using the XmlRpc API, you might get a surprise – depending on your web server configuration.

Reproducing the error

First, we check if our API responds by quickly opening http://www.yoursite.com/api/xmlrpc. You get the following response:

<?xml version="1.0" encoding="UTF-8"?>
<methodResponse>
    <fault>
        <value>
            <struct>
                <member>
                    <name>faultCode</name>
                    <value>
                        <int>630</int>
                    </value>
                </member>
                <member>
                    <name>faultString</name>
                    <value>
                        <string>Unable to read request</string>
                    </value>
                </member>
            </struct>
        </value>
    </fault>
</methodResponse>
Code language: HTML, XML (xml)

That’s alright, the message “Unable to read request” reminds us that we did not yet send some data in our request. On to the second check: logging in. So we send a POST request to our API endpoint at http://www.yoursite.com/api/xmlrpc containing a call to the method login along with our credentials. If everything works, we should get a session key.

This is our request payload:

<methodCall>
    <methodName>login</methodName>
    <params>
        <param>
            <value>
                <string>user</string>
            </value>
        </param>
        <param>
            <value>
                <string>password</string>
            </value>
        </param>
    </params>
</methodCall>
Code language: HTML, XML (xml)

And the response might be:

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>500 Internal Server Error</title>
</head><body>
<h1>Internal Server Error</h1>
<p>The server encountered an internal error or
misconfiguration and was unable to complete
your request.</p>
<p>Please contact the server administrator, 
    and inform them of the time the error occurred,
and anything you might have done that may have
caused the error.</p>
<p>More information about this error may be available
in the server error log.</p>
</body></html>Code language: HTML, XML (xml)

Our server tipped his hat along with a nice error 500 – great. A quick look at the web server’s error log should clarify this for us:

... FastCGI: comm with server "/usr/lib/cgi-bin/php5-fcgi" aborted: error parsing headers: duplicate header 'Content-Type'Code language: plaintext (plaintext)

Oh, really? Magento’s backend are frontend working fine, though. So Magento serves us with a duplicate header when we want to log in on it’s XmlRpc interface.

Finding the cause

Let’s dive deep into request processing.

Asking Google seems to lead to a solution. You’ll read about the class Mage_Core_Controller_Response_Http in app/code/core/Mage/Core/Controller/Response/Http.php, because there is a condition in line 56 that checks whether the calling server API (SAPI) begins with the letters cgi.

public function sendHeaders()
{
    if (!$this->canSendHeaders()) {
        Mage::log('HEADERS ALREADY SENT: '.mageDebugBacktrace(true, true, true));
        return $this;
    }
 
    if (substr(php_sapi_name(), 0, 3) == 'cgi') {
        $statusSent = false;
        foreach ($this->_headersRaw as $i=>$header) {
            if (stripos($header, 'status:')===0) {
                if ($statusSent) {
                    unset($this->_headersRaw[$i]);
                } else {
                    $statusSent = true;
                }
            }
        }
        foreach ($this->_headers as $i=>$header) {
            if (strcasecmp($header['name'], 'status')===0) {
                if ($statusSent) {
                    unset($this->_headers[$i]);
                } else {
                    $statusSent = true;
                }
            }
        }
    }
    return parent::sendHeaders();
}
Code language: PHP (php)

In this example the SAPI is FastCGI. We don’t want to change this code’s behaviour as it is library code. And even if we would, the problem would persist (try it if you don’t believe me 😉 )

Disabling headers by commenting out line 77 would be bad idea. Magento would (mostly) work, but some Ajax requests might fail and we couldn’t log into the backend. So no fooling around with our headers.

The actual root of all evil hides itself. It’s the class Zend_XmlRpc_Response_Http in lib/Zend/XmlRpc/Response/Http.php.

class Zend_XmlRpc_Response_Http extends Zend_XmlRpc_Response
{
    /**
     * Override __toString() to send HTTP Content-Type header
     *
     * @return string
     */
    public function __toString()
    {
        if (!headers_sent()) {
            header('Content-Type: text/xml; charset=' . strtolower($this->getEncoding()));
        }
 
        return parent::__toString();
    }
}
Code language: PHP (php)

The method __toString() does not use Magento’s header-manipulating function but instead goes the direct way (because it is library code, of course).

__toString() gets called on API requests by sendHeaders() of Mage_Core_Controller_Response_Http, and so the headers used by Magento will be set once again.

Almost all Content-Type headers won’t be set with the overwrite flag by Magento.

What we need to do is: set Content-Type with an overwrite flag on XmlRpc-Requests.

Fixing it

The actual place where the “redundant” Content-Type header gets set is Mage_Api_Model_Server_Adapter_Xmlrpc::run() in app/code/core/Mage/Api/Model/Server/Adapter/Xmlrpc.php

public function run()
{
    $apiConfigCharset = Mage::getStoreConfig("api/config/charset");
 
    $this->_xmlRpc = new Zend_XmlRpc_Server();
    $this->_xmlRpc->setEncoding($apiConfigCharset)
        ->setClass($this->getHandler());
    $this->getController()->getResponse()
        ->clearHeaders()
        ->setHeader('Content-Type','text/xml; charset='.$apiConfigCharset)
        ->setBody($this->_xmlRpc->handle());
    return $this;
}
Code language: PHP (php)

We can simply rewrite this model in our own extension and fix it’s method run() accordingly. All the magic is done by an additional boolean argument true:

->setHeader('Content-Type','text/xml; charset='.$apiConfigCharset, true)Code language: PHP (php)

And then, everything’s working fine 😉

We can now see the right response to our login request:

<?xml version="1.0" encoding="UTF-8"?>
<methodResponse>
    <params>
        <param>
            <value>
                <string>b32103d9c93707016a055aaace8fb294</string>
            </value>
        </param>
    </params>
</methodResponse>Code language: HTML, XML (xml)

You can find a ready-made module with all the necessary changes on GitHub: https://github.com/MageGyver/ApiImprovements

2 replies on “Duplicate Content-Type header on Magento 1 API responses”

Leave a Reply

Your email address will not be published. Required fields are marked *