1 | # @commonshost/manifest π
|
2 |
|
3 | **HTTP/2 Server Push Manifest** is a declarative configuration syntax for web servers, CDNs, and tools that offers web developers access to this powerful new protocol feature.
|
4 |
|
5 | ```js
|
6 | [
|
7 | {
|
8 | get: '/index.html',
|
9 | push: '/**/*.{js,css}'
|
10 | }
|
11 | ]
|
12 | ```
|
13 |
|
14 | ## JSON Schema
|
15 |
|
16 | See: [./schema.json](./schema.js)
|
17 |
|
18 | ## Manifest Format
|
19 |
|
20 | The idea is simple: specify a list of **trigger and action tuples**. The syntax is flexible and forgiving, with shorthand notation for ease of use.
|
21 |
|
22 | A **manifest** is a JSON array containing rules.
|
23 |
|
24 | ```js
|
25 | []
|
26 | ```
|
27 |
|
28 | Each **rule** is a JSON object containing a `get` and `push` property. These are the **trigger** and **action** respectively.
|
29 |
|
30 | ```js
|
31 | [
|
32 | {get, push},
|
33 | {get, push}, ...
|
34 | ]
|
35 | ```
|
36 |
|
37 | **Triggers** are *URI Template* patterns or file path *globs*. They match the response's pathname, or the resource on disk (in the case of static sites).
|
38 |
|
39 | ```js
|
40 | [
|
41 | {get: '/index.html', push},
|
42 | {get: 'https://example.net/', push}, ...
|
43 | ]
|
44 | ```
|
45 |
|
46 | **Actions** are the resources to be pushed. These are also file path *globs* or *URI templates*.
|
47 |
|
48 | ```js
|
49 | [
|
50 | {get: '/index.html', push: '/app.js'},
|
51 | {get: '/app.js', push: '/lib.js'}, ...
|
52 | ]
|
53 | ```
|
54 |
|
55 | **Globs**, and **extglobs**, are POSIX/Bash-compatible expressions to match file paths. They support wildcards, negations, expansions, and more. See [micromatch](https://github.com/micromatch/micromatch).
|
56 |
|
57 | ```js
|
58 | [
|
59 | {get: '/index.html',
|
60 | push: '/{css,js}/**/*'}, ...
|
61 | ]
|
62 | ```
|
63 |
|
64 | **URI Templates** are strings formatted to match URLs. They support for pathname expansion, order-independent query string fields, and wildcards. See [RFC 6570](https://tools.ietf.org/html/rfc6570).
|
65 |
|
66 | ```js
|
67 | [
|
68 | {get: 'https://example.net/shop{/brand}',
|
69 | push: 'https://example.net/banners{/brand}.png'},
|
70 | {get: 'https://example.net{/topic}',
|
71 | push: 'https://api.example.net/topics{/productId}.json'}, ...
|
72 | ]
|
73 | ```
|
74 |
|
75 | In **shorthand** notation, both actions and triggers can be expressed as JSON strings, or arrays of strings.
|
76 |
|
77 | ```js
|
78 | [
|
79 | {get: ['/*.html', '!/legacy.html'],
|
80 | push: ['/images/*.{png,jpg}', '!/images/huge.jpg']}, ...
|
81 | ]
|
82 | ```
|
83 |
|
84 | In **normalised** notation, they are arrays containing objects with `uri` and/or `glob` properties, which are in turn either strings or arrays of strings.
|
85 |
|
86 | The **priority** number is a property of the normalised push action and ranges from `0` to `255`. Defaults to `16` as per the HTTP/2 spec.
|
87 |
|
88 | ```js
|
89 | [
|
90 | {
|
91 | get: [
|
92 | {
|
93 | uri: [
|
94 | 'https://example.net/',
|
95 | 'https://example.net/about', ...
|
96 | ],
|
97 | glob: [
|
98 | '/images/*.{jpg,webp}',
|
99 | '/styles/**/*.css', ...
|
100 | ]
|
101 | }, ...
|
102 | ],
|
103 | push: [
|
104 | {
|
105 | glob: [
|
106 | '/fonts/*.ttf', ...
|
107 | ],
|
108 | uri: [
|
109 | 'https://cdn.example.net/logo.svg', ...
|
110 | ],
|
111 | priority: 100
|
112 | }, ...
|
113 | ]
|
114 | }, ...
|
115 | ]
|
116 | ```
|
117 |
|
118 | Take a look at the [test cases](./test) for more normalisation examples.
|
119 |
|
120 | ## FAQ
|
121 |
|
122 | ### What problem does this solve?
|
123 |
|
124 | One of the features introduced by HTTP/2 is Server Push. Difficulty in configuring servers has [inhibited widespread adoption](https://youtu.be/wR1gF5Lhcq0?t=48m14s) by web developers. This declarative **HTTP/2 Server Push Manifest** format offers a server- and language-agnostic abstraction.
|
125 |
|
126 | ### Why declarative rules?
|
127 |
|
128 | Declarative rules are passive, i.e. they do not require arbitrary code execution. This is ideal for processing by edge services like a CDN nodes, or other static file servers.
|
129 |
|
130 | ### Which software supports HTTP/2 Manifests?
|
131 |
|
132 | This is currently supported by [@commonshost/server](https://www.npmjs.com/package/@commonshost/server), a Node.js based static web server. The [commons.host](https://commons.host) static hosting & CDN service also supports it, as it is based on the same server. Build-tool support (i.e. dependency graph tracing) is currently in development.
|
133 |
|
134 | *Implementors: Please add your implementation to this paragraph by opening a pull request.*
|
135 |
|
136 | ### Aren't CDNs and web servers using preload link headers?
|
137 |
|
138 | Yes, and those too could be generated based on a Server Push manifest. For example, [@commonshost/server](https://www.npmjs.com/package/@commonshost/server) automatically outputs `Link: https://...; rel=preload` headers for clients connecting via HTTP/1.1, and uses `PUSH_PROMISE` frames for HTTP/2 clients.
|
139 |
|
140 | But those `Link` headers are arguably a leftover from the HTTP/1 era. Legacy CDN, proxy, and other middlebox support are often limiting factors.
|
141 |
|
142 | In practice it is unlikely that you can send more than a handful HTTP/1 `Link` headers. It is much more efficient to use `PUSH_PROMISE` frames, which are compressed, virtually unlimited in number, and offer more functionality.
|
143 |
|
144 | ### Can I still programmatically push assets?
|
145 |
|
146 | Sure, that is an implementation issue. Your backend web application could emit `PUSH_PROMISE` frames which an intermediary CDN edge combines with a push manifest.
|
147 |
|
148 | ### Does this work with a CDN?
|
149 |
|
150 | CDNs are ideally suited for server push manifests. Edge nodes can make full use of the *think time* between the receipt of a browser request and the response from an upstream, origin server.
|
151 |
|
152 | ### What about browser support?
|
153 |
|
154 | The manifest is not applicable to browsers. No real considerations.
|
155 |
|
156 | All current popular web browsers (Cr/Fx/Sf/Eg) support HTTP/2 Server Push. While some complications, and occasionally bugs, exist, these will be solved faster if more people start using HTTP/2 Server Push.
|
157 |
|
158 | ### Which files can be pushed?
|
159 |
|
160 | Anything goes. All you need is a URL or file name.
|
161 |
|
162 | ### Should this be an RFC?
|
163 |
|
164 | Probably not, since it is an implementation detail for servers and other tools. Parts of it rely on RFCs (URI Templates, HTTP/2 spec) and other "open standards" like JSON (it's complicated) or Bash extglobs (good luck referencing the exact ISO POSIX spec!).
|
165 |
|
166 | ### Why JSON?
|
167 |
|
168 | JSON is widely supported by programming languages used on the web.
|
169 |
|
170 | ### Why publish this as a spec?
|
171 |
|
172 | So that, hopefully, other server and tool builders avoid reinventing the wheel. Makes it easier to build tools that target the manifest format rather than specific server or CDN implementations.
|
173 |
|
174 | ### Is JavaScript required?
|
175 |
|
176 | No, the spec is a language-neutral JSON Schema. The JavaScript code is simply a proof-of-concept implementation.
|
177 |
|
178 | ### Can I use HTTP/2 Server Push with CORS assets?
|
179 |
|
180 | Yes, in theory. The manifest allows pushing URIs.
|
181 |
|
182 | In practice, browser support is still limited. This area is highly experimental with several related proposals currently under consideration at the IETF HTTP-WG.
|
183 |
|
184 | ### Are there security considerations?
|
185 |
|
186 | Servers and CDNs coalescing multiple origins must take care that a manifest's origin is authorised to push CORS assets from other origins.
|
187 |
|
188 | ### What about caching?
|
189 |
|
190 | Caching and other headers to be included in a `PUSH_PROMISE` frame are not currently part of this spec. Share your thoughts in the issue tracker if you would like to see support for this feature.
|
191 |
|
192 | ### What about HTTP methods, status codes, etc?
|
193 |
|
194 | Currently only pushes in response to a `GET` request are described in the manifest. However there is nothing technically preventing a future version of the spec from supporting other methods like `POST` or `PUT`, or other (pseudo-)headers. Please share your interest in these use-cases on the issue tracker.
|
195 |
|
196 | ### Will the server push too much stuff?
|
197 |
|
198 | That depends on your manifest. With great power comes great responsibility. Protip: Use negated globs (e.g. `!/**.*.map`) to exclude files.
|
199 |
|
200 | ### What if the browser already has some assets in its cache?
|
201 |
|
202 | When the server pushes a stream, the browser can quickly close it to avoid wasting resources. Unfortunately this still requires a round-trip.
|
203 |
|
204 | Consider using HTTP/2 Cache Digests. This spec allows browsers to send a condensed list of its cache contents to the server. The server can use this to avoid sending redundant assets.
|
205 |
|
206 | ### What about auto-push?
|
207 |
|
208 | Several attempts have been made at automatically pushing assets, by build-tools scanning for dependencies or by intellgent servers analysing traffic patterns. The result of these can be encoded as manifest rules for portability or caching.
|
209 |
|
210 | ### What about stream dependencies and priorities?
|
211 |
|
212 | Dependencies can be inferred by recursively tracing the manifest rules. For example resource A has a rule that pushes resource B, which pushes resource C in another rule. Now C is a dependency of B which is a dependency of A.
|
213 |
|
214 | Priorities are explicitly specified on each rule using the `priority` key. Its value is an integer from 0 to 255 and defaults to 16. This follows the HTTP/2 spec. All pushed resources in a rule share the same priority.
|
215 |
|
216 | ### I thought HTTP/2 priority was a client-only feature?
|
217 |
|
218 | Correct, oh wise one. The priority of HTTP/2 streams is a *suggestion* by the client that the server *may* follow. Ultimately, however, it is the server that controls the order of its data frames. This manifest spec introduces a way for web developers to instruct the server on the priority of streams.
|
219 |
|
220 | ### I (do/don't) like this
|
221 |
|
222 | Not really a question. But feel free to share your experience, thoughts, and suggestions on the issue tracker. Open discussion and feedback is most welcome.
|
223 |
|
224 | ## API
|
225 |
|
226 | ```js
|
227 | const {validate, normalise, schema} = require('@commonshost/manifest')
|
228 |
|
229 | const manifest = [
|
230 | {
|
231 | get: '/index.html',
|
232 | push: ['/app.js', '/design.css']
|
233 | }
|
234 | ]
|
235 | ```
|
236 |
|
237 | Check if the syntax is valid.
|
238 |
|
239 | ```js
|
240 | try {
|
241 | validate(manifest)
|
242 | // Returns `true` or throws a validation error
|
243 | } catch (error) {
|
244 | console.error(error)
|
245 | }
|
246 | ```
|
247 |
|
248 | Transform the shorthand rules into a fully exploded format.
|
249 |
|
250 | ```js
|
251 | normalise(manifest)
|
252 |
|
253 | // [
|
254 | // {
|
255 | // get: [{glob: ['/index.html']}],
|
256 | // push: [{glob: ['/app.js', '/design.css'], priority: 16}]
|
257 | // }
|
258 | // ]
|
259 | ```
|
260 |
|
261 | Use the JSON Schema with your validator of choice.
|
262 |
|
263 | ```js
|
264 | const validator = new MyValidator()
|
265 | validator.validate(manifest, schema)
|
266 | ```
|
267 |
|
268 | ## Colophon
|
269 |
|
270 | Made with π by Sebastiaan Deckers in πΈπ¬ Singapore.
|