Repository: traefik/traefik (language: go)

Issue:
Traefik v3.0.0 breaks video streams
### Welcome!

- [X] Yes, I've searched similar issues on [GitHub](https://github.com/traefik/traefik/issues) and didn't find any.
- [X] Yes, I've searched similar issues on the [Traefik community forum](https://community.traefik.io) and didn't find any.

### What did you do?

Seems like Traefik v3 breaks some http streams. After upgrading to v3.0.0 certain clients were not able to seek videos in Jellyfin. Weirdly it shows ` "DownstreamStatus": 0,` and `  "RequestContentSize": 0,`. No errors on the Jellyfin server side.

Downgrading traefik to `2.11` fixes the issue.

### What did you see instead?

Request on `traefik 3.0.0`

```json
{
...
  "DownstreamContentSize": 0,
  "DownstreamStatus": 0,
  "Duration": 11988789492,
  "OriginContentSize": 0,
  "OriginDuration": 0,
  "OriginStatus": 0,
  "Overhead": 11988789492,
...
  "RequestContentSize": 0,
  "RequestCount": 53985,
...
  "RequestMethod": "GET",
  "RequestPath": "/Videos/6db0e85d20dadacca25d553cc63815c4/stream.mkv...",
  "RequestPort": "-",
  "RequestProtocol": "HTTP/2.0",
  "RequestScheme": "https",
  "RetryAttempts": 0,
  "RouterName": "jellyfin_pub@docker",
  "ServiceAddr": "192.168.128.10:8096",
  "ServiceName": "jellyfin@docker",
  "ServiceURL": "http://192.168.128.10:8096",
  "StartLocal": "2024-05-02T14:40:26.710272631Z",
  "StartUTC": "2024-05-02T14:40:26.710272631Z",
  "TLSCipher": "TLS_AES_128_GCM_SHA256",
  "TLSVersion": "1.3",
  "entryPointName": "extranet",
  "level": "info",
  "msg": "",
  "time": "2024-05-02T14:40:38Z"
}
```

Same request on `traefik 2.11`

```json
{
...
  "ClientUsername": "-",
  "DownstreamContentSize": 47530,
  "DownstreamStatus": 206,
  "Duration": 278278129,
  "OriginContentSize": 47530,
  "OriginDuration": 270501887,
  "OriginStatus": 206,
  "Overhead": 7776242,
...
  "RequestContentSize": 0,
  "RequestCount": 422,
...
  "RequestMethod": "GET",
  "RequestPath": "/Videos/6db0e85d20dadacca25d553cc63815c4/stream.mkv...",
  "RequestPort": "-",
  "RequestProtocol": "HTTP/2.0",
  "RequestScheme": "https",
  "RetryAttempts": 0,
  "RouterName": "jellyfin_pub@docker",
  "ServiceAddr": "192.168.128.10:8096",
  "ServiceName": "jellyfin@docker",
  "ServiceURL": {
    "Scheme": "http",
    "Opaque": "",
    "User": null,
    "Host": "192.168.128.10:8096",
    "Path": "",
    "RawPath": "",
    "OmitHost": false,
    "ForceQuery": false,
    "RawQuery": "",
    "Fragment": "",
    "RawFragment": ""
  },
  "StartLocal": "2024-05-02T15:28:39.216701682Z",
  "StartUTC": "2024-05-02T15:28:39.216701682Z",
  "TLSCipher": "TLS_AES_128_GCM_SHA256",
  "TLSVersion": "1.3",
  "entryPointName": "extranet",
  "level": "info",
  "msg": "",
  "time": "2024-05-02T15:28:39Z"
}
```


### What version of Traefik are you using?

```
$ docker run traefik version
Version:      3.0.0
Codename:     beaufort
Go version:   go1.22.2
Built:        2024-04-29T14:25:59Z
OS/Arch:      linux/amd64
```

### What is your environment & configuration?

```yaml
      - "traefik.http.routers.jellyfin.rule=Host(`${DOMAIN}`)"
      - "traefik.http.routers.jellyfin.entrypoints=websecure"
      - "traefik.http.services.jellyfin.loadbalancer.server.port=8096"
      - "traefik.http.routers.jellyfin.service=jellyfin"
```


### If applicable, please paste the log output in DEBUG level

_No response_


Discussion / hints:
Did you see the release notes about the new shorter timeouts because of some CVEs?
I don't think it has anything to do with timeouts, because it happens immediately, not after some time.
Hey @Janhouse,

Thanks for reaching out.

Could you provide a minimal reproducible case (for instance, full Kubernetes manifest to reproduce the issue)?
In the meantime, if any community member can help us find verified steps to reproduce and fix the issue if possible, we would love the help.

Did you use compress middleware?

 I have same problem while using compress middleware with [filebrowser](https://github.com/filebrowser/filebrowser) inside my k3s cluster, even I set `includedContentTypes` only contain mime types like this:
```yaml
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: auth-body-size
spec:
  compress:
    includedContentTypes:
      - text/xml
      - text/css
      - text/html
      - text/plain
      - text/javascript
      - image/tiff
      - image/svg+xml
      - application/xml
      - application/json
      - application/rss+xml
      - application/javascript
      - application/x-javascript
```

It will cause status code rewrited from `206` to `200`, which cause seeking media progress not work in web browser

So I have to remove the compress middleware entirely to make it work
I write a k8s yaml to reproduce this problem, a simple html and mp4 video, can't seek progress, unless remove the compress middleware:

<details>

```yaml
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: test-server
data:
  index.html: |
    <html>
      <body>
        <video controls>
          <source src="video.mp4" type="video/mp4">
        </video>
      </body>
    </html>
  Caddyfile: |
    {
      admin off
      auto_https off
    }
    :8080 {
      handle_path /video.mp4 {
        rewrite * /html/mov_bbb.mp4
        reverse_proxy * https://www.w3schools.com {
          header_up Host {upstream_hostport}
          header_down -*
        }
      }
      file_server * {
        root /etc/caddy
      }
    }

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: test
  labels:
    app: test
spec:
  revisionHistoryLimit: 0
  selector:
    matchLabels:
      app: test
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: test
    spec:
      containers:
        - image: caddy:latest
          imagePullPolicy: IfNotPresent
          name: test
          ports:
            - containerPort: 8080
              name: test
          volumeMounts:
            - name: config
              mountPath: /etc/caddy
      volumes:
        - name: config
          configMap:
            name: test-server

---
apiVersion: v1
kind: Service
metadata:
  name: test
  labels:
    app: test
spec:
  ports:
    - port: 8080
  selector:
    app: test

---
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
  name: test-compress
spec:
  compress:
    includedContentTypes:
      - text/xml
      - text/css
      - text/html
      - text/plain
      - text/javascript
      - image/tiff
      - image/svg+xml
      - application/xml
      - application/json
      - application/rss+xml
      - application/javascript
      - application/x-javascript

---
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: test
  annotations:
    kubernetes.io/ingress.class: traefik
spec:
  entryPoints:
    - websecure
  routes:
    - match: Host(`test.dosk.host`)
      kind: Rule
      services:
        - name: test
          port: 8080
      middlewares:
        - name: test-compress
```

</details>
Hello @springhack,

Thank you for the full test case.

We think the error may be due to the [deletion of the automatic content type detection in v3.](https://doc.traefik.io/traefik/migration/v2-to-v3/#content-type-auto-detection)

Could you give a try to the proposed solution and provide feedback please?
@nmengin that did not help, issue persists when using contenttype middleware.
@nmengin add a contenttype middleware won't solve this problem, I read the code and I guess maybe [here](https://github.com/traefik/traefik/blob/6a06560318c4d940a211e7007f5e0b715480c360/pkg/middlewares/compress/brotli/brotli.go#L162) and [here](https://github.com/traefik/traefik/blob/6a06560318c4d940a211e7007f5e0b715480c360/pkg/middlewares/compress/brotli/brotli.go#L169), request with `range` header go into brotli handler, which contenttype not include in `includedContentTypes` will write directly to `rw` without `r.rw.WriteHeader(r.statusCode)`, then the statusCode will be `200` not `206`
The same problem was encountered when using the Traefik proxy Nginx. Reproducible in both v3.0.0 and v3.0.1.

<details>
<summary>Debug logs are as follows</summary>

```
2024-05-23T01:38:29Z DBG github.com/traefik/traefik/v3/pkg/middlewares/compress/compress.go:102 > Unable to parse MIME type error="mime: no media type" middlewareName=gzip@docker middlewareType=Compress
2024-05-23T01:38:29Z DBG github.com/traefik/traefik/v3/pkg/server/service/loadbalancer/wrr/wrr.go:196 > Service selected by WRR: 7cc267a38fc94db2
2024-05-23T01:38:32Z DBG github.com/traefik/traefik/v3/pkg/middlewares/compress/compress.go:102 > Unable to parse MIME type error="mime: no media type" middlewareName=gzip@docker middlewareType=Compress
2024-05-23T01:38:32Z DBG github.com/traefik/traefik/v3/pkg/server/service/loadbalancer/wrr/wrr.go:196 > Service selected by WRR: 7cc267a38fc94db2
2024-05-23T01:38:32Z DBG github.com/traefik/traefik/v3/pkg/middlewares/compress/compress.go:102 > Unable to parse MIME type error="mime: no media type" middlewareName=gzip@docker middlewareType=Compress
2024-05-23T01:38:32Z DBG github.com/traefik/traefik/v3/pkg/server/service/loadbalancer/wrr/wrr.go:196 > Service selected by WRR: c7f1e6d2a67551de
2024-05-23T01:38:35Z DBG github.com/traefik/traefik/v3/pkg/middlewares/compress/compress.go:102 > Unable to parse MIME type error="mime: no media type" middlewareName=gzip@docker middlewareType=Compress
2024-05-23T01:38:35Z DBG github.com/traefik/traefik/v3/pkg/server/service/loadbalancer/wrr/wrr.go:196 > Service selected by WRR: 7cc267a38fc94db2
```

</details>


More of a me too but I can reproduce this error in 3.0.0 and 3.0.1 when `compress` is enabled on my site. If I go back to 2.11.2 with `compress` enabled, it works. I have to disable `compress` on 3.0.0/3.0.1 (either completely or by specifically excluding a content type) so that the video will play.

The following YAML config enables me to play the video I want to play, which is specifically an mp4, using 3.0.1 (didn't test on 3.0.0 but assume it'll work).

```yaml
http:
  middlewares:
    gzip_compress_v3:
      compress:
        excludedContentTypes:
          - text/event-stream
          - video/mp4
```

I do realise that although I've named the `middleware gzip_compress_v3` ...it's actually using Brotli.
Same problem here. The conditions to reproduce the problem:

Traefik 3.0.1 + compress middleware +  streaming audio and video files + chromium based browsers.

I could solve the issue with AUDIO files, adding "audio/x-wav" and "audio/mpeg" to the list of excluded content types to compress:

```
http:
  middlewares:
    compressor:
      compress: 
        excludedContentTypes: 
          - text/event-stream
          - audio/x-wav
          - audio/mpeg
          - video/mp4
```

But I had no luck with VIDEO file types.

The considered workarounds are:

1. disable the "compress" middleware (if we do this, the video works, but we lost the benefit of compression, which is not acceptable)
2. revert to previous version of Traefik 2.*
3. disable the "Accept-Ranges" header (disable the range requests)
4. wait for a different workaround

Applied workaround:
Finally, we decided to disable the "Accept-ranges" (disable range request) header for video file types. Our site does not have a lot of videos, and most of them are quite small. So that workaround is acceptable at this time.



Fix patch:
diff --git a/pkg/middlewares/compress/brotli/brotli.go b/pkg/middlewares/compress/brotli/brotli.go
index 17225f2527..27802b0f68 100644
--- a/pkg/middlewares/compress/brotli/brotli.go
+++ b/pkg/middlewares/compress/brotli/brotli.go
@@ -160,6 +160,7 @@ func (r *responseWriter) Write(p []byte) (int, error) {
 			}
 			if !found {
 				r.compressionDisabled = true
+				r.rw.WriteHeader(r.statusCode)
 				return r.rw.Write(p)
 			}
 		}
@@ -167,6 +168,7 @@ func (r *responseWriter) Write(p []byte) (int, error) {
 		for _, excludedContentType := range r.excludedContentTypes {
 			if excludedContentType.equals(mediaType, params) {
 				r.compressionDisabled = true
+				r.rw.WriteHeader(r.statusCode)
 				return r.rw.Write(p)
 			}
 		}


Test patch:
diff --git a/pkg/middlewares/compress/brotli/brotli_test.go b/pkg/middlewares/compress/brotli/brotli_test.go
index 005587411f..67c794c462 100644
--- a/pkg/middlewares/compress/brotli/brotli_test.go
+++ b/pkg/middlewares/compress/brotli/brotli_test.go
@@ -356,7 +356,7 @@ func Test_ExcludedContentTypes(t *testing.T) {
 			h := mustNewWrapper(t, cfg)(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
 				rw.Header().Set(contentType, test.contentType)
 
-				rw.WriteHeader(http.StatusOK)
+				rw.WriteHeader(http.StatusAccepted)
 
 				_, err := rw.Write(bigTestBody)
 				require.NoError(t, err)
@@ -368,7 +368,7 @@ func Test_ExcludedContentTypes(t *testing.T) {
 			rw := httptest.NewRecorder()
 			h.ServeHTTP(rw, req)
 
-			assert.Equal(t, http.StatusOK, rw.Code)
+			assert.Equal(t, http.StatusAccepted, rw.Code)
 
 			if test.expCompression {
 				assert.Equal(t, "br", rw.Header().Get(contentEncoding))
@@ -460,7 +460,7 @@ func Test_IncludedContentTypes(t *testing.T) {
 			h := mustNewWrapper(t, cfg)(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
 				rw.Header().Set(contentType, test.contentType)
 
-				rw.WriteHeader(http.StatusOK)
+				rw.WriteHeader(http.StatusAccepted)
 
 				_, err := rw.Write(bigTestBody)
 				require.NoError(t, err)
@@ -472,7 +472,7 @@ func Test_IncludedContentTypes(t *testing.T) {
 			rw := httptest.NewRecorder()
 			h.ServeHTTP(rw, req)
 
-			assert.Equal(t, http.StatusOK, rw.Code)
+			assert.Equal(t, http.StatusAccepted, rw.Code)
 
 			if test.expCompression {
 				assert.Equal(t, "br", rw.Header().Get(contentEncoding))