# 📖 Pingclairfile

Pingclairfile 使用 Caddyfile 語言，由可省略的全域選項區塊與網站區塊組成。只使用支援語法的 Caddyfile 可直接載入。[指令參考](/zh-TW/reference/directives/)說明各指令的作用。

📌 本頁描述 **v0.2.2**。

## 🔤 詞法規則

| 規則 | 說明 |
| --- | --- |
| 註解 | 從 `#` 到行尾。 |
| 引號 | 有空白的值以 `"` 括起，解析前移除引號。 |
| 區塊 | 開啟區塊的 `{` 必須結束該行；`route { respond "hi"` 會被拒絕。 |
| 時間長度 | `30s`、`5m`、`1h` 必須有單位。 |
| 大小 | `kb`、`mb`、`gb`、`tb` 使用 1000 的冪；`kib`、`mib`、`gib`、`tib` 使用 1024 的冪。`10MB` 是 10,000,000 位元組。 |
| 大小寫 | 指令與選項名稱小寫。 |
| 佔位符 | `{host}`、`{path}`、`{args[0]}`、`{block}` 等在指令支援的位置展開。 |

## 🌐 位址

網站位址決定連接埠及是否使用 HTTPS。

```text
example.com {          # HTTPS on 443 with a public certificate; 80 redirects
example.com:8443 {     # HTTPS on 8443: a host with a port is still HTTPS
localhost:8080 {       # HTTPS on 8080, from the internal authority
:8080 {                # Plaintext HTTP on 8080, for any host.
http://example.com {   # Plaintext HTTP on http_port.
http://[::1]:8080 {    # Plaintext HTTP for the host [::1].
*.example.com {        # One label, such as a.example.com.
```

帶連接埠但沒有 scheme 的具名網站仍是 HTTPS；明文請加 `http://`。只有 scheme 而沒有連接埠的位址使用全域 `http_port` 或 `https_port`。方括號 IPv6 會命名網站，不再成為任意 Host 的後備；錯誤括號或 IPv6 會被拒絕。`http://0.0.0.0:8080` 是該連接埠的全域匹配。主機名稱比較忽略大小寫與尾端的點。

### 🔌 一個連接埠共用一個監聽器

同一連接埠的網站共用 socket，以 Host 區分。明確 IP 位址與全介面網站合併後，前者也能從其他介面以相符 Host 存取；需要隔離時請用獨立連接埠。載入時會記錄合併。`0.0.0.0` 與 `[::]` 合併後使用 IPv6。

若同一連接埠的 socket 政策不一致，設定會被拒絕：例如 `bind`／`default_bind` 限制的網站與全介面網站、明文與 TLS，或只有其中一個要求 PROXY protocol。

## 🧭 匹配器

匹配器可直接使用 `/api/*`，或先宣告 `@name` 再引用。

```caddyfile
example.com {
    @api path /api/*
    header @api Cache-Control "no-store"
    handle /assets/* {
        file_server ./assets
    }
}
```

- ASCII 大小寫不影響路徑比較；需要區分時使用 `path_regexp`。
- 百分比編碼解碼一次。`/secret%21` 匹配 `path /secret!`；空白請以 `"/a b"` 撰寫匹配路徑。
- 開頭的 `*` 匹配任意深度的後綴，兩端 `*` 匹配子字串，中間的 `*` 不跨越路徑片段。`?`、`[…]` 與反斜線是字面字元。
- 大括號是字面值；擷取群組請用 `path_regexp`。
- `client_ip` 匹配套用受信任代理政策後的用戶端；`remote_ip` 匹配連線對端。無效 IP 範圍在載入時被拒絕。
- 這兩個匹配器也可以用 Caddy 的 `private_ranges` 關鍵字取代整串範圍；它會在載入時展開成與 `trusted_proxies static private_ranges` 相同的六個範圍：`192.168.0.0/16`、`172.16.0.0/12`、`10.0.0.0/8`、`127.0.0.1/8`、`fd00::/8` 與 `::1`。因此 `@local client_ip private_ranges` 會匹配私有或回送位址上的用戶端。
- `handle`、`handle_path` 與 `route` 在區塊前最多接受一個 `*`、以 `/` 開頭的路徑或 `@name`。`*.php` 等裸字串會被拒絕。

## 🏷️ 位址佔位符

| 佔位符 | 值 |
| --- | --- |
| `{client_ip}`、`{http.request.client_ip}` | 受信任代理政策套用後的用戶端。 |
| `{remote_host}`、`{http.request.remote.host}` | 連線對端位址。 |
| `{remote_port}`、`{http.request.remote.port}` | 連線對端連接埠。 |
| `{remote}`、`{http.request.remote}` | 對端的 `host:port`。 |

`{remote_ip}` 不是支援的佔位符，會被拒絕。要轉送用戶端位址，請寫 `header_up X-Real-IP {client_ip}`。

## 🧭 哪一條路由回應

依指令順序選第一條匹配路由：`redir`、`handle` 與 `route` 在 `respond` 之前；`respond` 在 `reverse_proxy`、`php_fastcgi` 與 `file_server` 之前。以下 `/assets/a.txt` 會得到 `hello`：

```caddyfile
example.com {
    root * /srv
    file_server /assets/*
    respond "hello" 200
}
```

同一指令的單一路徑先比較移除尾端 `*` 後的長度，較長者優先；`/foo` 排在 `/foo*` 之前，其餘保留檔案順序。多路徑或無路徑匹配器排在單一路徑之後。等長不同路徑在 Caddy 依字母排序，此處保留書寫順序。

帶匹配器的中介指令會保護或修飾相符的回應路由。需要較窄路由優先時，使用互斥 `handle`、全域 `order`，或保留書寫順序的 `route`。

## 🧩 片段與匯入

以 `(name)` 宣告片段，透過 `import name` 引入：

```caddyfile
(proxied) {
    https://{args[0]} {
        encode zstd gzip
        {block}
    }
}

import proxied example.com {
    reverse_proxy 127.0.0.1:3000
}
```

`{args[0]}` 是第一個參數，`{block}` 插入呼叫端區塊，沒有提供時展開為空。具名子區塊使用 `{blocks.<name>}`。

## 🧰 命令列工具

- `pingclair validate` 編譯、讀取 TLS 檔案並檢查金鑰配對。
- `pingclair adapt --pretty` 經相同驗證後印出 JSON。
- `pingclair fmt` 使用每層一個 tab；未格式化的輸入以狀態碼 1 結束。

[命令列](/zh-TW/reference/command-line/)列出所有旗標。

## 🚫 不屬於這個語言的部分

尚未實作的名稱在載入時被指名拒絕。[專案狀態](/zh-TW/project/status/)列出支援範圍與差異；[CHANGELOG](https://github.com/dorianverlaine/pingclair/blob/main/CHANGELOG.md)提供 0.2.0 變更的依據。
