Magento 2 REST API Response Formats

Published: March 28, 2017

Tags:

The Magento 2 REST API can return responses in XML or JSON format. This is done based on the Accept HTTP Header. Per the developer documentation..

HTTP headers

Accept

Optional. Specifies the format of the response body. Default is JSON.

Accept: application/FORMAT

Where FORMAT is either JSON or XML.

If you omit this header, the response is returned in JSON format.

http://devdocs.magento.com/guides/v2.0/get-started/gs-web-api-request.html#http-headers

Here, I’ll dive into the code, to investigate how this works…

Passing The Data To The Renderer

NOTE: This blog post is based on the Magento v2.1.5 code base

As mentioned in Magento 2 REST API method return processing, REST requests are handled by Magento\Webapi\Controller\Rest::processApiRequest(). To understand how the response format is negotiated, we need to start with that last line of that method…

$this->_response->prepareResponse($outputData);

$_response is an instance of Magento\Framework\Webapi\Rest\Response. We can see that prepareResponse passes the data to the protected _render method…

public function prepareResponse($outputData = null)
{
    $this->_render($outputData);
    if ($this->getMessages()) {
        $this->_render(['messages' => $this->getMessages()]);
    }
    return $this;
}

Then, _render passes the data to an implementation of Magento\Framework\Webapi\Rest\Response\RendererInterface calling the renderer’s render method…

protected function _render($data)
{
    $mimeType = $this->_renderer->getMimeType();
    $body = $this->_renderer->render($data);
    $this->setMimeType($mimeType)->setBody($body);
}

Resolving The Renderer

Magento resolves the renderer by calling get on Magento\Framework\Webapi\Rest\Response\RendererFactory.

public function __construct(
    \Magento\Framework\Webapi\Rest\Response\RendererFactory $rendererFactory,
    \Magento\Framework\Webapi\ErrorProcessor $errorProcessor,
    \Magento\Framework\App\State $appState
) {
    $this->_renderer = $rendererFactory->get();
    $this->_errorProcessor = $errorProcessor;
    $this->_appState = $appState;
}

get calls _getRendererClass to resolve the class name, and then uses the object manager to get an instance.

$renderer = $this->_objectManager->get($this->_getRendererClass());

_getRendererClass is where the logic to resolve the renderer for the response lives. Here is the entire method…

/**
 * Find renderer which can render response in requested format.
 *
 * @return string
 * @throws \Magento\Framework\Webapi\Exception
 */
protected function _getRendererClass()
{
    $acceptTypes = $this->_request->getAcceptTypes();
    if (!is_array($acceptTypes)) {
        $acceptTypes = [$acceptTypes];
    }
    foreach ($acceptTypes as $acceptType) {
        foreach ($this->_renders as $rendererConfig) {
            $rendererType = $rendererConfig['type'];
            if ($acceptType == $rendererType || $acceptType == current(
                explode('/', $rendererType)
            ) . '/*' || $acceptType == '*/*'
            ) {
                return $rendererConfig['model'];
            }
        }
    }
    /** If server does not have renderer for any of the accepted types it SHOULD send 406 (not acceptable). */
    throw new \Magento\Framework\Webapi\Exception(
        new Phrase(
            'Server cannot match any of the given Accept HTTP header media type(s) from the request: "%1" '.
            'with media types from the config of response renderer.',
            $acceptTypes
        ),
        0,
        \Magento\Framework\Webapi\Exception::HTTP_NOT_ACCEPTABLE
    );
}

The most important line is here…

$acceptTypes = $this->_request->getAcceptTypes();

From this we can assume that Magento uses the Accept HTTP header to resolve the appropriate renderer for the response (check Magento\Framework\Webapi\Rest\Request::getAcceptTypes() for the exact implementation).

In Magento_Webapi’s di.xml file we can see a list of available renderers…

<type name="Magento\Framework\Webapi\Rest\Response\RendererFactory">
    <arguments>
        <argument name="renders" xsi:type="array">
            <item name="default" xsi:type="array">
                <item name="type" xsi:type="string">*/*</item>
                <item name="model" xsi:type="string">Magento\Framework\Webapi\Rest\Response\Renderer\Json</item>
            </item>
            <item name="application_json" xsi:type="array">
                <item name="type" xsi:type="string">application/json</item>
                <item name="model" xsi:type="string">Magento\Framework\Webapi\Rest\Response\Renderer\Json</item>
            </item>
            <item name="text_xml" xsi:type="array">
                <item name="type" xsi:type="string">text/xml</item>
                <item name="model" xsi:type="string">Magento\Framework\Webapi\Rest\Response\Renderer\Xml</item>
            </item>
            <item name="application_xml" xsi:type="array">
                <item name="type" xsi:type="string">application/xml</item>
                <item name="model" xsi:type="string">Magento\Framework\Webapi\Rest\Response\Renderer\Xml</item>
            </item>
            <item name="application_xhtml_xml" xsi:type="array">
                <item name="type" xsi:type="string">application/xhtml+xml</item>
                <item name="model" xsi:type="string">Magento\Framework\Webapi\Rest\Response\Renderer\Xml</item>
            </item>
        </argument>
    </arguments>
</type>

As you can see, out-of-box, Magento ships with JSON and XML renderers.

Demo

Here’s a quick demo showing how the Accept header can be used to specify format…

text/xml
$ curl -H "Accept: text/xml" http://example.com/rest/V1/mpchadwick_helloapi/hello
<?xml version="1.0"?>
<response>
  <item>
    <foo>bar</foo>
    <hello>world</hello>
  </item>
</response>
application/json
$ curl -H "Accept: application/json" http://example.com/rest/V1/mpchadwick_helloapi/hello
[{"foo":"bar","hello":"world"}]

If no Accept header is sent, Magento will send a JSON response…

$ curl http://example.com/rest/V1/mpchadwick_helloapi/hello
[{"foo":"bar","hello":"world"}]

Conclusion

If you have any questions or comments, feel free to drop a note below, or, as always, you can reach me on Twitter as well.

Hi, I'm Max!

I'm a software developer who mainly works in PHP, but also dabbles in Ruby and Go. Technical topics that interest me are monitoring, security and performance.

During the day I solve challenging technical problems at Something Digital where I mainly work with the Magento platform. I also blog about tech, work on open source and hunt for bugs.

If you'd like to get in touch with me the best way is on Twitter.