Skip to content

Commit 4cb8e55

Browse files
committed
Compression on service level
1 parent 5d8f4b7 commit 4cb8e55

File tree

7 files changed

+83
-9
lines changed

7 files changed

+83
-9
lines changed

docs/config.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ The following environment variables can be used to configure the *Docker Flow Pr
1717
|CFG_TEMPLATE_PATH |Path to the configuration template. The path can be absolute (starting with `/`) or relative to `/cfg/tmpl`.<br>**Default value:** `/cfg/tmpl/haproxy.tmpl`|
1818
|CHECK_RESOLVERS |Enable `docker` as a resolver. Provides higher reliability at the cost of backend initialization time. If enabled, it might take a few seconds until a backend is resolved and operational.<br>**Default value:** `false`|
1919
|CERTS |This parameter is **deprecated** as of February 2017. All the certificates from the `/certs/` directory are now loaded automatically.|
20-
|COMPRESSION_ALGO |Enable HTTP compression. The currently supported algorithms are:<br>**identity**: this is mostly for debugging.<br>**gzip**: applies gzip compression. This setting is only available when support for zlib or libslz was built in.<br>**deflate** same as *gzip*, but with deflate algorithm and zlib format. Note that this algorithm has ambiguous support on many browsers and no support at all from recent ones. It is strongly recommended not to use it for anything else than experimentation. This setting is only available when support for zlib or libslz was built in.<br>**raw-deflate**: same as *deflate* without the zlib wrapper, and used as an alternative when the browser wants "deflate". All major browsers understand it and despite violating the standards, it is known to work better than *deflate*, at least on MSIE and some versions of Safari. This setting is only available when support for zlib or libslz was built in.<br>Compression will be activated depending on the Accept-Encoding request header. With identity, it does not take care of that header. If backend servers support HTTP compression, these directives will be no-op: haproxy will see the compressed response and will not compress again. If backend servers do not support HTTP compression and there is Accept-Encoding header in request, haproxy will compress the matching response.<br>Compression is disabled when:<br>* the request does not advertise a supported compression algorithm in the "Accept-Encoding" header<br>* the response message is not HTTP/1.1<br>* HTTP status code is not 200<br>* response header "Transfer-Encoding" contains "chunked" (Temporary Workaround)<br>* response contain neither a "Content-Length" header nor a "Transfer-Encoding" whose last value is "chunked"<br>* response contains a "Content-Type" header whose first value starts with "multipart"<br>* the response contains the "no-transform" value in the "Cache-control" header<br>* User-Agent matches "Mozilla/4" unless it is MSIE 6 with XP SP2, or MSIE 7 and later<br>* The response contains a "Content-Encoding" header, indicating that the response is already compressed (see compression offload)<br>**Example:** gzip|
20+
|COMPRESSION_ALGO |Enable HTTP compression. The currently supported algorithms are:<br>**identity**: this is mostly for debugging.<br>**gzip**: applies gzip compression. This setting is only available when support for zlib or libslz was built in.<br>**deflate**: same as *gzip*, but with deflate algorithm and zlib format. Note that this algorithm has ambiguous support on many browsers and no support at all from recent ones. It is strongly recommended not to use it for anything else than experimentation. This setting is only available when support for zlib or libslz was built in.<br>**raw-deflate**: same as *deflate* without the zlib wrapper, and used as an alternative when the browser wants "deflate". All major browsers understand it and despite violating the standards, it is known to work better than *deflate*, at least on MSIE and some versions of Safari. This setting is only available when support for zlib or libslz was built in.<br>Compression will be activated depending on the Accept-Encoding request header. With identity, it does not take care of that header. If backend servers support HTTP compression, these directives will be no-op: haproxy will see the compressed response and will not compress again. If backend servers do not support HTTP compression and there is Accept-Encoding header in request, haproxy will compress the matching response.<br>Compression is disabled when:<br>* the request does not advertise a supported compression algorithm in the "Accept-Encoding" header<br>* the response message is not HTTP/1.1<br>* HTTP status code is not 200<br>* response header "Transfer-Encoding" contains "chunked" (Temporary Workaround)<br>* response contain neither a "Content-Length" header nor a "Transfer-Encoding" whose last value is "chunked"<br>* response contains a "Content-Type" header whose first value starts with "multipart"<br>* the response contains the "no-transform" value in the "Cache-control" header<br>* User-Agent matches "Mozilla/4" unless it is MSIE 6 with XP SP2, or MSIE 7 and later<br>* The response contains a "Content-Encoding" header, indicating that the response is already compressed (see compression offload)<br>**Example:** gzip|
2121
|COMPRESSION_TYPE |The type of files that will be compressed.<br>**Example:** text/css text/html text/javascript application/javascript text/plain text/xml application/json|
2222
|CONNECTION_MODE |HAProxy supports 5 connection modes.<br><br>`http-keep-alive`: all requests and responses are processed.<br>`http-tunnel`: only the first request and response are processed, everything else is forwarded with no analysis.<br>`httpclose`: tunnel with "Connection: close" added in both directions.<br>`http-server-close`: the server-facing connection is closed after the response.<br>`forceclose`: the connection is actively closed after end of response.<br><br>In general, it is preferred to use `http-server-close` with application servers, and some static servers might benefit from `http-keep-alive`.<br>**Example:** `http-server-close`<br>**Default value:** `http-keep-alive`|
2323
|DEBUG |Enables logging of each request sent through the proxy. Please consult [Debug Format](#debug-format) for info about the log entries. This feature should be used with caution. **Do not enable debugging in production unless necessary.**<br>**Example:** true<br>**Default value:** `false`|

docs/usage.md

+4
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ The following query parameters can be used only when `reqMode` is set to `http`
4545
|Query |Description |
4646
|-------------|--------------------------------------------------------------------------------|
4747
|allowedMethods|The list of allowed methods. If specified, a request with a method that is not on the list will be denied. Multiple methods can be separated with comma (`,`). Change the environment variable `SEPARATOR` if comma is to be used for other purposes. The parameter can be prefixed with an index thus allowing definition of multiple destinations for a single service (e.g. `allowedMethods.1`, `allowedMethods.2`, and so on).<br>**Example:** `GET,DELETE`|
48+
|compressionAlgo|Enable HTTP compression for the given service. The currently supported algorithms are:<br>**identity**: this is mostly for debugging.<br>**gzip**: applies gzip compression. This setting is only available when support for zlib or libslz was built in.<br>**deflate**: same as *gzip*, but with deflate algorithm and zlib format. Note that this algorithm has ambiguous support on many browsers and no support at all from recent ones. It is strongly recommended not to use it for anything else than experimentation. This setting is only available when support for zlib or libslz was built in.<br>**raw-deflate**: same as *deflate* without the zlib wrapper, and used as an alternative when the browser wants "deflate". All major browsers understand it and despite violating the standards, it is known to work better than *deflate*, at least on MSIE and some versions of Safari. This setting is only available when support for zlib or libslz was built in.<br>Compression will be activated depending on the Accept-Encoding request header. With identity, it does not take care of that header. If backend servers support HTTP compression, these directives will be no-op: haproxy will see the compressed response and will not compress again. If backend servers do not support HTTP compression and there is Accept-Encoding header in request, haproxy will compress the matching response.<br>Compression is disabled when:<br>* the request does not advertise a supported compression algorithm in the "Accept-Encoding" header<br>* the response message is not HTTP/1.1<br>* HTTP status code is not 200<br>* response header "Transfer-Encoding" contains "chunked" (Temporary Workaround)<br>* response contain neither a "Content-Length" header nor a "Transfer-Encoding" whose last value is "chunked"<br>* response contains a "Content-Type" header whose first value starts with "multipart"<br>* the response contains the "no-transform" value in the "Cache-control" header<br>* User-Agent matches "Mozilla/4" unless it is MSIE 6 with XP SP2, or MSIE 7 and later<br>* The response contains a "Content-Encoding" header, indicating that the response is already compressed (see compression offload)<br>**Example:** gzip|
49+
|compressionType|The type of files that will be compressed.<br>**Example:** text/css text/html text/javascript application/javascript text/plain text/xml application/json|
4850
|deniedMethods|The list of denied methods. If specified, a request with a method that is on the list will be denied. Multiple methods can be separated with comma (`,`). Change the environment variable `SEPARATOR` if comma is to be used for other purposes. The parameter can be prefixed with an index thus allowing definition of multiple destinations for a single service (e.g. `deniedMethods.1`, `deniedMethods.2`, and so on).<br>**Example:** `PUT,POST`|
4951
|denyHttp |Whether to deny HTTP requests thus allowing only HTTPS. The parameter can be prefixed with an index thus allowing definition of multiple destinations for a single service (e.g. `denyHttp.1`, `denyHttp.2`, and so on).<br>**Example:** `true`<br>**Default Value:** `false`|
5052
|httpsRedirectCode|HTTP code for HTTP to HTTPS redirects. This parameter is used only if `httpsOnly` is set to `true`<br>**Example:** `301`|
@@ -114,6 +116,8 @@ The map between the HTTP query parameters and environment variables is as follow
114116
|addResHeader |ADD_RES_HEADER |
115117
|allowedMethods |ALLOWED_METHODS |
116118
|backendExtra |BACKEND_EXTRA |
119+
|compressionAlgo |COMPRESSION_ALGO |
120+
|compressionType |COMPRESSION_TYPE |
117121
|deniedMethods |DENIED_METHODS |
118122
|distribute |DISTRIBUTE |
119123
|httpsOnly |HTTPS_ONLY |

integration_tests/integration_swarm_test.go

+18-4
Original file line numberDiff line numberDiff line change
@@ -170,10 +170,9 @@ func (s IntegrationSwarmTestSuite) Test_Metrics() {
170170
}
171171

172172
func (s IntegrationSwarmTestSuite) Test_Compression() {
173-
defer func() {
174-
exec.Command("/bin/sh", "-c", `docker service update --env-rm "COMPRESSION_ALGO" --env-rm "COMPRESSION_TYPE" proxy`).Output()
175-
s.waitForContainers(1, "proxy")
176-
}()
173+
174+
// Compression defined for all services
175+
177176
_, err := exec.Command(
178177
"/bin/sh",
179178
"-c",
@@ -194,6 +193,21 @@ func (s IntegrationSwarmTestSuite) Test_Compression() {
194193
s.Equal(200, resp.StatusCode, s.getProxyConf(""))
195194
s.Contains(resp.Header["Content-Encoding"], "gzip", s.getProxyConf(""))
196195
}
196+
197+
exec.Command("/bin/sh", "-c", `docker service update --env-rm "COMPRESSION_ALGO" --env-rm "COMPRESSION_TYPE" proxy`).Output()
198+
s.waitForContainers(1, "proxy")
199+
200+
// Compression defined on a service level
201+
202+
s.reconfigureGoDemo("&compressionAlgo=gzip&compressionType=text/css%20text/html%20text/javascript%20application/javascript%20text/plain%20text/xml%20application/json")
203+
204+
resp, err = client.Do(req)
205+
206+
s.NoError(err)
207+
if resp != nil {
208+
s.Equal(200, resp.StatusCode, s.getProxyConf(""))
209+
s.Contains(resp.Header["Content-Encoding"], "gzip", s.getProxyConf(""))
210+
}
197211
}
198212

199213
func (s IntegrationSwarmTestSuite) Test_ZombieProcesses() {

proxy/ha_proxy_test.go

+35
Original file line numberDiff line numberDiff line change
@@ -1564,6 +1564,41 @@ func (s HaProxyTestSuite) Test_CreateConfigFromTemplates_ForwardsToDomain_WhenRe
15641564
s.Equal(expectedData, actualData)
15651565
}
15661566

1567+
func (s HaProxyTestSuite) Test_CreateConfigFromTemplates_AddsCompressionAlgoToTheFrontent() {
1568+
var actualData string
1569+
tmpl := s.TemplateContent
1570+
expectedData := fmt.Sprintf(
1571+
`%s
1572+
compression algo my-compression-algo
1573+
compression type my-compression-type
1574+
acl url_my-service1111_0 path_beg /path
1575+
use_backend my-service-be1111_0 if url_my-service1111_0%s`,
1576+
tmpl,
1577+
s.ServicesContent,
1578+
)
1579+
writeFile = func(filename string, data []byte, perm os.FileMode) error {
1580+
actualData = string(data)
1581+
return nil
1582+
}
1583+
p := NewHaProxy(s.TemplatesPath, s.ConfigsPath)
1584+
dataInstance.Services["my-service"] = Service{
1585+
CompressionAlgo: "my-compression-algo",
1586+
CompressionType: "my-compression-type",
1587+
PathType: "path_beg",
1588+
ServiceName: "my-service",
1589+
ServiceDest: []ServiceDest{
1590+
{
1591+
Port: "1111",
1592+
ServicePath: []string{"/path"},
1593+
},
1594+
},
1595+
}
1596+
1597+
p.CreateConfigFromTemplates()
1598+
1599+
s.Equal(expectedData, actualData)
1600+
}
1601+
15671602
func (s HaProxyTestSuite) Test_CreateConfigFromTemplates_UsesServiceHeader() {
15681603
var actualData string
15691604
tmpl := s.TemplateContent

proxy/template.go

+6
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ func getFrontTemplate(s Service) string {
1414
tmplString := `
1515
{{- range $sd := .ServiceDest}}
1616
{{- if eq .ReqMode "http"}}
17+
{{- if ne $.CompressionAlgo ""}}
18+
compression algo {{$.CompressionAlgo}}
19+
{{- if ne $.CompressionType ""}}
20+
compression type {{$.CompressionType}}
21+
{{- end}}
22+
{{- end}}
1723
{{- if ne .Port ""}}
1824
acl url_{{$.AclName}}{{.Port}}_{{.Index}}{{range .ServicePath}} {{if eq $.PathType ""}}path_beg{{end}}{{if ne $.PathType ""}}{{$.PathType}}{{end}} {{.}}{{end}}{{.SrcPortAcl}}
1925
{{- end}}

proxy/types.go

+5
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,11 @@ type Service struct {
7575
BackendExtra string `split_words:"true"`
7676
// Whether to use `docker` as a check resolver. Set through the environment variable CHECK_RESOLVERS
7777
CheckResolvers bool `split_words:"true"`
78+
// Enable HTTP compression.
79+
// The currently supported algorithms are: identity, gzip, deflate, raw-deflate.
80+
CompressionAlgo string `split_words:"true"`
81+
// The type of files that will be compressed.
82+
CompressionType string `split_words:"true"`
7883
// One of the five connection modes supported by the HAProxy.
7984
// `http-keep-alive`: all requests and responses are processed.
8085
// `http-tunnel`: only the first request and response are processed, everything else is forwarded with no analysis.

server/server_test.go

+14-4
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,8 @@ func (s *ServerTestSuite) Test_GetServiceFromUrl_ReturnsProxyService() {
520520
AclName: "aclName",
521521
AddReqHeader: []string{"add-header-1", "add-header-2"},
522522
AddResHeader: []string{"add-header-1", "add-header-2"},
523+
CompressionAlgo: "compressionAlgo",
524+
CompressionType: "compressionType",
523525
ConnectionMode: "my-connection-mode",
524526
DelReqHeader: []string{"add-header-1", "add-header-2"},
525527
DelResHeader: []string{"add-header-1", "add-header-2"},
@@ -556,7 +558,7 @@ func (s *ServerTestSuite) Test_GetServiceFromUrl_ReturnsProxyService() {
556558
{Username: "user2", Password: "pass2", PassEncrypted: true}},
557559
}
558560
addr := fmt.Sprintf(
559-
"%s?serviceName=%s&users=%s&usersPassEncrypted=%t&aclName=%s&serviceCert=%s&outboundHostname=%s&pathType=%s&reqPathSearch=%s&reqPathReplace=%s&templateFePath=%s&templateBePath=%s&timeoutServer=%s&timeoutTunnel=%s&reqMode=%s&httpsOnly=%t&httpsRedirectCode=%s&isDefaultBackend=%t&redirectWhenHttpProto=%t&httpsPort=%d&serviceDomain=%s&redirectFromDomain=%s&distribute=%t&sslVerifyNone=%t&serviceDomainAlgo=%s&addReqHeader=%s&addResHeader=%s&setReqHeader=%s&setResHeader=%s&delReqHeader=%s&delResHeader=%s&servicePath=/&port=1234&connectionMode=%s&serviceHeader=X-Version:3,name:Viktor&allowedMethods=GET,DELETE&deniedMethods=PUT,POST",
561+
"%s?serviceName=%s&users=%s&usersPassEncrypted=%t&aclName=%s&serviceCert=%s&outboundHostname=%s&pathType=%s&reqPathSearch=%s&reqPathReplace=%s&templateFePath=%s&templateBePath=%s&timeoutServer=%s&timeoutTunnel=%s&reqMode=%s&httpsOnly=%t&httpsRedirectCode=%s&isDefaultBackend=%t&redirectWhenHttpProto=%t&httpsPort=%d&serviceDomain=%s&redirectFromDomain=%s&distribute=%t&sslVerifyNone=%t&serviceDomainAlgo=%s&addReqHeader=%s&addResHeader=%s&setReqHeader=%s&setResHeader=%s&delReqHeader=%s&delResHeader=%s&servicePath=/&port=1234&connectionMode=%s&serviceHeader=X-Version:3,name:Viktor&allowedMethods=GET,DELETE&deniedMethods=PUT,POST&compressionAlgo=%s&compressionType=%s",
560562
s.BaseUrl,
561563
expected.ServiceName,
562564
"user1:pass1,user2:pass2",
@@ -589,6 +591,8 @@ func (s *ServerTestSuite) Test_GetServiceFromUrl_ReturnsProxyService() {
589591
strings.Join(expected.DelReqHeader, ","),
590592
strings.Join(expected.DelResHeader, ","),
591593
expected.ConnectionMode,
594+
expected.CompressionAlgo,
595+
expected.CompressionType,
592596
)
593597
req, _ := http.NewRequest("GET", addr, nil)
594598
srv := serve{}
@@ -652,9 +656,11 @@ func (s *ServerTestSuite) Test_GetServiceFromUrl_SetsServicePathToSlash_WhenDoma
652656

653657
func (s *ServerTestSuite) Test_GetServicesFromEnvVars_ReturnsServices() {
654658
service := proxy.Service{
655-
AclName: "my-AclName",
656-
AddReqHeader: []string{"add-header-1", "add-header-2"},
657-
AddResHeader: []string{"add-header-1", "add-header-2"},
659+
AclName: "my-AclName",
660+
AddReqHeader: []string{"add-header-1", "add-header-2"},
661+
AddResHeader: []string{"add-header-1", "add-header-2"},
662+
CompressionAlgo: "compressionAlgo",
663+
// CompressionType: "compressionType",
658664
ConnectionMode: "my-connection-mode",
659665
DelReqHeader: []string{"del-header-1", "del-header-2"},
660666
DelResHeader: []string{"del-header-1", "del-header-2"},
@@ -691,6 +697,8 @@ func (s *ServerTestSuite) Test_GetServicesFromEnvVars_ReturnsServices() {
691697
os.Setenv("DFP_SERVICE_ACL_NAME", service.AclName)
692698
os.Setenv("DFP_SERVICE_ADD_REQ_HEADER", strings.Join(service.AddReqHeader, ","))
693699
os.Setenv("DFP_SERVICE_ADD_RES_HEADER", strings.Join(service.AddResHeader, ","))
700+
os.Setenv("DFP_SERVICE_COMPRESSION_ALGO", service.CompressionAlgo)
701+
os.Setenv("DFP_SERVICE_COMPRESSION_TYPE", service.CompressionType)
694702
os.Setenv("DFP_SERVICE_CONNECTION_MODE", service.ConnectionMode)
695703
os.Setenv("DFP_SERVICE_DEL_REQ_HEADER", strings.Join(service.DelReqHeader, ","))
696704
os.Setenv("DFP_SERVICE_DEL_RES_HEADER", strings.Join(service.DelResHeader, ","))
@@ -724,6 +732,8 @@ func (s *ServerTestSuite) Test_GetServicesFromEnvVars_ReturnsServices() {
724732
os.Unsetenv("DFP_SERVICE_ACL_NAME")
725733
os.Unsetenv("DFP_SERVICE_ADD_REQ_HEADER")
726734
os.Unsetenv("DFP_SERVICE_ADD_RES_HEADER")
735+
os.Unsetenv("DFP_SERVICE_COMPRESSION_ALGO")
736+
os.Unsetenv("DFP_SERVICE_COMPRESSION_TYPE")
727737
os.Unsetenv("DFP_SERVICE_CONNECTION_MODE")
728738
os.Unsetenv("DFP_SERVICE_DEL_REQ_HEADER")
729739
os.Unsetenv("DFP_SERVICE_DEL_RES_HEADER")

0 commit comments

Comments
 (0)