├── __init__.py ├── site ├── http2push.py ├── .bowerrc ├── static │ ├── img │ │ ├── pushstats.jpg │ │ ├── netinternals.png │ │ └── pushgaehowto.jpg │ ├── elements.html │ ├── js │ │ └── app.js │ ├── css │ │ └── app.css │ └── index.html ├── bower.json ├── package.json ├── scripts │ ├── build.sh │ └── deploy.sh ├── push_manifest.json ├── app.yaml └── main.py ├── examples ├── python │ ├── http2push.py │ ├── push_manifest.json │ ├── package.json │ ├── static │ │ ├── js │ │ │ └── app.js │ │ ├── css │ │ │ └── app.css │ │ ├── index.html │ │ └── static.html │ ├── app.yaml │ └── main.py └── go │ ├── gopher.png │ ├── app.yaml │ ├── preload.json │ ├── app.js │ ├── app.css │ ├── README.md │ ├── server.go │ └── index.html ├── .gitignore ├── package.json ├── EXPLAINER.md ├── http2push.py ├── README.md └── LICENSE /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /site/http2push.py: -------------------------------------------------------------------------------- 1 | ../http2push.py -------------------------------------------------------------------------------- /examples/python/http2push.py: -------------------------------------------------------------------------------- 1 | ../../http2push.py -------------------------------------------------------------------------------- /site/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "static/bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | bower_components 3 | node_modules 4 | /push_manifest.json 5 | -------------------------------------------------------------------------------- /examples/go/gopher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/http2push-gae/master/examples/go/gopher.png -------------------------------------------------------------------------------- /site/static/img/pushstats.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/http2push-gae/master/site/static/img/pushstats.jpg -------------------------------------------------------------------------------- /site/static/img/netinternals.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/http2push-gae/master/site/static/img/netinternals.png -------------------------------------------------------------------------------- /site/static/img/pushgaehowto.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GoogleChromeLabs/http2push-gae/master/site/static/img/pushgaehowto.jpg -------------------------------------------------------------------------------- /examples/python/push_manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/css/app.css": { 3 | "weight": 1, 4 | "type": "style" 5 | }, 6 | "/js/app.js": { 7 | "weight": 1, 8 | "type": "script" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /examples/go/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: go 2 | api_version: go1 3 | 4 | handlers: 5 | - url: /(.*\.(js|css))$ 6 | static_files: \1 7 | upload: .*\.(js|css)$ 8 | 9 | - url: /.* 10 | script: _go_app 11 | secure: always 12 | -------------------------------------------------------------------------------- /examples/go/preload.json: -------------------------------------------------------------------------------- 1 | { 2 | "/": { 3 | "/app.css": { 4 | "type": "style" 5 | }, 6 | "/app.js": { 7 | "type": "script" 8 | }, 9 | "/img.png": { 10 | "type": "image" 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "private": true, 4 | "author": "Eric Bidelman - @ebidel", 5 | "license": "Apache2", 6 | "scripts": { 7 | "postinstall" : "cd site; npm install" 8 | }, 9 | "dependencies": { 10 | "dom5": "^1.1.1", 11 | "hydrolysis": "^1.15.4", 12 | "vulcanize": "^1.13.1" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/python/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "author": "Eric Bidelman - @ebidel", 4 | "license": "Apache2", 5 | "engines": { 6 | "node": ">=1.0.0" 7 | }, 8 | "scripts": { 9 | "push": "http2-push-manifest -f static/index.html" 10 | }, 11 | "devDependencies": { 12 | "dom5": "^1.1.1", 13 | "hydrolysis": "^1.15.4", 14 | "vulcanize": "^1.13.1", 15 | "http2-push-manifest": "^1.0.0" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /site/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "httppush-gae", 3 | "version": "0.0.0", 4 | "authors": [ 5 | "Eric Bidelman " 6 | ], 7 | "private": true, 8 | "license": "Apache2", 9 | "ignore": [ 10 | "**/.*", 11 | "node_modules", 12 | "bower_components", 13 | "test", 14 | "tests" 15 | ], 16 | "dependencies": { 17 | "iron-elements": "PolymerElements/iron-elements#^1.0.3", 18 | "paper-elements": "PolymerElements/paper-elements#^1.0.5" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /site/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "postinstall": "bower install", 5 | "push": "http2-push-manifest -f static/index.html" 6 | }, 7 | "author": "Eric Bidelman - @ebidel", 8 | "license": "Apache2", 9 | "engines": { 10 | "node": ">=1.0.0" 11 | }, 12 | "dependencies": { 13 | "dom5": "^1.1.1", 14 | "hydrolysis": "^1.15.4", 15 | "vulcanize": "^1.13.1" 16 | }, 17 | "devDependencies": { 18 | "http2-push-manifest": "^1.0.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /examples/go/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | console.log('I was http2 pushed!'); 18 | -------------------------------------------------------------------------------- /examples/python/static/js/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | console.log('I was http2 pushed!'); 18 | -------------------------------------------------------------------------------- /site/static/elements.html: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /examples/python/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: python27 2 | api_version: 1 3 | threadsafe: yes 4 | 5 | libraries: 6 | - name: webapp2 7 | version: "2.5.2" 8 | 9 | handlers: 10 | 11 | - url: /css 12 | static_dir: static/css 13 | secure: always 14 | 15 | - url: /js 16 | static_dir: static/js 17 | secure: always 18 | 19 | - url: /static 20 | static_files: static/static.html 21 | upload: static/static.html 22 | http_headers: 23 | Link: '; rel=preload; as=script, ; rel=preload; as=style' 24 | 25 | - url: .* 26 | script: main.app 27 | secure: always 28 | 29 | skip_files: 30 | - ^(.*/)?app\.yaml 31 | - ^(.*/)?app\.yml 32 | - ^(.*/)?index\.yaml 33 | - ^(.*/)?index\.yml 34 | - ^(.*/)?bower\.json 35 | - ^(.*/)?#.*# 36 | - ^(.*/)?.*~ 37 | - ^(.*/)?.*\.py[co] 38 | - ^(.*/)?.*/RCS/.* 39 | - ^(.*/)?\..* 40 | - ^(.*/)?.*\.bak$ 41 | - ^(.*/)?node_modules/.* 42 | - ^.*.md|markdown 43 | -------------------------------------------------------------------------------- /examples/go/app.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | body { 18 | font-family: "Roboto", sans-serif; 19 | font-size: 16px; 20 | font-weight: 300; 21 | margin: 3em; 22 | line-height: 2; 23 | color: #757575; 24 | } 25 | 26 | h2, h3 { 27 | font-weight: 400; 28 | } 29 | -------------------------------------------------------------------------------- /examples/python/static/css/app.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | body { 18 | font-family: "Roboto", sans-serif; 19 | font-size: 16px; 20 | font-weight: 300; 21 | margin: 3em; 22 | line-height: 2; 23 | color: #757575; 24 | } 25 | 26 | h2, h3 { 27 | font-weight: 400; 28 | } 29 | -------------------------------------------------------------------------------- /site/static/js/app.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 18 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 19 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 20 | })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); 21 | 22 | ga('create', 'UA-67347604-1', 'auto'); 23 | ga('send', 'pageview'); 24 | -------------------------------------------------------------------------------- /site/scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Copyright 2015 Google Inc. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | # The directory in which this script resides. 18 | readonly BASEDIR=$(dirname $BASH_SOURCE) 19 | 20 | readonly STATICDIR=$BASEDIR/../static 21 | 22 | echo '=== Vulcanizing HTML Imports ===' 23 | vulcanize $STATICDIR/elements.html --inline-script --inline-css \ 24 | --strip-comments > $STATICDIR/elements.vulcanize.html 25 | 26 | echo '=== Generating HTTP2 push resource manifest ===' 27 | npm run push 28 | -------------------------------------------------------------------------------- /site/scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Copyright 2015 Google Inc. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | deployVersion=$1 18 | 19 | if [ -z "$deployVersion" ] 20 | then 21 | echo "App version not specified." 22 | exit 1 23 | fi 24 | 25 | readonly BASEDIR=$(dirname $BASH_SOURCE) 26 | 27 | echo "\nBuilding app version: $deployVersion\n" 28 | $BASEDIR/build.sh 29 | 30 | echo "Deploying app version: $deployVersion" 31 | gcloud preview app deploy $BASEDIR/../app.yaml \ 32 | --project http2-push --version $deployVersion 33 | -------------------------------------------------------------------------------- /examples/python/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015 Google Inc. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | __author__ = 'Eric Bidelman ' 18 | 19 | import os 20 | import sys 21 | import webapp2 22 | 23 | from google.appengine.ext.webapp import template 24 | 25 | import http2push as http2 26 | 27 | 28 | class MainHandler(http2.PushHandler): 29 | 30 | @http2.push('push_manifest.json') 31 | def get(self): 32 | path = os.path.join(os.path.dirname(__file__), 'static/index.html') 33 | return self.response.write(template.render(path, {})) 34 | 35 | 36 | app = webapp2.WSGIApplication([ 37 | ('/', MainHandler), 38 | ], debug=True) 39 | -------------------------------------------------------------------------------- /examples/go/README.md: -------------------------------------------------------------------------------- 1 | # http2preload demo 2 | 3 | A sample app demonstrating the usage of http2preload package. 4 | 5 | To run the sample, you'll need Go and App Engine SDK for Go installed. 6 | 7 | 1. Make sure you have the cli tool installed: 8 | `go get -u github.com/google/http2preload/cmd/http2preload-manifest`. 9 | 2. Generate `preload.json` manifest with `go generate`. 10 | 3. Upload the app to appspot with `$GAE_GO/goapp deploy -application -version `. 11 | 12 | Navigate to `.appspot.com` and follow the instructions. 13 | For more details check out [the explainer](https://github.com/GoogleChrome/http2push-gae/blob/master/EXPLAINER.md). 14 | 15 | Note: you don't really have to use http2preload-manifest tool. Alternatives are: 16 | 17 | - npm package [http2-push-manifest](https://www.npmjs.com/package/http2-push-manifest) 18 | - manually create a preload.json manifest. 19 | 20 | ### License 21 | 22 | (c) Google, 2015. Licensed under [Apache-2](../LICENSE) license. 23 | 24 | The Go gopher image was designed by Renee French. (http://reneefrench.blogspot.com/) 25 | The design is licensed under the Creative Commons 3.0 Attributions license. 26 | Read this article for more details: https://blog.golang.org/gopher 27 | -------------------------------------------------------------------------------- /site/push_manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/css/app.css": { 3 | "weight": 1, 4 | "type": "style" 5 | }, 6 | "/js/app.js": { 7 | "weight": 1, 8 | "type": "script" 9 | }, 10 | "/bower_components/webcomponentsjs/webcomponents-lite.min.js": { 11 | "weight": 1, 12 | "type": "script" 13 | }, 14 | "/bower_components/iron-selector/iron-selection.html": { 15 | "weight": 1, 16 | "type": "document" 17 | }, 18 | "/bower_components/iron-selector/iron-selectable.html": { 19 | "weight": 1, 20 | "type": "document" 21 | }, 22 | "/bower_components/iron-selector/iron-multi-selectable.html": { 23 | "weight": 1, 24 | "type": "document" 25 | }, 26 | "/bower_components/polymer/polymer-micro.html": { 27 | "weight": 1, 28 | "type": "document" 29 | }, 30 | "/bower_components/polymer/polymer-mini.html": { 31 | "weight": 1, 32 | "type": "document" 33 | }, 34 | "/bower_components/polymer/polymer.html": { 35 | "weight": 1, 36 | "type": "document" 37 | }, 38 | "/bower_components/iron-selector/iron-selector.html": { 39 | "weight": 1, 40 | "type": "document" 41 | }, 42 | "/elements.html": { 43 | "weight": 1, 44 | "type": "document" 45 | }, 46 | "/elements.vulcanize.html": { 47 | "weight": 1, 48 | "type": "document" 49 | } 50 | } -------------------------------------------------------------------------------- /examples/python/static/index.html: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | 18 | 19 | 20 | HTTP 2.0 push on App Engine - example 21 | 22 | 23 | 24 | 25 |

The static resources on this page (app.js and app.css) have been server pushed!

26 |

Verify by opening chrome://net-internals, drilling into HTTP2 in the dropdown, and refreshing the page.

27 | 28 |

Note: resources won't be pushed if you're running this page locally using the dev server. 29 | The app needs to be deployed to App Engine.

30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /examples/go/server.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/google/http2preload" 8 | ) 9 | 10 | // gopher is the gopher image. 11 | const gopher = "gopher.png" 12 | 13 | //go:generate http2preload-manifest -o preload.json 14 | 15 | // init registers HTTP handlers. 16 | func init() { 17 | m, err := http2preload.ReadManifest("preload.json") 18 | if err != nil { 19 | panic(err) 20 | } 21 | http.Handle("/", m.Handler(handleRoot)) 22 | http.HandleFunc("/img.png", handleImg) 23 | http.HandleFunc("/gopher", handleGopher) 24 | } 25 | 26 | // handleRoot serves the landing page. 27 | func handleRoot(w http.ResponseWriter, r *http.Request) { 28 | http.ServeFile(w, r, "index.html") 29 | } 30 | 31 | // handleImg serve gopher image. 32 | func handleImg(w http.ResponseWriter, r *http.Request) { 33 | http.ServeFile(w, r, gopher) 34 | } 35 | 36 | // handleGopher demonstrates how to push resources 37 | // without a manifest file. 38 | func handleGopher(w http.ResponseWriter, r *http.Request) { 39 | assets := map[string]http2preload.AssetOpt{ 40 | gopher: {Type: http2preload.Image}, 41 | } 42 | s := "http" 43 | if r.TLS != nil { 44 | s = "https" 45 | } 46 | // the next line will add the following header: 47 | // Link: ; rel=preload; as=image 48 | http2preload.AddHeader(w.Header(), s, r.Host, assets) 49 | // respond with minimal HTML5, omitting and 50 | fmt.Fprintf(w, ``, gopher) 51 | } 52 | -------------------------------------------------------------------------------- /examples/go/index.html: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | 18 | 19 | 20 | Example of HTTP 2.0 push on App Engine for Go 21 | 22 | 23 | 24 | 25 |

The static resources on this page (app.js, app.css and img.png) have been server pushed!

26 |

Verify by opening chrome://net-internals/#http2, refreshing this page and drilling into the Host serving this page.

27 | 28 |

Note: resources won't be pushed if you're running this page locally using the dev server. 29 | The app needs to be deployed to appspot.com.

30 | 31 |

32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /examples/python/static/static.html: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | 18 | 19 | 20 | HTTP 2.0 push on App Engine - static handler example 21 | 22 | 23 | 24 | 25 |

Static handler demo

26 | 27 |

The static resources on this page (app.js and app.css) have been server pushed!

28 |

Verify by opening chrome://net-internals, drilling into HTTP2 in the dropdown, and refreshing the page.

29 | 30 |

Note: resources won't be pushed if you're running this page locally using the dev server. 31 | The app needs to be deployed to App Engine.

32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /site/app.yaml: -------------------------------------------------------------------------------- 1 | runtime: python27 2 | api_version: 1 3 | threadsafe: yes 4 | 5 | libraries: 6 | - name: webapp2 7 | version: "2.5.2" 8 | - name: jinja2 9 | version: latest 10 | 11 | handlers: 12 | # - url: /favicon\.ico 13 | # static_files: favicon.ico 14 | # upload: favicon\.ico 15 | # secure: always 16 | 17 | # - url: /$ 18 | # static_files: static/index.html 19 | # upload: static/index.html 20 | # http_headers: 21 | # X-Associated-Content: '"/js/app.js": 0' 22 | 23 | # - url: /css/slowfile.css 24 | # script: main.app 25 | # secure: always 26 | 27 | - url: /css 28 | static_dir: static/css 29 | secure: always 30 | 31 | - url: /js 32 | static_dir: static/js 33 | secure: always 34 | 35 | - url: /bower_components 36 | static_dir: static/bower_components 37 | secure: always 38 | 39 | - url: /elements.html 40 | static_files: static/elements.html 41 | upload: static/elements.html 42 | secure: always 43 | 44 | - url: /elements.vulcanize.html 45 | static_files: static/elements.vulcanize.html 46 | upload: static/elements.vulcanize.html 47 | secure: always 48 | 49 | - url: .* 50 | script: main.app 51 | secure: always 52 | 53 | skip_files: 54 | - ^(.*/)?app\.yaml 55 | - ^(.*/)?app\.yml 56 | - ^(.*/)?index\.yaml 57 | - ^(.*/)?index\.yml 58 | - ^(.*/)?bower\.json 59 | - ^(.*/)?#.*# 60 | - ^(.*/)?.*~ 61 | - ^(.*/)?.*\.py[co] 62 | - ^(.*/)?.*/RCS/.* 63 | - ^(.*/)?\..* 64 | - ^(.*/)?.*\.bak$ 65 | - ^(.*/)?node_modules/.* 66 | - ^(.*/)?tests/.* 67 | - ^.*.md|markdown 68 | -------------------------------------------------------------------------------- /site/static/css/app.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2015 Google Inc. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | body { 18 | font-family: "Roboto", sans-serif; 19 | font-size: 16px; 20 | font-weight: 300; 21 | margin: 3em; 22 | line-height: 2; 23 | color: #757575; 24 | } 25 | h1, h2, h3, h4 { 26 | margin: 0; 27 | font-weight: 400; 28 | color: #424242; 29 | } 30 | h1 { 31 | font-weight: 400; 32 | color: #00B0FF; 33 | line-height: 1; 34 | } 35 | h1, h2 { 36 | font-weight: 300; 37 | } 38 | header { 39 | margin-bottom: 24px; 40 | } 41 | h2, h4 { 42 | margin: 0; 43 | font-weight: 400; 44 | } 45 | h2 { 46 | font-weight: 300; 47 | } 48 | a { 49 | text-decoration: none; 50 | color: #2979FF; 51 | } 52 | section { 53 | display: -webkit-flex; 54 | display: flex; 55 | } 56 | .about { 57 | margin-right: 24px; 58 | font-size: 14px; 59 | } 60 | img { 61 | max-height: 300px; 62 | border: 1px solid #eee; 63 | } 64 | footer { 65 | margin-top: 48px; 66 | position: fixed; 67 | bottom: 0; 68 | left: 0; 69 | width: 100%; 70 | padding: 16px; 71 | background-color: #03A9F4; 72 | color: white; 73 | text-align: center; 74 | } 75 | footer a { 76 | color: white; 77 | border-bottom: 1px solid rgba(255,255,255,0.5); 78 | padding-bottom: 3px; 79 | } 80 | iron-selector > * { 81 | cursor: pointer; 82 | } 83 | .iron-selected { 84 | font-weight: bold; 85 | } 86 | 87 | @media (max-width: 600px) { 88 | section { 89 | display: none; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /EXPLAINER.md: -------------------------------------------------------------------------------- 1 | ## HTTP2 push improves performance 2 | 3 | HTTP2 allows servers to preemptively push resources at high priority to the 4 | client by using a header: 5 | 6 | Link: ; rel=preload; as= 7 | 8 | The client can also ask for resource that it knows it will need by using: 9 | 10 | 11 | 12 | The performance benefits of doing this for critical resources is very promising. 13 | 14 | ![Effects of HTTP2 push performance](https://raw.githubusercontent.com/GoogleChrome/http2push-gae/master/site/static/img/pushstats.jpg) 15 | 16 | Full [WebPageTest results](http://www.webpagetest.org/video/compare.php?tests=150827_DY_13KF-l%3Anopush%2C150826_KA_1928-l%3Avulcanize%2C150826_YH_16K7-l%3Apush%2C150826_GQ_190C-l%3Avulcanize+(push)&thumbSize=100&ival=100&end=visual) 17 | 18 | TL;DR; 19 | 20 | - HTTP2 push means we no longer need to concatenate all our JS/CSS together in one file! This reduces the amount of required tooling to make a great, fast web app. 21 | - In my testing, the browser can handle smaller, individual files better than one large file. More testing needs to be done here, but the initial results are promising. 22 | - For HTML Import resources, we no longer need to run `vulcanize` (crushes sub-imports into a single file). In testing, the latter is actually slower. Boom! 23 | - In a world of web components, authors write components in a modular way using HTML Imports, a bit of CSS/JS, and markup. Push means we can ship our code _exactly_ as it was authored, minimizing the differences between dev and code shipped to production. 24 | 25 | ## HTTP2 push on App Engine 26 | 27 | GAE supports the `Link rel=preload` header! **The max number of resources you can push is currently 10.** 28 | 29 | ### Verify resources are pushed 30 | 31 | To verify resources are being pushed on production GAE: 32 | 33 | 1. `chrome://net-internals` in Chrome. 34 | 2. Change the dropdown to `HTTP/2` 35 | 3. Reload your app URL 36 | 4. Go back to `chrome://net-internals` and drill into your app. 37 | 38 | Pushed resources will show a `HTTP2_STREAM_ADOPTED_PUSH_STREAM` in the report. 39 | 40 | chrome://net-internals 41 | -------------------------------------------------------------------------------- /site/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015 Google Inc. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | __author__ = 'Eric Bidelman ' 18 | 19 | import os 20 | import sys 21 | import jinja2 22 | import webapp2 23 | 24 | import http2push as http2 25 | 26 | JINJA_ENVIRONMENT = jinja2.Environment( 27 | loader=jinja2.FileSystemLoader(os.path.dirname(__file__)) 28 | ) 29 | 30 | 31 | # TODO(ericbidelman): investigate + remove 32 | # ATM, this is necessary to only add the vulcanized bundle URL when it's actually 33 | # being requested by the browser. There appears to be a bug in GAE s.t. other 34 | # files won't be pushed if one of the URLs is never requested by the browser. 35 | # by the browser. 36 | def fixup_for_vulcanize(vulcanize, urls): 37 | """Replaces element.html URL with a vulcanized version or 38 | elements.vulcanize.html with the unvulcanized version. 39 | 40 | Args: 41 | vulcanize: True if the URL should be replaced by the vulcanized version. 42 | urls: A dict of url: priority mappings. 43 | 44 | Returns: 45 | An update dict of URL mappings. 46 | """ 47 | 48 | # TODO: don't hardcode adding the vulcanized import bundle. 49 | UNVULCANIZED_FILE = 'elements.html' 50 | VULCANIZED_FILE = 'elements.vulcanize.html' 51 | 52 | push_urls = {} 53 | 54 | for k,v in urls.iteritems(): 55 | url = k 56 | 57 | if vulcanize is not None: 58 | if k.endswith(UNVULCANIZED_FILE): 59 | url = k.replace(UNVULCANIZED_FILE, VULCANIZED_FILE) 60 | else: 61 | if k.endswith(VULCANIZED_FILE): 62 | url = k.replace(VULCANIZED_FILE, UNVULCANIZED_FILE) 63 | 64 | push_urls[url] = v 65 | 66 | return push_urls 67 | 68 | 69 | class MainHandler(http2.PushHandler): 70 | 71 | def get(self): 72 | vulcanize = self.request.get('vulcanize', None) 73 | 74 | # TODO: Remove (see above). 75 | push_urls = self.push_urls; 76 | noextras = self.request.get('noextras', None) 77 | if noextras is not None: 78 | push_urls = fixup_for_vulcanize(vulcanize, self.push_urls) 79 | 80 | # HTTP2 server push resources? 81 | if self.request.get('nopush', None) is None: 82 | # Send Link: ; rel=preload header. 83 | header = self._generate_link_preload_headers(push_urls) 84 | self.response.headers.add_header('Link', header) 85 | 86 | template = JINJA_ENVIRONMENT.get_template('static/index.html') 87 | 88 | return self.response.write(template.render({ 89 | 'vulcanize': vulcanize is not None 90 | })) 91 | 92 | # # Example - decorators. 93 | # class MainHandler(http2.PushHandler): 94 | 95 | # @http2.push('push_manifest.json') 96 | # def get(self): 97 | # vulcanize = self.request.get('vulcanize', None) 98 | 99 | # # TODO: Remove (see above). 100 | # fixup_for_vulcanize(vulcanize, self.push_urls) 101 | 102 | # path = os.path.join(os.path.dirname(__file__), 'static/index.html') 103 | 104 | # return self.response.write(template.render(path, { 105 | # 'vulcanize': vulcanize is not None 106 | # })) 107 | 108 | 109 | app = webapp2.WSGIApplication([ 110 | ('/', MainHandler), 111 | ], debug=True) 112 | -------------------------------------------------------------------------------- /site/static/index.html: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | HTTP 2.0 push on App Engine 22 | 23 | 24 | 25 | 26 | 27 | {% if vulcanize %} 28 | 29 | {% else %} 30 | 31 | {% endif %} 32 | 33 | 34 | 35 |
36 |

HTTP2 push tester

37 |

what effect does server push have on critical CSS/JS and HTML Import loading perf?

38 |
39 | 40 |
41 |
42 |

For this app, pushing resources reduced total load time by ~60%!

43 | 44 |
    45 |
  • Push means we no longer need to concatenate all JS/CSS together in one file! This reduces the amount of tooling to make a great, fast web app.
  • 46 |
  • The browser appears to handle smaller, individual files better than one large file. More testing needs to be done here, but the initial results are promising.
  • 47 |
  • HTML Import resources no longer need to be vulcanized to crush sub-imports into a single file. In testing, the latter (large single file of JS/HTML/CSS) is slower. Boom!
  • 48 |
  • In a world of web components, authors write components in a modular way using HTML Imports, a bit of CSS/JS, and markup. Push means we can ship our code exactly as it was authored, minimizing the differences between dev and prod.
  • 49 |
50 | 51 |
52 |
53 | Server push results 54 |
55 |
56 | 57 |
58 |
59 |

Ways to run this app:

60 |
    61 |
  1. / - push all static resources for this page
  2. 62 |
  3. ?nopush - no push
  4. 63 |
  5. ?vulcanize - flattened html imports, push all static resources
  6. 64 |
  7. ?vulcanize&nopush - flattened imports, no push
  8. 65 |
66 |
67 | 77 |
78 | 79 | 82 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /http2push.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Copyright 2015 Google Inc. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | __author__ = 'Eric Bidelman ' 18 | 19 | import logging 20 | import json 21 | import webapp2 22 | 23 | 24 | PUSH_MANIFEST = 'push_manifest.json' 25 | 26 | manifest_cache = {} # filename -> list of push URL mapping. 27 | 28 | def use_push_manifest(filename): 29 | global manifest_cache 30 | 31 | push_urls = {} 32 | 33 | # Read file only if it's not in memory. 34 | if filename in manifest_cache: 35 | push_urls = manifest_cache[filename]['push_urls'] 36 | else: 37 | try: 38 | with open(filename) as f: 39 | push_urls = json.loads(f.read()) 40 | 41 | manifest_cache[filename] = {'push_urls': push_urls} # cache it. 42 | except IOError as e: 43 | logging.error("Error reading %s: %s" % (filename, e.strerror)) 44 | 45 | return push_urls 46 | 47 | 48 | class PushHandler(webapp2.RequestHandler): 49 | """Base handler for constructing Link rel=preload header.""" 50 | 51 | push_urls = use_push_manifest(PUSH_MANIFEST) 52 | 53 | # def __init__(self, request, response): 54 | # self.initialize(request, response) 55 | 56 | def _generate_associate_content_header(self, urls=None): 57 | """Constructs a value for the X-Associated-Content header. 58 | 59 | Deprecated. Use _generate_link_preload_headers instead. 60 | 61 | The format of the header value is a comma-separated list of double-quoted 62 | URLs, each of which may optionally be followed by a colon and a SPDY 63 | priority number (from 0 to 7 inclusive). URL needs to be a full absolute 64 | URL. Whitespace between tokens is optional, and is ignored if present. 65 | 66 | For example: 67 | 68 | X-Associated-Content: "https://www.example.com/styles/foo.css", 69 | "/scripts/bar.js?q=4":2, "https://www.example.com/images/baz.png": 5, 70 | "https://www.example.com/generate_image.php?w=32&h=24" 71 | 72 | App Engine supports this header for now. Link: rel=preload is the standard 73 | and you should use that to be compliant with the HTTP2 spec. 74 | 75 | Args: 76 | url: A dict of url: priority mappings to use in the header. 77 | 78 | Returns: 79 | Comma separated string for the X-Associated-Content header. 80 | """ 81 | 82 | if urls is None: 83 | urls = self.push_urls 84 | 85 | host = self.request.host_url 86 | 87 | associate_content = [] 88 | for url,v in urls.iteritems(): 89 | url = '%s%s' % (host, str(url)) # Construct absolute URLs. 90 | 91 | weight = v.get('weight', None) 92 | if weight is None: 93 | associate_content.append('"%s"' % url) 94 | else: 95 | associate_content.append('"%s":%s' % (url, weight)) 96 | 97 | headers = list(set(associate_content)) # remove duplicates 98 | 99 | return ','.join(headers) 100 | 101 | def _generate_link_preload_headers(self, urls=None): 102 | """Constructs a value for the Link: rel=preload header. 103 | 104 | The format of the preload header is described in the spec 105 | http://w3c.github.io/preload/: 106 | 107 | Link: ; rel=preload; as=font 108 | 109 | Args: 110 | url: A list of urls to use in the header. 111 | 112 | Returns: 113 | A list of Link header values. 114 | """ 115 | 116 | if urls is None: 117 | urls = self.push_urls 118 | 119 | host = self.request.host_url 120 | 121 | preload_links = [] 122 | for url,v in urls.iteritems(): 123 | # Construct absolute URLs. Not really needed, but spec only contains full URLs. 124 | url = '%s%s' % (host, str(url)) 125 | t = str(v.get('type', '')) 126 | if len(t): 127 | preload_links.append('<%s>; rel=preload; as=%s' % (url, t)) 128 | else: 129 | preload_links.append('<%s>; rel=preload' % url) 130 | 131 | headers = list(set(preload_links)) # remove duplicates 132 | 133 | # GAE supports single Link header. 134 | return ','.join(headers) 135 | 136 | """ 137 | Example: 138 | 139 | @http2push.push() 140 | def get(self): 141 | pass 142 | 143 | @http2push.push('push_manifest.json') # Use a custom manifest. 144 | def get(self): 145 | pass 146 | 147 | ?nopush on the URL prevents the header from being included. 148 | """ 149 | def push(manifest=PUSH_MANIFEST): 150 | def decorator(handler): 151 | push_urls = use_push_manifest(manifest) 152 | 153 | def wrapper(*args, **kwargs): 154 | instance = args[0] 155 | # nopush URL param prevents the Link header from being included. 156 | if instance.request.get('nopush', None) is None and push_urls: 157 | preload_headers = instance._generate_link_preload_headers(push_urls) 158 | if type(preload_headers) is list: 159 | for h in preload_headers: 160 | instance.response.headers.add_header('Link', h) 161 | else: 162 | instance.response.headers.add_header('Link', preload_headers) 163 | 164 | return handler(*args, **kwargs) 165 | 166 | return wrapper 167 | return decorator 168 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HTTP 2.0 push on App Engine 2 | 3 | This project contains a drop-in library for doing HTTP2 push on Google App Engine. 4 | 5 | Demo test site: https://http2-push.appspot.com/ 6 | 7 | ## TL;DR how it works 8 | 9 | 1. Generate a `push_manifest.json` using a node script, [http2-push-manifest](https://www.npmjs.com/package/http2-push-manifest). See below. 10 | - Annotate your request handlers with the `@http2push.push()` decorator. 11 | 12 | How this works 13 | 14 | ## Requirements & Setup 15 | 16 | 1. [App Engine Python SDK](https://cloud.google.com/appengine/downloads?hl=en). You'll want the dev server for testing. 17 | - Node 18 | 19 | The example server is written in Python. If you'd like to see other App Engine 20 | runtimes feel free to submit a pull request or visit the [EXPLAINER](EXPLAINER.md) 21 | to read up on how to construct the `Link rel=preload` header yourself. 22 | 23 | ### Run the example server(s) 24 | 25 | **Important** the dev server does not support http2 push. An app needs to be 26 | deployed to production App Engine. 27 | 28 | `example/` contains a fully working example of an App Engine Python server and Go 29 | servers. This walk-through will use the Python server, which uses the 30 | http2push.py library. You'll also see an example `examples/python/push_manifest.json` in that folder. 31 | 32 | To try the test server, start the App Engine dev server in the `example` folder: 33 | 34 | cd example/python 35 | dev_appserver.py --port 8080 . 36 | 37 | Open `http://localhost:8080/`. 38 | 39 | ## Installing in your project 40 | 41 | 1. Get the drop-in library: `git clone https://github.com/GoogleChrome/http2push-gae` 42 | - Move `http2push-gae/http2push.py` into your project folder. 43 | - Install [http2-push-manifest](https://www.npmjs.com/package/http2-push-manifest) script: `npm install http2-push-manifest --save-dev` 44 | 45 | ## Usage 46 | 47 | ### Generating a push manifest 48 | 49 | This project uses [http2-push-manifest](https://www.npmjs.com/package/http2-push-manifest) 50 | to generate the list of files to push. The JSON file is loaded by `http2push.py` 51 | to constructor the `Link` header. 52 | 53 | Re-run the script whenever your list of resources changes. An easy way to do that 54 | is by adding a script to your project's `package.json`. 55 | 56 | For example, assuming `app/index.html` is your main page, you could add: 57 | 58 | "scripts": { 59 | "push": "http2-push-manifest -f app/index.html" 60 | } 61 | 62 | An run `npm run push` to re-generate the file. 63 | 64 | Read[http2-push-manifest](https://www.npmjs.com/package/http2-push-manifest)'s 65 | full documentation for more information on generating the manifest. 66 | 67 | ### Drop in the http2push server module 68 | 69 | The `http2push.py` module provides a base handler and decorator to use in your 70 | own server. The decorator is the simplest to integrate. Handlers which are annotated 71 | with the `@http2push.push()` decorator will server-push the resources in 72 | `push_manifest.json`. 73 | 74 | > **Tip** - when testing your pages, the `?nopush` URL parameter disables push. 75 | Use this parameter to test the effectiveness of server push on your own resources 76 | or to run performance tests at webpagetest.org. 77 | 78 | **Example** - using the decorator: 79 | 80 | app.yaml: 81 | 82 | ```yaml 83 | runtime: python27 84 | api_version: 1 85 | threadsafe: yes 86 | 87 | libraries: 88 | - name: webapp2 89 | version: latest 90 | 91 | handlers: 92 | 93 | - url: /css 94 | static_dir: static/css 95 | secure: always 96 | 97 | - url: /js 98 | static_dir: static/js 99 | secure: always 100 | 101 | - url: .* 102 | script: main.app 103 | secure: always 104 | ``` 105 | 106 | main.py: 107 | 108 | ```python 109 | import os 110 | import webapp2 111 | 112 | from google.appengine.ext.webapp import template 113 | import http2push as http2 114 | 115 | class Handler(http2.PushHandler): 116 | 117 | @http2.push() # push_manifest.json is used by default. 118 | def get(self): 119 | # Resources in push_manifest.json will be server-pushed with index.html. 120 | path = os.path.join(os.path.dirname(__file__), 'static/index.html') 121 | return self.response.write(template.render(path, {})) 122 | 123 | app = webapp2.WSGIApplication([('/', Handler)]) 124 | ``` 125 | 126 | To use a custom manifest file name, use `@http2push.push('FILENAME')`. 127 | 128 | **Example** - using a custom manifest file: 129 | 130 | ```python 131 | import http2push 132 | 133 | class Handler(http2push.PushHandler): 134 | 135 | @http2push.push('custom_manifest.json') 136 | def get(self): 137 | ... 138 | ``` 139 | 140 | For more control, you can also set the headers yourself. 141 | 142 | **Example** - Explicitly set `Link: rel=preload` (no decorators): 143 | 144 | ```python 145 | import http2push 146 | 147 | class Handler(http2push.PushHandler): 148 | 149 | def get(self): 150 | # Optional: use custom manifest file. 151 | # self.push_urls = http2push.use_push_manifest('custom_manifest.json') 152 | 153 | header = self._generate_link_preload_headers() 154 | self.response.headers.add_header('Link', header) 155 | 156 | path = os.path.join(os.path.dirname(__file__), 'static/index.html') 157 | return self.response.write(template.render(path, {})) 158 | ``` 159 | 160 | ## Pushing content from a static handler 161 | 162 | If you don't have a dynamic script handler, you can still push resources from 163 | App Engine page by setting the `http_headers` on your static page handler in app.yaml. 164 | 165 | app.yaml: 166 | 167 | ```yaml 168 | ... 169 | 170 | handlers: 171 | 172 | - url: /css 173 | static_dir: static/css 174 | secure: always 175 | 176 | - url: /js 177 | static_dir: static/js 178 | secure: always 179 | 180 | - url: /$ 181 | static_files: path/to/index.html 182 | upload: path/to/index.html 183 | http_headers: 184 | Link: '; rel=preload; as=script, ; rel=preload; as=style' 185 | ``` 186 | 187 | ## Deployment (test site) 188 | 189 | *Note: this section is only for maintainers of this project.* 190 | 191 | ### Build it 192 | 193 | There's a one-stop convenience script to vulcanize the app and generate `push_manifest.json`: 194 | 195 | cd site 196 | ./scripts/build.sh 197 | 198 | ### Deploy it 199 | 200 | Run `deploy.sh` to deploy the demo site. Note: `build.sh` is ran as part of this process. 201 | 202 | ./scripts/deploy.sh 203 | 204 | Where `` is the app version you'd like to deploy as. 205 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2014 Google Inc. 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | --------------------------------------------------------------------------------