We have moved our forum to GitHub Discussions. For questions about Phalcon v3/v4/v5 you can visit here and for Phalcon v6 here.

Sending PUT request with content type other than text/html fails

Hi all I am experienceing some weird behaviour on a PUT request (Phalcon 1.3.4)

If I send a body of json data encoded as text/html, I get a response back. If I send a body of json data with Content-Type: application/json I get a null back.

I am not using the micro part here, but a full blown phalcon mvc multi module app.

Here is the controller;

public function updateAction()
    {
        if ($this->request->isPut()) {
            $data = $this->request->getJsonRawBody();
            $this->view->disable();
            $this->response->setContentType('application/json', 'utf-8');
            echo json_encode($data);
        }
    }

Curl Request

curl 'https://test.dev/profile' -v -X PUT -H 'Content-Type: application/json' -d '{"firstName": "AJ","surname": "McKee"}'
> PUT /profile HTTP/1.1
> User-Agent: curl/7.37.1
> Host: test.dev
> Accept: */*
> Content-Type: application/json
> Content-Length: 38
>
* upload completely sent off: 38 out of 38 bytes
< HTTP/1.1 200 OK
< Date: Fri, 17 Apr 2015 11:36:21 GMT
* Server Apache/2.4.7 (Ubuntu) is not blacklisted
< Server: Apache/2.4.7 (Ubuntu)
< X-Powered-By: PHP/5.5.9-1ubuntu4.6
< Content-Length: 4
< Content-Type: application/json; charset=utf-8
<
* Connection #0 to host test.dev left intact

However setting the Content-Type to text/html seems to work

curl 'https://test.dev/profile' -v -X PUT -H 'Content-Type: text/html' -d '{"firstName": "AJ","surname": "McKee"}'
> PUT /profile HTTP/1.1
> User-Agent: curl/7.37.1
> Host: test.dev
> Accept: */*
> Content-Type: text/html
> Content-Length: 38
>
* upload completely sent off: 38 out of 38 bytes
< HTTP/1.1 200 OK
< Date: Fri, 17 Apr 2015 11:43:03 GMT
* Server Apache/2.4.7 (Ubuntu) is not blacklisted
< Server: Apache/2.4.7 (Ubuntu)
< X-Powered-By: PHP/5.5.9-1ubuntu4.6
< Content-Length: 36
< Content-Type: application/json; charset=utf-8
<
* Connection #0 to host test.dev left intact
{"firstName":"AJ","surname":"McKee"}

As you can see the response when setting the content type to text/html is as expected (Echo back what I send for testing).

What is the correct manner to handle multiple content types in this instance. As some requests will undoubtly send the application/json header.

Is this a feature, or bug within Phalcon or PHP?

Any light you can send on, would be great. TIA

It's working for me



15.2k

Hi @Óscar ,

Mmm weird, I'm using full blown MVC multi module project..

Wonder if its because of my routes;

/***********************************************************************************************
 * Profile
 **********************************************************************************************/
$profile = new Group(array('namespace' => 'Application\Profile\Controllers', 'module' => 'profile'));
$profile->addGet('/profile', array('controller' => 'index', 'action' => 'index'))->setName('profile.get');
$profile->addPut('/profile', array('controller' => 'index', 'action' => 'update'))->setName('profile.put');

I am actually stumped as to what may be causing this.



15.2k
Accepted
answer

Ah ha!

I am using bshaffer/oauth2-server-php and the /profile enpoint is emmiting via a dispatcher:beforeExecuteRoute even a check on the validity of the auth tokens. Disable that and all is good again.

May need to override this somehow.



15.2k

For posterity reasons, I am including the class used to override \OAuth2\Request

<?php
namespace App\Oauth;

use OAuth2\Request as Oauth2Request;
use Phalcon\Di\InjectionAwareInterface;

/**
 * Class Request
 * @package App\Oauth
 */
class Request extends Oauth2Request implements InjectionAwareInterface
{
    protected $di;

    public function setDI($dependencyInjector)
    {
        $this->di = $dependencyInjector;
    }

    public function getDI()
    {
        return $this->di;
    }

    /**
     * Get the content from the request, using Phalcon\Request methods instead of using the php://input
     * which makes the content unavailable to Phalcon.
     * 
     * @param bool $asResource
     * @return resource
     */
    public function getContent($asResource = false)
    {
        $this->getDI()->get('logger')->debug(__METHOD__);
        $this->getDI()->get('logger')->debug(json_encode($this->getDI()->get('request')->getJsonRawBody()));
        if (false === $this->content || (true === $asResource && null !== $this->content)) {
            throw new \LogicException('getContent() can only be called once when using the resource return type.');
        }
        if (true === $asResource) {
            $this->content = false;
            return fopen('php://input', 'rb');
        }
        if (null === $this->content) {
            $this->content = $this->getDI()->get('request')->getRawBody();
        }
        return $this->content;
    }

    /**
     * Overrides the create from globals static method in \OAuth2\Request
     *
     * @param \Phalcon\DiInterface $di
     * @return mixed
     */
    public static function createFromGlobals($di)
    {
        $class = __CLASS__;
        $request = new $class($_GET, $_POST, array(), $_COOKIE, $_FILES, $_SERVER);
        $request->setDi($di);
        $contentType = $request->server('CONTENT_TYPE', '');
        $requestMethod = $request->server('REQUEST_METHOD', 'GET');
        if (0 === strpos($contentType, 'application/x-www-form-urlencoded')
            && in_array(strtoupper($requestMethod), array('PUT', 'DELETE'))
        ) {
            parse_str($request->getContent(), $data);
            $request->request = $data;
        } elseif (0 === strpos($contentType, 'application/json')
            && in_array(strtoupper($requestMethod), array('POST', 'PUT', 'DELETE'))
        ) {
            $data = json_decode($request->getContent(), true);
            $request->request = $data;
        }
        return $request;
    }
}

And the plugin thats listening to dispatcher events now changes ever so slightly

<?php
namespace App\Plugin\Oauth;

use Phalcon\Events\Event;
use Phalcon\Mvc\User\Plugin;
use Phalcon\Mvc\Dispatcher;
use OAuth2\Response;
use App\Oauth\Request;
/**
 * Class AuthorizeRequest
 * @package App\Plugin\Oauth
 */
class AuthorizeRequest extends Plugin
{
    public function beforeDispatchLoop(Event $event, Dispatcher $dispatcher)
    {
        /** @var \Oauth2\Server $server */
        $server = $this->getDI()->get('oauth2Service');
        $request = Request::createFromGlobals($this->getDI());
        if (!$server->verifyResourceRequest($request)) {
            $this->getDI()->get('logger')->debug('Denied access to API');
            $this->view->disable();
            $server->getResponse()->send();
            return false;
        }
        // Helper, if the controller inherits our RestFulBaseController, add the token.
        if (method_exists($dispatcher->getActiveController(), 'setOauthToken')) {
            $token = $server->getAccessTokenData($request);
            $dispatcher->getActiveController()->setOauthToken($token);
        }
        return true;
    }
}