LoginSignup
1
0

More than 5 years have passed since last update.

サーバのキャッシュ情報を利用してキャッシュする実装(Ruby)

Posted at

参考URL

Working with HTTP cache
https://www.brianstorti.com/working-with-http-cache/

はじめに

httpのレスポンスのヘッダに last-modifiedEtag 等のキャッシュに関する情報が載っていることがあります.
Webサーバ側がせっかくキャッシュ情報を設定しているんだから,クライアント側も実装しよう.

基本

以降,Ruby 2.4.3(Windows10 64bit),2.5.1(Windows Subsystem for Linux)で説明します.

次のコードは,httpsでdocs.ruby-lang.org から / をダウンロードします.
httpのヘッダやステータスコードも出力させています.

require 'net/https'

https = Net::HTTP.new("docs.ruby-lang.org", 443)
https.use_ssl = true
https.verify_mode = OpenSSL::SSL::VERIFY_NONE
https.start do |http|
    response = http.get("/")
    p response.code
    puts response.each.map{|k,v| k+"\t => "+v }.to_a
    puts response.body[0..200] if response.body
end

結果はこんな感じでした.

"200"
server   => nginx/1.10.3
content-type     => text/html
last-modified    => Thu, 27 Dec 2018 00:19:32 GMT
etag     => W/"5c241a94-394"
cache-control    => public, max-age=43200, s-maxage=172800, stale-while-revalidate=86400, stale-if-error=604800
via      => 1.1 varnish, 1.1 varnish
fastly-debug-digest      => 33272ba139e924bc977ca4803fe9bfe699d76ef3818ac8b578bbfe42b3afa6cc
content-length   => 579
accept-ranges    => bytes
date     => Sat, 02 Mar 2019 01:49:34 GMT
age      => 102622
connection       => keep-alive
x-served-by      => cache-nrt6147-NRT, cache-itm18822-ITM
x-cache  => HIT, HIT
x-cache-hits     => 1, 3
x-timer  => S1551491375.978827,VS0,VE5
vary     => Accept-Encoding
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<!-- Global Site Tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-620926-3"></script

キャッシュ

last-modified を使う

ヘッダに含まれているlast-modifiedは,「最後にそのページが更新された時間」です.
last-modifiedの値が前回アクセスした時と同じならば,
更新が無かったとみなすことができるので,bodyを取得する必要はありません.

require 'net/https'
require 'time'

@cache = {}

def get(path)
    https = Net::HTTP.new("docs.ruby-lang.org", 443)
    https.use_ssl = true
    https.verify_mode = OpenSSL::SSL::VERIFY_NONE
    https.start do |http|
        # ヘッダのみを取得
        response = http.head(path)
        # そのパスのキャッシュが無い OR キャッシュが古いならば
        if @cache[path].nil? || Time.parse(@cache[path][:last_modified]) < Time.parse(response["last-modified"])
            STDERR.puts "get"
            # bodyも含めて取得
            response = http.get(path)
            # 取得結果を記憶
            @cache[path] = {
                last_modified: response["last-modified"],
                body: response.body
            }
            @cache[path][:body]
        else
            STDERR.puts "cached"
            # 記憶済みの取得結果を返す
            @cache[path][:body]
        end
    end
end

puts get("/")[0..50]
sleep 1
puts get("/")[0..50]
sleep 1
puts get("/")[0..50]

If-Modified-Sinceを使う

クライアント側で更新時刻を比較するのではなく,サーバ側で比較してもらう方法です.
If-Modified-Since ヘッダを付けて,クエリを投げます.
更新がなかった場合,304が返ってきます.

require 'net/https'
require 'time'

@cache = {}

def get(path)
    https = Net::HTTP.new("docs.ruby-lang.org", 443)
    https.use_ssl = true
    https.verify_mode = OpenSSL::SSL::VERIFY_NONE
    https.start do |http|
        # 最後に取得した日付
        since = @cache[path] ? @cache[path][:last_modified] : ""
        # サーバから取得する
        response = http.get(path, {"If-Modified-Since"=>since})
        if (response.code == "200") # OK(変更がある)
            STDERR.puts "get"
            # 取得結果を記憶
            @cache[path] = {
                last_modified: response["last-modified"],
                body: response.body
            }
            @cache[path][:body]
        elsif (response.code == "304") # Not Modified
            STDERR.puts "cached"
            # 記憶済みの取得結果を返す
            @cache[path][:body]
        else
            "🤔"
        end
    end
end

puts get("/")[0..50]
sleep 1
puts get("/")[0..50]
sleep 1
puts get("/")[0..50]

Etag

ヘッダにEtagが記載されている場合があります.
ウェブページのハッシュ値のような識別子で,前回のクエリとEtag値が同じならば,
ページに変化がない,とみなすことが出来ます.
If-Modified-Since同様に,If-None-Match ヘッダを付けてクエリを投げることで,
サーバ側でEtag値の比較をしてくれるはずです.

require 'net/https'
require 'time'

@cache = {}

def get(path)
    https = Net::HTTP.new("docs.ruby-lang.org", 443)
    https.use_ssl = true
    https.verify_mode = OpenSSL::SSL::VERIFY_NONE
    https.start do |http|
        # 最後に取得した日付
        etag = @cache[path] ? @cache[path][:etag] : ""
        # サーバから取得する
        response = http.get(path, {"If-None-Match"=>etag})
        if (response.code == "200") # OK(変更がある)
            STDERR.puts "get"
            # 取得結果を記憶
            @cache[path] = {
                etag: response["Etag"],
                body: response.body
            }
            @cache[path][:body]
        elsif (response.code == "304") # Not Modified
            STDERR.puts "cached"
            # 記憶済みの取得結果を返す
            @cache[path][:body]
        else
            "🍣"
        end
    end
end

puts get("/")[0..50]
sleep 1
puts get("/")[0..50]
sleep 1
puts get("/")[0..50]

ETagには「強いETag値」「弱いETag値」の2種類があるようです.
詳細は日本語版Wikipediaに記載されています.
https://ja.wikipedia.org/wiki/HTTP_ETag

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0