Repository: traefik/traefik (language: go)

Issue:
Custom error pages and original request URL
<!--
DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS.

The issue tracker is for reporting bugs and feature requests only.
For end-user related support questions, please refer to one of the following:

- Stack Overflow (using the "traefik" tag): https://stackoverflow.com/questions/tagged/traefik
- the Traefik community Slack channel: https://traefik.herokuapp.com

-->


### Do you want to request a *feature* or report a *bug*?

Feature

### What did you expect to see?

I have a frontend written with [Next.js](https://github.com/zeit/next.js/), which contains quite a lot of images and also renders some react stuff on the server. Requests to images can make the Node.js process slower, so I'm thinking of serving them via an nginx container, which is configured to respond to only a folder with images (traefik's `pathPrefix`).

Requests to the folder with images can be combined with a 404 message rendered by Next.js, thanks to the [custom error pages](https://docs.traefik.io/configuration/commons/#custom-error-pages) feature. There is one scenario I cannot get working though:

When nginx returns 404, traefik knocks the Node.js container, but does not pass the original request URL anywhere in the headers. Without this, it is impossible to implement a redirect to a correct image:

```
user
↓ /images/products/PRODUCT_ID-wrong-slug.jpg
traefik
↓
nginx
↓ 404
traefik
↓ /images/products/PRODUCT_ID-wrong-slug.jpg
nodejs
↓ redirect to /images/products/PRODUCT_ID-correct-slug.jpg
traefik
↓
user
```

It'd be great if in addition to `{status}`, it was possible to use `{path}` or `{url}` in `traefik.frontend.errors.XYZ.query`. My nginx container would be configured like this

```
traefik.frontend.errors.all.backend=nextjs
traefik.frontend.errors.all.status=400-599
traefik.frontend.errors.all.query={path}
```
or
```
traefik.frontend.errors.all.query=/custom-error?url={url}
```

(the latter is useful if a website is multilingual and is available at several domain names)

WDYT?

<!--

HOW TO WRITE A GOOD ISSUE?

- Respect the issue template as much as possible.
- If possible, use the command `traefik bug`. See https://www.youtube.com/watch?v=Lyz62L8m93I.
- The title should be short and descriptive.
- Explain the conditions which led you to report this issue: the context.
- The context should lead to something, an idea or a problem that you’re facing.
- Remain clear and concise.
- Format your messages to help the reader focus on what matters and understand the structure of your message, use Markdown syntax https://help.github.com/articles/github-flavored-markdown

-->



Discussion / hints:
I'm not quite familiar enough with golang to run this locally and test a change.
Essentially, the following pseudo code would need to be implemented in the errorpages code.  It'd be great in v1.8 ;) 

https://github.com/containous/traefik/blob/f1b085fa364f3d3184bccd294974db408e77cbf3/pkg/middlewares/customerrors/custom_errors.go#L91 

```

if len(h.backendQuery) > 0 {
  query = "/" + strings.TrimPrefix(h.backendQuery, "/") 
  query = strings.Replace(query, "{status}", strconv.Itoa(recorder.GetCode()), -1)
  query = strings.Replace(query, "{path}", recorder.GetUrl() // the trailing end of the path without the domain.
}
```
i think we need a global error page. 
Since i have many context rules, i don't like to set this configuration for each rule.


Fix patch:
diff --git a/docs/content/middlewares/http/errorpages.md b/docs/content/middlewares/http/errorpages.md
index 25d3e508d8..81986d6675 100644
--- a/docs/content/middlewares/http/errorpages.md
+++ b/docs/content/middlewares/http/errorpages.md
@@ -1,16 +1,16 @@
 ---
-title: "Traefik ErrorPage Documentation"
-description: "In Traefik Proxy, the ErrorPage middleware returns custom pages according to configured ranges of HTTP Status codes. Read the technical documentation."
+title: "Traefik Errors Documentation"
+description: "In Traefik Proxy, the Errors middleware returns custom pages according to configured ranges of HTTP Status codes. Read the technical documentation."
 ---
 
-# ErrorPage
+# Errors
 
 It Has Never Been Easier to Say That Something Went Wrong
 {: .subtitle }
 
-![ErrorPages](../../assets/img/middleware/errorpages.png)
+![Errors](../../assets/img/middleware/errorpages.png)
 
-The ErrorPage middleware returns a custom page in lieu of the default, according to configured ranges of HTTP Status codes.
+The Errors middleware returns a custom page in lieu of the default, according to configured ranges of HTTP Status codes.
 
 !!! important
 
@@ -21,16 +21,16 @@ The ErrorPage middleware returns a custom page in lieu of the default, according
 ```yaml tab="Docker"
 # Dynamic Custom Error Page for 5XX Status Code
 labels:
-  - "traefik.http.middlewares.test-errorpage.errors.status=500-599"
-  - "traefik.http.middlewares.test-errorpage.errors.service=serviceError"
-  - "traefik.http.middlewares.test-errorpage.errors.query=/{status}.html"
+  - "traefik.http.middlewares.test-errors.errors.status=500-599"
+  - "traefik.http.middlewares.test-errors.errors.service=serviceError"
+  - "traefik.http.middlewares.test-errors.errors.query=/{status}.html"
 ```
 
 ```yaml tab="Kubernetes"
 apiVersion: traefik.containo.us/v1alpha1
 kind: Middleware
 metadata:
-  name: test-errorpage
+  name: test-errors
 spec:
   errors:
     status:
@@ -43,32 +43,32 @@ spec:
 
 ```yaml tab="Consul Catalog"
 # Dynamic Custom Error Page for 5XX Status Code
-- "traefik.http.middlewares.test-errorpage.errors.status=500-599"
-- "traefik.http.middlewares.test-errorpage.errors.service=serviceError"
-- "traefik.http.middlewares.test-errorpage.errors.query=/{status}.html"
+- "traefik.http.middlewares.test-errors.errors.status=500-599"
+- "traefik.http.middlewares.test-errors.errors.service=serviceError"
+- "traefik.http.middlewares.test-errors.errors.query=/{status}.html"
 ```
 
 ```json tab="Marathon"
 "labels": {
-  "traefik.http.middlewares.test-errorpage.errors.status": "500-599",
-  "traefik.http.middlewares.test-errorpage.errors.service": "serviceError",
-  "traefik.http.middlewares.test-errorpage.errors.query": "/{status}.html"
+  "traefik.http.middlewares.test-errors.errors.status": "500-599",
+  "traefik.http.middlewares.test-errors.errors.service": "serviceError",
+  "traefik.http.middlewares.test-errors.errors.query": "/{status}.html"
 }
 ```
 
 ```yaml tab="Rancher"
 # Dynamic Custom Error Page for 5XX Status Code
 labels:
-  - "traefik.http.middlewares.test-errorpage.errors.status=500-599"
-  - "traefik.http.middlewares.test-errorpage.errors.service=serviceError"
-  - "traefik.http.middlewares.test-errorpage.errors.query=/{status}.html"
+  - "traefik.http.middlewares.test-errors.errors.status=500-599"
+  - "traefik.http.middlewares.test-errors.errors.service=serviceError"
+  - "traefik.http.middlewares.test-errors.errors.query=/{status}.html"
 ```
 
 ```yaml tab="File (YAML)"
 # Custom Error Page for 5XX
 http:
   middlewares:
-    test-errorpage:
+    test-errors:
       errors:
         status:
           - "500-599"
@@ -82,7 +82,7 @@ http:
 ```toml tab="File (TOML)"
 # Custom Error Page for 5XX
 [http.middlewares]
-  [http.middlewares.test-errorpage.errors]
+  [http.middlewares.test-errors.errors]
     status = ["500-599"]
     service = "serviceError"
     query = "/{status}.html"
@@ -121,8 +121,17 @@ The service that will serve the new requested error page.
 !!! info "Host Header"
 
     By default, the client `Host` header value is forwarded to the configured error [service](#service).
-    To forward the `Host` value corresponding to the configured error service URL, the [passHostHeader](../../../routing/services/#pass-host-header) option must be set to `false`.     
+    To forward the `Host` value corresponding to the configured error service URL, the [passHostHeader](../../../routing/services/#pass-host-header) option must be set to `false`.
 
 ### `query`
 
-The URL for the error page (hosted by `service`). You can use the `{status}` variable in the `query` option in order to insert the status code in the URL.
+The URL for the error page (hosted by [`service`](#service))).
+
+There are multiple variables that can be placed in the `query` option to insert values in the URL.
+
+The table below lists all the available variables and their associated values.
+
+| Variable   | Value                                                              |
+|------------|--------------------------------------------------------------------|
+| `{status}` | The response status code.                                          |
+| `{url}`    | The [escaped](https://pkg.go.dev/net/url#QueryEscape) request URL. |
diff --git a/pkg/middlewares/customerrors/custom_errors.go b/pkg/middlewares/customerrors/custom_errors.go
index 8a4431e8d4..dd28aeccd3 100644
--- a/pkg/middlewares/customerrors/custom_errors.go
+++ b/pkg/middlewares/customerrors/custom_errors.go
@@ -93,6 +93,7 @@ func (c *customErrors) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
 	if len(c.backendQuery) > 0 {
 		query = "/" + strings.TrimPrefix(c.backendQuery, "/")
 		query = strings.ReplaceAll(query, "{status}", strconv.Itoa(code))
+		query = strings.ReplaceAll(query, "{url}", url.QueryEscape(req.URL.String()))
 	}
 
 	pageReq, err := newRequest("http://" + req.Host + query)


Test patch:
diff --git a/pkg/middlewares/customerrors/custom_errors_test.go b/pkg/middlewares/customerrors/custom_errors_test.go
index 7b0a060ec3..c291b15d52 100644
--- a/pkg/middlewares/customerrors/custom_errors_test.go
+++ b/pkg/middlewares/customerrors/custom_errors_test.go
@@ -133,6 +133,24 @@ func TestHandler(t *testing.T) {
 				assert.Contains(t, recorder.Body.String(), "localhost")
 			},
 		},
+		{
+			desc:        "full query replacement",
+			errorPage:   &dynamic.ErrorPage{Service: "error", Query: "/?status={status}&url={url}", Status: []string{"503"}},
+			backendCode: http.StatusServiceUnavailable,
+			backendErrorHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+				if r.RequestURI != "/?status=503&url=http%3A%2F%2Flocalhost%2Ftest%3Ffoo%3Dbar%26baz%3Dbuz" {
+					t.Log(r.RequestURI)
+					return
+				}
+
+				_, _ = fmt.Fprintln(w, "My 503 page.")
+			}),
+			validate: func(t *testing.T, recorder *httptest.ResponseRecorder) {
+				t.Helper()
+				assert.Equal(t, http.StatusServiceUnavailable, recorder.Code, "HTTP status")
+				assert.Contains(t, recorder.Body.String(), "My 503 page.")
+			},
+		},
 	}
 
 	for _, test := range testCases {
@@ -153,7 +171,7 @@ func TestHandler(t *testing.T) {
 			errorPageHandler, err := New(context.Background(), handler, *test.errorPage, serviceBuilderMock, "test")
 			require.NoError(t, err)
 
-			req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost/test", nil)
+			req := testhelpers.MustNewRequest(http.MethodGet, "http://localhost/test?foo=bar&baz=buz", nil)
 
 			recorder := httptest.NewRecorder()
 			errorPageHandler.ServeHTTP(recorder, req)