Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for issue #21299. Change HEAD action mapping to GET action interface and add HEAD request handling #21378

Merged
merged 20 commits into from
Apr 20, 2019

Conversation

mattijv
Copy link
Contributor

@mattijv mattijv commented Feb 21, 2019

Description

M2.3 changed the FrontController to explicitly require actions to specify if they respond to HEAD requests. Magento was then caching the 404 response resulting from these HEAD requests and serving them to subsequent GET requests even if they normally would have resulted in 200 response.

Fixed Issues

  1. HEAD request returns 404 #21299: HEAD request returns 404

Manual testing scenarios

  1. M2.3 store using the built-in full page cache
  2. Make a HEAD request to for example the /customer/account/login route (curl -I HEAD http://magento.local/customer/account/login/)
  3. Navigate to http://magento.local/customer/account/login/ in browser

Expected result

Customer login page

Actual result

404 page

Contribution checklist

  • Pull request has a meaningful description of its purpose
  • All commits are accompanied by meaningful commit messages
  • All new or changed code is covered with unit/integration tests (if applicable)
  • All automated tests passed successfully (all builds on Travis CI are green)

@magento-cicd2
Copy link
Contributor

magento-cicd2 commented Feb 21, 2019

CLA assistant check
All committers have signed the CLA.

@magento-engcom-team
Copy link
Contributor

Hi @mattijv. Thank you for your contribution
Here is some useful tips how you can test your changes using Magento test environment.
Add the comment under your pull request to deploy test or vanilla Magento instance:

  • @magento-engcom-team give me test instance - deploy test instance based on PR changes
  • @magento-engcom-team give me 2.3-develop instance - deploy vanilla Magento instance

For more details, please, review the Magento Contributor Assistant documentation

@Nazar65
Copy link
Member

Nazar65 commented Feb 21, 2019

Hi @mattijv same here -> #21331

@mattijv
Copy link
Contributor Author

mattijv commented Feb 21, 2019

@Nazar65 #21331 is not a fix to the underlying issue, it just makes one specific controller accept HEAD requests. A lot of other routes still wont (e.g. /customer/account/login).

The real issue is that Magento caches a 404 response for HEAD requests.

I could see it argued that all routes responding to GET requests should also respond to a HEAD request, but then the issue lies with how the FrontController decides whether a controller can handle a request or not.

If the core team decides that all GET routes should also respond to a HEAD request, then the interface mapping should be changed.

@ihor-sviziev
Copy link
Contributor

ihor-sviziev commented Feb 22, 2019

Hi @mattijv and @Nazar65,
Thank you for communication. We'll discuss those PRs with @swnsma and @buskamuza (Architect) and will decide which one is more correct.

@ihor-sviziev ihor-sviziev self-assigned this Feb 22, 2019
@mattijv
Copy link
Contributor Author

mattijv commented Feb 22, 2019

@ihor-sviziev Thank you for your response. I have couple points I'd like to make, for your consideration.

To me it seems the issue is one of the two here:

  1. Magento should not require GET routes to explicitly define that they also respond to a HEAD request, since conceptually they are very close to each other.

or

  1. Magento is falsely caching the 404 response to a HEAD request and serving that to a later GET request.

In case you determine the problem to be number 1, then my pull request is not correct and the real fault lies in how the different requests are mapped to different intefaces in app/etc/di.xml. HEAD and GET should (in my opinion) both be mapped to the same interface.

If on the other hand you decide that routes should be able to respond to GET requests and ignore HEAD requests, then my pull request might be a way to fix the issue.

No offence meant, but in neither case does #21331 seem like the correct solution, as it is only making one affected controller respond to HEAD requests. This won't fix the issue for several other controllers that respond to GET requests, but not HEAD.

That said, I trust that a correct solution is implemented for this issue.

@Nazar65
Copy link
Member

Nazar65 commented Feb 23, 2019

@mattijv did you find why this issue not present in 2.2-develop ? just checked on 2.2-dev and works all perfectly. Maybe we need find why this works on 2.2-dev ?

@mattijv
Copy link
Contributor Author

mattijv commented Feb 23, 2019

@Nazar65 The problem doesn’t exist in M2.2 because M2.3 added validation that checks if a controller responds to a certain type of HTTP request (GET, HEAD, POST etc). In M2.2 GET and HEAD requests were treated the same, but now a controller must specify that it will respond to HEAD requests.

@Nazar65
Copy link
Member

Nazar65 commented Feb 23, 2019

@mattijv with your solution we always get 404 with curl i think this is not correct, if i do request like curl -X HEAD -i http://magento23dev.vg/index.php/ - response 404
but when i go to web i have 200 ok
Seems like not correct behavior
and all others like /customer/account/create/ always return "404" on curl

@mattijv
Copy link
Contributor Author

mattijv commented Feb 25, 2019

@Nazar65 Yes, like I stated in #21378 (comment), there are two ways this can be fixed. Either the routes SHOULD respond to HEAD requests, in which case my fix is wrong, or they can respond with a 404 to a HEAD request but 200 to a GET, in which case my fix should be something close to what should be implemented.

It's up to the core team to decide if GET routes should automatically respond to a HEAD request.

Copy link
Contributor

@ihor-sviziev ihor-sviziev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @mattijv,
We discussed this issue with Architects and here is result: #21299 (comment)
Could you update your PR based on following comment?

@mattijv
Copy link
Contributor Author

mattijv commented Mar 4, 2019

@ihor-sviziev Thank you for the message. I wrote some thoughts in that thread, but would be happy to update this PR if my proposed changes are deemed a suitable solution.

@mattijv
Copy link
Contributor Author

mattijv commented Mar 5, 2019

@ihor-sviziev I've updated the pull request based on conversations here: #21299 (comment)

The changes most likely require review.

The critical change for fixing the original issue is the change of the HEAD request map in the app/etc/di.xml to the GET request interface. I've left the now useless HttpHeadActionInterface for backwards compatibility, but marked it deprecated (hopefully I got that right).

I also added code to the framework that strips the body from HEAD requests as this was not present before (the HEAD requests were responded to with the full body).

@mattijv mattijv requested a review from ihor-sviziev March 5, 2019 16:03
*/
private function handleHeadRequest()
{
$contentLength = strlen($this->_response->getContent());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The strlen function isn’t binary safe (i.e. images and compressed content) and also doesn’t handle multi-byte characters. I‘m not sure about the proper things to do here, but it would be a good idea to do some more research and maybe add some tests for non-ascii response bodies.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was under the assumption that strlen specifically returns the number of bytes in the string, instead of the number of characters.[1] Isn't that what the Content-Length header should contain?

[1] http://php.net/manual/en/function.strlen.php#refsect1-function.strlen-notes

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do agree on adding non-ascii test cases, that is a good idea.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On further research it seems that some installations of PHP might have overloaded the strlen to use mb_strlen instead.[1] This would break the content length on multibyte characters as they would be counted as one, instead of the actual number of bytes.

Safer way seems to be to call mb_strlen explicitly with encoding that only supports byte characters.[2] This way we should always get the correct byte count for the body contents.

[1] http://php.net/manual/en/mbstring.overload.php
[2] https://stackoverflow.com/a/9718273

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the important thing to check for would be if it is null byte safe \0. That might be PHP version dependent, hopefully PHP7 does not rely on low level C strings. Otherwise your suggested use of mb_strlen sounds good to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I can see, the null byte is treated as a one byte character. It won't terminate strings prematurely.[1]

[1] https://stackoverflow.com/a/32263348

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated the pull request to use mb_strlen instead of strlen and added test cases for some potentially anomalous content.

mattijv added 3 commits March 7, 2019 13:16
…ontent length is calculated in bytes in all PHP installations. Added test cases to cover non-ascii content (multi-byte characters, null byte, LTR text).
@magento-engcom-team
Copy link
Contributor

Hi @sidolov, thank you for the review.
ENGCOM-4474 has been created to process this Pull Request

@soleksii
Copy link

✔️ QA Passed

Before:

before1

After:

after

@mattijv
Copy link
Contributor Author

mattijv commented Apr 16, 2019

Thank you @o-iegorov for fixing the issues pointed out by @joni-jones. I was unfortunately unable to find the time to work on them.

@SAN1TAR1UM
Copy link

I may be speaking out of turn here, but I created a composer patch from this PR diff, and the problem still exists.

After successful deployment on 2.3.1, and verifying the patch was applied properly I flushed the magento cache. Then ran curl -I http://domain which resulted in 404 header. Then when loading the site in a browser the homepage 404s.

@mattijv
Copy link
Contributor Author

mattijv commented Apr 17, 2019

@SAN1TAR1UM The actual fix for the 404s in this PR is the change to the app/etc/di.xml file. The PHP changes are mostly to handle the HEAD requests per spec. Can you make sure that the composer patch applied the changes to the app/etc/di.xml file?

@mattijv
Copy link
Contributor Author

mattijv commented Apr 17, 2019

@SAN1TAR1UM Just to confirm that the fix should work in M2.3.1 I made a clean install and changed the HEAD action mapping in app/etc/di.xml. Before the change the site was returning 404s on curl -I but after the change it correctly returned 200s.

@@ -57,6 +74,9 @@ public function testSend(): void
->method('getMimeType')
->with($file)
->will($this->returnValue($contentType));
$this->request->expects($this->once())
->method('isHead')
->will($this->returnValue(false));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, use $this->willReturnValue instead.

$this->frontControllerMock->expects($this->once())
->method('dispatch')
->with($this->requestMock)->will(
$this->returnCallback(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, use willReturnCallback or willThrowException instead

*/
public function __construct(
\Magento\Framework\HTTP\PhpEnvironment\Response $response,
\Magento\Framework\File\Mime $mime
\Magento\Framework\File\Mime $mime,
\Magento\Framework\App\Request\Http $request = null
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, avoid usage of FQCN

@SAN1TAR1UM
Copy link

@mattijv I confirmed the patch ran according to the jenkins logs, but upon review of the di.xml file it looks like the change is not there. Looks like I have a deployment issue, I will rectify it and re-test and advise.

@SAN1TAR1UM
Copy link

Looks like composer install in a fresh directory overwrites the app/etc/di.xml file from the code repository. Does anyone know which composer dep is making this change?

@hostep
Copy link
Contributor

hostep commented Apr 19, 2019

@SAN1TAR1UM: app/etc/di.xml gets copied from the magento/magento2-base package using the magento composer installer plugin, see the extra => map configuration in the composer.json file of the magento/magento2-base package to see which files gets copied.

It's best not to change the file app/etc/di.xml in your project directly, but patch the one in the magento/magento2-base package instead.

Hope this helps :)

@magento-engcom-team magento-engcom-team merged commit 740e4bd into magento:2.3-develop Apr 20, 2019
@m2-assistant
Copy link

m2-assistant bot commented Apr 20, 2019

Hi @mattijv, thank you for your contribution!
Please, complete Contribution Survey, it will take less than a minute.
Your feedback will help us to improve contribution process.

magento-engcom-team pushed a commit that referenced this pull request Apr 20, 2019
…action interface and add HEAD request handling #21378
@magento-engcom-team magento-engcom-team added this to the Release: 2.3.2 milestone Apr 20, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.