traefik: Malformed/garbled HTML response when compress is enabled

What version of Traefik are you using (traefik version)?

1.1.2-a

What is your environment & configuration (arguments, toml…)?

# traefik.toml
logLevel = "INFO"
defaultEntryPoints = ["http"]
[entryPoints]
  [entryPoints.http]
  address = ":80"
  compress = true
[kubernetes]
[web]
address = ":8081"
traefik service.yaml
# Source: traefik/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: rtraefik-traefik
  labels:
    app: rtraefik-traefik
    chart: "traefik-1.1.2-a"
    release: "rtraefik"
    heritage: "Tiller"
spec:
  type: LoadBalancer
  selector:
    app: rtraefik-traefik
  ports:
  - port: 80
    name: http
  - port: 443
    name: https
traefik deployment.yaml
# Source: traefik/templates/deployment.yaml
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
  name: rtraefik-traefik
  labels:
    app: rtraefik-traefik
    chart: "traefik-1.1.2-a"
    release: "rtraefik"
    heritage: "Tiller"
spec:
  template:
    metadata:
      labels:
        app: rtraefik-traefik
        chart: "traefik-1.1.2-a"
        release: "rtraefik"
        heritage: "Tiller"
    spec:
      terminationGracePeriodSeconds: 60
      hostNetwork: true
      containers:
      - image: traefik:v1.1.2
        name: rtraefik-traefik
        resources:
          requests:
            cpu: "100m"
            memory: "20Mi"
          limits:
            cpu: "100m"
            memory: "30Mi"
        readinessProbe:
          tcpSocket:
            port: 80
          failureThreshold: 1
          initialDelaySeconds: 10
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 2
        livenessProbe:
          tcpSocket:
            port: 80
          failureThreshold: 3
          initialDelaySeconds: 10
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 2
        volumeMounts:
        - mountPath: /config
          name: config
        ports:
        - containerPort: 80
        - containerPort: 443
        - containerPort: 8081
        args:
        - --configfile=/config/traefik.toml
      volumes:
      - name: config
        configMap:
          name: rtraefik-traefik
kubectl get ingress rjenkins-jenkins -o yaml
$ kubectl get ingress rjenkins-jenkins -o yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: '{"kind":"Ingress","apiVersion":"extensions/v1beta1","metadata":{"name":"rjenkins-jenkins","namespace":"default","selfLink":"/apis/extensions/v1beta1/namespaces/default/ingresses/rjenkins-jenkins","uid":"fd3bb42f-df57-11e6-bb42-0050568a2a2f","resourceVersion":"1052907","generation":2,"creationTimestamp":"2017-01-20T21:32:48Z","labels":{"app":"rjenkins-jenkins","chart":"traefik-1.1.2-a","heritage":"Tiller","release":"rtraefik"}},"spec":{"rules":[{"host":"jenkins.example.com","http":{"paths":[{"backend":{"serviceName":"rjenkins-jenkins","servicePort":8080}}]}}]},"status":{"loadBalancer":{}}}'
  creationTimestamp: 2017-01-20T21:32:48Z
  generation: 2
  labels:
    app: rjenkins-jenkins
    chart: traefik-1.1.2-a
    heritage: Tiller
    release: rtraefik
  name: rjenkins-jenkins
  namespace: default
  resourceVersion: "1053907"
  selfLink: /apis/extensions/v1beta1/namespaces/default/ingresses/rjenkins-jenkins
  uid: fd3bb42f-df57-11e6-bb42-0050568a2a2f
spec:
  rules:
  - host: jenkins.example.com
    http:
      paths:
      - backend:
          serviceName: rjenkins-jenkins
          servicePort: 8080
status:
  loadBalancer: {}

What did you do?

I’m using traefik in kubernetes, installed by helm. I have a jenkins instance that I’m trying to proxy to. I’m also using traefik to proxy traefik’s dashboard and prometheus server + alertmanager.

What did you expect to see?

I expected Jenkins page to load successfully. Other ingress resources like traefik’s dashboard and prometheus are successfully loading.

What did you see instead?

Jenkins gives malformed HTTP response. The headers look OK and in-tact but the HTML payload is malformed. This happens on all browsers tested including OSX Chrome and Firefox. My best guess is that browsers are trying to gzip decode the response when the response isn’t being compressed. See screenshots:

image

Jenkins will load OK if I hit the service IP and port directly: image

The headers from the malformed response: image

The raw response from the malformed response: image

curl http://jenkins.example.com/login -v
$ curl http://jenkins.example.com/login -v
*   Trying 10.163.148.196...
* TCP_NODELAY set
* Connected to jenkins.example.com (10.163.148.196) port 80 (#0)
> GET /login HTTP/1.1
> Host: jenkins.example.com
> User-Agent: curl/7.51.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Cache-Control: no-cache,no-store,must-revalidate
< Content-Type: text/html;charset=UTF-8
< Date: Mon, 23 Jan 2017 21:23:39 GMT
< Expires: 0
< Server: Jetty(9.2.z-SNAPSHOT)
< Set-Cookie: JSESSIONID.ab758541=tzum86vvk6es1iw413gtdcsql;Path=/;HttpOnly
< Vary: Accept-Encoding
< X-Content-Type-Options: nosniff
< X-Frame-Options: sameorigin
< X-Hudson: 1.395
< X-Hudson-Cli-Port: 50000
< X-Hudson-Theme: default
< X-Instance-Identity: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAv0hMZsLNMdkrNHMnwFvR8Ks+xjLupJnNzofDhIJaswpRfcFN1LvrzahQatRmbka7AhDt3G90Syo+3gaCMUxAFI0Px61nhTJ8hE8hhgNf6yYUnfxg0HRJ4j3ddumCvnQoPeq8x/rd+wxkZW7u/IpJHoS6ShFUzrfTuZSWI8v4k3A9SwXm+4x9r7c0VduEl56wefaBnSRi8cJXbXxyjtZ4/v4rurh6rnSoorlsTAmx1R9M+jZ9JFedFRc1e54Mnob1FOAAyvQryAr+HAGRAANv/EGIJ4FZBxjGMhliBzo6oifvaY6C/St/7r2FHFcp1VD4mt7naKgbaTbKExcZfsn1vwIDAQAB
< X-Jenkins: 2.19.4
< X-Jenkins-Cli-Port: 50000
< X-Jenkins-Cli2-Port: 50000
< X-Jenkins-Session: d200f825
< X-Ssh-Endpoint: jenkins.example.com:35985
< Transfer-Encoding: chunked
<





  <!DOCTYPE html><html><head resURL="/static/d200f825" data-rooturl="" data-resurl="/static/d200f825">


    <title>Jenkins</title><link rel="stylesheet" href="/static/d200f825/css/layout-common.css" type="text/css" /><link rel="stylesheet" href="/static/d200f825/css/style.css" type="text/css" /><link rel="stylesheet" href="/static/d200f825/css/color.css" type="text/css" /><link rel="stylesheet" href="/static/d200f825/css/responsive-grid.css" type="text/css" /><link rel="shortcut icon" href="/static/d200f825/favicon.ico" type="image/vnd.microsoft.icon" /><link color="black" rel="mask-icon" href="/images/mask-icon.svg" /><script>var isRunAsTest=false; var rootURL=""; var resURL="/static/d200f825";</script><script src="/static/d200f825/scripts/prototype.js" type="text/javascript"></script><script src="/static/d200f825/scripts/behavior.js" type="text/javascript"></script><script src='/adjuncts/d200f825/org/kohsuke/stapler/bind.js' type='text/javascript'></script><script src="/static/d200f825/scripts/yui/yahoo/yahoo-min.js"></script><script src="/static/d200f825/scripts/yui/dom/dom-min.js"></script><script src="/static/d200f825/scripts/yui/event/event-min.js"></script><script src="/static/d200f825/scripts/yui/animation/animation-min.js"></script><script src="/static/d200f825/scripts/yui/dragdrop/dragdrop-min.js"></script><script src="/static/d200f825/scripts/yui/container/container-min.js"></script><script src="/static/d200f825/scripts/yui/connection/connection-min.js"></script><script src="/static/d200f825/scripts/yui/datasource/datasource-min.js"></script><script src="/static/d200f825/scripts/yui/autocomplete/autocomplete-min.js"></script><script src="/static/d200f825/scripts/yui/menu/menu-min.js"></script><script src="/static/d200f825/scripts/yui/element/element-min.js"></script><script src="/static/d200f825/scripts/yui/button/button-min.js"></script><script src="/static/d200f825/scripts/yui/storage/storage-min.js"></script><script src="/static/d200f825/scripts/hudson-behavior.js" type="text/javascript"></script><script src="/static/d200f825/scripts/sortable.js" type="text/javascript"></script><script>crumb.init("", "");</script><link rel="stylesheet" href="/static/d200f825/scripts/yui/container/assets/container.css" type="text/css" /><link rel="stylesheet" href="/static/d200f825/scripts/yui/assets/skins/sam/skin.css" type="text/css" /><link rel="stylesheet" href="/static/d200f825/scripts/yui/container/assets/skins/sam/container.css" type="text/css" /><link rel="stylesheet" href="/static/d200f825/scripts/yui/button/assets/skins/sam/button.css" type="text/css" /><link rel="stylesheet" href="/static/d200f825/scripts/yui/menu/assets/skins/sam/menu.css" type="text/css" /><meta name="ROBOTS" content="INDEX,NOFOLLOW" /><script src="/static/d200f825/jsbundles/page-init.js" type="text/javascript"></script></head><body data-model-type="hudson.model.Hudson" id="jenkins" class="yui-skin-sam jenkins-2.19.4 two-column" data-version="2.19.4"><a href="#skip2content" class="skiplink">Skip to content</a><div id="page-head"><div id="header"><div class="logo"><a id="jenkins-home-link" href="/"><img src="/static/d200f825/images/headshot.png" alt="title" id="jenkins-head-icon" /><img src="/static/d200f825/images/title.png" alt="title" width="139" id="jenkins-name-icon" height="34" /></a></div><div class="login"> <a href="/loginEntry?from=%2F"><b>log in</b></a></div><div class="searchbox hidden-xs"><form method="get" name="search" action="/search/" style="position:relative;" class="no-json"><div id="search-box-minWidth"></div><div id="search-box-sizer"></div><div id="searchform"><input name="q" placeholder="search" id="search-box" class="has-default-text" /> <a href="http://wiki.jenkins-ci.org/display/JENKINS/Search+Box"><img src="/static/d200f825/images/16x16/help.png" style="width: 16px; height: 16px; " class="icon-help icon-sm" /></a><div id="search-box-completion"></div><script>createSearchBox("/search/");</script></div></form></div></div><div id="breadcrumbBar"><tr id="top-nav"><td id="left-top-nav" colspan="2"><link rel='stylesheet' href='/adjuncts/d200f825/lib/layout/breadcrumbs.css' type='text/css' /><script src='/adjuncts/d200f825/lib/layout/breadcrumbs.js' type='text/javascript'></script><div class="top-sticker noedge"><div class="top-sticker-inner"><div id="right-top-nav"></div><ul id="breadcrumbs"><li class="item"><a href="/" class="model-link inside">Jenkins</a></li><li href="/" class="children"></li></ul><div id="breadcrumb-menu-target"></div></div></div></td></tr></div></div><div id="page-body" class="clear"><div id="side-panel"></div><div id="main-panel"><a name="skip2content"></a><div style="margin: 2em;"><form method="post" name="login" action="j_security_check" style="text-size:smaller"><table><tr><td>User:</td><td><input type="text" name="j_username" id="j_username" autocorrect="off" autocapitalize="off" /></td></tr><tr><td>Password:</td><td><input type="password" name="j_password" /></td></tr></table><input name="from" type="hidden" /><input name="Submit" type="submit" value="log in" class="submit-button primary" /><script>
            $('j_username').focus();
* Curl_http_done: called premature == 0
* Connection #0 to host jenkins.example.com left intact
          </script></form></div></div></div><footer><div class="container-fluid"><div class="row"><div class="col-md-6" id="footer"></div><div class="col-md-18"><span class="page_generated">Page generated: Jan 23, 2017 9:23:39 PM UTC</span><span class="rest_api"><a href="api/">REST API</a></span><span class="jenkins_ver"><a href="http://jenkins-ci.org/">Jenkins ver. 2.19.4</a></span></div></div></div></footer></body></html>

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 1
  • Comments: 16 (6 by maintainers)

Commits related to this issue

Most upvoted comments

I opened a PR https://github.com/NYTimes/gziphandler/pull/29 which should fix this in the compression handler.

Hi gang! Maintainer of the gziphandler here.

I’d prefer our library to follow the principle of least surprise and simply compress any/all requests that flow through it.

If you’re running a mix of compressed and non-compressed endpoints on the same server, I would suggest using two separate middleware stacks and wrapping the endpoints accordingly. For a good example of this practice, take a look at how Go Kit composes endpoints and middleware in it’s examples: https://github.com/go-kit/kit/tree/master/examples

As for Traefik, I would suggest updating the service to move the compress configuration to individual back end services for a finer control.

I’m also using (or attempting to) use with helm, though turning off gzip doesn’t seem to resolve the issue for me. (v1.1.2-g) – not really sure what options I have in this state.

This can be looked at a little differently: traefik is forwarding an “Accept-Encoding: gzip” header, which depending on the fix (and current state), traefik does not support. It’s expecting to get a non-encoded/non-compressed response, but telling the service it accepts the encoding and promptly ignoring the Content-Encoding header sent back.

To add the feature of content-encoding (compression) at a proxy level, in HTTP, you can’t get away from handling & processing certain headers in some way or another (e.g. Transfer-Encoding, Content-Encoding, Content-Length, maybe others) – since the response from the service and response to the client differ.

I’m thinking we have three options to solve this without edge cases:

  1. Remove compression feature from traefik and leave it to the services
  2. Strip ‘Accept-Encoding’ headers before going to the service (at least if compress=true)
  3. Check the encoding headers to add compression, only when appropriate

1 - side-steps the whole thing, but it’s a useful feature and feels a bit extreme

2 - should make things work, though not ideal since the service could be highly compressible, and the service could have cached the data. Meaning it would be far more efficient to let it respond with the encoded data directly. We would also lose support for any other content-encoding types the backend may support (like br) by going through traefik.

3 - IMO the ideal state: only enable gzip (at a traefik level) in one specific case:

  • “Accept-Encoding” was set to “gzip” on the request from the client
  • “Content-Encoding” is unset on the response from the service
  • compress=true

I think “3” highlights the user’s expectation of what should happen. The key piece is only encoding the response to the client, if “Content-Encoding” is unset on the service response – meaning it’s not already encoded in whatever format (that might not be gzip anyway).

Ok before fixing this we should decide if we want behaviour like in @bakins https://github.com/NYTimes/gziphandler/pull/29, in which case we should fork gziphandler or write our own.

Or we make compression configurable at the backend level…

Or Both…

Personally I think pushing this down to the backend level is a reasonable thing to do, but then it would need to be supported on each provider…