Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Need URL rewrite to add trailing slash #563

Closed
javefang opened this issue Jul 28, 2016 · 58 comments
Closed

Need URL rewrite to add trailing slash #563

javefang opened this issue Jul 28, 2016 · 58 comments
Labels
Milestone

Comments

@javefang
Copy link

javefang commented Jul 28, 2016

I have a simple app, which has the following file structure at root

  • script.js
  • style.css
  • index.html (load the other two files using relative path script.js and style.css)

Since I want to access the app via URL http://example/app, I proxied the web app with rule PathStripPrefix:/app. The problem is when I try to access URL http://example/app (without trailing slash), it will load "index.html" fine, but not the JS and CSS file. When I look into the debugger, it tries to load:

Instead of (the correct one):

It only works when I type the original URL with a trailing slash, so http://example/app/. This is not a big deal for me but users sometimes find it annoying since we used to use Nginx, who sends an "301 Moved Permanently" to a URL with trailing slash when it's not there. I wonder if it is possible / makes sense to implement this in Traefik?

Thank you!

@godliness
Copy link

godliness commented Nov 1, 2016

same issue
version: v1.0.3

@emilevauge is this a bug?

@javefang
Copy link
Author

javefang commented Nov 1, 2016

Think should be a feature. Although you.probably want it most of the time, and begins does it by default if you use it as reverse proxy.

Expected behaviour is explained in my original post.

@royjs
Copy link

royjs commented Nov 1, 2016

I also have the same issue

@emilevauge emilevauge added the kind/enhancement a new or improved feature. label Nov 1, 2016
@emilevauge
Copy link
Member

Hi, this is due to the underlying muxer used in traefik http://www.gorillatoolkit.org/pkg/mux#Router.StrictSlash. We use it with StrictSlash enabled but there is an exception:

Special case: when a route sets a path prefix using the PathPrefix() method, strict slash is ignored for that route because the redirect behavior can't be determined from a prefix alone. However, any subrouters created from that route inherit the original StrictSlash setting.

We need to work on this indeed...
As a workaround for now, I suggest to use Host rules instead of Path based rules if possible.
Any help on this is welcome ;)

@ThinkBriK
Copy link

ThinkBriK commented Dec 5, 2016

I was able to workaround this issue using the regex on the entrypoint:

defaultEntryPoints = ["http"]
[entryPoints]
  [entryPoints.http]
  address = ":80"
    [entryPoints.http.redirect]
      regex = "(http:\\/\\/[^\\/]+\\/([^\\?]+)[^\\/])$"
      replacement = "$1/"

The redirection is fine but I ran into #679 :(

@pascalandy
Copy link
Contributor

pascalandy commented Feb 3, 2017

I have a similar issue here: #1123

@juanpaps03
Copy link

same issue #1106

@pascalandy
Copy link
Contributor

pascalandy commented Feb 7, 2017

Many issues are related to this.

@markhowells
Copy link

@emilevauge I hate to just add noise to this issue - but host based routing isn't really an option for us. Our customers have too many http proxy/firewalls in the way... Certainly we can organise html data such that the webserver container responds correctly with the full path but it pollutes the deployment - adding 'brittleness' since if we change the deployment path we have to change the data organisation to reflect the path and/or add configuration to what would otherwise be a trivial deployment...

@ldez
Copy link
Contributor

ldez commented May 24, 2017

Closed by #1638, in the version 1.3.0-rc3.

@ldez ldez closed this as completed May 24, 2017
@kachkaev
Copy link
Contributor

kachkaev commented Jul 23, 2017

@ldez could you please clarify how to achieve a 301/302 redirect from http://example/app to http://example/app/, as @javefang is asking? Still facing the problem he described, i.e. when I open http://example/app, the browser tries to load css and js from http://example/static/main.(js|css)instead of http://example/app/static/main.(js|css), because asset paths in index.html are relative. What I'm looking for is some easy way to automatically add a trailing slash to /app if it's not entered.

My current igress spec:

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: my-app
  annotations:
    kubernetes.io/ingress.class: traefik
    traefik.frontend.rule.type: PathPrefixStrip
    # need something else here?
spec:
  rules:
  - host: example
    http:
      paths:
      - path: /app
        backend:
          serviceName: my-app
          servicePort: http

There is a dirty workaround for nginx in kubernetes-retired/contrib#1884 (comment), but I haven't found anything similar for traefik. Could not understand what to add to my spec after reading the docs and #1638.

When I use - path: /app/, going to /app returns 404 from another container. Also not something I'm looking for.

@pascalandy
Copy link
Contributor

pascalandy commented Jul 23, 2017

I confirm this was resolve when Emile officially released the fix about 3 months ago.
I run v1.3.3 and it's all good.

@kachkaev
Copy link
Contributor

@pascalandy do you mean that traefik automatically redirects from /app to /app/ in your case? If yes, what does your config look like?

@pascalandy
Copy link
Contributor

pascalandy commented Jul 24, 2017

@ldez
Copy link
Contributor

ldez commented Jul 24, 2017

@pascalandy I don't know if you have see: we created https://github.com/containous/traefik/wiki/Awesome-Traefik.
We will very happy if you want to share your repositories in this wiki 😃

@kachkaev
Copy link
Contributor

kachkaev commented Jul 24, 2017

Thanks for the links guys. Not sure my kubernetes-specific question is answered in that material though :–)

@martinbaillie
Copy link
Contributor

Not sure this was answered either. Did you find a way to go from http://example/app to http://example/app/ @kachkaev?

@kachkaev
Copy link
Contributor

No, unfortunately. I still can't configure a separate backend at /app/ and make a rule that would redirect /app to /app/. Wondering if this issue should be reopened.

@kachkaev
Copy link
Contributor

kachkaev commented Sep 9, 2017

Any ideas?

@kachkaev
Copy link
Contributor

kachkaev commented Nov 21, 2017

Here is my current solution for k8s until I find something better 😆

Meet namespace redirects!

## add-trailing-slash.yaml
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: add-trailing-slash
  namespace: redirects
data:
  default.conf: |
    server {
      server_name $host;
      rewrite ^(.*) $scheme://$host$1/ permanent;
    }
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: add-trailing-slash
  namespace: redirects
  labels:
    part: add-trailing-slash
spec:
  replicas: 1
  template:
    metadata:
      labels:
        part: add-trailing-slash
    spec:
      containers:
      - name: main
        image: nginx:1.13.6-alpine
        volumeMounts:
        - name: nginx-config
          mountPath: /etc/nginx/conf.d/
      volumes:
      - name: nginx-config
        configMap:
          name: add-trailing-slash
---
apiVersion: v1
kind: Service
metadata:
  name: add-trailing-slash
  namespace: redirects
  labels:
    part: add-trailing-slash
spec:
  ports:
  - port: 80
    name: http
  selector:
    part: add-trailing-slash
kubectl apply -f add-trailing-slash.yaml

CASES=(
example.com/path1
another.example.com/path2
)

for CASE in "${CASES[@]}"; do
IFS='/' read CASE_DOMAIN CASE_PATH <<< "$CASE"

echo "
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: add-trailing-slash-for-${CASE_DOMAIN}--${CASE_PATH//\//-}
  namespace: redirects
spec:
  rules:
  - host: ${CASE_DOMAIN}
    http:
      paths:
      - path: /${CASE_PATH}
        backend:
          serviceName: add-trailing-slash
          servicePort: http
" | kubectl apply -f -;
done;

Thankfully I don't have that many client-facing microsercices that start with example.com/some-path. I know the solution is too bulky, so if anyone has any better suggestions, I'll be happy throw away all of the above manifests!

@pascalandy
Copy link
Contributor

Thanks @MindTooth

@jvenezia you're sure about your latest config?

@jvenezia
Copy link

jvenezia commented Sep 14, 2018

@MindTooth I should have told I was using Ansible, not docker compose. The syntax is a bit different.

With docker-compose, it should look like that:

labels:
- "traefik.frontend.redirect.regex=^(.*)/portainer$"
- "traefik.frontend.redirect.replacement=$1/portainer/"
- "traefik.frontend.rule=PathPrefix:/portainer;ReplacePathRegex: ^/portainer/(.*) /$1"

As you mentioned, you maybe need double the $'s. I don't have to do that, probably because of Ansible.

Did that work for you @pascalandy? Working like a charm form me!

@MindTooth
Copy link

No worries. Works like a champ AFAIK. Thanks to the both of you.

@pascalandy
Copy link
Contributor

I tried few possibilities and still have the same error:

"traefik.frontend.rule=PathPrefix:/portainer;ReplacePathRegex: ^/portainer/(.*) /$1"
invalid interpolation format for services.portainer.deploy.labels.[]: "traefik.frontend.redirect.regex=^(.*)/portainer$". You may need to escape any $ with another $.

"traefik.frontend.rule=PathPrefix:/portainer;ReplacePathRegex: ^/portainer/(.*) /#$1"
invalid interpolation format for services.portainer.deploy.labels.[]: "traefik.frontend.redirect.regex=^(.*)/portainer$". You may need to escape any $ with another $.

"traefik.frontend.rule=PathPrefix:/portainer;ReplacePathRegex: ^/portainer/(.*) /$$1"
invalid interpolation format for services.portainer.deploy.labels.[]: "traefik.frontend.redirect.regex=^(.*)/portainer$". You may need to escape any $ with another $.

@pascalandy
Copy link
Contributor

pascalandy commented Sep 14, 2018

You can easily try my configs here

@MindTooth
Copy link

@pascalandy: Care to try with "traefik.frontend.redirect.regex=^(.*)/portainer$$"? Seems to missing a $.

@pascalandy
Copy link
Contributor

pascalandy commented Sep 14, 2018

FULL PROJET

error:

invalid interpolation format for services.portainer.deploy.labels.[]: "traefik.frontend.redirect.replacement=$1/portainer/". You may need to escape any $ with another $.

config:

- "traefik.frontend.redirect.regex=^(.*)/portainer$$"
- "traefik.frontend.redirect.replacement=$1/portainer/"
- "traefik.frontend.rule=PathPrefix:/portainer;ReplacePathRegex: ^/portainer/(.*) /$$1"

@hartzell
Copy link

What happens if you change the second line of your config from:

- "traefik.frontend.redirect.replacement=$1/portainer/"

to:

- "traefik.frontend.redirect.replacement=$$1/portainer/"

(escaping the $ with a second $)?

@pascalandy
Copy link
Contributor

works!!!

- "traefik.frontend.redirect.regex=^(.*)/portainer$$"
- "traefik.frontend.redirect.replacement=$$1/portainer/"
- "traefik.frontend.rule=PathPrefix:/portainer;ReplacePathRegex: ^/portainer/(.*) /$$1"

@hartzell
Copy link

Woo hoo!

@pascalandy
Copy link
Contributor

pascalandy commented Sep 14, 2018

Thank you so much guys! @hartzell @MindTooth @jvenezia

I merged/squashed this change to master :)
Enjoy: https://github.com/pascalandy/docker-stack-this/tree/master/traefik_stack5

@pascalandy
Copy link
Contributor

@ldez I believe this trailing "/" issue should be part of the docs as an example. Cheers!

@ldez
Copy link
Contributor

ldez commented Sep 14, 2018

@pascalandy maybe your first first pull request 😃 ?

@pascalandy
Copy link
Contributor

pascalandy commented Sep 14, 2018

Sure, please point me in which section should this doc appear and I'll do it!
I looked into https://github.com/containous/traefik/tree/master/docs and I don't see examples for docker-compose. Let's me know :)

@ldez
Copy link
Contributor

ldez commented Sep 14, 2018

@pascalandy
Copy link
Contributor

pascalandy commented Sep 14, 2018

See PR here: #3891

@nyurik
Copy link

nyurik commented Nov 23, 2018

I created a docs issue #4221 with a similar problem - i think we should document, as a table, all the variations of each matcher and replacer, and when they would apply, and how they would change the request on the backend. Would love to get feedback from the experts here :)

@tiagoefreitas
Copy link

How to do this using file config instead of docker-compose? Doesn't seem to work due to different variables

@romprod
Copy link

romprod commented Feb 2, 2019

Ditto, would be interesting to do this using file config

@hartzell
Copy link

hartzell commented Feb 2, 2019

@romprod, @tiagoefreitas -- What have you tried? Can you show us a nearly working example?

@romprod
Copy link

romprod commented Feb 2, 2019

I have this working now, I'll post my config when I get back after the weekend hopefully

@romprod
Copy link

romprod commented Feb 3, 2019

Working code to translate 192.168.0.1:9000 to 123.co.uk/portainer

[backends.portainer]
    [backends.portainer.servers.portainer]
    url = "http://192.168.0.1:9000"

[frontends.portainer]
  backend = "portainer"
  passHostHeader = true

    [frontends.portainer.routes.portainer]
    rule = "Host:123.co.uk; PathPrefix:/portainer;ReplacePathRegex: ^/portainer/(.*) /$1"

[frontends.portainer.redirect]
      regex = "^(.*)/portainer$"
	  replacement = "$1/portainer/"

@richardburakowski
Copy link

I needed something backward compatible to 1.4 - Tested on 1.4.6 and 1.6.6
Adding a trailing slash while keeping the path prefix:
- traefik.frontend.rule=PathPrefixStripRegex:/api{u:$$},/api/;AddPrefix:/api/

regex matches and strips on/apiand/api/and puts back/api/so there's always at least/api/

Alternately,
- traefik.frontend.rule=PathPrefixStrip:/api;AddPrefix:/api
if you can handle/api123rewriting as/api/123

similarly to drop the prefix:
- traefik.frontend.rule=PathPrefixStripRegex:/api{u:$$},/api/;AddPrefix:/

@gerroon
Copy link

gerroon commented Apr 20, 2019

This is killing me. How am I supposed to employ this kind of rewriting site wide? I do not want to create regex for tens of apps. I am wondering why this is still not considered an optional thing.

@Franky1

This comment has been minimized.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

No branches or pull requests