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