diff --git a/examples/ssl_push/Makefile b/examples/ssl_push/Makefile
new file mode 100644
index 000000000..2e525bf23
--- /dev/null
+++ b/examples/ssl_push/Makefile
@@ -0,0 +1,9 @@
+PROJECT = ssl_push
+PROJECT_DESCRIPTION = Cowboy HTTP/2 push example
+PROJECT_VERSION = 1
+
+DEPS = cowboy
+LOCAL_DEPS = ssl
+dep_cowboy_commit = master
+
+include ../../erlang.mk
diff --git a/examples/ssl_push/README.asciidoc b/examples/ssl_push/README.asciidoc
new file mode 100644
index 000000000..c1a325a39
--- /dev/null
+++ b/examples/ssl_push/README.asciidoc
@@ -0,0 +1,122 @@
+= HTTP/2 push example
+
+To try this example, you need GNU `make` and `git` in your PATH.
+
+To build and run the example, use the following command:
+
+[source,bash]
+$ make run
+
+Then point your browser to https://localhost:8443
+
+You will need to temporarily trust the root certificate authority,
+which can also be found in `priv/ssl/cowboy-ca.crt`.
+
+Recent browsers will communicate using HTTP/2. Older browsers
+will use HTTP/1.1.
+
+== HTTP/1.1 example output
+
+[source,bash]
+----
+$ curl --cacert priv/ssl/cowboy-ca.crt -i https://localhost:8443
+HTTP/1.1 200 OK
+content-length: 117
+content-type: text/html
+date: Wed, 26 Jul 2017 08:07:13 GMT
+server: Cowboy
+
+
+
+
+
+
+Hello world!
+
+----
+
+== HTTP/2 example output
+
+[source,bash]
+----
+$ nghttp -v https://localhost:8443
+[ 0.001] Connected
+The negotiated protocol: h2
+[ 0.014] recv SETTINGS frame
+ (niv=0)
+[ 0.014] send SETTINGS frame
+ (niv=2)
+ [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
+ [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]
+[ 0.014] send SETTINGS frame
+ ; ACK
+ (niv=0)
+[ 0.014] send PRIORITY frame
+ (dep_stream_id=0, weight=201, exclusive=0)
+[ 0.014] send PRIORITY frame
+ (dep_stream_id=0, weight=101, exclusive=0)
+[ 0.014] send PRIORITY frame
+ (dep_stream_id=0, weight=1, exclusive=0)
+[ 0.014] send PRIORITY frame
+ (dep_stream_id=7, weight=1, exclusive=0)
+[ 0.014] send PRIORITY frame
+ (dep_stream_id=3, weight=1, exclusive=0)
+[ 0.014] send HEADERS frame
+ ; END_STREAM | END_HEADERS | PRIORITY
+ (padlen=0, dep_stream_id=11, weight=16, exclusive=0)
+ ; Open new stream
+ :method: GET
+ :path: /
+ :scheme: https
+ :authority: localhost:8443
+ accept: */*
+ accept-encoding: gzip, deflate
+ user-agent: nghttp2/1.19.0-DEV
+[ 0.014] recv SETTINGS frame
+ ; ACK
+ (niv=0)
+[ 0.014] recv (stream_id=13) :authority: localhost:8443
+[ 0.014] recv (stream_id=13) :method: GET
+[ 0.014] recv (stream_id=13) :path: /static/style.css
+[ 0.014] recv (stream_id=13) :scheme: https
+[ 0.014] recv (stream_id=13) accept: text/css
+[ 0.014] recv PUSH_PROMISE frame
+ ; END_HEADERS
+ (padlen=0, promised_stream_id=2)
+[ 0.014] recv (stream_id=13) :status: 200
+[ 0.014] recv (stream_id=13) content-length: 117
+[ 0.014] recv (stream_id=13) content-type: text/html
+[ 0.014] recv (stream_id=13) date: Wed, 26 Jul 2017 08:08:03 GMT
+[ 0.014] recv (stream_id=13) server: Cowboy
+[ 0.014] recv HEADERS frame
+ ; END_HEADERS
+ (padlen=0)
+ ; First response header
+
+
+
+
+
+Hello world!
+
+[ 0.014] recv DATA frame
+ ; END_STREAM
+[ 0.015] recv (stream_id=2) :status: 200
+[ 0.015] recv (stream_id=2) content-length: 21
+[ 0.015] recv (stream_id=2) content-type: text/css
+[ 0.015] recv (stream_id=2) date: Wed, 26 Jul 2017 08:08:03 GMT
+[ 0.015] recv (stream_id=2) etag: "2003476335"
+[ 0.015] recv (stream_id=2) last-modified: Wed, 26 Jul 2017 08:01:45 GMT
+[ 0.015] recv (stream_id=2) server: Cowboy
+[ 0.015] recv HEADERS frame
+ ; END_HEADERS
+ (padlen=0)
+ ; First push response header
+h1 {
+ color: red;
+}
+[ 0.015] recv DATA frame
+ ; END_STREAM
+[ 0.015] send GOAWAY frame
+ (last_stream_id=2, error_code=NO_ERROR(0x00), opaque_data(0)=[])
+----
diff --git a/examples/ssl_push/priv/ssl/cowboy-ca.crt b/examples/ssl_push/priv/ssl/cowboy-ca.crt
new file mode 100644
index 000000000..a35ac390a
--- /dev/null
+++ b/examples/ssl_push/priv/ssl/cowboy-ca.crt
@@ -0,0 +1,16 @@
+-----BEGIN CERTIFICATE-----
+MIICeDCCAeGgAwIBAgIJAOvpU0y2e5J4MA0GCSqGSIb3DQEBBQUAMFUxCzAJBgNV
+BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczETMBEGA1UECgwKTmluZSBOaW5lczEPMA0G
+A1UECwwGQ293Ym95MRAwDgYDVQQDDAdST09UIENBMB4XDTEzMDIyODA1MTAwMVoX
+DTMzMDIyMzA1MTAwMVowVTELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMRMw
+EQYDVQQKDApOaW5lIE5pbmVzMQ8wDQYDVQQLDAZDb3dib3kxEDAOBgNVBAMMB1JP
+T1QgQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMzmY7Us06yjyUbpqwPx
+Iv+xh/g3V7we07ClC9GEYnvr3OQvdA1jFEHccMBUUjRoQ8DPd6uSyK5UkixABs08
+Tt5B3VsnGKr0DIN+IO4SN2PkmBqIU/BN3KdcwN65YNr3iM0KsKWeFtAZdYx4CakX
+7REbO0wjK20AH3xSBn3uFGiBAgMBAAGjUDBOMB0GA1UdDgQWBBRKfZ8KF2jlLBDm
+NL6IuEuGY0pdbzAfBgNVHSMEGDAWgBRKfZ8KF2jlLBDmNL6IuEuGY0pdbzAMBgNV
+HRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAG1I0kBxXiLkM1b7rl2zPLizREYg
+1m+ajb6rWzPOBg6TXjv58Be+H4tqoHIL/M/crixew5emftBkuAGjiKMhbIokjvan
+aPTCV8U6HHvNvz9c68HpESWbd+56cHqfsS5XCKp1OpW5tbL2UQYpFKMP4qmbv3Ea
+pBfPPmSFMBb1i2AI
+-----END CERTIFICATE-----
diff --git a/examples/ssl_push/priv/ssl/server.crt b/examples/ssl_push/priv/ssl/server.crt
new file mode 100644
index 000000000..0bdfaed01
--- /dev/null
+++ b/examples/ssl_push/priv/ssl/server.crt
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICpTCCAg6gAwIBAgIJAOvpU0y2e5J5MA0GCSqGSIb3DQEBBQUAMFUxCzAJBgNV
+BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczETMBEGA1UECgwKTmluZSBOaW5lczEPMA0G
+A1UECwwGQ293Ym95MRAwDgYDVQQDDAdST09UIENBMB4XDTEzMDIyODA1MjMzNFoX
+DTMzMDIyMzA1MjMzNFowVzELMAkGA1UEBhMCVVMxDjAMBgNVBAgMBVRleGFzMRMw
+EQYDVQQKDApOaW5lIE5pbmVzMQ8wDQYDVQQLDAZDb3dib3kxEjAQBgNVBAMMCWxv
+Y2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAzbW1GjECzHUc/WST
+qLiAGqjCNccR5saVS+yoz2SPRhpoyf0/qBrX5BY0tzmgozoTiRfE4wCiVD99Cc+D
+rp/FM49r4EpZdocIovprmOmv/gwkoj95zaA6PKNn1OdmDp2hwJsX2Zm3kpbGUZTx
+jDkkccmgUb4EjL7qNHq7saQtivUCAwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgB
+hvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYE
+FB6jTEIWI8T1ckORA4GezbyYxtbvMB8GA1UdIwQYMBaAFEp9nwoXaOUsEOY0voi4
+S4ZjSl1vMA0GCSqGSIb3DQEBBQUAA4GBACMboVQjrx8u/fk3gl/sR0tbA0Wf/NcS
+2Dzsy2czndgVUAG4Sqb+hfgn0dqAyUKghRrj3JDcYxYksGPIklDfPzZb7yJ39l16
+6x5ZiIzhp8CAVdPvRxRznw5rZwaXesryXu1jVSZxTr3MYZdkG6KaAM0t90+YlGLZ
+UG8fAicx0Bf+
+-----END CERTIFICATE-----
diff --git a/examples/ssl_push/priv/ssl/server.key b/examples/ssl_push/priv/ssl/server.key
new file mode 100644
index 000000000..b6f737421
--- /dev/null
+++ b/examples/ssl_push/priv/ssl/server.key
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQDNtbUaMQLMdRz9ZJOouIAaqMI1xxHmxpVL7KjPZI9GGmjJ/T+o
+GtfkFjS3OaCjOhOJF8TjAKJUP30Jz4Oun8Uzj2vgSll2hwii+muY6a/+DCSiP3nN
+oDo8o2fU52YOnaHAmxfZmbeSlsZRlPGMOSRxyaBRvgSMvuo0eruxpC2K9QIDAQAB
+AoGAaD85c/h6bpq7Aj7CBbLaWKhFI3OqwsTITB22vsM7SE+B4zsP02UnG1OVi3UM
+zytTUxpUkKV1njQ+bYZYOVqGWF4Up8tTqUglHn0FTPok1AIemELWtz3sXvdSHC1T
+lqvFBAZ9kibn13qGyVOiyCFaMwfOM/05RvV7p3jfUMTWnNECQQDs7yCJZ8Ol8MyH
+TGZzvkjoN2zg1KwmTbSD1hkP6QAJtPdRuqFbjlEru0/PefgOXsWLRIa3/3v0qw2G
+xGkV6AXTAkEA3kNbFisqUydjPnZIYv/P6SvPdUimHJEjXbAbfNfzS9dzszrOVJd2
+XqGH7z5yzjoH3IyaIMW8GnubVzGDSjrHFwJAKSU5vELlygpwKkrNO+pelN0TLlQg
+dSJnZ8GlZorq88SWcn37iX/EftivenNO7YftvEqxLoDSkOGnnrC7Iw/A+wJBAIEe
+L/QY72WPJCBNJpAce/PA96vyoE1II3txqwZDjZspdpVQPDz4IFOpEwbxCFC1dYuy
+Qnd3Z2cbF4r3wIWGz9ECQQCJGNhUNtY+Om1ELdqPcquxE2VRV/pucnvJSTKwyo2C
+Rvm6H7kFDwPDuN23YnTOlTiho0zzCkclcIukhIVJ+dKz
+-----END RSA PRIVATE KEY-----
diff --git a/examples/ssl_push/priv/static_files/style.css b/examples/ssl_push/priv/static_files/style.css
new file mode 100644
index 000000000..adc68fa6a
--- /dev/null
+++ b/examples/ssl_push/priv/static_files/style.css
@@ -0,0 +1,3 @@
+h1 {
+ color: red;
+}
diff --git a/examples/ssl_push/relx.config b/examples/ssl_push/relx.config
new file mode 100644
index 000000000..f6f8dc0b0
--- /dev/null
+++ b/examples/ssl_push/relx.config
@@ -0,0 +1,2 @@
+{release, {ssl_push_example, "1"}, [ssl_push]}.
+{extended_start_script, true}.
diff --git a/examples/ssl_push/src/ssl_push_app.erl b/examples/ssl_push/src/ssl_push_app.erl
new file mode 100644
index 000000000..03e9cac14
--- /dev/null
+++ b/examples/ssl_push/src/ssl_push_app.erl
@@ -0,0 +1,30 @@
+%% Feel free to use, reuse and abuse the code in this file.
+
+%% @private
+-module(ssl_push_app).
+-behaviour(application).
+
+%% API.
+-export([start/2]).
+-export([stop/1]).
+
+%% API.
+
+start(_Type, _Args) ->
+ Dispatch = cowboy_router:compile([
+ {'_', [
+ {"/static/[...]", cowboy_static, {priv_dir, ssl_push, "static_files"}},
+ {"/", toppage_handler, []}
+ ]}
+ ]),
+ PrivDir = code:priv_dir(ssl_push),
+ {ok, _} = cowboy:start_tls(https, [
+ {port, 8443},
+ {cacertfile, PrivDir ++ "/ssl/cowboy-ca.crt"},
+ {certfile, PrivDir ++ "/ssl/server.crt"},
+ {keyfile, PrivDir ++ "/ssl/server.key"}
+ ], #{env => #{dispatch => Dispatch}}),
+ ssl_push_sup:start_link().
+
+stop(_State) ->
+ ok.
diff --git a/examples/ssl_push/src/ssl_push_sup.erl b/examples/ssl_push/src/ssl_push_sup.erl
new file mode 100644
index 000000000..0f50fc5d1
--- /dev/null
+++ b/examples/ssl_push/src/ssl_push_sup.erl
@@ -0,0 +1,23 @@
+%% Feel free to use, reuse and abuse the code in this file.
+
+%% @private
+-module(ssl_push_sup).
+-behaviour(supervisor).
+
+%% API.
+-export([start_link/0]).
+
+%% supervisor.
+-export([init/1]).
+
+%% API.
+
+-spec start_link() -> {ok, pid()}.
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+%% supervisor.
+
+init([]) ->
+ Procs = [],
+ {ok, {{one_for_one, 10, 10}, Procs}}.
diff --git a/examples/ssl_push/src/toppage_handler.erl b/examples/ssl_push/src/toppage_handler.erl
new file mode 100644
index 000000000..119578b8d
--- /dev/null
+++ b/examples/ssl_push/src/toppage_handler.erl
@@ -0,0 +1,20 @@
+%% Feel free to use, reuse and abuse the code in this file.
+
+%% @doc Hello world handler.
+-module(toppage_handler).
+
+-export([init/2]).
+
+init(Req0, Opts) ->
+ cowboy_req:push("/static/style.css", #{<<"accept">> => <<"text/css">>}, Req0),
+ Req = cowboy_req:reply(200, #{
+ <<"content-type">> => <<"text/html">>
+ }, <<"
+
+
+
+
+Hello world!
+
+">>, Req0),
+ {ok, Req, Opts}.