清新脫俗的 Web 伺服器 Caddy

清新脫俗的 Web 伺服器 Caddy 從屬於筆者的服務端應用程序開發與系統架構,我司之前一直使用 Nginx,不過其配置包括一些特性支持相較於 Caddy 略顯複雜,可以參考筆者的 Nginx 基本配置備忘。

清新脫俗的 Web 伺服器 Caddy

作為新興 Web 伺服器,Caddy 提供了很多簡單易用的功能而沒有歷史的包袱,其默認支持並且能幫你自動配置 HTTP/2、HTTPS,對於 IPV6、WebSockets 都有很好的支持。基於 Go 編寫的 Caddy 天生對於多核具有很好的支持,並且其豐富的插件系統提供了文件管理、文件上傳、基於 MarkDown 的博客系統等等開箱即用的擴展功能。

我們可以在官方下載界面選擇你需要的插件功能定製個性化二進位文件,下載完畢之後即可以使用caddy命令直接運行。其默認監聽 2015 埠,在瀏覽器中打開 http://localhost:2015 即可以查看其運行情況。我們也可以通過-conf參數指定配置文件:

$ caddy -conf="/path/to/Caddyfile"

下文我們會詳細介紹 Caddyfile 的配置語法,Caddy 的一大特性在於其使用所謂指令(Directives)來描述功能進行配置,相較於 Nginx 或者 Apache 其配置會簡化很多。如果我們希望支持多配置文件,可以使用import指令:

import config/common.conf

或者引入整個文件夾:

import ../vhosts/*

Reference

  • 新興的web伺服器caddy

站點配置

典型的 Caddyfile 配置文件如下所示:

localhostgzipbrowsewebsocket /echo catext .htmllog /var/log/access.logproxy /api 127.0.0.1:7005header /api Access-Control-Allow-Origin *

每個 Caddyfile 的第一行必須描述其服務的地址:

localhost:2020

之後的每一行都是官方提供的指令,譬如我們需要為伺服器添加 gzip 壓縮支持,只需要直接添加一個指令:

localhost:2020gzip

我們可以使用bind指令來指定當前伺服器綁定的地址:

bind hostbind 127.0.0.1

虛擬主機

如果我們需要配置獨立的虛擬主機,需要將配置信息移動到站點名之後的大括弧內:

mysite.com { root /www/mysite.com}sub.mysite.com { root /www/sub.mysite.com gzip log ../access.log}

注意,左括弧必須與站點名位於同一行,而右括弧則是必須單起一行。對於共享相同配置的站點,我們可以用逗號來聲明多個站點:

localhost:2020, https://site.com, http://mysite.com { ...}

當 Caddy 檢測到站點名符合下列條件時會自動使用 Let"s Encrypt 腳本來為站點添加 HTTPS 支持,並且自動監聽 80 與 443 埠:

  • 主機名不可為空並且沒有 localhost 與 IP 地址

  • 埠號未明確指定為 80

  • Scheme 未明確指定為 http

  • TLS 未被關閉

  • 未指明證書

緩存設置

我們可以通過 expires 指令來設置相較於請求時間的過期頭,其基本語法為:

expires { match regex duration}

regex 是用於匹配請求文件的正則表達式,而 duration 則是 0y0m0d0h0i0s 格式的描述時長的表達式,常用的匹配語法為:

expires { match some/path/.*.css$ 1y # expires css files in some/path after one year match .js$ 1m # expires js files after 30 days match .png$ 1d # expires png files after one day match .jpg$ 1h # expires jpg files after one hour match .pdf$ 1i # expires pdf file after one minute match .txt$ 1s # expires txt files after one second match .html$ 5i30s # expires html files after 5 minutes 30 seconds}

反向代理

proxy 指令提供了基本的反向代理功能,其支持 Health Checks 以及 Failovers,並且支持對於 WebSocket 的反向代理。其基本語法為:

proxy from to

from 即是請求匹配的基本路徑,to 則是請求轉發到的端點地址。我們也可以使用更複雜的配置:

proxy from to... { policy random | least_conn | round_robin | ip_hash fail_timeout duration max_fails integer try_duration duration try_interval duration health_check path health_check_interval interval_duration health_check_timeout timeout_duration header_upstream name value header_downstream name value keepalive number without prefix except ignored_paths... upstream to insecure_skip_verify preset}

將所有發往 /api 的請求轉發到後端系統:

proxy /api localhost:9005

使用隨機策略將所有請求負載均衡到三個後端伺服器:

proxy / web1.local:80 web2.local:90 web3.local:100

使用循環機制:

proxy / web1.local:80 web2.local:90 web3.local:100 { policy round_robin}

添加健康檢查並且透明轉發主機名、地址與上游:

proxy / web1.local:80 web2.local:90 web3.local:100 { policy round_robin health_check /health transparent}

轉發 WebSocket 請求:

proxy /stream localhost:8080 { websocket}

避免對於部分靜態請求的轉發:

proxy / backend:1234 { except /static /robots.txt}

WebSocket

Caddy 內建支持 WebSocket 連接,其允許客戶端發起 WebSocket 連接的時候客戶端執行某個簡單的指令,其基本語法如下:

websocket [path] command

我們可以在客戶端內構建簡單的 WebSocket 客戶端請求:

if (window.WebSocket != undefined) { var connection = new WebSocket("ws://localhost:2015/echo"); connection.onmessage = wsMessage; connection.onopen = wsOpen; function wsOpen(event) { connection.send("Hello World"); } function wsMessage(event) { console.log(event.data); }}function wsMessage(event) { console.log(event.data);}

然後在服務端接收該請求並且將客戶端輸入的內容返回:

var readline = require("readline");var rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: false});rl.on("line", function(line){ console.log(line);})

最後 Caddy 文件配置如下:

websocket /echo "node tmp.js"

文件上傳

我們可以使用 Caddy 提供的擴展指令 upload 來搭建簡單的文件上傳伺服器:

upload path { to "directory" yes_without_tls filenames_form none|NFC|NFD filenames_in u0000–uff00 [u0000–uff00| …] hmac_keys_in keyID_0=base64(binary) [keyID_n=base64(binary) | …] timestamp_tolerance 0..32 silent_auth_errors}

直接添加如下配置:

upload /web/path { to "/var/tmp"}

然後使用 curl 上傳文件:

# HTTP PUTcurl -T /etc/os-release https://127.0.0.1/web/path/from-release

或者同時上傳多個文件:

# HTTP POSTcurl -F gitconfig=@.gitconfig -F id_ed25519.pub=@.ssh/id_ed25519.pub https://127.0.0.1/web/path/

我們也可以使用指令來移動或者刪除這些文件:

# MOVE is "mv"curl -X MOVE -H "Destination: /web/path/to-release" https://127.0.0.1/web/path/from-release# DELETE is "rm -r"curl -X DELETE https://127.0.0.1/web/path/to-release

訪問控制

許可權認證

Basic Auth

Caddy 內建支持 HTTP Basic Authentication,能夠強制用戶使用指定的用戶名與密碼訪問某些目錄或者文件。其基本配置語法如下:

basicauth username password { resources}

如果我們希望為 /secret 目錄下所有文件添加許可權認證:

basicauth /secret Bob hiccup

也可以指明某些文件:

basicauth "Mary Lou" milkshakes { /notes-for-mary-lou.txt /marylou-files /another-file.txt}

JWT

jwt 指令是 Caddy 的擴展功能,我們需要在官網上選擇添加該功能並且獲取編譯後的版本,其基本語法為:

jwt path// 或者jwt { path resource allow claim value deny claim value}

譬如我們預設了兩個令牌:user: someone 與 role: member ,我們的配置項如下:

jwt { path /protected deny role member allow user someone}

該中間件會拒絕所有 role: member 的訪問,除了用戶名為 someone 的用戶。而另一個 role: admin 或者 role: foo 的用戶則可以正常訪問。我們可以通過三種方式來提交令牌:

MethodFormatAuthorization HeaderAuthorization: Bearer tokenCookie"jwt_token": tokenURL Query Parameter/protected?token=token

跨域請求

我們可以使用 cors 指令來為伺服器添加跨域請求的能力:

cors / { origin http://allowedSite.com origin http://anotherSite.org https://anotherSite.org methods POST,PUT allow_credentials false max_age 3600 allowed_headers X-Custom-Header,X-Foobar exposed_headers X-Something-Special,SomethingElse}

我們也可以添加 JSONP 的支持:

jsonp /api/status

譬如某個端點返回類似於{"status":"ok"}這樣的 JSON 響應,請求格式如下:

$ wget "http://example.com/api/status?callback=func3022933"

其會返回如下格式的響應:

func3022933({"status":"ok"});

地址過濾

我們可以使用 ipfilter 指令來基於用戶的 IP 來允許或者限制用戶訪問,其基本語法為:

ipfilter paths... { rule block | allow ip list or/and range of IPs... country countries ISO codes... database db_path blockpage block_page strict}

僅允許某個 IP 訪問:

ipfilter / { rule allow ip 93.168.247.245}

禁止兩段 IP 地址與某個具體的 IP 訪問,並且向他們返回默認界面:

ipfilter / { rule block ip 192.168.0.0/16 2E80::20:F8FF:FE31:77CF/16 5.23.4.24 blockpage /local/data/default.html}

僅允許來自法國的視野固定 IP 地址的客戶端訪問:

ipfilter / { rule allow country FR database /local/data/GeoLite2-Country.mmdb ip 99.23.4.24 2E80::20::FEF1:91C4}

僅支持來自美國與日本的客戶端訪問:

ipfilter / { rule allow country US JP database /local/data/GeoLite2-Country.mmdb}

禁止來自美國與日本的客戶端對於 /notglobal 與 /secret 的訪問,直接返回默認地址:

ipfilter /notglobal /secret { rule block country US JP database /local/data/GeoLite2-Country.mmdb blockpage /local/data/default.html}

請求限流

我們可以使用 ratelimit 這個擴展指令來為資源添加請求限流的功能,對於單資源可以使用如下指令:

ratelimit path rate burst unit// 限制客戶端每秒最多對於 /r 資源發起兩個請求,突發上限最多為 3 個ratelimit /r 2 3 second

對於多資源可以使用如下指令:

ratelimit rate burst unit { resources}// 限制對於資源文件的訪問時長為 2 分鐘ratelimit 2 2 minute { /foo.html /dir}

推薦閱讀:

python如何抓取本地數據包?
PHP寫的API如何防止拒絕服務攻擊?
怎樣通俗的講解 PHP 和 Apache 的關係?
Web 應用、服務是如何工作的?

TAG:Apache | Nginx | 服务器 |