Problem/Motivation

I have just updated Drupal to the newest version. I was working with Drupal version 8.2.6. REST endpoints and all configuration are the same as before. Since the update REST is not working anymore. I have tried the following methods:

  • GET: is still working fine
  • POST: is not working anymore and Apache error log file is showing this message: Uncaught PHP Exception Symfony\\Component\\Routing\\Exception\\MethodNotAllowedException: "" at /core/lib/Drupal/Core/Routing/MethodFilter.php line 47
  • PATCH: is not working anymore and error log file is showing this message: Uncaught PHP Exception Symfony\\Component\\Routing\\Exception\\MethodNotAllowedException: "" at /core/lib/Drupal/Core/Routing/Router.php line 158

Comments

remram created an issue. See original summary.

cilefen’s picture

Title: Since updating to Drupal v8.3.2 REST is not working anymore » MethodNotAllowedException using POST or PATCH with REST on 8.3.2
Priority: Normal » Major
Issue tags: -REST, -post, -patch
dawehner’s picture

@remram
It would be really nice if you could provide an example of the HTTP request you are sending here, maybe for example your line of CURL code/JS.

remram’s picture

Hi Daniel,

I have custom entities and have activated the REST endpoint configurations by using the REST UI module. As I mentioned, I didn't make any changes except updating Drupal from 8.2.6 to 8.3.2 by using composer.

Back to your question. I have made a test POST to my local system in the following way.

  • Endpoint: /entity/country?_format=json
  • Authorization: Basic cmVtcmFtOnJlbXJhbXBhc3M=
  • Content-Type: application/json
  • X-CSRF-Token: npCXrLNMiWd1igzqV3dBzI6-hiAlcLrox5e1JfTbpp3

and this is the body:

{
  "name":[{"value":"test"}]
}

Only the field name is required. I hope these information will help. If you have question, please let me know. Thank you for the help.

wim leers’s picture

Issue tags: +BC break

Is there any chance you could share your @RestResource plugin code? That'd make this much easier to debug.

P.S.: You only need the X-CSRF-Token request header if you're using Cookie authentication :)

remram’s picture

I'm using a chrome plugin for doing REST calls. This one I'm using right now: https://chrome.google.com/webstore/detail/restlet-client-rest-api-t/aejo...

Thank you very much for the hint about cookies :)

remram’s picture

And the config on DB side is:
name: rest.resource.entity.country
data: a:8:{s:4:"uuid";s:36:"513efa5c-50be-456e-ac77-ba1585b4d7ac";s:8:"langcode";s:2:"en";s:6:"status";b:1;s:12:"dependencies";a:1:{s:6:"module";a:5:{i:0;s:10:"basic_auth";i:1;s:7:"country";i:2;s:3:"hal";i:3;s:13:"serialization";i:4;s:12:"simple_oauth";}}s:2:"id";s:14:"entity.country";s:9:"plugin_id";s:14:"entity:country";s:11:"granularity";s:6:"method";s:13:"configuration";a:4:{s:3:"GET";a:2:{s:17:"supported_formats";a:2:{i:0;s:8:"hal_json";i:1;s:4:"json";}s:14:"supported_auth";a:2:{i:0;s:10:"basic_auth";i:1;s:6:"oauth2";}}s:4:"POST";a:2:{s:17:"supported_formats";a:2:{i:0;s:8:"hal_json";i:1;s:4:"json";}s:14:"supported_auth";a:2:{i:0;s:10:"basic_auth";i:1;s:6:"oauth2";}}s:6:"DELETE";a:2:{s:17:"supported_formats";a:2:{i:0;s:8:"hal_json";i:1;s:4:"json";}s:14:"supported_auth";a:2:{i:0;s:10:"basic_auth";i:1;s:6:"oauth2";}}s:5:"PATCH";a:2:{s:17:"supported_formats";a:2:{i:0;s:8:"hal_json";i:1;s:4:"json";}s:14:"supported_auth";a:2:{i:0;s:10:"basic_auth";i:1;s:6:"oauth2";}}}}

wim leers’s picture

#7: thanks, but that's not what I asked.

I didn't ask for a REST resource config entity (and that'd be far better in YAML form, which you can get by exporting that config). I asked for the PHP code of the REST resource plugin. With just the configuration, I cannot reproduce this.

Note that this pretty much must be something about your custom REST resource plugin, because POSTing and PATCHing using core's REST resource plugins works fine, we know that for certain thanks to our functional test coverage.

remram’s picture

Oh sorry for the misunderstanding... I didn't create any REST resources. As I understood, I just need to activate the web services part and the permission to the user. Do I need to create custom REST resources? Thank you for the help.

remram’s picture

I have created a custom RestResponse. It works for me now. But I still have the question: Why I was able to make REST request before the update of Drupal without this custom RestResponse?


namespace Drupal\country\Plugin\rest\resource;

use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\rest\Plugin\rest\resource\EntityResourceValidationTrait;
use Drupal\rest\ResourceResponse;
use Symfony\Component\HttpFoundation\Request;
use Drupal\Core\Entity\EntityStorageException;
use Drupal\rest\Plugin\ResourceBase;
use Psr\Log\LoggerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\rest\ModifiedResourceResponse;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\HttpException;


/**
 * Provides a resource to get view modes by entity and bundle.
 *
 * @RestResource(
 *   id = "country_rest_resource",
 *   label = @Translation("Country rest resource"),
 *   uri_paths = {
 *     "canonical" = "/admin/structure/country/{entity}",
 *     "https://www.drupal.org/link-relations/create" = "/admin/structure/country"
 *   }
 * )
 */
class CountryRestResource extends ResourceBase {

  use EntityResourceValidationTrait;

  /**
   * A current user instance.
   *
   * @var \Drupal\Core\Session\AccountProxyInterface
   */
  protected $currentUser;

  /**
   * A current entity type manager interface
   *
   * @var EntityTypeManagerInterface
   */
  protected $entityTypeManager;

  /**
   * Constructs a new CountryRestResource object.
   *
   * @param array $configuration
   *   A configuration array containing information about the plugin instance.
   * @param string $plugin_id
   *   The plugin_id for the plugin instance.
   * @param mixed $plugin_definition
   *   The plugin implementation definition.
   * @param array $serializer_formats
   *   The available serialization formats.
   * @param \Psr\Log\LoggerInterface $logger
   *   A logger instance.
   * @param \Drupal\Core\Session\AccountProxyInterface $current_user
   *   A current user instance.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   A current entity type manager interface
   */
  public function __construct(
    array $configuration,
    $plugin_id,
    $plugin_definition,
    array $serializer_formats,
    LoggerInterface $logger,
    AccountProxyInterface $current_user,
    EntityTypeManagerInterface $entity_type_manager) {
    parent::__construct($configuration, $plugin_id, $plugin_definition, $serializer_formats, $logger);

    $this->currentUser = $current_user;
    $this->entityTypeManager = $entity_type_manager;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
    return new static(
      $configuration,
      $plugin_id,
      $plugin_definition,
      $container->getParameter('serializer.formats'),
      $container->get('logger.factory')->get('country'),
      $container->get('current_user'),
      $container->get('entity_type.manager')
    );
  }

  /**
   * Responds to POST requests.
   *
   * Returns a list of bundles for specified entity.
   *
   * @throws \Symfony\Component\HttpKernel\Exception\HttpException
   *   Throws exception expected.
   *
   * @param array $data
   * @param \Symfony\Component\HttpFoundation\Request $request
   * @return \Drupal\rest\ResourceResponse
   */
  public function post(array $data, Request $request) {

    // You must to implement the logic of your REST Resource here.
    // Use current user after pass authentication to validate access.
    if (!$this->currentUser->hasPermission('access content')) {
      throw new AccessDeniedHttpException();
    }

    $pluginDefinition = $this->getPluginDefinition();

    if(isset($pluginDefinition['provider'])) {
      $entity = $this->entityTypeManager->getStorage($pluginDefinition['provider'])->create($data);
      $this->validate($entity);

      try {
        $entity->save();
        $this->logger->notice('Created entity %type with ID %id.', ['%type' => $entity->getEntityTypeId(), '%id' => $entity->id()]);

        // 201 Created responses return the newly created entity in the response
        // body. These responses are not cacheable, so we add no cacheability
        // metadata here.
        $headers = [];
        if (in_array('canonical', $entity->uriRelationships(), TRUE)) {
          $url = $entity->toUrl('canonical', ['absolute' => TRUE])->toString(true);
          $headers['Location'] = $url->getGeneratedUrl();
        }
        return new ModifiedResourceResponse($entity, 201, $headers);
      }
      catch (EntityStorageException $e) {
        throw new HttpException(500, 'Internal Server Error', $e);
      }
    }

    return new ResourceResponse('Data provider was missing. No data has been added.', 500);
  }
}
wim leers’s picture

Category: Bug report » Support request
Priority: Major » Normal
Status: Active » Postponed (maintainer needs more info)
Issue tags: +Needs steps to reproduce

D'oh, sorry, I missed that you were actually using a custom entity type! The URL you cited sounded very custom, which is why I assumed you'd been using a custom REST resource plugin! I'm very sorry.

I should be able to reproduce this problem with another entity type then… but we haven't been able to do so, because we have plenty of tests, and those tests still work fine. Furthermore, going from Drupal 8.2 to Drupal 8.3, there are no update paths to run either. So I really have no idea how to even begin to reproduce this…

The only change that could possibly have been related is https://www.drupal.org/node/2820197.

The first step to be able to reproduce this I think, would be for you to provide a dump the rest.entity.country.POST route from your router table after updating from 8.2 to 8.3.

remram’s picture

StatusFileSize
new83.64 KB

No problem :) Indeed I have followed your hints and I have compared both config files of rest.entity.country.POST.

The result you can see it on image below. The path_prefix is empty since my update to Drupal 8.3.2. After updating Drupal I have run Drush command to update the DB. Maybe Drush is doing something wrong! drush updb

The path is missing in all other config files like:

  • rest.entity.country.PATCH
  • rest.entity.country.GET.json
  • rest.entity.country.DELETE

rest_entity_country_POST_D832_vs_D826

Dump of rest.entity.country.POST in Drupal 8.3.2
C:31:"Symfony\Component\Routing\Route":1365:{a:9:{s:4:"path";s:15:"/entity/country";s:4:"host";s:0:"";s:8:"defaults";a:2:{s:11:"_controller";s:34:"Drupal\rest\RequestHandler::handle";s:21:"_rest_resource_config";s:14:"entity.country";}s:12:"requirements";a:4:{s:7:"_access";s:4:"TRUE";s:7:"_method";s:4:"POST";s:26:"_csrf_request_header_token";s:4:"TRUE";s:20:"_content_type_format";s:13:"hal_json|json";}s:7:"options";a:6:{s:14:"compiler_class";s:34:"\Drupal\Core\Routing\RouteCompiler";s:10:"parameters";a:1:{s:7:"country";a:2:{s:4:"type";s:14:"entity:country";s:9:"converter";s:21:"paramconverter.entity";}}s:5:"_auth";a:2:{i:0;s:10:"basic_auth";i:1;s:6:"oauth2";}s:14:"_route_filters";a:2:{i:0;s:13:"method_filter";i:1;s:27:"content_type_header_matcher";}s:16:"_route_enhancers";a:1:{i:0;s:31:"route_enhancer.param_conversion";}s:14:"_access_checks";a:2:{i:0;s:20:"access_check.default";i:1;s:24:"access_check.header.csrf";}}s:7:"schemes";a:0:{}s:7:"methods";a:1:{i:0;s:4:"POST";}s:9:"condition";s:0:"";s:8:"compiled";C:33:"Drupal\Core\Routing\CompiledRoute":340:{a:11:{s:4:"vars";a:0:{}s:11:"path_prefix";s:0:"";s:10:"path_regex";s:20:"#^/entity/country$#s";s:11:"path_tokens";a:1:{i:0;a:2:{i:0;s:4:"text";i:1;s:15:"/entity/country";}}s:9:"path_vars";a:0:{}s:10:"host_regex";N;s:11:"host_tokens";a:0:{}s:9:"host_vars";a:0:{}s:3:"fit";i:3;s:14:"patternOutline";s:15:"/entity/country";s:8:"numParts";i:2;}}}}

Dump of rest.entity.country.POST in Drupal 8.2.6
C:31:"Symfony\Component\Routing\Route":1385:{a:9:{s:4:"path";s:15:"/entity/country";s:4:"host";s:0:"";s:8:"defaults";a:2:{s:11:"_controller";s:34:"Drupal\rest\RequestHandler::handle";s:21:"_rest_resource_config";s:14:"entity.country";}s:12:"requirements";a:4:{s:7:"_access";s:4:"TRUE";s:7:"_method";s:4:"POST";s:20:"_content_type_format";s:17:"hal_json|json|xml";s:26:"_csrf_request_header_token";s:4:"TRUE";}s:7:"options";a:6:{s:14:"compiler_class";s:34:"\Drupal\Core\Routing\RouteCompiler";s:10:"parameters";a:1:{s:7:"country";a:2:{s:4:"type";s:14:"entity:country";s:9:"converter";s:21:"paramconverter.entity";}}s:5:"_auth";a:2:{i:0;s:10:"basic_auth";i:1;s:6:"oauth2";}s:14:"_route_filters";a:2:{i:0;s:13:"method_filter";i:1;s:27:"content_type_header_matcher";}s:16:"_route_enhancers";a:1:{i:0;s:31:"route_enhancer.param_conversion";}s:14:"_access_checks";a:2:{i:0;s:20:"access_check.default";i:1;s:24:"access_check.header.csrf";}}s:7:"schemes";a:0:{}s:7:"methods";a:1:{i:0;s:4:"POST";}s:9:"condition";s:0:"";s:8:"compiled";C:33:"Drupal\Core\Routing\CompiledRoute":356:{a:11:{s:4:"vars";a:0:{}s:11:"path_prefix";s:15:"/entity/country";s:10:"path_regex";s:20:"#^/entity/country$#s";s:11:"path_tokens";a:1:{i:0;a:2:{i:0;s:4:"text";i:1;s:15:"/entity/country";}}s:9:"path_vars";a:0:{}s:10:"host_regex";N;s:11:"host_tokens";a:0:{}s:9:"host_vars";a:0:{}s:3:"fit";i:3;s:14:"patternOutline";s:15:"/entity/country";s:8:"numParts";i:2;}}}}

remram’s picture

In case of patching I'm still getting the same error like before. It doesn't matter if I'm using the default functionality or using my custom RestResource.

Uncaught PHP Exception Symfony\\Component\\Routing\\Exception\\MethodNotAllowedException: "" at /core/lib/Drupal/Core/Routing/Router.php line 158

Are there any workarounds?

JugglerX’s picture

I'm getting the same issue. Drupal log error.

255108 23/May 21:47 error php Symfony\Component\Routing\Exception\MethodNotAllowedException: in Drupal\Core\Routing\Router->matchRequest() (line 158 of /var/www/drupalvm/drupal/docroot/core/lib/Drupal/Core/Routing/Rou

I've enabled PATCH on a node using the REST UI.

Note that the PATCH operation is successful, the node is updated. But the response is a 500 error.

remram’s picture

After a while i gave up with my attempts... After replacing the database, REST for my custom entities is working as it should. I really have no clue where the problem was.

askibinski’s picture

Very interested in the cause of this. We are experiencing the same problem which we cannot reproduce local, but does occur on our test environment. It must be the database/config related.

ltimmers’s picture

I'm a colleague of askibinnski and as he mentioned at #16 we experience the same problem.

The problem looks related to https://www.drupal.org/node/2706241

Read the comment of lslinnet at #8 and his suggested fix at #9. This suggest fix fixes the problem in our setup.

Note: using a non-admin account also fixes the problem.

@askibinnski: the reason we can't reproduce this problem in a fresh setup (site install + config import) is because in that situation the preferred_admin_langcode is not set yet. And because of this the LanguageNegotiationUserAdmin->isAdminPath method is never called.

remram’s picture

This really strange by using none-admin account it should work. Well I can't reproduce the problem again, because I have replaced the database and all configuration.

Thank you guys for the inputs and I really hope for a proper solution for all who stucks with this issue.

wim leers’s picture

We still need steps to reproduce to fix this bug. If you're able to reproduce this, you can easily get steps to reproduce by using xdebug and putting a breakpoint on the line where that exception is being thrown.

wim leers’s picture

Status: Postponed (maintainer needs more info) » Closed (duplicate)

Oh wait, I see what you mean now, #2706241: AccessAwareRouter does not respect HTTP method is the root cause!

But this is not a change in 8.3 at all, it means that @remram must've added the use of \Drupal\user\Plugin\LanguageNegotiation\LanguageNegotiationUserAdmin while updating from 8.2 to 8.3, and then jumping to the understandable but wrong conclusion that 8.3 was to blame.

cicciomaltese’s picture

Hi, I found the same error: the custom rest api (written by me) cause the error only in POST and PATCH.
I think the problem depends on the language of the administration.
My solution was to set the "Administration pages language" parameter to "no preference" in the user profile

fy1128’s picture

#21 OMG!, you just saved my day. I got this error with jsonapi.

azinck’s picture

Version: 8.3.2 » 10.0.x-dev

Yes! #21, thank you for the pointer in the right direction. Now I just have to figure out why the language setting causes this issue.

azinck’s picture