Kamal Nasser

Serve a JSON resume to cURL with Caddy

July 07 2018 post

Open your terminal and run curl kamal.io. You should see a JSON "résumé." Browse to kamal.io in your browser however, and you'll get a regular site! What's going on?

How

When a request is sent to kamal.io, the webserver, Caddy treats the curl client differently. It does so by checking the User-agent header and processing the request like so:

  • if the User-agent begins with curl and the request path is /:
    • rewrite the request internally to /about.json
  • otherwise, serve the request as normal

This is what the relevant parts of my Caddy config look like:

rewrite {
    if {path} is "/"
    if {>User-agent} starts_with curl
    to /about.json
}

But

This works, but there's one thing we need to take care of. Caddy configures a forced HTTP → HTTPS redirect by default. curl doesn't follow redirect headers unless the -L flag is passed. So, if you run curl kamal.io with only the config above, it won't work.

~ ❯❯❯ curl kamal.io
<a href="https://kamal.io/">Moved Permanently</a>.

This works, however:

~ ❯❯❯ curl -L kamal.io
{
    "name": "Kamal Nasser",
    ...

What Then

To fix this issue, we need to configure Caddy to not redirect HTTP curl requests to / to HTTPS. To do so, you will need to add an HTTP server block, like so:

http://kamal.io/ {
    ...
}

This will let you override the default HTTP → HTTPS redirect and configure HTTP requests however you like. This matches all requests under / (e.g. /index.html), not only / itself.

Similar to the rewrite directive above, we will need to add a redir directive that redirects only if the request isn't from a curl client and isn't exactly /.

http://kamal.io/ {
	redir 301 {
		if {path} not "/"
		if {>User-agent} not_starts_with "curl"
		if_op or
		/  https://{host}{uri}
	}

	... webroot config ...
}

Now, it will be possible to run curl kamal.io as-is and get a proper response and get an HTTPS redirect for every other kind of request.

But I'm using nginx. Can I still do this?

Yes. I just switched to Caddy and had been using nginx before. This is what my config looked like:

server {
    server_name www.kamal.io kamal.io;
    listen 80;
    listen 443 ssl http2;

    if ($http_user_agent ~* ^curl) {
        set $redirect N;
        rewrite ^/$ /about.json;
        break;
    }

    # I had my website behind Cloudflare, so I needed to check the X-Forwarded-Proto header.
    # If you're using plain Nginx, use if ($https = "") instead.
    if ($http_x_forwarded_proto = "http") {
        set $redirect "${redirect}Y";
    }

    if ($redirect = Y) {
        return 301 https://kamal.io$uri;
    }
    
    ... rest of config ...
}

Without HTTPS, you can use a simpler config:

location / {
    if ($http_user_agent ~* ^curl) {
        rewrite ^/$ /about.json;
        break;
    }
}

Another option would be splitting your server blocks into separate HTTP and HTTPS server blocks (which I preferred when I used nginx) and then adapting the snippet above to them.