Skip to content
This repository was archived by the owner on Jan 29, 2020. It is now read-only.

Cache limiter support attempt #12

Closed
wants to merge 21 commits into from

Conversation

pine3ree
Copy link
Contributor

Inject cache headers based on cache_limiter value, while removing the automatic job from the php engine.

The php engine automatically sends cache-http-headers based on the values of session.cache_limiter, session.cache_expire ini/session_start settings and the script last mtime.

The idea is to steal the value of session.cache_limiter inside the constructor and set an empty cache_limiter when starting the session, thus disabling the automatic headers.

Then we recreate the logic of the zend-engine to build the headers ourselves and inject them into the response.

TODO/TOCHECK

  • find a better/proper way to check the running script mtime
  • decide if/when to inject the cache header: right now if a cache header is found we assume that it was added programmatically in an internal middleware layer, so we skip injecting any of them. What should we do: override-headers? add-(multi-value)-headers?

@pine3ree
Copy link
Contributor Author

need to fix lowered coverage...

@pine3ree
Copy link
Contributor Author

pine3ree commented Apr 26, 2018

Fixes #10

use function sprintf;
use function gmdate;
use function time;
use function filemtime;
Copy link
Member

Choose a reason for hiding this comment

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

Import statements should be alphabetized (simplifies finding declarations, and reduces chances of duplication).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

sorry, that's ignorance on my part...I always used to set the order based on which func was called first

Copy link
Member

Choose a reason for hiding this comment

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

No worries - just providing some guidance. :)

'private_no_expire' => true,
];

public const CACHE_PAST_DATE = 'Thu, 19 Nov 1981 08:52:00 GMT';
Copy link
Member

Choose a reason for hiding this comment

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

Why this date in particular, and not the unix epoch?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

Wow, that's super bizarre! I guess we should use the value for consistency with the engine... maybe throw a comment in indicating where we get the value from, though. :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, I used it for consistency....but I'm still wondering where (or when) it comes from....

Copy link
Member

Choose a reason for hiding this comment

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

Probably some PHP contributor's birthday. 😀

Copy link
Contributor Author

Choose a reason for hiding this comment

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

}
}

return $response;
Copy link
Member

Choose a reason for hiding this comment

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

Invert the conditional so you can return early, combine conditions, and promote the body of the conditional:

if (! $this->cacheLimiter || $this->responseAlreadyHasCacheHeaders($response)) {
    return $response;
}

$cacheHeaders = $this->generateCacheHeaders($this->cacheLimiter, $this->cacheExpire);
// ...
return $response;

/**
* Generate cache headers for a given session cache_limiter value.
* @param string $cacheLimiter
* @param int $cacheExpire
Copy link
Member

Choose a reason for hiding this comment

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

Parameter annotations are discouraged unless they add descriptions; these can be removed.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

do I keep the method's purpose explaination?

Copy link
Member

Choose a reason for hiding this comment

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

Yes.

}

$maxAge = 60 * $cacheExpire;
$lastModified = $this->getLastModified($_SERVER['SCRIPT_FILENAME'] ?? '');
Copy link
Member

Choose a reason for hiding this comment

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

We typically try not to access superglobals directly. Please pass this value to the method by using the following from within initializeSessionFromRequest():

$request->getServerParams()['SCRIPT_FILENAME']

Considering that this may not be set, you'll need to come up with a default value to use; in that case, I'd likely use __FILE__:

$request->getServerParams()['SCRIPT_FILENAME'] ?? __FILE__

Copy link
Contributor Author

@pine3ree pine3ree Apr 26, 2018

Choose a reason for hiding this comment

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

Yeah, I thought about that too...I was actually waiting for your review. I actually used __FILE__ in the test case

I'd have a few questions before applying the changes:

  1. generateCacheHeaders(...) is privately used only with internal properties, should I remove the arguments and use them directly?

  2. same question getLastModified, since the scriptFile will be saved into an internal property

  3. Should I remove thi PR and re-add a 1-commit PR? (I started uploading to upstream while forgetting i had already opened the PR, I apologize for the number of commits)

Copy link
Member

Choose a reason for hiding this comment

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

  1. Sure, that should work fine.
  2. Same.
  3. No, I'm fine with multiple commits. If YOU are not, then run git rebase -i and squash some commits and force push back to your branch.

/**
* Check if the response already carries cache headers
* @param ResponseInterface $response
* @return bool
Copy link
Member

Choose a reason for hiding this comment

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

Both of these annotations may be removed, as they duplicate the typehints.

private function responseAlreadyHasCacheHeaders(ResponseInterface $response) : bool
{
return (
$response->hasHeader('Expires')
Copy link
Member

Choose a reason for hiding this comment

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

Reduce the indentation of this line; || operators should align with this line.

@@ -23,6 +23,8 @@
use function session_name;
use function session_start;
use function session_status;
use function time;
use function gmdate;
Copy link
Member

Choose a reason for hiding this comment

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

Alphabetize imports, please.

$expires = strtotime($expires);

$this->assertTrue($expires >= $expiresMin);
$this->assertTrue($expires <= $expiresMax);
Copy link
Member

Choose a reason for hiding this comment

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

Please use assertGreaterThanOrEqual() and/or assertLessThanOrEqual().


// temporarily set SCRIPT_FILENAME to current file for testing
$scriptFilename = $_SERVER['SCRIPT_FILENAME'] ?? null;
$_SERVER['SCRIPT_FILENAME'] = __FILE__;
Copy link
Member

Choose a reason for hiding this comment

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

With the changes I suggested earlier around usage of superglobals, this test will need to change.

What I didn't realize is that you're accessing it during persistSession() as well. I'm wondering if the script filename should be memoized in the Session instance, and removed during persistence?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, see my previous reply...I don't think we need to remove it though whit it being refreshed at every request...and now that I think about it...should we just memoize it once....the running script is not going to change even in asynchronous contexts?

Copy link
Member

Choose a reason for hiding this comment

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

Sure.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

....mhmmm....but mocked requests could contain a different SCRIPT_FILENAME....

@pine3ree
Copy link
Contributor Author

pine3ree commented Apr 26, 2018

..i forgot another question:
I used public constants for past date and http date format and reused them in the tests, but, aside from the tests, I don't they are useful outside the class

@weierophinney
Copy link
Member

I used public constants for past date and http date format and reused them in the tests, but, aside form the tests, I don't they are useful outside the class

Using them in tests is public behavior, so you can mark them public if you want. Otherwise you'll need to use reflection in your tests to get at the values, which is generally not fun. :)

@pine3ree
Copy link
Contributor Author

mhmm...I'm not sure why test are failing on travis, everything works locally...I'll check later....

@pine3ree
Copy link
Contributor Author

Still no clue, locally all test pass and in a test app the appropriate headers are correctly sent.
Could it be a travic issue?
What I find weird is that even a simple unchanged test case like the cache_limiter === nocache is failing, even if the session config is memoized as before and the code generating that constant headers array is basically unchanged (just remove the arguments and use the internal properties directly)

@pine3ree
Copy link
Contributor Author

pine3ree commented Apr 27, 2018

@weierophinney

Travis build continue to fail, I believe this has smt to do with the merging of my previous (unrelated) merged PR, even if there is no report of conflicts. My same fork and another test branch forked from it are still passing tests successfully. I also see a strange decrease in coverage.

https://travis-ci.org/pine3ree/zend-expressive-session-ext/builds/371829367

any idea?
kind regards

PS
there is another small change that I believe could make sense.

https://github.com/pine3ree/zend-expressive-session-ext/blob/00e0f7c9a3af7c0cc8ae4a456e83438946c1388f/src/PhpSessionPersistence.php#L222

In the getLastModified() method we may have a memoized scriptFile either from a request server param or from the current __FILE__. In cases when the request scritpFile is an empty string or representes a non existent file, we still end up with a false value. I believe we should set it to __FILE__ for those cases as well inside the function.

In the end we should always get a value, but since gmdate could also return false, we should assume that value can be returned as well and leave the fals check when injecting the headers

    //....
    public function initializeSessionFromRequest(ServerRequestInterface $request) : SessionInterface
    {
         // comment note: we set it to __FILE__ only once inside getLastModified if necessary
        $this->scriptFile = $request->getServerParams()['SCRIPT_FILENAME'] ?? null;
        //....
    }
    //....
    private function getLastModified()
    {
        if (! $this->scriptFile || ! is_file($this->scriptFile)) {
            $this->scriptFile = __FILE__;
        }

        return gmdate(self::HTTP_DATE_FORMAT, filemtime($this->scriptFile));
    }
    //----

(edited)
The test cases were not compatible with the last merge, I fixed it. regards.

@pine3ree
Copy link
Contributor Author

pine3ree commented May 2, 2018

Hello @weierophinney,
I rebased to the latest merge and squashed into fewer commits for a cleaner history into a new branch:

When you review last commits/comments pls let me also know if you prefer a PR from the new branch (closing this one)

kind regards

Copy link
Member

@weierophinney weierophinney left a comment

Choose a reason for hiding this comment

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

Final changes look perfect, @pine3ree !

weierophinney added a commit that referenced this pull request May 10, 2018
weierophinney added a commit that referenced this pull request May 10, 2018
weierophinney added a commit that referenced this pull request May 10, 2018
@weierophinney
Copy link
Member

Thanks, @pine3ree! Merged to develop for release with 1.1.0.

@pine3ree pine3ree deleted the cache-limiter-support branch May 11, 2018 02:28
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants