Caddy: A Fresh and Free Web Server

Posted by blogger3 on Thu, 18 Jul 2019 01:50:54 +0200

Caddy: A Fresh and Free Web Server Subordinate to the author Server-side Application Development and System Architecture Nginx has been used in our company before, but its configuration includes some feature support which is slightly more complicated than Caddy, which can be referred to by the author. Nginx Basic Configuration Memorandum.

Caddy: A Fresh and Free Web Server

As a new Web server, Caddy provides many simple and easy-to-use functions without the burden of history. Its default support and can help you automatically configure HTTP/2, HTTPS, IPV6, WebSockets have good support. Caddy, written on Go, naturally supports multiple cores, and its rich plug-in system provides out-of-the-box extensions for file management, file upload, MarkDown-based blog system, and so on.
We can be at Official Download Interface Select the plug-in function you need to customize the personalized binary file, after downloading, you can use the caddy command to run directly. Its default listens on port 2015 and opens in browser http://localhost:2015 That is to say, you can see how it works. We can also specify configuration files by using the - conf parameter:

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

We will introduce the configuration syntax of Caddyfile in detail below. One of the characteristics of Caddy is that it uses so-called Directives to describe functions for configuration. Compared with Nginx or Apache, its configuration is much simpler. If we want to support multiple configuration files, we can use the import directive:

import config/common.conf

Or introduce the entire folder:

import ../vhosts/*

Reference

Site Configuration

The typical Caddyfile configuration file is as follows:

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 *

The first line of each Caddyfile must describe the address of its service:

localhost:2020

Each subsequent line is officially provided with instructions. For example, we need to add gzip compression support to the server. We just need to add one instruction directly:

localhost:2020
gzip

We can use the bind instruction to specify the address of the current server binding:

bind host
bind 127.0.0.1

Virtual Host

If we need to configure a separate virtual host, we need to move the configuration information to brackets after the site name:

mysite.com {
    root /www/mysite.com
}

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

Note that the left parentheses must be on the same line as the site name, while the right parentheses must be on a single line. For sites sharing the same configuration, we can use commas to declare multiple sites:

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

When Caddy detects that the site name meets the following criteria, Let's Encrypt scripts are automatically used to add HTTPS support to the site, and ports 80 and 443 are automatically monitored:

  • Host name cannot be empty and there is no localhost and IP address

  • Port number not specified explicitly as 80

  • Scheme is not explicitly specified as http

  • TLS not turned off

  • Unspecified Certificate

Cache settings

We can use the expires command to set the expiration header relative to the request time. Its basic grammar is:

expires {
    match regex duration
}

regex is a regular expression for matching request files, while duration is an expression for describing the length of time in 0y0m0d0h0i0s format. The commonly used matching grammar is:

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
}

Reverse proxy

The proxy Directive provides basic reverse proxy functionality, which supports Health Checks and Failovers, as well as reverse proxy for WebSocket. Its basic grammar is:

proxy from to 

from is the basic path for request matching, and to is the endpoint address to which the request is forwarded. We can also use more complex configurations:

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
}

Forward all requests to / api to the back-end system:

proxy /api localhost:9005

The random strategy is used to balance all requests to three back-end servers:

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

Using a recycling mechanism:

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

Add health check and transparently forward host name, address and upstream:

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

Forwarding WebSocket requests:

proxy /stream localhost:8080 {    
    websocket
}

Avoid forwarding of partial static requests:

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

WebSocket

Caddy has built-in support for WebSocket connections. It allows clients to execute a simple instruction when they initiate a WebSocket connection. Its basic syntax is as follows:

websocket [path] command

We can build simple WebSocket client requests within the client:

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);
}

Then it receives the request at the server and returns the input from the client:

var readline = require('readline');
var rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
  terminal: false
});

rl.on('line', function(line){
    console.log(line);
})

Finally, the Caddy file is configured as follows:

websocket /echo "node tmp.js"

File upload

We can use the extension instruction upload provided by Caddy to build a simple file upload server:

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
}

Add the following configuration directly:

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

Then use curl to upload files:

# HTTP PUT
curl \
  -T /etc/os-release \
  https://127.0.0.1/web/path/from-release

Or upload multiple files at the same time:

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

We can also use instructions to move or delete these files:

# 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

access control

Authentication of privileges

Basic Auth

Caddy has built-in support for HTTP Basic Authentication, which forces users to access certain directories or files with specified usernames and passwords. Its basic configuration grammar is as follows:

basicauth username password {
    resources
}

If we want to add permission authentication to all files in the / secret directory:

basicauth /secret Bob hiccup

Certain documents may also be specified:

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

JWT

The jwt directive is an extended function of Caddy. We need to add this function on the official website and get the compiled version. Its basic grammar is as follows:

jwt path
// perhaps
jwt {
    path  resource
    allow claim value
    deny  claim value
}

For example, we preset two tokens: user: someone and role: member. Our configuration items are as follows:

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

The middleware will deny access to all roles: members, except for users whose user name is someone. Users of another role: admin or role: foo have normal access. We can submit tokens in three ways:

Method Format
Authorization Header Authorization: Bearer token
Cookie "jwt_token": token
URL Query Parameter /protected?token=token

Cross-domain requests

We can use cors instructions to add cross-domain requests to servers:

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
}

We can also add JSONP support:

jsonp /api/status

For example, an endpoint returns a JSON response like {"status":"ok"} in the following format:

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

It returns a response in the following format:

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

Address filtering

We can use the ipfilter instruction to allow or restrict user access based on the user's IP. Its basic syntax is:

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

Only one IP access is allowed:

ipfilter / {
    rule allow
    ip   93.168.247.245
}

Prohibit two IP addresses from accessing a specific IP and return them to the default interface:

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

Only clients with fixed IP addresses from France are allowed to access:

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

Only client visits from the United States and Japan are supported:

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

Prohibit clients from the United States and Japan from accessing / notglobal and / secret, and return to the default address directly:

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

Request Current Limitation

We can use the ratelimit extension instruction to add the function of requesting current limit to resources. For a single resource, we can use the following instructions:

ratelimit path rate burst unit
// Limit the client to initiate at most two requests per second for / r resources, with a maximum burst cap of 3
ratelimit /r 2 3 second

For multiple resources, the following instructions can be used:

ratelimit rate burst unit {
    resources
}
// Restrict access to resource files for 2 minutes
ratelimit 2 2 minute {
    /foo.html
    /dir
}

Topics: Web Server curl Database Nginx