I have a bilangual website and want, depending on the Accept-Language header, deliver either the german or the english websites. Apache has the module ‘mod_negotiation’ for this. Is there something similar for Caddy?
If not, what are the alternatives?
Hi @georg.cachinger,
Both the rewrite
and redir
directives support conditional operators based on request headers (in the format {>Header}
.
For example, assuming:
- Your English site is located under
/en
- Your German site is located under
/de
- You want to serve English as a default
- You don’t want the URL to change by locale
You might structure a rewrite
-based solution like this:
example.com
rewrite {
if {path} not_starts_with /de
if {path} not_starts_with /en
if {>Accept-Language} is de
to /de{uri}
}
rewrite {
if {path} not_starts_with /de
if {path} not_starts_with /en
to /en{uri}
}
A redir
solution would be quite similar, the major difference being exposing the localisation prefix to the user in their URL bars.
https://caddyserver.com/docs/rewrite
https://caddyserver.com/docs/redir
https://caddyserver.com/docs/placeholders
Thanks for the response. I will try to implement it as suggested. I will give you feedback as soon as I have done (approx. end of june).
PS: I wonder why the search couldn’t find anything concerning “content negotiation an Caddy”. May something for the docs.
I should also mention this project:
It’s not been added to the official plugin list, though, so I’m not sure where it’s at right now in terms of stability/compatibility. I remember it coming up here and there a while back on these forums, though.
I’m trying to implement the suggested rewrite solution, but end up with a syntax error.
This is my Caddyfile:
http://printmaps-osm.de:8383 {
root /home/printmaps/test/printmaps-osm.de
gzip
log ./access.log
errors ./error.log
rewrite {
if {path} not_starts_with /de
if {path} not_starts_with /en
if {>Accept-Language} is de
to /de{uri}
}
rewrite {
if {path} not_starts_with /de
if {path} not_starts_with /en
to /en{uri}
}
}
… and the error message:
printmaps@mercator:~/test$ caddy
Activating privacy features... done.
2017/06/26 11:19:20 Invalid operator not_starts_with
Hi @georg.cachinger,
Apologies for that one, I’ve mistakenly advised people a few times on this particular feature.
The operator not_starts_with
is available on the master branch of the Caddy repository, but there hasn’t been an actual release with it yet. It should be usable with version 0.10.4. The docs were mistakenly updated early.
Until then, you can use not_match
instead, like so:
rewrite {
if {path} not_match ^/de.*
if {path} not_match ^/en.*
if {>Accept-Language} is de
to /de{uri}
}
Once 0.10.4 comes out, it’ll be benificial to swap to not_starts_with
at that time due to the much more efficient nature of substring search vs regex matching.
That’s my directory structure:
design.css
favicon.png
robots.txt
en/
de/
files/
images/
markers/
The stuff in “files, images and markers” are shared by the german and the english website. An image is referenced for example as “…/images/europe.png”. But this doesn’t work, the file is not found. I couldn’t figure out what’s going wrong …
http://printmaps-osm.de:8383 {
root /home/printmaps/test/printmaps-osm.de
gzip
log ./access.log
errors ./error.log
rewrite {
if {path} not_match ^/de.*
if {path} not_match ^/en.*
if {>Accept-Language} match ^de.*
to /de{uri}
}
rewrite {
if {path} not_match ^/de.*
if {path} not_match ^/en.*
if {>Accept-Language} not_match ^de.*
to /en{uri}
}
}
I know there’s some path.Clean()
ing going on under the hood of rewrite
, and that gets rid of ..
elements… Pretty sure that would be after it gets prepended, though, so it should come out correctly regardless.
Might be helpful if we can see an example of the request rewriting in action. That should shine some light on where the config may have gone awry for what we want to achieve.
Change your log
directive slightly:
log / ./access.log "{common} {rewrite_path}"
Then start up your server, provoke a few 404s, and let us know what you get from the logs.
Here is the log after calling the rewritten english main page (index.html):
- /en/
- /en/images/template.png
- /design.css
- /en/images/europe.png
- /en/images/seperator.gif
- /en/images/hover.png
The main page calls:
...
<link href="../design.css" rel="stylesheet" type="text/css">
<link rel="icon" type="image/png" href="../favicon.png">
...
<img src="../images/template.png" style="border-width: 1px; border-style: solid; border-color: lightgray;">
...
<img src="../images/europe.png" style="border-width: 1px; border-style: solid; border-color: lightgray;">
Err, is that the whole log? It didn’t print out the pre rewrite request with the usual details? I expected each line to have a normal log line followed by the rewritten section.
Yes, that was the complete log file.
http://printmaps-osm.de:8383 {
root /home/printmaps/test/printmaps-osm.de
gzip
log / ./access.log "{common} {rewrite_path}"
errors ./error.log
rewrite {
if {path} not_match ^/de.*
if {path} not_match ^/en.*
if {>Accept-Language} match ^de.*
to /de{uri}
}
rewrite {
if {path} not_match ^/de.*
if {path} not_match ^/en.*
if {>Accept-Language} not_match ^de.*
to /en{uri}
}
}
That’s very confusing. {common}
should expand out to the full default logging string. Is your Caddy up-to-date?
Anyway, try this:
rewrite {
if {path} not_match ^/de.*
if {path} not_match ^/en.*
if {>Accept-Language} match ^de.*
to {path} /de{uri}
}
rewrite {
if {path} not_match ^/de.*
if {path} not_match ^/en.*
if {>Accept-Language} not_match ^de.*
to {path} /en{uri}
}
That behaviour will first try to serve the real file that matches the request. If it’s not an existing file on disk, Caddy will then try with /en
or /de
prepended, as appropriate.
Then you should be able to remove the ..
elements from your src
and href
attributes and use the absolute path.
Sorry, absolute pathes are no an option for me. Dito it’s no option to copy the shared data (files, images, …) into the language directories (de, en).
To answer your question concerning the version:
printmaps@mercator:~/test$ caddy -version
Caddy 0.10.2
printmaps@mercator:~/test$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 16.04.2 LTS
Release: 16.04
Codename: xenial
Maybe interesting:
- /en/
- /design.css
- /en/images/template.png
The first reference (link href=“…/design.css” …) is working, but the next (img src=“…/images/template.png” …) isn’t. Confusing …
No problem, using to {path} /de{uri}
instead should solve the issue regardless of whether you use relative links - I mention it only for the sake of cleaning up the HTML a little, since the ..
will be both unnecessary and useless (Caddy will ignore it, squishing /de
and ../favicon.png
together to create /favicon.png
anyway). If it’s needed for some other reason, no worries.
Not too much of a mystery; see -
rewrite {
if {path} not_match ^/de.*
/design.css
matches ^/de.*
by regex. It’s not caught by either rewrite.
You could instead use ^/de(/.*)?$
, which will only match either /de
or /de/*
. Same for ^/en(/.*)?$
. A minor optimisation until the relase of 0.10.4 and the much faster not_starts_with
becomes usable.
… good point !
That’s my latest Caddyfile (as suggested):
http://printmaps-osm.de:8383 {
root /home/printmaps/test/printmaps-osm.de
gzip
log / ./access.log "{common} {rewrite_path}"
errors ./error.log
rewrite {
if {path} not_match ^/de.*
if {path} not_match ^/en.*
if {>Accept-Language} match ^de.*
to {path} /de{uri}
}
rewrite {
if {path} not_match ^/de.*
if {path} not_match ^/en.*
if {>Accept-Language} not_match ^de.*
to {path} /en{uri}
}
}
Hmm, the result is this:
And the access.log:
- /
If it’s helpful, I could send you copy of the website.
The idea was that Caddy would reject the first option if it wasn’t a file and go to the second, but Caddy’s criteria is that it checks for a file unless it ends with a trailing slash, then it checks for a directory instead. Since /
technically ends in a trailing slash, and it is a real directory, so Caddy gives it the go ahead, and there’s obviously no index file directly under /
…
Could try reversing them. Try the /de/
version, if that fails, fall back to the original path. Might just work.
to /de{uri} {path}
Also, 0.10.2 has a known problem with using the {common}
placeholder – it was fixed in 0.10.3 I think, or will definitely go out in 0.10.4 this week.
Now everything is working fine … thanks for the support. Conclusion:
This is the directory structure:
de/
en/
files/
images/
markers/
design.css
favicon.png
robots.txt
And this the Caddyfile:
http://printmaps-osm.de:8080 {
root /home/printmaps/printwebsite/printmaps-osm.de
gzip
log ./access.log
errors ./error.log
rewrite {
if {path} not_match ^/de.*
if {path} not_match ^/en.*
if {>Accept-Language} match ^de.*
to /de{uri} {path}
}
rewrite {
if {path} not_match ^/de.*
if {path} not_match ^/en.*
if {>Accept-Language} not_match ^de.*
to /en{uri} {path}
}
}
FYI: You can find the german and english websites here (depending on the language settings in your browser): http://printmaps-osm.de:8080/
BTW: My project, a web service for generating large-format, OSM-based maps, is also written in go.