Terapkan conditional GET dengan mengirim validator yang stabil bersama setiap dokumen, memeriksa validator yang tersimpan di klien, lalu mengembalikan dokumen saat ini atau 304 Not Modified tanpa isi respons. Tutorial Mac ini mendemonstrasikan prosesnya menggunakan halaman teks sintetis.

Tujuannya adalah membuat permintaan berulang untuk dokumen publik hanya mentransfer isinya saat diperlukan. Anda akan menjalankan server Python lokal yang sederhana, mengujinya dari Terminal, lalu menyimulasikan pembaruan dokumen. Contoh ini tetap berjalan secara lokal; memublikasikannya adalah tugas terpisah.

Bagaimana server memilih antara 200 dan 304?

Permintaan awal menerima 200 OK, isi dokumen, dan validatornya. Permintaan bersyarat berikutnya mengirimkan kembali validator yang tersimpan. Jika dokumen tidak berubah, server mengembalikan 304 Not Modified, sehingga klien dapat menggunakan kembali isi yang tersimpan. Konten yang berubah akan menerima respons 200 yang baru. Permintaan bersyarat HTTP.

Untuk dokumen yang ada dan jika tidak, akan mengembalikan 200, terapkan urutan keputusan berikut. If-None-Match diprioritaskan daripada If-Modified-Since; keduanya bukan dua pengujian yang berdiri sendiri. RFC 9110, kolom permintaan bersyarat.

Kondisi permintaan Respons
Tidak ada header bersyarat 200 dengan dokumen
If-None-Match cocok dengan tag saat ini 304, tanpa isi respons
If-None-Match ada tetapi tidak cocok 200, meskipun tanggal yang diberikan menunjukkan tidak ada perubahan
Hanya If-Modified-Since yang valid yang diberikan, dan dokumen tidak lebih baru 304, tanpa isi respons
Hanya If-Modified-Since yang valid yang diberikan, dan dokumen lebih baru 200 dengan dokumen

Evaluasi kondisi ini setelah memilih dokumen dan menetapkan respons normalnya. Dokumen yang tidak ada tetap harus menghasilkan respons normal untuk sumber daya yang tidak ditemukan. 304 tidak boleh berisi konten atau trailer. Respons tersebut harus memuat metadata cache yang berlaku, termasuk kolom Date, ETag, Vary, Cache-Control, Expires, dan Content-Location yang akan menyertai respons 200. RFC 9110, evaluasi prasyarat dan persyaratan 304.

Pilih validator yang menggambarkan dokumen yang dipublikasikan

ETag adalah pengenal versi yang diapit tanda kutip, seperti "doc-v1". Metode pembuatannya ditentukan oleh implementasi: pengenal revisi atau hash konten dapat digunakan. Pertahankan nilainya agar stabil untuk representasi yang sama dan ubah saat representasi tersebut berubah. Contoh ini menggunakan dua revisi eksplisit dengan konten tetap. Header ETag.

Tag kuat mengidentifikasi representasi tertentu; tag lemah memiliki awalan W/ dan dapat menggambarkan konten yang secara semantik setara meski byte-nya berbeda. Perbandingan conditional GET dengan If-None-Match menggunakan perbandingan lemah, sehingga W/"doc-v1" cocok dengan "doc-v1". Permintaan juga dapat memuat daftar tag atau wildcard tunggal *. RFC 9110, If-None-Match.

Last-Modified menunjukkan kapan dokumen berubah. Gunakan waktu modifikasi yang ditentukan secara konsisten dan memperhitungkan komponen dokumen yang relevan, bukan waktu setiap permintaan. Nilainya tidak boleh lebih baru daripada Date pada respons. RFC 9110, pembuatan Last-Modified.

Contoh ini menetapkan waktu publikasi ilustratif untuk dua revisi sintetisnya: tengah hari GMT pada 1 dan 2 Januari 2024. Tanggal tersebut adalah data contoh. Untuk alur kerja penerbitan nyata, gunakan metadata publikasi yang terpelihara. Last-Modified kurang presisi daripada ETag untuk mendeteksi perbedaan konten dan dapat menjadi alternatif yang berguna. Header Last-Modified.

Pilih kebijakan cache secara terpisah

Validator menjawab apakah salinan yang tersimpan masih cocok. Kebijakan kesegaran menentukan kapan cache perlu bertanya. Tutorial ini mengirim Cache-Control: no-cache, yang mengizinkan penyimpanan tetapi mengharuskan validasi berhasil sebelum digunakan kembali. Dengan begitu, perilaku permintaan berulang mudah diamati. no-store melarang penyimpanan, sedangkan max-age mengizinkan penggunaan kembali selama respons masih segar. RFC 9111, direktif cache respons.

Untuk dokumen publik yang dapat menoleransi jeda singkat sebelum pembaruan terlihat, kebijakan seperti max-age=60 mungkin sesuai. Itu adalah pilihan editorial ilustratif: tentukan interval berdasarkan kebutuhan pembaruan dokumen. Respons yang masih segar di cache dapat digunakan kembali tanpa menghubungi server, sehingga setiap kunjungan berulang tidak harus menghasilkan 304. RFC 9111, kesegaran.

Jalankan contoh lokal lengkap di Mac Anda

Buka Terminal dan periksa prasyarat dengan perintah berikut. Gunakan Python 3.9 atau yang lebih baru karena program ini menggunakan str.removeprefix. Jika python3 tidak tersedia, instal Python 3 sebelum melanjutkan. Tidak diperlukan paket Python pihak ketiga.

python3 --version
curl --version

Di folder kerja baru, simpan program lengkap berikut sebagai server.py menggunakan editor kode teks biasa. Program ini hanya menyajikan /page.txt, mengimplementasikan GET dan HEAD, serta memilih revisi dokumen dari argumen baris perintahnya. BaseHTTPRequestHandler milik Python mengarahkan permintaan ke metode yang sesuai dan menyediakan helper untuk menulis respons. Dokumentasi server HTTP Python.

import argparse
import re
from datetime import datetime, timezone
from email.utils import format_datetime, parsedate_to_datetime
from http.server import BaseHTTPRequestHandler, HTTPServer

parser = argparse.ArgumentParser()
parser.add_argument("version", type=int, choices=(1, 2))
args = parser.parse_args()

BODY = {
    1: b"Paper Shapes\nA circle and a square.\n",
    2: b"Paper Shapes\nA circle, a square, and a triangle.\n",
}[args.version]
ETAG = '"doc-v%d"' % args.version
MODIFIED = datetime(2024, 1, args.version, 12, tzinfo=timezone.utc)
LAST_MODIFIED = format_datetime(MODIFIED, usegmt=True)
TOKEN = r'(?:W/)?"[\x21\x23-\x7e\x80-\xff]*"'
TAG_LIST = re.compile(rf'[ \t]*(?:{TOKEN})?(?:[ \t]*,[ \t]*(?:{TOKEN})?)*[ \t]*')

def tag_matches(value):
    if value.strip() == "*":
        return True
    if not TAG_LIST.fullmatch(value):
        return False
    return any(tag.removeprefix("W/") == ETAG
               for tag in re.findall(TOKEN, value))

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.serve_document()

    def do_HEAD(self):
        self.serve_document()

    def serve_document(self):
        if self.path != "/page.txt":
            self.send_error(404)
            return
        not_modified = False
        tags = self.headers.get_all("If-None-Match")
        if tags is not None:
            not_modified = tag_matches(",".join(tags))
        else:
            dates = self.headers.get_all("If-Modified-Since", [])
            if len(dates) == 1:
                try:
                    since = parsedate_to_datetime(dates[0])
                    if since.tzinfo is None:
                        since = since.replace(tzinfo=timezone.utc)
                    not_modified = MODIFIED <= since
                except (TypeError, ValueError, OverflowError):
                    pass
        self.send_response(304 if not_modified else 200)
        self.send_header("ETag", ETAG)
        self.send_header("Cache-Control", "no-cache")
        if not not_modified:
            self.send_header("Last-Modified", LAST_MODIFIED)
            self.send_header("Content-Type", "text/plain; charset=utf-8")
            self.send_header("Content-Length", str(len(BODY)))
        self.end_headers()
        if not not_modified and self.command == "GET":
            self.wfile.write(BODY)

if __name__ == "__main__":
    HTTPServer(("127.0.0.1", 8000), Handler).serve_forever()

Ini adalah demonstrasi lokal untuk dua validator yang disebutkan, bukan tumpukan penyajian HTTP yang lengkap. Konversi tanggalnya menggunakan parser tanggal email Python yang permisif; implementasi yang diterapkan sebaiknya menggunakan validasi tanggal khusus HTTP dan penanganan permintaan bersyarat. Dokumentasi Python menjelaskan masukan yang diterima parser tersebut dan pengecualiannya. Utilitas tanggal email Python.

Di Terminal, pindah ke folder yang berisi server.py dan mulai revisi 1. Biarkan jendela tersebut tetap berjalan. Jika port 8000 sudah digunakan, pilih port lain yang tersedia di program dan gunakan port yang sama di setiap URL pengujian.

python3 server.py 1

Uji konten yang tidak berubah, konten yang berubah, dan prioritas

Buka jendela Terminal lainnya. Permintaan berikut menampilkan header respons dan isi yang dikembalikan. Opsi -i menyertakan header, -H memberikan header permintaan, dan --max-time membatasi durasi setiap transfer. Manual curl.

Dapatkan respons awal

curl --max-time 5 -sS -i http://127.0.0.1:8000/page.txt

Harapkan 200 OK, teks halaman sintetis, ETag: "doc-v1", dan Last-Modified: Mon, 01 Jan 2024 12:00:00 GMT. Catat nilai yang benar-benar dikembalikan saat menerapkan prosedur ini pada dokumen lain; validator berlaku untuk respons tertentu tersebut.

Validasi ulang dengan setiap validator

curl --max-time 5 -sS -i \
  -H 'If-None-Match: "doc-v1"' \
  http://127.0.0.1:8000/page.txt

curl --max-time 5 -sS -i \
  -H 'If-Modified-Since: Mon, 01 Jan 2024 12:00:00 GMT' \
  http://127.0.0.1:8000/page.txt

Kedua permintaan seharusnya mengembalikan 304 Not Modified tanpa teks halaman. Respons tetap menyertakan ETag dan Cache-Control; helper respons Python menambahkan Date. Mengulangi permintaan pertama dengan W/"doc-v1" juga seharusnya mengembalikan 304. Contoh ini mengurai tag yang diapit tanda kutip alih-alih memisahkannya secara membabi buta berdasarkan koma, karena koma dapat muncul di dalam tag.

Buktikan bahwa ETag diprioritaskan

curl --max-time 5 -sS -i \
  -H 'If-None-Match: "older-revision"' \
  -H 'If-Modified-Since: Wed, 03 Jan 2024 12:00:00 GMT' \
  http://127.0.0.1:8000/page.txt

Harapkan 200 OK dengan dokumen. Tag tersebut tidak cocok, jadi tanggal yang lebih baru tidak boleh memicu 304. Permintaan yang sengaja saling bertentangan ini mendeteksi implementasi yang keliru menggabungkan kedua pengujian.

Simulasikan penerbitan revisi berikutnya

Di jendela server, tekan Control-C, lalu mulai revisi 2. Jangan ubah URL permintaan.

python3 server.py 2

Ulangi dua permintaan validasi ulang sebelumnya yang memuat validator revisi 1. Keduanya kini seharusnya menerima 200, kalimat yang diperbarui dan menyebutkan segitiga, ETag: "doc-v2", serta tanggal modifikasi 2 Januari. Mengulangi permintaan dengan If-None-Match: "doc-v2" seharusnya mengembalikan 304.

Hasil ini memberikan urutan penerimaan: unduhan awal, validasi saat tidak ada perubahan, validator yang bertentangan, dokumen yang berubah, lalu validasi tanpa perubahan untuk revisi baru. Contoh ini telah diuji dengan 24 pemeriksaan lokal untuk kedua revisi, termasuk tag lemah, daftar tag, pencocokan wildcard, HEAD, dan jalur yang tidak ditemukan.

Mengapa permintaan browser dan Google bisa tampak berbeda

Browser biasanya mengelola validator HTTP melalui cache-nya. Algoritme WHATWG Fetch menambahkan validator tersimpan saat melakukan validasi ulang dan, setelah 304 berhasil, memperbarui header respons yang tersimpan lalu mengembalikan respons tersebut. Akibatnya, JavaScript aplikasi dapat menerima respons 200 yang dapat digunakan beserta isi dokumen, meskipun validasi ulang jaringan menerima 304. Standar Fetch, pengambilan melalui jaringan HTTP atau cache.

Saat mendiagnosis perilaku server, mulailah dengan permintaan curl eksplisit di atas. Memberikan header bersyarat secara manual di JavaScript browser mengubah pengujian: Fetch mengalihkan permintaan dengan cache default yang memuat header tersebut ke no-store. Biarkan browser mengelola cache-nya saat menguji validasi ulang browser biasa. Standar Fetch, mode cache permintaan.

Infrastruktur perayapan Google mendukung kedua pasangan validator dan merekomendasikan ETag karena menghindari masalah pemformatan tanggal. Dokumentasinya juga menyatakan bahwa direktif cache HTTP lainnya tidak didukung oleh infrastruktur tersebut. Pilih kebijakan cache browser secara terpisah dari ekspektasi tentang perilaku perayapan ulang Google. Ikhtisar perayap dan pengambil Google.

Google tidak mengirim header bersyarat pada setiap upaya perayapan. Dokumentasi Search-nya menjelaskan bahwa 304 mengizinkan penggunaan kembali konten yang sebelumnya dirayapi, sedangkan konten yang lebih baru dapat menerima 200 beserta isi yang diperbarui. Pastikan permintaan tanpa syarat tetap berfungsi; permintaan perayap tanpa validator bukan bukti bahwa implementasi Anda gagal. Memecahkan masalah error perayapan Google Search.

Sebelum menerapkan pola ini pada dokumen yang dipublikasikan, ulangi urutan penerimaan di URL penyajiannya yang sebenarnya. Pastikan revisi yang tidak berubah mempertahankan validatornya, suntingan memperbaruinya, dan 304 tidak membawa isi dokumen. Hasil yang diinginkan adalah penggunaan kembali dokumen yang benar sekaligus memastikan konten yang berubah tetap dapat diambil.

Sumber