Match on http-version

1. Caddy version (caddy version):

Docker: caddy:2.3.0-alpine

2. How I run Caddy:

I run Caddy in a docker container.

a. System environment:

Docker

c. Service/unit/compose file:

  caddy:
    image: caddy:2.3.0-alpine
    volumes:
      - ../caddy/Caddyfile.dev:/etc/caddy/Caddyfile
    depends_on:
      - client
      - server
      - autoupdate
    ports:
      - "8000:8000"

d. My complete Caddyfile or JSON config:

{
    # Enable debug output
    debug
}

localhost:8000

reverse_proxy /system/* autoupdate:8002 {
    flush_interval -1
}

@server {
    path /apps/*
    path /rest/*
    path /server-version.txt
    path /media/*
}
reverse_proxy @server server:8000

reverse_proxy client:4200

3. The problem I’m having:

The reverse_proxy autoupdate can only handle http2 traffic. Therefore I want to add a request_matcher that checks, that the incoming request uses http2. If http1 or http1.1 is used, then I want to return an error message to the client.

The reason is, that the autoupdate-server uses long living http connections. If a browser would use http1.1, it would block the connection and the max-connections-limit by the browser would be reached very soon.

5. What I already tried:

I looked at this list of request matchers but could not find any that could fit

Hmm. I think you could match on the {http.request.proto} placeholder, which should contain either HTTP/1.0, HTTP/1.1 or HTTP/2.

You can pair this with a CEL expression matcher, since there’s no matcher that can do this on its own:

@http2only expression {http.request.proto} == "HTTP/2"

Or maybe, for example:

@http1 expression {http.request.proto}.startsWith("HTTP/1")
1 Like

Thank you. That was the correct tip.

I use the following config:

@autoupdate {
    path /system/*
    expression {http.request.proto}.startsWith("HTTP/2")
}
reverse_proxy @autoupdate autoupdate:8002 {
    flush_interval -1
}

@autoupdate_http1 {
    path /system/*
    expression {http.request.proto}.startsWith("HTTP/1")
}
respond @autoupdate_http1 "HTTP1 not supported" 400 {
    close
}

This is the result:

curl -k --http1.1 https://localhost:8000/system/health
HTTP1 not supported`

curl -k --http2 https://localhost:8000/system/health
{"healthy": true}

The full value of http.request.proto seems to be HTTP/2.0

I also tried:

@autoupdate {
    path /system/*
    expression {http.request.proto} == "HTTP/2.0"
}
reverse_proxy @autoupdate autoupdate:8002 {
    flush_interval -1
}

But this does not work. Caddy fails to start and logs:

caddy_1       | run: loading initial config: loading new config: loading http app module: provision http: server srv0: setting up route handlers: route 0: loading handler modules: position 0: loading module 'subroute': provision http.handlers.subroute: setting up subroutes: route 1: loading matcher modules: module name 'expression': provision http.matchers.expression: compiling CEL program: ERROR: <input>:1:52: undeclared reference to 'HTTP' (in container '')
caddy_1       |  | caddyPlaceholder(request, "http.request.proto") == HTTP/2.0
caddy_1       |  | ...................................................^

Do you know what is wrong? I think to match the hole string instead of using .startsWith(…) would be nicer.

Ah, it’s because Caddy is removing the " characters because it uses them as a token delimiter. If you instead wrap the whole thing with ` (backticks), the other token delimiter in Caddy, then it should work:

expression `{http.request.proto} == "HTTP/2.0"`

The CEL compiler was thinking HTTP is a variable name and that you’re trying to divide it by two :sweat_smile:

1 Like

Thank you. That works!

1 Like

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