Skip to content

Commit e8e51ea

Browse files
Gergmoleske
andcommitted
Add configurable http/2 support
- nginx can not serve both HTTP/1 and HTTP/2 on the same port, if the port does not use TLS. See https://trac.nginx.org/nginx/ticket/816. - Implementation mirrors force_https to make it user-configurable - new staticfile option of `enable_http2` and use of environment variable `ENABLE_HTTP2` to configure nginx - Route HTTP/2 configuration is available via manifest when using cf cli v7 or with the map-route command when using cf cli v8 - since cutlass only supports cf cli v6, testing bypasses cutlass with "cf curl" commands. These can be replaced with proper cf cli commands when upgrading the cf cli version - integration tests only runs on api 2.172.0 or higher [cloudfoundry/routing-release#200] Co-authored-by: Michael Oleske <[email protected]>
1 parent f06df36 commit e8e51ea

File tree

10 files changed

+220
-2
lines changed

10 files changed

+220
-2
lines changed

fixtures/enable_http2/Staticfile

Whitespace-only changes.

fixtures/enable_http2/index.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<html>
2+
<head>
3+
<title>Static file demo app</title>
4+
</head>
5+
<body>
6+
<p>
7+
This is an example app for Cloud Foundry that is only static HTML/JS/CSS assets.
8+
</p>
9+
</body>
10+
</html>

fixtures/enable_http2/v3-manifest.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
applications:
3+
- name: SED_APP_NAME
4+
routes:
5+
- route: SED_ROUTE
6+
protocol: http2
7+
env:
8+
ENABLE_HTTP2: true
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
enable_http2: enabled
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<html>
2+
<head>
3+
<title>Static file demo app</title>
4+
</head>
5+
<body>
6+
<p>
7+
This is an example app for Cloud Foundry that is only static HTML/JS/CSS assets.
8+
</p>
9+
</body>
10+
</html>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
applications:
3+
- name: SED_APP_NAME
4+
routes:
5+
- route: SED_ROUTE
6+
protocol: http2

src/staticfile/finalize/data.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,15 @@ http {
7474
server_tokens off;
7575
7676
server {
77-
listen <%= ENV["PORT"] %>;
77+
{{if .EnableHttp2}}
78+
listen <%= ENV["PORT"] %> http2;
79+
{{else}}
80+
<% if ENV["ENABLE_HTTP2"] %>
81+
listen <%= ENV["PORT"] %> http2;
82+
<% else %>
83+
listen <%= ENV["PORT"] %>;
84+
<% end %>
85+
{{end}}
7886
server_name localhost;
7987
8088
root <%= ENV["APP_ROOT"] %>/public;

src/staticfile/finalize/finalize.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type Staticfile struct {
2424
HSTSIncludeSubDomains bool `yaml:"http_strict_transport_security_include_subdomains"`
2525
HSTSPreload bool `yaml:"http_strict_transport_security_preload"`
2626
ForceHTTPS bool `yaml:"force_https"`
27+
EnableHttp2 bool `yaml:"enable_http2"`
2728
BasicAuth bool
2829
StatusCodes map[string]string `yaml:"status_codes"`
2930
}
@@ -50,6 +51,7 @@ type StaticfileTemp struct {
5051
HSTSIncludeSubDomains string `yaml:"http_strict_transport_security_include_subdomains"`
5152
HSTSPreload string `yaml:"http_strict_transport_security_preload"`
5253
ForceHTTPS string `yaml:"force_https"`
54+
EnableHttp2 string `yaml:"enable_http2"`
5355
StatusCodes map[string]string `yaml:"status_codes"`
5456
}
5557

@@ -176,6 +178,10 @@ func (sf *Finalizer) LoadStaticfile() error {
176178
sf.Log.BeginStep("Enabling HSTS Preload")
177179
conf.HSTSPreload = true
178180
}
181+
if isEnabled(hash.EnableHttp2) {
182+
sf.Log.BeginStep("Enabling HTTP/2")
183+
conf.EnableHttp2 = true
184+
}
179185
if isEnabled(hash.ForceHTTPS) {
180186
sf.Log.BeginStep("Enabling HTTPS redirect")
181187
conf.ForceHTTPS = true

src/staticfile/finalize/finalize_test.go

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,7 @@ var _ = Describe("Compile", func() {
139139
Expect(finalizer.Config.HSTS).To(Equal(false))
140140
Expect(finalizer.Config.HSTSIncludeSubDomains).To(Equal(false))
141141
Expect(finalizer.Config.HSTSPreload).To(Equal(false))
142+
Expect(finalizer.Config.EnableHttp2).To(Equal(false))
142143
Expect(finalizer.Config.ForceHTTPS).To(Equal(false))
143144
Expect(finalizer.Config.BasicAuth).To(Equal(false))
144145
})
@@ -279,6 +280,20 @@ var _ = Describe("Compile", func() {
279280
})
280281
})
281282

283+
Context("and sets enable_http2", func() {
284+
BeforeEach(func() {
285+
mockYaml.EXPECT().Load(filepath.Join(buildDir, "Staticfile"), gomock.Any()).Do(func(_ string, hash *finalize.StaticfileTemp) {
286+
(*hash).EnableHttp2 = "true"
287+
})
288+
})
289+
It("sets enable_http2", func() {
290+
Expect(finalizer.Config.EnableHttp2).To(Equal(true))
291+
})
292+
It("Logs", func() {
293+
Expect(buffer.String()).To(Equal("-----> Enabling HTTP/2\n"))
294+
})
295+
})
296+
282297
Context("and sets force_https", func() {
283298
BeforeEach(func() {
284299
mockYaml.EXPECT().Load(filepath.Join(buildDir, "Staticfile"), gomock.Any()).Do(func(_ string, hash *finalize.StaticfileTemp) {
@@ -569,6 +584,16 @@ var _ = Describe("Compile", func() {
569584
rewrite ^(.*)$ / break;
570585
}
571586
`)
587+
enableHttp2Conf := stripStartWsp(`
588+
listen <%= ENV["PORT"] %> http2;
589+
`)
590+
enableHttp2Erb := stripStartWsp(`
591+
<% if ENV["ENABLE_HTTP2"] %>
592+
listen <%= ENV["PORT"] %> http2;
593+
<% else %>
594+
listen <%= ENV["PORT"] %>;
595+
<% end %>
596+
`)
572597
forceHTTPSConf := stripStartWsp(`
573598
set $updated_host $host;
574599
if ($http_x_forwarded_host != "") {
@@ -751,6 +776,27 @@ var _ = Describe("Compile", func() {
751776
})
752777
})
753778

779+
Context("enable_http2 is set in staticfile", func() {
780+
BeforeEach(func() {
781+
staticfile.EnableHttp2 = true
782+
})
783+
It("the listener uses the http2 directive", func() {
784+
data := readNginxConfAndStrip()
785+
Expect(string(data)).To(ContainSubstring(enableHttp2Conf))
786+
Expect(string(data)).NotTo(ContainSubstring(`<% if ENV["ENABLE_HTTP2"] %>`))
787+
})
788+
})
789+
790+
Context("enable_http2 is NOT set in staticfile", func() {
791+
BeforeEach(func() {
792+
staticfile.EnableHttp2 = false
793+
})
794+
It("using the http2 directive depends on ENV['ENABLE_HTTP2']", func() {
795+
data := readNginxConfAndStrip()
796+
Expect(string(data)).To(ContainSubstring(enableHttp2Erb))
797+
})
798+
})
799+
754800
Context("force_https is set in staticfile", func() {
755801
BeforeEach(func() {
756802
staticfile.ForceHTTPS = true
@@ -759,7 +805,6 @@ var _ = Describe("Compile", func() {
759805
data := readNginxConfAndStrip()
760806
Expect(string(data)).To(ContainSubstring(forceHTTPSConf))
761807
Expect(string(data)).NotTo(ContainSubstring(`<% if ENV["FORCE_HTTPS"] %>`))
762-
Expect(string(data)).NotTo(ContainSubstring(`<% end %>`))
763808
})
764809
})
765810

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package integration_test
2+
3+
import (
4+
"fmt"
5+
"io/ioutil"
6+
"os/exec"
7+
"path/filepath"
8+
"strings"
9+
10+
"github.com/blang/semver"
11+
"github.com/cloudfoundry/libbuildpack/cutlass"
12+
13+
. "github.com/onsi/ginkgo"
14+
. "github.com/onsi/gomega"
15+
)
16+
17+
var _ = Describe("deploy a HTTP/2 app", func() {
18+
var app *cutlass.App
19+
var app_name string
20+
AfterEach(func() {
21+
if app != nil {
22+
app.Destroy()
23+
}
24+
app = nil
25+
app_name = ""
26+
})
27+
28+
BeforeEach(func() {
29+
apiVersionString, err := cutlass.ApiVersion()
30+
Expect(err).To(BeNil())
31+
apiVersion, err := semver.Make(apiVersionString)
32+
Expect(err).To(BeNil())
33+
apiHasHttp2, err := semver.ParseRange("> 2.172.0")
34+
Expect(err).To(BeNil())
35+
if !apiHasHttp2(apiVersion) {
36+
Skip("HTTP/2 not supported for this API version.")
37+
}
38+
})
39+
40+
JustBeforeEach(func() {
41+
Expect(app_name).ToNot(BeEmpty())
42+
app = cutlass.New(Fixtures(app_name))
43+
PushAppAndConfirm(app)
44+
45+
By("inserting the app name and default route into the manifest with HTTP/2 configuration")
46+
manifestPath := filepath.Join(app.Path, "v3-manifest.yml")
47+
manifestTemplate, err := ioutil.ReadFile(manifestPath)
48+
Expect(err).To(BeNil())
49+
50+
appName := app.Name
51+
appRoute, err := app.GetUrl("")
52+
Expect(err).To(BeNil())
53+
54+
manifestBody := strings.ReplaceAll(string(manifestTemplate), "SED_APP_NAME", appName)
55+
manifestBody = strings.ReplaceAll(manifestBody, "SED_ROUTE", appRoute)
56+
57+
By("applying manifest to set http2 protocol on route")
58+
err = v3ApplyManifest(app, manifestBody)
59+
Expect(err).To(BeNil())
60+
})
61+
62+
Context("Using ENV Variable", func() {
63+
BeforeEach(func() { app_name = "enable_http2" })
64+
65+
It("serves HTTP/2 traffic", func() {
66+
By("ensuring that the manifest background job has set the env variable", func() {
67+
runEnvCommand := func() string {
68+
envCommand := exec.Command("cf", "env", app.Name)
69+
output, err := envCommand.Output()
70+
Expect(err).To(BeNil())
71+
72+
return string(output)
73+
}
74+
Eventually(runEnvCommand).Should(ContainSubstring("ENABLE_HTTP2"))
75+
})
76+
77+
By("restarting the app to apply the environment variable change", func() {
78+
Expect(app.Restart()).To(Succeed())
79+
Eventually(app.InstanceStates).Should(Equal([]string{"RUNNING"}))
80+
})
81+
82+
_, headers, err := app.Get("/", map[string]string{})
83+
Expect(err).To(BeNil())
84+
Expect(headers).To(HaveKeyWithValue("StatusCode", []string{"200"}))
85+
})
86+
})
87+
88+
Context("Using Staticfile", func() {
89+
BeforeEach(func() { app_name = "enable_http2_in_staticfile" })
90+
91+
It("serves HTTP/2 traffic", func() {
92+
getAppRoot := func() map[string][]string {
93+
_, headers, err := app.Get("/", map[string]string{})
94+
Expect(err).To(BeNil())
95+
return headers
96+
}
97+
By("polling the app until the HTTP/2 route configuration has propogated", func() {
98+
Eventually(getAppRoot).Should(HaveKeyWithValue("StatusCode", []string{"200"}))
99+
})
100+
})
101+
})
102+
})
103+
104+
func v3ApplyManifest(a *cutlass.App, manifestBody string) error {
105+
spaceGUID, err := a.SpaceGUID()
106+
if err != nil {
107+
return err
108+
}
109+
manifestURL := fmt.Sprintf("/v3/spaces/%s/actions/apply_manifest", spaceGUID)
110+
command := exec.Command(
111+
"cf", "curl",
112+
"-X", "POST",
113+
"-H", "Content-Type: application/x-yaml",
114+
manifestURL,
115+
"-d", manifestBody,
116+
)
117+
command.Stdout = cutlass.DefaultStdoutStderr
118+
command.Stderr = cutlass.DefaultStdoutStderr
119+
if err = command.Run(); err != nil {
120+
return err
121+
}
122+
123+
return nil
124+
}

0 commit comments

Comments
 (0)