Map #

Direktif map adalah salah satu fitur pemrograman deklaratif paling canggih yang ditawarkan oleh server web Caddy. Direktif ini memungkinkan kita untuk memetakan nilai dari variabel input atau placeholder Caddy (seperti alamat IP {remote_host}, header permintaan {header.Origin}, atau jalur URL {path}) ke satu atau lebih variabel output baru menggunakan tabel pencarian (lookup table) yang kita definisikan. Hasil pemetaan ini kemudian disimpan ke dalam variabel kustom yang dapat kita panggil di bagian konfigurasi Caddyfile berikutnya menggunakan sintaks placeholder standar (seperti {nama_variabel}). Dengan menggunakan map, kita dapat menghindari penulisan ratusan baris logika percabangan kondisional (if/else atau nested matchers) yang rumit dan rentan kesalahan, menjaga konfigurasi Caddyfile kita tetap bersih, modular, dan sangat mudah dipelihara pada skala produksi. Kita akan membahas secara mendalam konsep kerja direktif map, mempraktikkan pemetaan banyak variabel sekaligus (multiple outputs), menerapkan pola pencocokan ekspresi reguler (regex), serta mengeksplorasi use case termaju seperti perutean klaster backend dinamis, pengelolaan status pemeliharaan (maintenance mode), dan konfigurasi header keamanan dinamis.


Konsep Dasar dan Sintaksis Direktif map #

Sebelum kita menggunakan direktif map, kita harus memahami filosofi di balik keberadaannya. Di server web tradisional, untuk memetakan rute berdasarkan kondisi tertentu—misalnya, mengarahkan lalu lintas ke server backend yang berbeda tergantung pada subdomain penyewa (tenant)—kita biasanya harus menuliskan banyak blok server atau puluhan aturan pencocokan bersarang (nested matchers):

// ANTI-PATTERN: Menuliskan matcher manual berulang untuk setiap kondisi rute
// Cara ini sangat buruk karena mempersulit proses penambahan tenant baru di masa depan
@tenant_acme host acme.example.com
reverse_proxy @tenant_acme acme-service:8080

@tenant_globex host globex.example.com
reverse_proxy @tenant_globex globex-service:8080

Dengan direktif map, kita memisahkan logika perutean dari data pemetaan. Kita mendefinisikan satu lookup table pusat yang memetakan domain ke alamat backend, kemudian memanggil alamat tersebut secara dinamis.

Sintaks dasar direktif map adalah sebagai berikut:

map <input> <outputs...> {
    <input_value> <output_values...>
    default       <default_output_values...>
}
  • <input>: Sumber data input yang ingin dievaluasi. Umumnya berupa placeholder Caddy seperti {host}, {path}, atau {header.X-API-Version}.
  • <outputs...>: Satu atau lebih nama variabel kustom baru yang dibungkus dengan kurung kurawal, yang akan menampung hasil pemetaan.
  • default: Nilai cadangan (fallback) yang akan diisikan ke variabel output jika nilai input klien tidak cocok dengan aturan mana pun di tabel pencarian.

Contoh Implementasi Dasar #

# Contoh: Perutean Backend Dinamis Berdasarkan Versi API
api.example.com {
    # 1. Definisikan pemetaan dari header X-API-Version ke alamat backend
    map {header.X-API-Version} {backend_address} {
        "v1"    "api-v1-service:8081"
        "v2"    "api-v2-service:8082"
        "v3"    "api-v3-service:8083"
        
        # Sediakan nilai default jika header tidak dikirim oleh klien
        default "api-v2-service:8082"
    }

    # 2. Panggil variabel hasil pemetaan di reverse_proxy
    # Caddy akan mengevaluasi {backend_address} secara dinamis untuk setiap request
    reverse_proxy {backend_address}
}

Pemetaan Banyak Variabel Output Sekaligus (Multiple Outputs) #

Salah satu keunggulan terbesar direktif map Caddy adalah kemampuannya untuk menghasilkan beberapa variabel output secara paralel dari satu kali evaluasi input tunggal. Ini sangat berguna ketika kita ingin mengatur beberapa konfigurasi respons yang saling berhubungan.

Misalnya, kita ingin mendeteksi preferensi bahasa pengguna dari header Accept-Language dan secara bersamaan menentukan kode bahasa untuk tag HTML (lang) serta arah penulisan teks halaman (dir - kiri-ke-kanan atau kanan-ke-kiri):

# Contoh: Internasionalisasi Dinamis dengan Multiple Outputs
example.com {
    # Memetakan header Accept-Language ke dua variabel output sekaligus
    map {header.Accept-Language} {bahasa_klien} {arah_teks} {
        # Kita menggunakan prefiks tilde (~) untuk mengaktifkan pencocokan regex
        ~^id    "id"  "ltr"    # Indonesia -> Left to Right
        ~^ar    "ar"  "rtl"    # Arab -> Right to Left
        ~^he    "he"  "rtl"    # Ibrani -> Right to Left
        ~^ja    "ja"  "ltr"    # Jepang -> Left to Right
        
        # Fallback jika tidak ada bahasa yang cocok
        default "en"  "ltr"    # Inggris -> Left to Right
    }

    # Gunakan variabel hasil pemetaan untuk menyusun header respons HTTP
    header Content-Language {bahasa_klien}
    header X-Text-Direction {arah_teks}

    reverse_proxy localhost:8080
}

Pada contoh di atas, ketika Caddy mengevaluasi header Accept-Language yang bernilai ar-EG (Arab Mesir), Caddy mencocokkan ekspresi regex ~^ar dan langsung menyisipkan nilai "ar" ke variabel {bahasa_klien} dan nilai "rtl" ke variabel {arah_teks} dalam satu waktu komputasi yang sangat cepat.


Aturan Pencocokan di Lookup Table: Statis dan Ekspresi Reguler #

Caddy memberikan fleksibilitas tinggi dalam cara lookup table mencocokkan nilai input klien. Ada tiga cara utama pencocokan:

1. Pencocokan Statis Persis (Exact Match) #

Nilai input dicocokkan secara persis (case-sensitive). Ini adalah metode bawaan yang paling cepat karena menggunakan perbandingan string langsung.

"dashboard.example.com" "backend-dashboard:3000"

2. Pencocokan Ekspresi Reguler (Regex Match) #

Jika nilai lookup diawali dengan karakter tilde (~), Caddy akan memperlakukan sisa string tersebut sebagai ekspresi reguler.

~(?i)mobile|android|iphone  "mobile-content"

Grup penangkap (capturing groups) dalam regex tidak dapat digunakan secara langsung di dalam kolom output peta, namun kita dapat menggunakan pencocokan regex untuk memetakan kategori.

3. Pencocokan Sub-domain Wildcard #

Caddy membolehkan kita menggunakan karakter asterisk (*) di awal lookup value untuk mencocokkan subdomain secara dinamis:

*.example.com "subdomain-backend:8080"

Diagram Alur Kerja Evaluasi Direktif map #

Untuk memahami bagaimana Caddy mengevaluasi lookup table dari request masuk hingga menentukan rute, mari kita perhatikan diagram flowchart berikut:

flowchart TD
    A["1. Request HTTP Masuk di Caddy"] --> B["2. Caddy membaca nilai input variabel\n(e.g., {header.Origin} atau {host})"]
    B --> C{"3. Apakah input kosong?"}
    
    C -- "Ya" --> D["4. Gunakan Nilai Default\n(Aturan fallback)"]
    C -- "Tidak" --> E{"5. Apakah cocok dengan baris Lookup Table?\n(Pencocokan statis persis)"}
    
    E -- "Ya" --> F["6. Ambil nilai output yang didefinisikan di baris tersebut"]
    E -- "Tidak" --> G{"7. Apakah diawali tilde (~)?\n(Evaluasi Regex)"}
    
    G -- "Ya" --> H{"Apakah pola regex cocok?"}
    G -- "Tidak" --> D
    
    H -- "Ya" --> F
    H -- "Tidak" --> D
    
    F --> I["8. Simpan nilai ke variabel kustom\n(e.g. {backend_addr})"]
    D --> I
    
    I --> J["9. Gunakan variabel kustom di direktif Caddy berikutnya\n(e.g. reverse_proxy {backend_addr})"]

Integrasi File Eksternal untuk Skala SaaS Besar #

Ketika kita menjalankan layanan Software-as-a-Service (SaaS) multi-tenant berskala besar dengan ribuan pelanggan kustom domain, menuliskan seluruh baris pemetaan langsung di dalam file Caddyfile utama akan membuatnya sangat panjang dan sulit dikelola secara kolaboratif.

Caddy memecahkan masalah ini dengan membolehkan kita mengimpor baris pemetaan dari file teks eksternal yang diisolasi. File eksternal ini dapat diperbarui secara otomatis menggunakan skrip otomatisasi atau sistem administrasi database backend kita tanpa mengganggu struktur Caddyfile:

# Contoh: SaaS Routing dengan Database File Eksternal
example.com {
    # Petakan host permintaan klien ke upstream database eksternal
    map {host} {tenant_upstream} {
        # Kita menggunakan direktif import untuk memuat database pemetaan
        import /etc/caddy/tenants_mapping.txt
        
        # Alamat fallback default jika domain belum terdaftar
        default "default-signup-landing:8080"
    }

    reverse_proxy {tenant_upstream}
}

Format isi berkas /etc/caddy/tenants_mapping.txt adalah baris sederhana dengan pemisah spasi:

# Berkas database pemetaan domain tenant
client-a.com tenant-a-service:8081
client-b.com tenant-b-service:8082
www.client-c.org tenant-c-service:8083

Dengan struktur di atas, tim DevOps kita dapat melakukan pembaruan berkas tenants_mapping.txt secara instan, lalu memicu perintah caddy reload untuk menerapkan pemetaan baru secara aman tanpa downtime.


Pola Keamanan: Deteksi Bot & Pemblokiran Otomatis #

Kita dapat memanfaatkan direktif map untuk mendeteksi bot scraper atau web crawler nakal berdasarkan header User-Agent yang mereka kirimkan, kemudian secara otomatis memblokir akses atau memberikan respons kustom yang menghemat bandwidth server kita.

# Contoh: Mitigasi Bot Nakal Dinamis
example.com {
    # 1. Petakan header User-Agent ke status pemblokiran
    map {header.User-Agent} {is_malicious_bot} {
        # Deteksi string agen bot yang diketahui menguras sumber daya
        ~(?i)semrushbot         "block"
        ~(?i)ahrefsbot          "block"
        ~(?i)mj12bot            "block"
        ~(?i)dotbot             "block"
        ~(?i)rogue-crawler      "block"
        
        # Pengunjung manusia atau search engine baik (Google/Bing)
        default                 "allow"
    }

    # 2. Blokir jika terdeteksi bot nakal
    @blocked_request expression {is_malicious_bot} == "block"
    handle @blocked_request {
        # Kembalikan respons 403 Forbidden secara instan di edge server
        respond "Akses Ditolak: Crawler tidak diizinkan di domain ini." 403
    }

    # 3. Rute normal untuk pengunjung asli
    handle {
        root * /var/www/html
        file_server
    }
}

Use Case Produksi Termaju Lainnya #

Use Case 1: Pengelolaan Status Pemeliharaan Dinamis (Dynamic Maintenance Mode) #

Saat kita melakukan pembaruan database besar, kita sering kali perlu mengaktifkan status pemeliharaan (maintenance mode) secara instan. Namun, kita (tim developer) harus tetap dapat mengakses website untuk menguji hasil pembaruan, sementara pengguna umum harus dialihkan ke halaman pemeliharaan.

Kita dapat menggunakan map untuk memetakan alamat IP pengunjung. Jika IP adalah IP tim dev, kita beri izin akses; jika IP luar, kita lempar status pemeliharaan.

# Contoh: Maintenance Mode Dinamis berbasis IP
example.com {
    # 1. Petakan IP pengunjung ({remote_host}) ke status pemeliharaan
    map {remote_host} {maintenance_status} {
        # Daftar IP Tim Developer yang dibebaskan dari pemeliharaan
        "192.168.1.50"  "bypass"
        "203.0.113.10"  "bypass"
        "36.85.12.99"   "bypass"
        
        # Pengguna umum dialihkan ke mode pemeliharaan
        default         "maintenance"
    }

    # 2. Definisikan rute berdasarkan hasil pemetaan
    # Jika status adalah maintenance, sajikan halaman pemeliharaan statis
    @under_maintenance expression {maintenance_status} == "maintenance"
    handle @under_maintenance {
        root * /var/www/maintenance
        rewrite * /index.html
        file_server
    }

    # Jika status bypass, teruskan request ke backend aplikasi utama
    handle {
        reverse_proxy app-backend:8080
    }
}

Use Case 2: Penyesuaian Header Keamanan Dinamis Berdasarkan Rute #

Beberapa halaman di website kita—seperti halaman pembayaran sensitif—memerlukan kebijakan Content Security Policy (CSP) yang sangat ketat. Namun, halaman publik biasa (seperti blog dengan plugin media sosial eksternal) membutuhkan CSP yang lebih laks/longgar.

Kita dapat memetakan jalur URL ({path}) ke bodi aturan CSP kustom secara dinamis:

# Contoh: Konfigurasi CSP Dinamis
example.com {
    # Petakan rute ke aturan CSP
    map {path} {csp_policy} {
        # Rute admin: melarang total inline script
        ~^/admin/ "default-src 'self'; script-src 'self'"
        
        # Rute transaksi: sangat ketat
        ~^/checkout/ "default-src 'self'; connect-src 'self' api.payment-gateway.com"
        
        # Rute blog publik: izinkan plugin media sosial luar
        ~^/blog/ "default-src 'self'; script-src 'self' platform.twitter.com"
        
        # Default CSP global untuk rute lainnya
        default "default-src 'self'"
    }

    # Setel header respons keamanan menggunakan variabel dinamis hasil pemetaan
    header Content-Security-Policy {csp_policy}

    root * /var/www/html
    file_server
}

Use Case 3: A/B Testing Perutean Backend (Canary Deployments) #

Ketika kita merilis versi baru dari aplikasi backend (v2.0), kita mungkin ingin menguji stabilitasnya terlebih dahulu dengan mengalirkan 10% traffic pengguna ke server baru (Canary Server), sementara 90% pengguna tetap berada di server stabil (v1.0).

Kita dapat memanfaatkan fungsi pembuatan angka acak di Caddy yang dipadukan dengan direktif map:

# Contoh: Canary Deployment A/B Testing
example.com {
    # Kita menggunakan placeholder UUID bawaan Caddy untuk menghasilkan karakter acak.
    # Kita ambil karakter terakhir dari UUID request, lalu memetakannya
    # untuk membagi traffic secara rata berdasarkan nilai hash karakter terakhir tersebut.
    map {http.request.uuid} {upstream_server} {
        # Jika karakter terakhir UUID berakhiran 0, 1, atau 2 (~30% traffic)
        ~[0-2]$ "canary-backend:8080"
        
        # Karakter lainnya (~70% traffic) diarahkan ke server stabil
        default "stable-backend:8080"
    }

    # Tambahkan header kustom untuk analisis log di backend
    header_up X-Routing-Group {upstream_server}

    reverse_proxy {upstream_server}
}

Optimasi Performa Lookup Table di Caddy #

Direktif map dieksekusi oleh Caddy untuk setiap permintaan HTTP yang masuk. Oleh karena itu, kita harus memastikan bahwa lookup table kita dirancang secara efisien agar tidak menambah latensi pemrosesan internal server web.

Berikut adalah beberapa panduan optimalisasi performa yang wajib kita perhatikan:

  1. Prioritaskan Pencocokan Statis: Caddy mencari kecocokan baris lookup table dari atas ke bawah secara berurutan. Aturan statis persis dievaluasi jauh lebih cepat dibanding aturan ekspresi reguler (regex). Letakkan pencocokan statis di bagian atas tabel pencarian, dan pencocokan regex di bagian bawah.
  2. Hindari Regex yang Kompleks: Jika kita menggunakan regex di lookup table, pastikan regex kita sangat efisien dan tidak memicu komputasi melingkar (backtracking) yang berlebihan yang dapat meningkatkan penggunaan CPU server.
  3. Gunakan Default Value dengan Bijak: Pastikan kita selalu mendefinisikan baris default pada setiap blok map kita. Jika input klien tidak cocok dan baris default tidak ditentukan, Caddy akan mengisikan string kosong ("") ke variabel output. Hal ini dapat memicu kegagalan sistem pada direktif berikutnya (misalnya, reverse_proxy mencoba memanggil alamat kosong).

Ringkasan #

  • Pusat Logika Dinamis: Direktif map memetakan nilai variabel input/placeholder Caddy ke variabel output kustom baru menggunakan lookup table yang efisien.
  • Sentralisasi Data: Membantu kita memisahkan logika perutean dengan data pemetaan, menjaga berkas Caddyfile tetap ramping dan modular.
  • Multiple Output Variables: Mampu menghasilkan beberapa variabel output secara paralel dari satu kali evaluasi input (seperti menentukan bahasa dan arah penulisan teks).
  • Fleksibilitas Pencocokan: Mendukung pencocokan string statis persis, pencocokan ekspresi reguler (regex) menggunakan prefiks tilde (~), dan pencocokan wildcard subdomain.
  • SaaS Database Eksternal: Mendukung pemuatan tabel pemetaan massal dari berkas luar privat untuk menyederhanakan pengelolaan rute ribuan pelanggan domain.
  • Mitigasi Bot Spammer: Mengidentifikasi identitas agen bot jahat di lookup table untuk memicu pemblokiran HTTP 403 secara instan di tingkat edge server.
  • Canary Deployment: Memudahkan pengaturan A/B testing lalu lintas rute backend dinamis dengan memanfaatkan placeholder UUID acak.
  • Default Value Wajib: Selalu sertakan aturan cadangan default pada tabel pencarian guna menghindari variabel bernilai kosong yang dapat merusak alur reverse proxy.

← Sebelumnya: Templates   Berikutnya: Access Log →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact