Proxying based on URL parameter/ Cookie value/ Header

Hello Community,

Is it possible with caddy to route requests(proxy) based on a url parameter value or a cookie value to the target.

Ex :
http://abc.com?region=eu → routes to http://eu.abc.com
http://abc.com?region=us → routes to http://us.abc.com
http://abc.com?region=sg → routes to http://sg.abc.com

Thanks

Check out the {?key} and {~cookie} functionality in the placeholder docs.

I would probably enact this functionality in multiple steps - first rewrite using if conditionals, then redir based on the outcome - this prevents issues where the cookie or query is not set or empty. See other post

example.com

# For EU
rewrite {
  # Set the if operator to OR so any match triggers the rewrite - by default is is AND
  if_op or
  if {~region-cookie} is eu
  if {?region-query} is eu
  to /eu
}
redir /eu eu.example.com


# For US
rewrite {
  if_op or
  if {~region-cookie} is us
  if {?region-query} is us
  to /us
}
redir /us us.example.com

# For SG
rewrite {
  if_op or
  if {~region-cookie} is sg
  if {?region-query} is sg
  to /sg
}
redir /sg sg.example.com

https://caddyserver.com/docs/placeholders
https://caddyserver.com/docs/rewrite
https://caddyserver.com/docs/redir

Thanks @Whitestrake. This worked.

However the parameters I pass in the original request is not propagated to the redirect endpoint. I’m afraid if it’s the same for cookies, header values and request payload. Is there any option to preserve the original request parameters, cookies ,payload and just change the redirect endpoint?

Actually, disregard the rewrite stage, no idea why I thought that was necessary. This is entirely doable with just redir, and you can preserve the URI at the same time.

redir {
  if_op or
  if {~region-cookie} is eu
  if {?region-query} is eu
  to eu.example.com{uri}
}

You know, I just remembered why I thought a rewrite approach would be appropriate. It makes way more sense if you want to proxy instead of just redirecting. This might be more in line with your original request - both this and the redir method are valid options with different pros and cons.

example.com

rewrite {
  if_op or
  if {~region-cookie} is eu
  if {?region-query} is eu
  to /eu{uri}
}
proxy /eu eu.example.com {
  transparent
  without /eu
}

This approach serves the locale specific site in one request, avoiding redirections, hiding the locale-specific URL from the client, preserving the URI, passing on request details and stripping the /eu prefix before sending it upstream in order to be as transparent as possible.

One gotcha to be aware of - if example.com is managed by Caddy using Automatic HTTPS, the proxy requests will be 301’d to the HTTPS version of the site, so you might need to use proxy /eu https://eu.example.com instead, as appropriate.

1 Like

@Whitestrake Proxying seems ideal in this case. Thanks for the great support!.

I’m trying to modify the above logic a bit with the following scenario.

All the request with the uri /api/mgt should route to the end point http://localhost:8088.
Other requests should be routed to a regional server depending on the header value or the query parameter value. I’m using the following configuration. Requests without either of these parameters should receive a 400 bad request as the response.

live3.me.com:80/api/mgt {
  log
  tls off
  proxy / http://localhost:8088/{uri} {
   transparent
  }
}

live3.me.com:80/api/* {
  log
  tls off
  # For EU
  rewrite {
    # Set the if operator to OR so any match triggers the rewrite - by default is is AND
    if_op or
    if {~my-region} is eu
    if {>My-Region} is eu
    to /eu
  }
  proxy /eu  http://localhost:8089/{uri} {
   transparent
   without /eu
  }

  # For US
  rewrite {
    if_op or
    if {~my-region} is us
    if {>My-Region} is us
    to /us
  }
  proxy /us http://localhost:8090/{uri} {
   transparent
   without /us
  }
}

I wasn’t able to pass the original URI to the destination endpoint while proxying. Appreciate your assistance on this.

Do you have logs showing how the requests are coming from Caddy? It should be unnecessary to include {uri} in the proxy destination. I’d have thought it might double up the URI, actually. For a proxy configured like:

example.com
proxy / foo.example.com:2015

A request like:

example.com/some-uri/folder

Should be received by the proxy destination as:

foo.example.com:2015/some-uri/folder

I also think the wildcard * at the end of the second server block’s label is unnecessary. I understand that Caddy selects the virtual host by the longest (most specific) match, so just live3.proceq.com:80/api/ would work fine.

@Whitestrake thanks for the fast repsponse :smiley: Appreciate that very much.

I was able to achieve this with the following configuration.

live3.me.com:80/api {
  log
  tls off
  # For EU
  rewrite {
    # Set the if operator to OR so any match triggers the rewrite - by default is is AND
    if_op or
    if {~my-region} is eu
    if {>My-Region} is eu
    to /eu{uri}
  }
  proxy /eu  http://localhost:8088/ {
   transparent
   without /eu
  }

  # For US
  rewrite {
    if_op or
    if {~my-region} is us
    if {>My-Region} is us
    to /us{uri}
  }
  proxy /us http://localhost:8089/ {
   transparent
   without /us
  }
}

live3.me.com:80/api/mgt {
  log
  tls off
  proxy / http://localhost:8090/api/mgt {
   transparent
  }
}

It’s good that you confirmed Caddy selects the virtual host by the longest matching path.

I’m trying to understand how the if ladder is evaluated? does it happen in the defined order? Can we add an else in the last block to return a custom error code (400 . bad request in my case.)

The execution of the if ladder is irrelevant because it’s a simple OR (or AND) statement - it can be ordered any way you like. There’s no functionality to define an else; if none of the conditions meet, the rewrite simply doesn’t take place.

You could rewrite the site root / to some other URI and handle that as your exception, or just 403 the whole vhost since you’re catching /api in other server blocks:

live3.me.com:80 {
	status 403 /
}

To catch people who don’t get a region, you could add a final rewrite to check if none of the above rewrites took place:

live3.me.com:80/api/* {
	...
	rewrite {
		if {path} not_starts_with /api/
		to /forbidden
	}
	status 403 /forbidden
}

I would recommend issuing 403s instead of 400s, as the request is ostensibly properly formed, but for a resource you don’t want to action.

@Whitestrake That’s a very clever way of doing this. I was able to achieve the whole use case. We are going to use caddy as the the first line of API gateway in the above mentioned manner. So far it looks promising. Again thank you so much for the support. :slight_smile:

1 Like

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