清新脫俗的 Web 服務器 Caddy

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

清新脫俗的 Web 服務器 Caddy

做爲新興 Web 服務器,Caddy 提供了不少簡單易用的功能而沒有歷史的包袱,其默認支持而且能幫你自動配置 HTTP/二、HTTPS,對於 IPV六、WebSockets 都有很好的支持。基於 Go 編寫的 Caddy 天生對於多核具備很好的支持,而且其豐富的插件系統提供了文件管理、文件上傳、基於 MarkDown 的博客系統等等開箱即用的擴展功能。
咱們能夠在官方下載界面選擇你須要的插件功能定製個性化二進制文件,下載完畢以後便可以使用caddy命令直接運行。其默認監聽 2015 端口,在瀏覽器中打開 http://localhost:2015 便可以查看其運行狀況。咱們也能夠經過-conf參數指定配置文件:html

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

下文咱們會詳細介紹 Caddyfile 的配置語法,Caddy 的一大特性在於其使用所謂指令(Directives)來描述功能進行配置,相較於 Nginx 或者 Apache 其配置會簡化不少。若是咱們但願支持多配置文件,可使用import指令:node

import config/common.conf

或者引入整個文件夾:git

import ../vhosts/*

Reference

站點配置

典型的 Caddyfile 配置文件以下所示:正則表達式

localhost

gzip
browse
websocket /echo cat
ext    .html
log    /var/log/access.log
proxy  /api 127.0.0.1:7005
header /api Access-Control-Allow-Origin *

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

localhost:2020

以後的每一行都是官方提供的指令,譬如咱們須要爲服務器添加 gzip 壓縮支持,只須要直接添加一個指令:後端

localhost:2020
gzip

咱們可使用bind指令來指定當前服務器綁定的地址:api

bind host
bind 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 PUT
curl \
  -T /etc/os-release \
  https://127.0.0.1/web/path/from-release

或者同時上傳多個文件:

# HTTP POST
curl \
  -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: someonerole: member ,咱們的配置項以下:

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

該中間件會拒絕全部 role: member 的訪問,除了用戶名爲 someone 的用戶。而另外一個 role: admin 或者 role: foo 的用戶則能夠正常訪問。咱們能夠經過三種方式來提交令牌:

Method Format
Authorization Header Authorization: Bearer token
Cookie "jwt_token": token
URL 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
}
相關文章
相關標籤/搜索