• No results found

Let’s make two changes, reflecting two different scenarios. First, we want photo search results to be instant. Let’s instruct CloudFront to never cache the results. Of course, this is going to be a problem down the road with a large user base, but at least we will know how to do it. Second, we will use the Expires request header to make a request route expire at a specific interval.

In our first scenario, we are going to configure our request headers such that neither the browser nor CloudFront will ever attempt to cache the response. In your code editor, navigate to /routes/photos.js. Locate the handler for the /search rout, and paste the following line at the beginning of the function:

res.header('Cache-Control', 'no-cache, no-store');

This should be straightforward, as we’re simply passing a key-value pair to the response header using ExpressJS syntax. It belongs at the beginning of the function, so as to avoid copy-pasting. To recap, the function should look like Listing 4-3.

Listing 4-3. /photos/search with Cache-Control Header

router.get('/search', function(req, res) {

res.header('Cache-Control', 'no-cache, no-store'); if(req.param('query')){

var params = {

query : req.param('query') }

model.getPhotosSearch(params, function(err, obj){ if(err){

res.status(400).send({error: 'Invalid photo search'}); } else {

res.send(obj); }

}); } else {

res.status(400).send({error: 'No search term found'}); }

Next, we’ll add our second use case. Let’s say that the response when requesting an album by ID can be cached by CloudFront for ten seconds. This will keep the response up to date without taxing the servers as much as disabling the cache entirely. Open /routes/albums.js and find the handler for the /id/:id route. At the top of the function, add the following two rows:.

res.header("Cache-Control", "public, max-age=10");

res.header("Expires", new Date(Date.now() + 10).toUTCString());

Your handler should look like Listing 4-4.

Listing 4-4. /albums/id/:albumID with Cache-Control Header

router.get('/id/:albumID', function(req, res) { res.header("Cache-Control", "public, max-age=10");

res.header("Expires", new Date(Date.now() + 10000).toUTCString()); if(req.param('albumID')){

var params = {

albumID : req.param('albumID') }

model.getAlbumByID(params, function(err, obj){ if(err){

res.status(400).send({error: 'Invalid album ID'}); } else {

res.send(obj); }

}); } else {

res.status(400).send({error: 'Invalid album ID'}); }

});

The response takes the current date and adds ten seconds (in milliseconds) to it, then sets the header. One thing to note is that this ten seconds will not be exact, as it will still take time to retrieve the data from the model before sending the response. If you want it to be as close to perfect as possible, set the header in the callback for model.getAlbumByID instead.

You might be wondering why the header is set at the top instead, then. There are other possible responses, such as the 400 error sent when the album ID is missing from the URL, or when no album is found for the provided ID, the latter of which could be caused by some sort of database error. It is possible for CloudFront to cache the error response the user would see, resulting in this error being shown to other users, perhaps incorrectly. In this scenario, CloudFront could backfire by prolonging errors on the client side unnecessarily. As such, it’s best to make all responses include the Expires header. You could set the header each time a response is sent from this route, but it would just make the listing look messier. After all, what good is a code sample if it’s too cluttered?

Anyway, go ahead and commit your changes to your code repository. In the AWS Console, navigate back to OpsWorks. Click the Photoalbums stack. Open the Navigation menu, and click Apps. Click the deploy button to return to the Deploy App view. In the Comment field, add a note to the effect of “added Cache-Control headers,” then click Deploy. Give OpsWorks a few minutes to push your code.

113

Listing 4-5. New and Improved, Ten-Second Cache Response Headers

Response Header Cache-Control:public, max-age=10 Connection:keep-alive Content-Length:631 Content-Type:application/json; charset=utf-8 Date:Wed, 26 Nov 2014 01:29:02 GMT ETag:W/"277-3646801943" Expires:Wed, 26 Nov 2014 01:29:02 GMT

Via:1.1 a2c541774483a4b9c153c3cb7c7a7753.cloudfront.net (CloudFront) X-Amz-Cf-Id:pcxqj03svkFItzzQ3KWi4OK5jJf4eGXs91PCQLjv2liWf9f7iP-KaQ== X-Cache:Miss from cloudfront

X-Powered-By:Express

The important parts of the response header are bolded. First, you can see that the Cache-Control header we added appears verbatim. The Expires header should appear roughly ten seconds after the current time, accommodating for differences in time zones. You’ll also see a header we didn’t add, X-Cache. The first time, this might read “Miss from cloudfront.” Make a couple more requests in rapid succession, and you’ll see “Hit from cloudfront.” This header informs you whether CloudFront provided a cached response (a hit) or had to retrieve a new response from the origin (a miss).

However, your browser or REST client may also be conforming to the caching headers, caching the X-Cache header, thus you might not see the expected result. If this is the case, you’ll have to test using cURL. Open your command-line interface (Terminal), and type the following command:

curl –I http://[cloudfront-id].cloudfront.net/albums/id/1

If you get a response header with a miss from CloudFront, run the command a few more times. You should receive a hit on the second or third request.

You will also notice the X-Amz-Cf-Id header. You may have deduced that this is a CloudFront ID for the request. If you enable logging in CloudFront, this is the unique ID for each request received by CloudFront. If you ever have to seek support from AWS in debugging CloudFront issues, it may ask you for the X-Amz-Cf-Id

requests with which you are experiencing issues.

Next, let’s test our no-caching solution for photo searching. To demonstrate this, we will do a search, upload another photo, and then run the search again. First, search for photos with the words “New York,” by making a request to /photos/search?query=New%20York. You should see something similar to Listing 4-6.

Listing 4-6. No-Cache Response Headers and Body

Response Header Cache-Control:no-cache, no-store Connection:keep-alive Content-Length:67 Content-Type:application/json; charset=utf-8 Date:Wed, 26 Nov 2014 04:23:44 GMT ETag:W/"43-3955827999"

Via:1.1 b05dafe95c8baade280459c121e622be.cloudfront.net (CloudFront) X-Amz-Cf-Id:zhNlH4MXzU9G7Mrb5tVgBq8qtMLlW3XONjZsmEZOmQ5MhXmCqdJxAg== X-Cache:Miss from cloudfront

X-Powered-By:Express Response Body

Once again, the important headers are bolded. You can see our Cache-Control header at the top. Our first search got a Miss from cloudfront in the X-Cache header. This makes sense, as this should be the first search we’ve run since we invalidated the object in our cache. Now let’s create another photo and search again to make sure we get the results we expect.

Make a new POST to /photos/upload with the same album and user IDs as before and the caption “Hello New York.” When you get a 200 response back, run the search query again. Your response should look like Listing 4-7, with the new photo showing up almost instantly in the next search.

Listing 4-7. Search Results Showing Up Instantly in the Next Request

Response Header Cache-Control:no-cache, no-store Connection:keep-alive Content-Length:132 Content-Type:application/json; charset=utf-8 Date:Wed, 26 Nov 2014 04:35:58 GMT ETag:W/"84-3303063004"

Via:1.1 2b0986af7f8d32d3d4b4cf9330702abf.cloudfront.net (CloudFront) X-Amz-Cf-Id:KTpgTxO9XBebAzuS0MSP1f2EkrcRGfqijMFz3Fc6xGqI93TPXsnldw== X-Cache:RefreshHit from cloudfront

X-Powered-By:Express Response Body [

{

"photoID":3,

"caption":"Goodbye New York", "albumID":1,

"userID":1 },

{

"photoID":10,

"caption":"Hello New York", "albumID":1,"userID":1 }

]

This time, the value of the X-Cache header is RefreshHit from cloudfront. This means that CloudFront recognized that it needed to refresh the request, and it did so. This is exactly what we wanted to happen!

The difference between these two scenarios may be confusing, owing to the browser’s behavior, as both CloudFront and the browser respond to the same HTTP response headers. With the /albums/id/1 request, both CloudFront and the browser were responding to the header instructions to cache the response, so more often than not, the browser would cache the entire response, including the response headers. You can validate this by keeping an eye on the X-Amz-Cf-Id header and watching for it to change.

In the case of the /photos/search response, the browser obeys the Cache-Control: no-cache, no-store header, so a new request is always made to CloudFront, which in turn response to the header by always forwarding the request to the origin.

115