Skip to content

Instantly share code, notes, and snippets.

@mholt
Last active Jul 9, 2019
Embed
What would you like to do?
How do you like your handler configs?

Caddy 2 HTTP handlers come in two flavors: middleware and responders.

  • Middleware are in the middle of a request chain; i.e. they have a next handler to invoke.
  • Responders are content origins, at the end of a request chain; i.e. there is no next handler. Any handlers defined after it would not be invoked.

Caveat: Sometimes a handler's role is ambiguous. For example, a caching handler would be middleware on a cache miss (it needs to invoke the upstream handlers for a response, then cache it), but on a cache hit it would be a responder, since no further handlers would be invoked (it would simply write the response).

With either option, the general rule or idea is that handlers are executed from top-to-bottom, with the responder being last, and the return value (response or error) propagates back up to the top.

OPTION 1: LINEAR

  • Pros: All handlers in one, linear list. This allows handlers with ambiguous roles like a cache handler to be defined the same as all others; less nesting.
  • Cons: Not always obvious which handler provides the response; for example, adding handlers after file_server would be a logical error because the file server will never invoke a later middleware.
"handle": [
    {
        "handler": "cache"
    },
    {
        "handler": "encode",
        "encodings": {"gzip": {}, "zstd": {}}
    },
    {
        "handler": "templates"
    },
    {
        "handler": "file_server",
        "root": "/www/example.com"
    }
]

OPTION 2: NESTED

  • Pros: Responder is separated out from the middleware, so it's obvious where the content comes from. Since there is only a single responder, it's impossible to put it in the middle of a handler chain by accident.
  • Cons: Not obvious where handlers with ambiguous role go: does a cache handler go in middleware or responder? Truth is, it depends on the request; sometimes it is a middleware (cache miss), other times it is a responder (cache hit). So it would be configured as a responder, then on cache miss would invoke a nested set of handlers. This approach leads to more nesting in the config. That can be confusing, but also clearer in some ways.
"respond": {
    "responder": "cache",
    "if_cache_miss": [
        {
            "apply": [
                {
                    "middleware": "encode",
                    "encodings": {"gzip": {}, "zstd": {}}
                },
                {
                    "middleware": "templates"
                }
            ],
            "respond": {
                "responder": "file_server",
                "root": "/www/example.com"
            }
        }
    ]
}

(some of these configs are contrived, but the basic structure is represented)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment