Magento 2: Sending a Custom Header/Response from a Controller

First of all, to comply with action controller interface \Magento\Framework\App\ActionInterface::execute(), your action must return an instance of \Magento\Framework\Controller\ResultInterface (\Magento\Framework\App\ResponseInterface is also supported, but is legacy and will be removed in future releases of M2, when all core usages are refactored).

So choose from available implementations of \Magento\Framework\Controller\ResultInterface. The most suitable for custom REST API (assuming it operates with JSON) seems to be \Magento\Framework\Controller\Result\Json. However, if you need something even more custom, consider \Magento\Framework\Controller\Result\Raw.

Working sample:

<?php
namespace VendorName\ModuleName\Controller;

/**
 * Demo of authorization error for custom REST API
 */
class RestAuthorizationDemo extends \Magento\Framework\App\Action\Action
{
    /** @var \Magento\Framework\Controller\Result\JsonFactory */
    protected $jsonResultFactory;

    public function __construct(
        \Magento\Framework\App\Action\Context $context,
        \Magento\Framework\Controller\Result\JsonFactory $jsonResultFactory
    ) {
        parent::__construct($context);
        $this->jsonResultFactory = $jsonResultFactory;
    }

    public function execute()
    {
        /** @var \Magento\Framework\Controller\Result\Json $result */
        $result = $this->jsonResultFactory->create();
        /** You may introduce your own constants for this custom REST API */
        $result->setHttpResponseCode(\Magento\Framework\Webapi\Exception::HTTP_FORBIDDEN);
        $result->setData(['error_message' => __('What are you doing here?')]);
        return $result;
    }
}

The code above will result in response with HTTP status code 403 and body {"error_message":"What are you doing here?"}


Magento 2 provides a Magento\Framework\App\ResponseInterface abstract type for you to instantiate via the dependency injection. Object manager used below for simplicity's sake.

/** @var \Magento\Framework\App\ObjectManager $om */
$om = \Magento\Framework\App\ObjectManager::getInstance();
/** @var     \Magento\Framework\App\ResponseInterface|\Magento\Framework\App\Response\Http $response */
$response = $om->get('Magento\Framework\App\ResponseInterface');
$response->setHeader('<header name>', '<header value>', $overwriteExisting = true);

For example

$response->setHeader('Content-Transfer-Encoding', 'binary', true);

Status headers are considered a special case -- you'll want to use the setStatusCode message here. Also, controller's have access to a getResponse message, so you can avoid the messy business of injecting on a controller

public function execute()
{      
    $this->getResponse()
        ->setStatusCode(\Magento\Framework\App\Response\Http::STATUS_CODE_403)
        ->setContent('Error');
    return ;
}

In Magento 2.1 and above, easiest way is to use the result factory:

public function execute()
{
    $resultPage = $this->resultFactory
        ->create(ResultFactory::TYPE_JSON)
        ->setHeader("myheaderkey", "value")
        ->setHttpResponseCode(403);

    return $resultPage;
}

The ResultFactory is already injected into your controller because of inheritance of \Magento\Framework\App\Action\Action.

Moreover, you're programming against Magento\Framework\Controller\ResultInterface, which is the expected (or recommended) return type of a controller.