Caddy: Caddyfile's directive handle behaves weirdly.

Created on 7 Jun 2020  路  3Comments  路  Source: caddyserver/caddy

This is the configuration.

http://localhost:3120 {
  header {
    Access-Control-Allow-Origin *
    Access-Control-Allow-Headers authorization
  }
  @option {
    method OPTIONS
  }
  handle @option {
    respond 200 {
      close
    }
  }
  handle /api/* {
    respond "xx" 201
  }
}

I used curl to test this.

$ caddy version
v2.0.0 h1:pQSaIJGFluFvu8KDGDODV8u4/QRED/OPyIR+MWYYse8=
$ curl -s -v 'http://localhost:3120/api/xx' -X OPTIONS  2>&1 | grep '< HTTP'
< HTTP/1.1 201 Created

The status code returned should be 200.
I tried using the beta version, and it returned the expected results.

$caddy version
v2.0.0-beta.15 h1:Td1esMk7bebftnoBuT3gOqUGxew5HqdIKw3s36S8tNw=
$ curl -s -v 'http://localhost:3120/api/xx' -X OPTIONS  2>&1 | grep '< HTTP'
< HTTP/1.1 200 OK

I did the same test on Windows 10 and Ubuntu with the same results.
Caddy v2.0.0 behaves weirdly.

question

Most helpful comment

If you adapt your config to JSON, you'll notice that Caddy sorts the handlers are sorted in the opposite order than you expect:

{
  "apps": {
    "http": {
      "servers": {
        "srv0": {
          "listen": [
            ":3120"
          ],
          "routes": [
            {
              "match": [
                {
                  "host": [
                    "localhost"
                  ]
                }
              ],
              "handle": [
                {
                  "handler": "subroute",
                  "routes": [
                    {
                      "handle": [
                        {
                          "handler": "headers",
                          "response": {
                            "set": {
                              "Access-Control-Allow-Headers": [
                                "authorization"
                              ],
                              "Access-Control-Allow-Origin": [
                                "*"
                              ]
                            }
                          }
                        }
                      ]
                    },
                    {
                      "group": "group3",
                      "handle": [
                        {
                          "handler": "subroute",
                          "routes": [
                            {
                              "handle": [
                                {
                                  "body": "xx",
                                  "handler": "static_response",
                                  "status_code": 201
                                }
                              ]
                            }
                          ]
                        }
                      ],
                      "match": [
                        {
                          "path": [
                            "/api/*"
                          ]
                        }
                      ]
                    },
                    {
                      "group": "group3",
                      "handle": [
                        {
                          "handler": "subroute",
                          "routes": [
                            {
                              "handle": [
                                {
                                  "close": true,
                                  "handler": "static_response",
                                  "status_code": 200
                                }
                              ]
                            }
                          ]
                        }
                      ],
                      "match": [
                        {
                          "method": [
                            "OPTIONS"
                          ]
                        }
                      ]
                    }
                  ]
                }
              ],
              "terminal": true
            }
          ],
          "automatic_https": {
            "skip": [
              "localhost"
            ]
          }
        }
      }
    }
  }
}

Specifically, Caddy sorts handlers by the length of their path matchers. This is usually the right thing to do for most usecases because by far the most commonly used request matcher is the path matcher. Your case is a pretty representative example where that doesn't have the expected effect.

To fix this, I'd recommend wrapping your handle blocks in a route block which tells the Caddyfile parser to preserve the order as written in the Caddyfile.

http://localhost:3120 {
  header {
    Access-Control-Allow-Origin *
    Access-Control-Allow-Headers authorization
  }

  @option {
    method OPTIONS
  }

  route {
    handle @option {
      respond 200 {
        close
      }
    }
    handle /api/* {
      respond "xx" 201
    }
  }
}

For next time, please ask your usage questions on the Caddy community forums. We prefer to keep the GitHub issue board for bugs and feature requests. Don't forget to fill out the thread template so we can help you!

All 3 comments

If you adapt your config to JSON, you'll notice that Caddy sorts the handlers are sorted in the opposite order than you expect:

{
  "apps": {
    "http": {
      "servers": {
        "srv0": {
          "listen": [
            ":3120"
          ],
          "routes": [
            {
              "match": [
                {
                  "host": [
                    "localhost"
                  ]
                }
              ],
              "handle": [
                {
                  "handler": "subroute",
                  "routes": [
                    {
                      "handle": [
                        {
                          "handler": "headers",
                          "response": {
                            "set": {
                              "Access-Control-Allow-Headers": [
                                "authorization"
                              ],
                              "Access-Control-Allow-Origin": [
                                "*"
                              ]
                            }
                          }
                        }
                      ]
                    },
                    {
                      "group": "group3",
                      "handle": [
                        {
                          "handler": "subroute",
                          "routes": [
                            {
                              "handle": [
                                {
                                  "body": "xx",
                                  "handler": "static_response",
                                  "status_code": 201
                                }
                              ]
                            }
                          ]
                        }
                      ],
                      "match": [
                        {
                          "path": [
                            "/api/*"
                          ]
                        }
                      ]
                    },
                    {
                      "group": "group3",
                      "handle": [
                        {
                          "handler": "subroute",
                          "routes": [
                            {
                              "handle": [
                                {
                                  "close": true,
                                  "handler": "static_response",
                                  "status_code": 200
                                }
                              ]
                            }
                          ]
                        }
                      ],
                      "match": [
                        {
                          "method": [
                            "OPTIONS"
                          ]
                        }
                      ]
                    }
                  ]
                }
              ],
              "terminal": true
            }
          ],
          "automatic_https": {
            "skip": [
              "localhost"
            ]
          }
        }
      }
    }
  }
}

Specifically, Caddy sorts handlers by the length of their path matchers. This is usually the right thing to do for most usecases because by far the most commonly used request matcher is the path matcher. Your case is a pretty representative example where that doesn't have the expected effect.

To fix this, I'd recommend wrapping your handle blocks in a route block which tells the Caddyfile parser to preserve the order as written in the Caddyfile.

http://localhost:3120 {
  header {
    Access-Control-Allow-Origin *
    Access-Control-Allow-Headers authorization
  }

  @option {
    method OPTIONS
  }

  route {
    handle @option {
      respond 200 {
        close
      }
    }
    handle /api/* {
      respond "xx" 201
    }
  }
}

For next time, please ask your usage questions on the Caddy community forums. We prefer to keep the GitHub issue board for bugs and feature requests. Don't forget to fill out the thread template so we can help you!

To add more explanation, handle blocks are kept in their written order except when sorted by length of path matcher. Length, or "specificity", of other matchers (like method matchers) are not defined. Which is more specific (or "longer"): a path of /api/foo or any OPTIONS request? It doesn't even make sense. Hence, the written order is preserved for handle blocks without a path matcher.

@francislavoie @mholt Thanks

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mschneider82 picture mschneider82  路  3Comments

SteffenDE picture SteffenDE  路  3Comments

crvv picture crvv  路  3Comments

kilpatty picture kilpatty  路  3Comments

klaasel picture klaasel  路  3Comments