Routing mismatch with templates, file server and subdomains

1. The problem I’m having:

Please consider the CaddyTaylor Project (Github). It aims to use Caddy templates in a way that split contents and, well, templates, so you can have your contents on one hand and your themes on the other. CaddyTaylor comes as a docker development environment so you may run it on your side to test.

In this configuration, Caddy serves two directories:

  • subdomains that stores contents for each subdomains of localhost (currently, sample.localhost, sampleonepage.localhost and sampleonepage2d.localhost, one demo site for each theme I’m working on)
  • themes that stores the themes.

At the same level, you have the index.html file which is a Caddy template that makes the routing work to inject the contents in the templates. And, yes, it works nearly well.

So the expected behavior is:

  • when a file, not a page, is requested, given its full path, it is served: https://sample.localhost/subdomains/sample/img/caddy-logo.svg for example — this works ;
  • when a page is requested, it is handled by the template system: https://sampleonepage.localhost is a working example — there is still problems in some cases and https://sample.localhost does not work it the current state of the code, but that’s not the point here ;
  • when a file, not a page, is requested with out the path to content directory of the subdomain, it SHOULD be provided, so https://sample.localhost/subdomains/sample/img/caddy-logo.svg SHOULD be equivalent to https://sample.localhost/img/caddy-logo.svg — but it IS NOT and it DOES NOT work at all, which is my problem.

This behavior is handled by the line 12 of the Caddyfile:

try_files /subdomains/{http.request.host.labels.2}/{path} {path} index.html

If you comment it and uncomment the line 13 which becomes:

try_files /subdomains/sample/{path} {path} index.html

It works, but only for the sample.localhost domain.

So, what’s going wrong ? Considering the error log (see below, point 2), the request IS handled but the template system, but it SHOULD NOT.

How to test:

$ git clone git@github.com:taophp/caddy-tailor.git
$ cd caddy-taylor
$ docker-compose up -d
$ curl -vLk https://sample.localhost/img/caddy-logo.svg
*   Trying ::1:443...
* Connected to sample.localhost (::1) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* TLSv1.0 (OUT), TLS header, Certificate Status (22):
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS header, Certificate Status (22):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS header, Finished (20):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.2 (OUT), TLS header, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256
* ALPN, server accepted to use h2
* Server certificate:
*  subject: [NONE]
*  start date: Apr 14 13:38:15 2023 GMT
*  expire date: Apr 15 01:38:15 2023 GMT
*  issuer: CN=Caddy Local Authority - ECC Intermediate
*  SSL certificate verify result: unable to get local issuer certificate (20), continuing anyway.
* Using HTTP2, server supports multiplexing
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* Using Stream ID: 1 (easy handle 0x556d4bc1e020)
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
> GET /img/caddy-logo.svg HTTP/2
> Host: sample.localhost
> user-agent: curl/7.81.0
> accept: */*
> 
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* Connection state changed (MAX_CONCURRENT_STREAMS == 250)!
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.2 (IN), TLS header, Supplemental data (23):
* TLSv1.2 (OUT), TLS header, Supplemental data (23):
* HTTP/2 stream 0 was not closed cleanly: PROTOCOL_ERROR (err 1)
* stopped the pause stream!
* Connection #0 to host sample.localhost left intact
curl: (92) HTTP/2 stream 0 was not closed cleanly: PROTOCOL_ERROR (err 1)

2. Error messages and/or full log output:

$ docker logs caddytailor_caddy_1 # I prettyfied the last output to make it readable... or nearly!
{
  "level": "error",
  "ts": 1681484819.6419573,
  "logger": "http.log.error",
  "msg": "template: /index.html:36:12: executing \"/index.html\" at <include $pageTemplate $config>:
 error calling include: template: themes/base//index.html:2:6: executing \"themes/base//index.html\" at <include (printf \"/themes/%s/head.html\" $config.theme) $config>:
error calling include: template: /themes/base/head.html:9:17: executing \"/themes/base/head.html\" at <include (printf \"/themes/%s/menu.html\" $config.theme) $config $config.rootdir \"\">:
error calling include: template: /themes/base/menu.html:19:12: executing \"/themes/base/menu.html\" at <include (printf \"/themes/%s/menu.html\" $config.theme) $config (printf \"%s/%s\" $dir $entry) (printf \"%s/%s\" $urldir $entry)>:
error calling include: template: /themes/base/menu.html:19:12: executing \"/themes/base/menu.html\" at <include (printf \"/themes/%s/menu.html\" $config.theme) $config (printf \"%s/%s\" $dir $entry) (printf \"%s/%s\" $urldir $entry)>:
error calling include: template: /themes/base/menu.html:5:17: executing \"/themes/base/menu.html\" at <listFiles $dir>:
error calling listFiles: subdomains/sample/img/caddy-logo.svg is not a directory",
  "request": {
    "remote_ip": "172.18.0.1",
    "remote_port": "49708",
    "proto": "HTTP/2.0",
    "method": "GET",
    "host": "sample.localhost",
    "uri": "/img/caddy-logo.svg",
    "headers": {
      "User-Agent": [
        "curl/7.81.0"
      ],
      "Accept": [
        "*/*"
      ]
    },
    "tls": {
      "resumed": false,
      "version": 772,
      "cipher_suite": 4865,
      "proto": "h2",
      "server_name": "sample.localhost"
    }
  },
  "duration": 0.004958801,
  "status": 500,
  "err_id": "7kk2u58ji",
  "err_trace": "templates.(*Templates).executeTemplate (templates.go:402)"
}

3. Caddy version:

$ docker exec caddytailor_caddy_1 caddy version
v2.6.4 h1:2hwYqiRwk1tf3VruhMpLcYTg+11fCdr8S3jhNAdnPy8=

4. How I installed and ran Caddy:

a. System environment:

$ docker -v
Docker version 20.10.21, build 20.10.21-0ubuntu1~22.04.2
$ docker-compose -v
docker-compose version 1.29.2, build unknown
$ uname -a
Linux pc-steph 5.15.0-69-generic #76-Ubuntu SMP Fri Mar 17 17:19:29 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux

b. Command:

docker-compose up

c. Docker-compose file:

version: "3.7"

services:
  caddy:
    image: caddy:latest
    ports:
      - "443:443"
    volumes:
      - $PWD/Caddyfile:/etc/caddy/Caddyfile
      - $PWD/sites:/usr/share/caddy
      - $PWD/caddy_data:/data
      - $PWD/caddy_config:/config

d. My complete Caddy config:

# To reload use this command : ` docker exec caddytailor_caddy_1 caddy reload --config /etc/caddy/Caddyfile `
{
        debug
}

*.localhost {
        root * /usr/share/caddy
        tls internal
        file_server
        templates
        route {
                try_files /subdomains/{http.request.host.labels.2}/{path} {path} index.html
                #try_files /subdomains/sample/{path} {path} index.html
        }
}

5. Links to relevant resources:

CaddyTaylor repository: https://github.com/taophp/caddy-tailor

Labels are zero-indexed. Your domain sample.localhost only has two parts, so 0 and 1. There is no 2-th label.

If your domain was sample.example.com then 2 would work.

Also, you can use {labels.1} instead, shorter.

2 Likes

Thanks a lot Francis !

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.