·

photo credit: sjunnesson Traces of Touch Inputs via photopin (license)

Add a Custom Renderer to the Webapi of Magento 2

Tags: ,

So I recently asked an interesting question on Magento Stack Exchange, because I was running in a problem:
I was given the task to export a plain TXT list with SKU’s and a status of some sort. I wanted to be cool and learn something new so I decided to utilize Magentos’ web API functionality for this. How cool would that be? Just call the API and get the result you expect.
However… I bumped into one of the many hidden features of Magento 2. It turns out, that when you’re using the REST API, Magento looks at the Accept-header you’ve sent in your request to determine what the resulted outcome should be.
For instance, if I would send my request with Accept: application/json, I would get a JSON result, but if I would send my request with Accept: application/xml, I would get an XML result. This – of course – is a very cool and useful feature of Magento 2 (and besides that, it’s a general aspect on how a REST API is supposed to work), but in my case I wanted always to force my output to be text/plain.
After a little while of trial and error I got it to work. In this article I’ll share with you how exactly I did that.

Step 1: Add a new renderer

Magento 2 comes by default with 2 types of renderers:

  • JSON
  • XML

You can see this when you open the etc/di.xml from the web api module:

You can see that by default, 5 mime-types are registered and assigned to 2 different types of renderers:

  • */* (which is a wildcard) maps to the JSON renderer.
  • application/json maps to JSON.
  • text/xml maps to XML.
  • application/xml maps to XML.
  • application/xhtml+xml maps to XML.

What’s missing from this list, is the mime-type for plain text (text/plain) and the appropriate renderer. So let’s add these to our custom module. In our etc/di.xml we can add our own renderers to the RendererFactory like so:

This registers a new type of renderer to the RendererFactory. We register it for the MIME Type text/plain, and add our own customer renderer model to it (Vendor\Module\Webapi\Rest\Response\Renderer\Txt).
Now, how complex it might seem, a renderer for the web API is actually a very simple thing. It only has one requirement: it should implement Magento\Framework\Webapi\Rest\Response\RendererInterface. So this is what I came up with:

Now, this works when I explicitly set my Accept-header to text/plain. But by default, a browser doesn’t ask for text/plain. In fact, if you look at the default request headers sent by Chrome you’ll see the following headers:

Now if you take a look at \Magento\Framework\Webapi\Rest\Response\RendererFactory::_getRendererClass(), you’ll see that this method iterates over all Accept-types and returns the model as soon as there’s a match. So in this example the first match with Magento 2 will be application/xhtml+xml, which maps to the XML renderer (as we’ve seen above).
But in my case I want to explicitly set the response header to text/plain. No matter what the Accept-header wants. So how can I do this? Well, we’ve seen how the renderer is determined: it looks at the the Accept-header in my request. So where does Magento read this Accept-header anyway? Well, just look at the first line of
_getRendererClass():

So there is a method called getAcceptTypes(), and if we look at where it’s declared
(Magento\Framework\Webapi\Rest\Request::getAcceptTypes()) we can see that it’s a public method. Yay! Now we all know what we can do! Write a plugin and modify the outcome to our bidding!
In our etc/di.xml, add the plugin:

And in our plugin, check for the right conditions that determine whether we should force our response to be text/plain:

Now we’re all set! If we make an API request to /rest/V1/export/txt, our Accept-headers are forced to
text/plain, which makes sure that the _getRendererClass() picks our freshly added txt-renderer from Magentos’ RendererFactory. It all just adds up.

Edit:
I just ran into an issue with Magento on how the order of arguments is handled with dependency injection. To solve this, you need to add the default renderer as well to your custom module. So your di.xml will look like this:

It’s a bit overhead, but it’ll make sure that the */* Accept-header falls back to Magentos’ default renderer.

Visitors give this article an average rating of 3.7 out of 5.

How would you rate this article?

Leave a Reply