5
5
use Http \Client \Common \Plugin ;
6
6
use Http \Message \StreamFactory ;
7
7
use Http \Promise \FulfilledPromise ;
8
+ use Psr \Cache \CacheItemInterface ;
8
9
use Psr \Cache \CacheItemPoolInterface ;
9
10
use Psr \Http \Message \RequestInterface ;
10
11
use Psr \Http \Message \ResponseInterface ;
@@ -57,7 +58,6 @@ public function __construct(CacheItemPoolInterface $pool, StreamFactory $streamF
57
58
public function handleRequest (RequestInterface $ request , callable $ next , callable $ first )
58
59
{
59
60
$ method = strtoupper ($ request ->getMethod ());
60
-
61
61
// if the request not is cachable, move to $next
62
62
if ($ method !== 'GET ' && $ method !== 'HEAD ' ) {
63
63
return $ next ($ request );
@@ -68,15 +68,41 @@ public function handleRequest(RequestInterface $request, callable $next, callabl
68
68
$ cacheItem = $ this ->pool ->getItem ($ key );
69
69
70
70
if ($ cacheItem ->isHit ()) {
71
- // return cached response
72
71
$ data = $ cacheItem ->get ();
73
- $ response = $ data ['response ' ];
74
- $ response = $ response ->withBody ($ this ->streamFactory ->createStream ($ data ['body ' ]));
72
+ if (isset ($ data ['expiresAt ' ]) && time () > $ data ['expiresAt ' ]) {
73
+ // This item is still valid according to previous cache headers
74
+ return new FulfilledPromise ($ this ->createResponseFromCacheItem ($ cacheItem ));
75
+ }
76
+
77
+ // Add headers to ask the server if this cache is still valid
78
+ if ($ mod = $ this ->getModifiedAt ($ cacheItem )) {
79
+ $ mod = new \DateTime ('@ ' .$ mod );
80
+ $ mod ->setTimezone (new \DateTimeZone ('GMT ' ));
81
+ $ request = $ request ->withHeader ('If-Modified-Since ' , sprintf ('%s GMT ' , $ mod ->format ('l, d-M-y H:i:s ' )));
82
+ }
75
83
76
- return new FulfilledPromise ($ response );
84
+ if ($ etag = $ this ->getETag ($ cacheItem )) {
85
+ $ request = $ request ->withHeader ('If-None-Match ' , $ etag );
86
+ }
77
87
}
78
88
79
89
return $ next ($ request )->then (function (ResponseInterface $ response ) use ($ cacheItem ) {
90
+ if (304 === $ response ->getStatusCode ()) {
91
+ if (!$ cacheItem ->isHit ()) {
92
+ // We do not have the item in cache. We can return the cached response.
93
+ return $ response ;
94
+ }
95
+
96
+ // The cached response we have is still valid
97
+ $ data = $ cacheItem ->get ();
98
+ $ maxAge = $ this ->getMaxAge ($ response );
99
+ $ data ['expiresAt ' ] = time () + $ maxAge ;
100
+ $ cacheItem ->set ($ data )->expiresAfter ($ this ->config ['cache_lifetime ' ] + $ maxAge );
101
+ $ this ->pool ->save ($ cacheItem );
102
+
103
+ return $ this ->createResponseFromCacheItem ($ cacheItem );
104
+ }
105
+
80
106
if ($ this ->isCacheable ($ response )) {
81
107
$ bodyStream = $ response ->getBody ();
82
108
$ body = $ bodyStream ->__toString ();
@@ -86,8 +112,16 @@ public function handleRequest(RequestInterface $request, callable $next, callabl
86
112
$ response = $ response ->withBody ($ this ->streamFactory ->createStream ($ body ));
87
113
}
88
114
89
- $ cacheItem ->set (['response ' => $ response , 'body ' => $ body ])
90
- ->expiresAfter ($ this ->getMaxAge ($ response ));
115
+ $ maxAge = $ this ->getMaxAge ($ response );
116
+ $ cacheItem
117
+ ->expiresAfter ($ this ->config ['cache_lifetime ' ] + $ maxAge )
118
+ ->set ([
119
+ 'response ' => $ response ,
120
+ 'body ' => $ body ,
121
+ 'expiresAt ' => time () + $ maxAge ,
122
+ 'createdAt ' => time (),
123
+ 'etag ' => $ response ->getHeader ('ETag ' ),
124
+ ]);
91
125
$ this ->pool ->save ($ cacheItem );
92
126
}
93
127
@@ -194,11 +228,73 @@ private function getMaxAge(ResponseInterface $response)
194
228
private function configureOptions (OptionsResolver $ resolver )
195
229
{
196
230
$ resolver ->setDefaults ([
231
+ 'cache_lifetime ' => 2592000 , // 30 days
197
232
'default_ttl ' => null ,
198
233
'respect_cache_headers ' => true ,
199
234
]);
200
235
236
+ $ resolver ->setAllowedTypes ('cache_lifetime ' , 'int ' );
201
237
$ resolver ->setAllowedTypes ('default_ttl ' , ['int ' , 'null ' ]);
202
238
$ resolver ->setAllowedTypes ('respect_cache_headers ' , 'bool ' );
203
239
}
240
+
241
+ /**
242
+ * @param CacheItemInterface $cacheItem
243
+ *
244
+ * @return ResponseInterface
245
+ */
246
+ private function createResponseFromCacheItem (CacheItemInterface $ cacheItem )
247
+ {
248
+ $ data = $ cacheItem ->get ();
249
+
250
+ /** @var ResponseInterface $response */
251
+ $ response = $ data ['response ' ];
252
+ $ response = $ response ->withBody ($ this ->streamFactory ->createStream ($ data ['body ' ]));
253
+
254
+ return $ response ;
255
+ }
256
+
257
+ /**
258
+ * Get the timestamp when the cached response was stored.
259
+ *
260
+ * @param CacheItemInterface $cacheItem
261
+ *
262
+ * @return int|null
263
+ */
264
+ private function getModifiedAt (CacheItemInterface $ cacheItem )
265
+ {
266
+ $ data = $ cacheItem ->get ();
267
+ if (!isset ($ data ['createdAt ' ])) {
268
+ return ;
269
+ }
270
+
271
+ return $ data ['createdAt ' ];
272
+ }
273
+
274
+ /**
275
+ * Get the ETag from the cached response.
276
+ *
277
+ * @param CacheItemInterface $cacheItem
278
+ *
279
+ * @return string|null
280
+ */
281
+ private function getETag (CacheItemInterface $ cacheItem )
282
+ {
283
+ $ data = $ cacheItem ->get ();
284
+ if (!isset ($ data ['etag ' ])) {
285
+ return ;
286
+ }
287
+
288
+ if (is_array ($ data ['etag ' ])) {
289
+ foreach ($ data ['etag ' ] as $ etag ) {
290
+ if (!empty ($ etag )) {
291
+ return $ etag ;
292
+ }
293
+ }
294
+
295
+ return ;
296
+ }
297
+
298
+ return $ data ['etag ' ];
299
+ }
204
300
}
0 commit comments