drahak / Restful

Drahak\Restful - Nette REST API bundle
0 stars 0 forks source link

Crash when sending OPTIONS method on CrudRoute #106

Closed Salamek closed 7 years ago

Salamek commented 8 years ago

Hi i have simple CrudRoute API

Router:

$router[] = $restApi = new RouteList('RestApi');
$restApi[] = new CrudRoute('api/1.0/<module>/<presenter>[/<id>]', 'User');

Everything works as expected, but when i try to call OPTIONS on any REST url (AJAX call do that from node) i get "Method not supported. Available methods: POST, GET, PUT, PATCH, DELETE". But it should return only header (200 code) with Allowed options for specified url (POST, GET, PUT, PATCH, DELETE for Crud) like this:

HTTP/1.1 200 OK
Server: nginx/1.8.1
Date: Thu, 21 Apr 2016 16:09:29 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked
Connection: keep-alive
X-Frame-Options: SAMEORIGIN
X-Powered-By: Nette Framework
Allow: POST, GET, PUT, PATCH, DELETE
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Set-Cookie: PHPSESSID=something; path=/

Some doc to OPTIONS https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html

I had to ughly hotfix it for now by modifying (deadline is ... passed 14days ago, i dont have time to implement this properly and send PR, sorry):

protected function checkAllowedMethods()
    {
        $availableMethods = $this->methods->getOptions($this->request->getUrl());
        if (!$availableMethods || in_array($this->request->method, $availableMethods)) {
            return;
        }

        $allow = implode(', ', $availableMethods);
        $this->response->setHeader('Allow', $allow);
/*
        $this->response->setHeader('Access-Control-Allow-Origin', '*');
        $this->response->setHeader('Access-Control-Allow-Methods', $allow);
        $this->response->setHeader('Access-Control-Allow-Headers', 'X-HTTP-AUTH-TOKEN');
*/

        if ($this->request->method != 'OPTIONS')
        {
            throw BadRequestException::methodNotSupported(
                'Method '.$this->request->method.' not supported. Available methods: ' . $allow);
        }
        else
        {
            exit();
        }
    }

PS: and would be great to have option to configure Access-Control-Allow headers somewhere... so we can call rest api from JS directly (JSONP is not a good solution when we can use pure AJAX)

marten-cz commented 8 years ago

@Salamek I'm not using CrudRouter but ResourceRoute which is parent. You can pass OPTIONS as a flag (third parameter)

$restApi[] = new CrudRoute('api/1.0/<module>/<presenter>[/<id>]', 'User', CrudRoute::CRUD | CrudRoute::OPTIONS);

If that will work for you, it can be better solution than rewriting extension.

Salamek commented 7 years ago

@marten-cz that wont work, cos ResourceRoute::$actionDictionary is overwriten in CrudRoute after parent::__construct():

public function __construct($mask, $metadata = array(), $flags = IResourceRouter::CRUD)
    {
        if (is_string($metadata) && count(explode(':', $metadata)) === 1) {
            $metadata .= ':default';
        }
                parent::__construct($mask, $metadata, $flags);
        $this->actionDictionary = array(
            IResourceRouter::POST => self::ACTION_CREATE,
            IResourceRouter::GET => self::ACTION_READ,
            IResourceRouter::PUT => self::ACTION_UPDATE,
            IResourceRouter::PATCH => self::ACTION_PATCH,
            IResourceRouter::DELETE => self::ACTION_DELETE
        );
    }

i implemented my own CrudRoute and changed it to this to make it work properly:

public function __construct($mask, $metadata = array(), $flags = IResourceRouter::CRUD)
    {
        if (is_string($metadata) && count(explode(':', $metadata)) === 1) {
            $metadata .= ':default';
        }
                parent::__construct($mask, $metadata, $flags);

        $actions = array(
            IResourceRouter::POST => self::ACTION_CREATE,
            IResourceRouter::GET => self::ACTION_READ,
            IResourceRouter::PUT => self::ACTION_UPDATE,
            IResourceRouter::PATCH => self::ACTION_PATCH,
            IResourceRouter::DELETE => self::ACTION_DELETE,
        );

        foreach($actions AS $resource => $action)
        {
            $this->actionDictionary[$resource] = $action;
        }
    }

I will create PR if it passes unittests