Sign Up

Sign Up to our social questions and Answers Engine to ask questions, answer people’s questions, and connect with other people.

Have an account? Sign In

Have an account? Sign In Now

Sign In

Login to our social questions & Answers Engine to ask questions answer people’s questions & connect with other people.

Sign Up Here

Forgot Password?

Don't have account, Sign Up Here

Forgot Password

Lost your password? Please enter your email address. You will receive a link and will create a new password via email.

Have an account? Sign In Now

Sorry, you do not have permission to ask a question, You must login to ask a question.

Forgot Password?

Need An Account, Sign Up Here

Please type your username.

Please type your E-Mail.

Please choose an appropriate title for the post.

Please choose the appropriate section so your post can be easily searched.

Please choose suitable Keywords Ex: post, video.

Browse

Need An Account, Sign Up Here

Please briefly explain why you feel this question should be reported.

Please briefly explain why you feel this answer should be reported.

Please briefly explain why you feel this user should be reported.

Sign InSign Up

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise Logo Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise Logo

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise Navigation

  • Home
  • About Us
  • Contact Us
Search
Ask A Question

Mobile menu

Close
Ask a Question
  • Home
  • About Us
  • Contact Us
Home/ Questions/Q 3741

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise Latest Questions

Author
  • 61k
Author
Asked: November 26, 20242024-11-26T05:31:08+00:00 2024-11-26T05:31:08+00:00

Web Caching – ETag/If-None-Match

  • 61k

Support code

In the previous post, we explored the usefulness of the Last-Modified Response Header and If-Modified-Since Request Header. They work really well when dealing with an endpoint returning a file.

What about data retrieved from a database or assembled from different sources?

Request Response Value example
Last-Modified If-Modified-Since Thu, 15 Nov 2023 19:18:46 GMT
ETag If-None-Match 75e7b6f64078bb53b7aaab5c457de56f

Also here, we have a tuple of headers. One must be provided by the requester (ETag), while the other is returned by the sender (If-None-Match). The value is a hash generated on the content of the response.

If you want to go directly to using headers, go to the endpoint. Otherwise, observe (but don't spend too much time on) the implementation.

Preparation

For simplicity, we use an in-memory DB. It is exposed via the endpoint /db. It contains a list of posts. Each post contains a title and a tag. Posts can be added via POST, and modified via PATCH.

Retrieval is via a GET function, which optionally filters by tag.

src/db.mjs
  import { getJSONBody } from "./utils.mjs";  const POSTS = [   { title: "Caching", tag: "code" },   { title: "Headers", tag: "code" },   { title: "Dogs", tag: "animals" }, ];  export function GET(tag) {   let posts = POSTS;   if (tag) posts = posts.filter((post) => post.tag === tag);   return posts; }  export default async function db(req, res) {   switch (req.method) {     case "POST": {       const [body, err] = await getJSONBody(req);       if (err) {         res.writeHead(500).end("Something went wrong");         return;       }        POSTS.push(body);       res.writeHead(201).end();       return;     }     case "PATCH":       const [body, err] = await getJSONBody(req);       if (err) {         res.writeHead(500).end("Something went wrong");         return;       }        POSTS.at(body.index).title = body.title;       res.writeHead(200).end();       return;   } }   
Enter fullscreen mode Exit fullscreen mode

src/utils.mjs
  export function getURL(req) {   return new URL(req.url, `http://${req.headers.host}`); }  export async function getJSONBody(req) {   return new Promise((resolve) => {     let body = "";     req.on("data", (chunk) => (body += chunk));     req.on("error", (err) => resolve([null, err]));     req.on("end", () => resolve([JSON.parse(body), null]));   }); }   
Enter fullscreen mode Exit fullscreen mode

Endpoint

By registering the db, we will be able to modify the content of the responses in real-time, appreciating the usefulness of ETag.
Also, let's register and create the /only-etag endpoint.

  // src/index.mjs import { createServer } from "http"; import db from ".src/db.mjs"; import onlyETag from "./src/only-etag.mjs"; import { getURL } from "./src/utils.mjs";  createServer(async (req, res) => {   switch (getURL(req).pathname) {     case "/only-etag":       return await onlyETag(req, res);     case "/db":       return await db(req, res);   } }).listen(8000, "127.0.0.1", () =>   console.info("Exposed on http://127.0.0.1:8000") );   
Enter fullscreen mode Exit fullscreen mode

The onlyETag endpoint accepts an optional query parameter tag. If present, it is used to filter the retrieved posts.
Thus, the template is loaded in memory.

src/views/posts.html
  <html>   <body>     <h1>Tag: %TAG%</h1>     <ul>%POSTS%</ul>     <form method="GET">       <input type="text" name="tag" id="tag" autofocus />       <input type="submit" value="filter" />     </form>   </body> </html>   
Enter fullscreen mode Exit fullscreen mode

When submitted, the form uses as action the current route (/only-etag) appending as query parameter the name attribute. For example, typing code in the input and submitting the form would result in GET /only-etag?name=code), No JavaScript required!


And the posts are injected into it.

  import * as db from "./db.mjs"; import { getURL, getView, createETag } from "./utils.mjs";  export default async (req, res) => {   res.setHeader("Content-Type", "text/html");    const tag = getURL(req).searchParams.get("tag");   const posts = await db.GET(tag);    let [html, errView] = await getView("posts");   if (errView) {     res.writeHead(500).end("Internal Server Error");     return;   }    html = html.replace("%TAG%", tag ?? "all");   html = html.replace(     "%POSTS%",     posts.map((post) => `<li>${post.title}</li>`).join("
")   );    res.setHeader("ETag", createETag(html));    res.writeHead(200).end(html); };   
Enter fullscreen mode Exit fullscreen mode

As you notice, before dispatching the response, the ETag is generated and included under the ETag Response header.

  // src/utils.mjs import { createHash } from "crypto";  export function createETag(resource) {   return createHash("md5").update(resource).digest("hex"); }   
Enter fullscreen mode Exit fullscreen mode

Changing the content of the resource changes the Entity Tag.

Performing the request from the browser you can inspect the Response Headers via the Network tab of the Developer Tools.

  HTTP/1.1 200 OK Content-Type: text/html ETag: 4775245bd90ebbda2a81ccdd84da72b3   
Enter fullscreen mode Exit fullscreen mode

If you refresh the page, you'll notice the browser adding the If-None-Match header to the request. The value corresponds of course to the one it received before.

  GET /only-etag HTTP/1.1 If-None-Match: 4775245bd90ebbda2a81ccdd84da72b3   
Enter fullscreen mode Exit fullscreen mode

As seen in the previous posts per Last-Modified and If-Modified-Since, let's instruct the endpoint to deal with If-None-Match.

  export default async (req, res) => {   res.setHeader("Content-Type", "text/html");    retrieve (filtered) posts; // as seen before   load html; // as seen before   fill template; // as seen before    const etag = createETag(html);   res.setHeader("ETag", etag);   const ifNoneMatch = new Headers(req.headers).get("If-None-Match");   if (ifNoneMatch === etag) {     res.writeHead(304).end();     return;   }    res.writeHead(200).end(html); };   
Enter fullscreen mode Exit fullscreen mode

Indeed, subsequent requests on the same resource return 304 Not Modified, instructing the browser to use previously stored resources. Let's request:

  • /only-etag three times in a row;
  • /only-etag?tag=code twice;
  • /only-etag?tag=animals twice;
  • /only-etag, without tag, once again;

A screenshot of the Developer Tools showing what is listed above.

The presence of the query parameter determines a change in response, thus in ETag.

Notice the last one. It does not matter that there have been other requests in the meantime; the browser keeps a map of requests (including the query parameters) and ETags.

Detect entity change

To further underscore the significance of this feature, let's add a new post to the DB from another process.

  curl -X POST http://127.0.0.1:8000/db  -d '{ "title": "ETag", "tag": "code" }'   
Enter fullscreen mode Exit fullscreen mode

And request again /only-etag?tag=code.
The same cached response is not useful anymore since the returned ETag is now different.
After the db has been updated, the same request generated a different ETag. Thus, the server sent the client a new version of the resource, with a newly generated ETag. Subsequent requests will fall back to the expected behavior.

The same happens if we modify an element of the response.

  curl -X PATCH http://127.0.0.1:8000/db  -d '{ "title": "Amazing Caching", "index": 0 }'   
Enter fullscreen mode Exit fullscreen mode

The same behaviour seen before is replicated if the content changes once again.

While ETag is a more versatile solution, applicable regardless of the data type since it is content-based, it should be considered that the server must still retrieve and assemble the response, then pass it into the hashing function and compare it with the received value.

Thanks to another header, Cache-Control, it is possible to optimize the number of requests the server has to process.

cacheperformancetutorialwebdev
  • 0 0 Answers
  • 0 Views
  • 0 Followers
  • 0
Share
  • Facebook
  • Report

Leave an answer
Cancel reply

You must login to add an answer.

Forgot Password?

Need An Account, Sign Up Here

Sidebar

Ask A Question

Stats

  • Questions 4k
  • Answers 0
  • Best Answers 0
  • Users 2k
  • Popular
  • Answers
  • Author

    ES6 - A beginners guide - Template Literals

    • 0 Answers
  • Author

    Understanding Higher Order Functions in JavaScript.

    • 0 Answers
  • Author

    Build a custom video chat app with Daily and Vue.js

    • 0 Answers

Top Members

Samantha Carter

Samantha Carter

  • 0 Questions
  • 20 Points
Begginer
Ella Lewis

Ella Lewis

  • 0 Questions
  • 20 Points
Begginer
Isaac Anderson

Isaac Anderson

  • 0 Questions
  • 20 Points
Begginer

Explore

  • Home
  • Add group
  • Groups page
  • Communities
  • Questions
    • New Questions
    • Trending Questions
    • Must read Questions
    • Hot Questions
  • Polls
  • Tags
  • Badges
  • Users
  • Help

Footer

Querify Question Shop: Explore Expert Solutions and Unique Q&A Merchandise

Querify Question Shop: Explore, ask, and connect. Join our vibrant Q&A community today!

About Us

  • About Us
  • Contact Us
  • All Users

Legal Stuff

  • Terms of Use
  • Privacy Policy
  • Cookie Policy

Help

  • Knowledge Base
  • Support

Follow

© 2022 Querify Question. All Rights Reserved

Insert/edit link

Enter the destination URL

Or link to existing content

    No search term specified. Showing recent items. Search or use up and down arrow keys to select an item.