├── .gitignore ├── README.md ├── browser_implementation_considerations.md └── server ├── README.md ├── ch-dpr-server ├── .autotest ├── .rspec ├── Gemfile ├── Gemfile.lock ├── Rakefile ├── assets │ ├── photo-1.0x.jpg │ ├── photo-1.5x.jpg │ ├── photo-2.0x.jpg │ ├── photo-340-1.0x.jpg │ ├── photo-510-1.5x.jpg │ └── photo-680-2.0x.jpg ├── config.ru ├── dpr_server.rb └── spec │ └── dpr_server_spec.rb └── nginx-ch-dpr.conf /.gitignore: -------------------------------------------------------------------------------- 1 | .refcache 2 | .DS_Store 3 | .grunt 4 | node_modules 5 | build 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Development of this specification has moved to the HTTPWG repository. For latest drafts and issue discussions, head to: 2 | 3 | - https://github.com/httpwg/http-extensions#client-hints 4 | - https://httpwg.github.io/http-extensions/client-hints.html 5 | 6 | --- 7 | 8 | ## HTTP Client Hints Explainer 9 | 10 | This specification defines a set of HTTP request header fields, colloquially known as Client Hints, that are intended to be used as input to proactive content negotiation; just as the `Accept` header allows clients to indicate what formats they prefer, Client Hints allow clients to indicate a list of device and agent specific preferences. 11 | 12 | 13 | * [Available hints](#available-hints) 14 | * [Opt-in hint delivery](#opt-in-hint-delivery) 15 | * [Use cases](#use-cases) 16 | - [Responsive Design + Server Side Components (RESS)](#responsive-design--server-side-components-ress) 17 | - [`` element](#img-element) 18 | + [Delivering DPR-aware images](#delivering-dpr-aware-images) 19 | + [Delivering DPR and resource width aware images](#delivering-dpr-and-resource-width-aware-images) 20 | - [`` element](#picture-element) 21 | + [Device-pixel-ratio-based selection](#device-pixel-ratio-based-selection) 22 | + [Device-pixel-ratio and viewport-based selection](#device-pixel-ratio-and-viewport-based-selection) 23 | + [Resource selection](#resource-selection) 24 | - [Maximum downlink speed](#maximum-downlink-speed) 25 | * [Hands-on example](#hands-on-example) 26 | * [Implementation status](#implementation-status) 27 | 28 | --- 29 | 30 | ### Available hints 31 | Current list includes `DPR` (device pixel ratio), `Width` (resource width), `Viewport-Width` (layout viewport width), and `Downlink` (maximum downlink speed) request headers, and `Content-DPR` response header that is used to confirm the DPR of selected image resources - see full definitions in latest spec. 32 | 33 | _Note: have a proposal for another hint? Open an issue, document your use case._ 34 | 35 | ### Opt-in hint delivery 36 | To reduce request overhead the hints are sent based on opt-in basis: the server advertises supported hints, the user agent sends the appropriate hint request headers for subsequent requests - see Advertising Support for Client Hints. 37 | 38 | Note that this means that the user agent will not send hints on the very first request. However, if the site provides correct opt-in information in the response, hints will be delivered by all subsequent requests. Also, the user agent may remember site opt-in across browsing sessions, enabling hint delivery of all subsequent requests. 39 | 40 | 41 | ### Use cases 42 | #### Responsive Design + Server Side Components (RESS) 43 | 44 | The application may want to deliver alternate set of optimized resources based on advertised hints. For example, it may use the device pixel ratio (`DPR`), or the layout viewport width (`Viewport-Width`) to respond with optimized HTML markup, CSS, or script resources - see [Responsive Design + Server Side Components (RESS)](http://www.lukew.com/ff/entry.asp?1392). 45 | 46 | _Note: Applications that use this approach must also serve appropriate `Vary` and `Cache-Control` response headers to ensure correct delivery of optimized assets._ 47 | 48 | #### `` element 49 | ##### Delivering DPR-aware images 50 | `DPR` hint automates device-pixel-ratio-based selection and enables delivery of optimal image variant without any changes in markup. For example, given the following HTML markup: 51 | 52 | ```html 53 | I'm a DPR-aware image! 54 | ``` 55 | 56 | The client and server can negotiate the appropriate resolution of `img.jpg` via HTTP negotiation: 57 | 58 | ```http 59 | GET /img.jpg HTTP/1.1 60 | User-Agent: Awesome Browser 61 | Accept: image/webp, image/jpg 62 | DPR: 2.0 63 | ``` 64 | ```http 65 | HTTP/1.1 200 OK 66 | Server: Awesome Server 67 | Content-Type: image/jpg 68 | Content-Length: 124523 69 | Vary: DPR 70 | Content-DPR: 2.0 71 | 72 | (image data) 73 | ``` 74 | 75 | In the above example, the user agent advertises its device pixel ratio via `DPR` request header on the image request. Given this information, the server is able to select and respond with the optimal resource variant for the client. For full details refer to the latest [spec](https://httpwg.github.io/http-extensions/client-hints.html). 76 | 77 | _Note: when server side DPR-selection is used the server must confirm the DPR of the selected resource via `Content-DPR` response header to allow the user agent to compute the correct intrinsic size of the image._ 78 | 79 | ##### Delivering DPR and resource width aware images 80 | If the image resource width is known at request time, the user agent can communicate it to the server to enable selection of an optimized resource. For example, given the following HTML markup: 81 | 82 | ```html 83 | I'm a DPR and width aware image! 84 | ``` 85 | 86 | The client and server can negotiate an optimized asset based on `DPR` and `Width` request hints: 87 | 88 | ```http 89 | GET /img.jpg HTTP/1.1 90 | User-Agent: Awesome Browser 91 | Accept: image/webp, image/jpg 92 | DPR: 2.0 93 | Width: 320 94 | ``` 95 | ```http 96 | HTTP/1.1 200 OK 97 | Server: Awesome Server 98 | Content-Type: image/jpg 99 | Content-Length: 124523 100 | Vary: Width 101 | Content-DPR: 2.0 102 | 103 | (image data) 104 | ``` 105 | 106 | In the above example, the user agent advertises its device pixel ratio and image resource width via respective `DPR` and `Width` headers on the image request. Given this information, the server is able to select and respond with the optimal resource variant for the client: 107 | 108 | * The server can scale the asset to requested width, or return the closest available match to help reduce number of transfered bytes. 109 | * The server can factor in the device pixel ratio of the device in its selection algorithm. 110 | 111 | Note that the width of the image may not be available at request time, in which case the user agent would omit the `Width` hint. Also, the exact logic as to which asset is selected is deferred to the server, which can optimize its selection based on available resources, cache hit rates, and other criteria. 112 | 113 | 114 | #### `` element 115 | 116 | Client Hints can be used alongside [picture element](http://www.whatwg.org/specs/web-apps/current-work/multipage/embedded-content.html#the-picture-element) to automate resolution switching, simplify art-direction, and automate delivery of variable-sized images. 117 | 118 | ##### Device-pixel-ratio-based selection 119 | DPR header automates [device-pixel-ratio-based selection](http://www.whatwg.org/specs/web-apps/current-work/multipage/embedded-content.html#introduction-3:device-pixel-ratio-2) by eliminating the need to write `x` descriptors for `img` and `picture` elements: 120 | 121 | ```html 122 | 123 | 124 | 125 | A rad wolf. 126 | 127 | 128 | 129 | A rad wolf. 130 | 131 | 132 | A rad wolf. 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | The president giving an award. 141 | 142 | 143 | 144 | 145 | 146 | 147 | The president giving an award. 148 | 149 | ``` 150 | 151 | Note that the second example with [art direction-based selection](http://www.whatwg.org/specs/web-apps/current-work/multipage/embedded-content.html#introduction-3:art-direction-3) illustrates that hints do not eliminate the need for the `picture` element. Rather, Client Hints is able to simplify and automate certain parts of the negotiation, allowing the developer to focus on art direction, which by definition requires developer/designer input. 152 | 153 | ##### Device-pixel-ratio and viewport-based selection 154 | The combination of `DPR` and `Width` hints also simplifies delivery of variable sized images when [viewport-based selection](http://www.whatwg.org/specs/web-apps/current-work/multipage/embedded-content.html#introduction-3:viewport-based-selection-2) is used. The developer specifies the resource width of the image in `vw` units (which are relative to viewport width) via `sizes` attribute and the user agent handles the rest: 155 | 156 | ```html 157 | 158 | The rad wolf 160 | 161 | 162 | The rad wolf 163 | ``` 164 | 165 | * Device pixel ratio is communicated via the `DPR` request header 166 | * The `vw` size is converted to physical `px` size based on client's layout viewport size and the resulting value is communicated via the `Width` request header 167 | * The server computes the optimal image variant based on communicated `DPR` and `Width` values and responds with the optimal image variant. 168 | 169 | HTTP negotiation flow for the example above: 170 | 171 | ``` 172 | > GET /wolf.jpg HTTP/1.1 173 | > DPR: 2.0 174 | > Width: 800 175 | 176 | < 200 OK 177 | < Content-DPR: 2.0 178 | < Vary: Width 179 | < ... 180 | ``` 181 | 182 | In situations where multiple layout breakpoints impact the image's dimensions the workflow is similar to that of the previous example: 183 | 184 | ```html 185 | 186 | Kettlebell Swing 189 | 190 | 191 | Kettlebell Swing 193 | ``` 194 | 195 | The combination of the `DPR` and `Width` hints allows the server to deliver 'pixel perfect' images that match the device resolution and exact display size. However, the server is not required to do so: it can round or bin the advertised values based on own logic and serve the closest matching resource - just as `srcset` picks the nearest resource based on the provided parameters in the markup. 196 | 197 | ##### Resource selection 198 | When request hints are used the resource selection algorithm logic is shared between the user agent and the server: the user agent may apply own selection rules based on specified markup and defer other decisions to the server by communicating the appropriate `DPR` and `Width` values within the image request. With that, a few considerations to keep in mind: 199 | 200 | * The device pixel ratio and the resource width may change after the initial image request was sent to the server - e.g. layout change, desktop zoom, etc. When this occurs, and if resource selection is done via `sizes` or `srcset` attributes, the decision to initiate a new request is deferred to the user agent: it may send a new request advertising new hint values, or it may choose to reuse and rescale the existing asset. Note that this is the [default behavior of the user agent](https://github.com/ResponsiveImagesCG/picture-element/issues/230) - i.e. the user agent is **not** required to initiate a new request and use of hints does not modify this behavior. 201 | * For cases where an environment change (layout, zoom, etc.) must trigger a new asset download, you should use art-direction with `source` and appropriate media queries. 202 | 203 | Use of Client Hints does not incur additional or unnecessary requests. However, as an extra optimization, the server should [advertise the Key caching header](https://httpwg.github.io/http-extensions/client-hints.html#interaction-with-caches) to improve cache efficiency. 204 | 205 | 206 | #### Maximum downlink speed 207 | The application may want to deliver an alternate set of resources (e.g. - alternate image asset, stylesheet, HTML document, media stream, and so on) based on the maximum downlink (`Downlink`) speed of the client, as defined by the [`downlinkMax` attribute](https://w3c.github.io/netinfo/#downlinkmax-attribute) in the W3C Network Information API. 208 | 209 | 210 | ### Hands-on example 211 | 212 | A hands-on example courtesy of [resrc.it](http://www.resrc.it/): 213 | 214 | ```bash 215 | # Request 100 CSS px wide asset with DPR of 1.0 216 | $> curl -s http://app.resrc.it/http://www.resrc.it/img/demo/preferred.jpg \ 217 | -o /dev/null -w "Image bytes: %{size_download}\n" \ 218 | -H "DPR: 1.0" -H "Width: 100" 219 | Image bytes: 9998 220 | 221 | # Request 100 CSS px wide asset with DPR of 1.5 222 | $> curl -s http://app.resrc.it/http://www.resrc.it/img/demo/preferred.jpg \ 223 | -o /dev/null -w "Image bytes: %{size_download}\n" \ 224 | -H "DPR: 1.5" -H "Width: 150" 225 | Image bytes: 17667 226 | 227 | # Request 200 CSS px wide asset with DPR of 1.0 228 | $> curl -s http://app.resrc.it/http://www.resrc.it/img/demo/preferred.jpg \ 229 | -o /dev/null -w "Image bytes: %{size_download}\n" \ 230 | -H "DPR: 1.0" -H "Width: 200" 231 | Image bytes: 28535 232 | ``` 233 | 234 | ReSRC.it servers automate the delivery of optimal image assets based on advertised `DPR` and `Width` hint values and append the correct caching header (`Vary: DPR, Width`), which allows the asset to be cached on the client and by any Vary-capable intermediaries. 235 | 236 | 237 | ### Implementation status 238 | * Blink: shipped in M46. 239 | - [Automating resource selection with Client Hints](https://developers.google.com/web/updates/2015/09/automating-resource-selection-with-client-hints?hl=en) (Web Fundamentals). 240 | - [Leaner Responsive Images With Client Hints](https://www.smashingmagazine.com/2016/01/leaner-responsive-images-client-hints/) (Smashing Magazine). 241 | * IE: [Under Consideration](http://status.modern.ie/httpclienthints?term=client%20hints) 242 | * Mozilla: [935216 - Implement Client-Hints HTTP header](https://bugzilla.mozilla.org/show_bug.cgi?id=935216) 243 | 244 | 245 | ### Feedback 246 | Please feel free to open a new issue, or send a pull request! 247 | -------------------------------------------------------------------------------- /browser_implementation_considerations.md: -------------------------------------------------------------------------------- 1 | # Client Hints - Browser implementation considerations 2 | 3 | This document serves as an extension to the [Client Hints 4 | specification][client-hints]. 5 | 6 | The Client Hints specification is intended for a wide audience, and does not specify a number of details relevant to user agent implementors. This document aims to fill that gap. 7 | 8 | [client-hints]: http://igrigorik.github.io/http-client-hints/ 9 | 10 | ## `Width` 11 | 12 | The [`Width` request header][width] is sent by the client and indicates the width of an HTMLImageElement in physical pixels - i.e. same value as provided by the [w descriptor](https://html.spec.whatwg.org/multipage/embedded-content.html#introduction-3:viewport-based-selection-2) for viewport-based selection. 13 | 14 | User agents request images long before page layout occurs. 15 | For this reason, the `Width` hint can only be sent by user agents when the layout width of the image is indicated in markup, via the `sizes` attributes. 16 | 17 | A `Width` hint should be sent only with request in the context of an 18 | image, when these requests are initiated by an HTMLImageElement with a 19 | `sizes` attribute. The value of the `Width` attribute should be the 20 | return value of the [parse a sizes attribute][parse-sizes] algorithm. 21 | 22 | [width]: http://igrigorik.github.io/http-client-hints/#the-width-client-hint 23 | [parse-sizes]: https://html.spec.whatwg.org/multipage/embedded-content.html#parse-a-sizes-attribute 24 | 25 | ISSUE: Add the 'width' attribute into the mix, once [it's added to the srcset logic](https://github.com/ResponsiveImagesCG/picture-element/issues/268). 26 | TODO: Shape the above to be normative text. 27 | 28 | ## Request contexts 29 | 30 | ISSUE: Does it make sense to limit CH to image contexts? 31 | 32 | TODO: Insert normative text here? 33 | 34 | ## Content-DPR 35 | 36 | Once the server has modified the dimensions of an image resource following the user agent's inclusion of one or more Client Hints in the request, 37 | the server’s response must include a [`Content-DPR` header][content-dpr]. 38 | This header confirms that the hints sent by the user agent were acted upon, 39 | and tells the user agent that the intrinsic dimensions of the received resource should be adjusted using the `Content-DPR` value 40 | in order to match the original resource's intrinsic dimensions. 41 | 42 | This prevents page layouts (which may rely on the original image's intrinsic dimensions) from breaking, 43 | and insures that the image is viewed at the correct dimensions if and when it is viewed standalone, outside of the context of an HTML document. 44 | 45 | When information correcting an image’s intrinsic dimensions is provided by both the 46 | `Content-DPR` header and `srcset` attribute's descriptors, the `Content-DPR` header takes precedence. 47 | 48 | TODO: Turn this into normative text. 49 | 50 | [content-dpr]: http://igrigorik.github.io/http-client-hints/#confirming-selected-dpr 51 | 52 | ## Viewport-Width 53 | 54 | The `Viewport-Width` value should be the size of the [initial containing block](http://www.w3.org/TR/CSS21/visudet.html#containing-block-details) in CSS pixels. 55 | When the height or width of the initial containing block is changed, the value sent for consecutive requests should be scaled accordingly. 56 | 57 | _Note:_ The initial containing block's size is affected 58 | by the presence of scrollbars on the viewport. 59 | 60 | ## Server preference persistence 61 | 62 | User agents MAY maintain a server's `Accept-CH` preference beyond the current browsing session. 63 | When they do, they MUST clear that preference in the usual cases where such state is cleared. (Browsing history cleared, etc). 64 | 65 | TODO: Turn this into normative text. 66 | 67 | ## Viewport changes 68 | User agents MAY re-request image resources in case that the viewport 69 | have changed since the time in which these resources were requested. 70 | 71 | TODO: Turn this into normative text. 72 | 73 | ## `Accept-CH` 74 | As defined, the [`Accept-CH`][accept-ch] header is not mandatory, and 75 | user agents may send the various CH hints without relying on the 76 | `Accept-CH` header. 77 | User agents may also use the presence of the `Accept-CH` opt-in header 78 | in the response to the navigational request as a signal to send the 79 | various `CH` hints on sub-resource requests for that Document, including 80 | ones that are retrieved from third-party hosts. 81 | That is to address the full range of use-cases, e.g. authors hosting 82 | their HTML on a single server, but serving them from a different one. 83 | 84 | ISSUE: Do we want to keep `Accept-CH` or should we just send hints on all 85 | requests with an image context? 86 | 87 | ## Background images and `Width` 88 | 89 | Sending `Width` request headers for background images is hard for two reasons: 90 | 91 | * Background images are requested at style calculation time, but 92 | their layout dimensions are only known later, at layout time. 93 | * Background images' dimensions are not constrained by the dimensions of 94 | their container, and can be influenced by a multitude of CSS 95 | properties. We need to account for that before we can use the container 96 | dimensions as the `Width` value. 97 | 98 | At this time, it seems like there's no way to send `Width` 99 | info for background images. 100 | 101 | ## Implementation notes 102 | 103 | ### Handling `Accept-CH` before the document is created 104 | 105 | The [`Accept-CH`][accept-ch] header is used by the server to notify the 106 | user agent that the server supports certain hints, and will act upon them. 107 | 108 | When provided by the server as an HTTP header, the browser often 109 | encounters the header before the Document object exists. That means that 110 | the server preference state has to be maintained and passed over to the 111 | Document object once it is created. 112 | 113 | [accept-ch]: http://igrigorik.github.io/http-client-hints/#advertising-support-for-client-hints 114 | 115 | ### Handling `Accept-CH` in the preloader 116 | 117 | When the `Accept-CH` header is provided as an HTMLMetaElement (``), user agents need to be able to process and apply this 119 | preference even when the images are requested by the preloader. 120 | 121 | Currently, preloader-based image requests are sent out before the HTMLMetaElement is parsed and the 122 | preference is applied on the Document. In order to meet the above requirement, preloaders must scan for `` tags and maintain the parsed out preference themselves. 123 | -------------------------------------------------------------------------------- /server/README.md: -------------------------------------------------------------------------------- 1 | To see Client-Hints in action you will need both a CH-aware server and a CH-aware client... 2 | 3 | ### Client-Hints servers 4 | 5 | * See checked in **ch-dpr-server** for a sample ruby server 6 | * See checked in **nginx-ch-dpr.conf** for sample nginx implementation 7 | 8 | You can run both of the above locally to test the server. Alternatively, you can seem them in action here: 9 | 10 | * Ruby server: http://ch-dpr-demo.herokuapp.com/photo.jpg 11 | * Nginx server: http://www.igvita.com/downloads/ch/photos/awesome.jpg 12 | 13 | For an end-to-end test, setup your client (see below) and head to: http://www.igvita.com/downloads/ch/ 14 | 15 | 16 | ### Client-Hints clients 17 | 18 | * Chrome Canary supports Client-Hints: 19 | * Enable _chrome://flags/#enable-experimental-web-platform-features_ 20 | * Launch Canary with `--enable-clients` from comand line 21 | * You can also install [Client-Hints extension for Chrome](https://chrome.google.com/webstore/detail/client-hints/gdghpgmkfaedgngmnahnaaegpacanlef) to easily overwrite the advertised client DPR. 22 | 23 | Chrome will automatically send the device DPR (via CH-DPR header), and will also use the returned DPR confirmation (via DPR header) to adjust the intrinsic size calculation of the displayed asset. Finally, head to http://www.igvita.com/downloads/ch/ to see Client-Hints in action - if you've enabled the right flags then all images should be displayed at correct intrinsic size and resolution. Now try changing your DPR value via the extension - voila! 24 | 25 | Alternatively, you can test Client-Hints with your favorite command line client. Simply pass the right CH headers when making the request. For example, using curl: 26 | 27 | ```bash 28 | $> curl -H'CH-DPR: 1.8' -v http://ch-dpr-demo.herokuapp.com/photo.jpg | wc -l 29 | 30 | > GET /photo.jpg HTTP/1.1 31 | > User-Agent: curl/7.30.0 32 | > Host: ch-dpr-demo.herokuapp.com 33 | > Accept: */* 34 | > CH-DPR: 1.8 35 | > 36 | < HTTP/1.1 200 OK 37 | < Content-Type: image/jpeg 38 | < Date: Wed, 30 Oct 2013 19:02:18 GMT 39 | < Server: WEBrick/1.3.1 (Ruby/2.0.0/2013-06-27) 40 | < DPR: 1.5 41 | < Vary: CH-DPR 42 | < X-Content-Type-Options: nosniff 43 | < Content-Length: 381135 44 | < Connection: keep-alive 45 | < 46 | [data not shown] 47 | 1361 48 | ``` 49 | 50 | In example above we're sending a "1.8" DPR hint to the server, and the server has decided to respond with a "1.5" (as confirmed via "DPR: 1.5" header) asset. Try changing it to a different value, or omit the header entirely. 51 | 52 | 53 | -------------------------------------------------------------------------------- /server/ch-dpr-server/.autotest: -------------------------------------------------------------------------------- 1 | require 'autotest/growl' 2 | -------------------------------------------------------------------------------- /server/ch-dpr-server/.rspec: -------------------------------------------------------------------------------- 1 | autotest 2 | --color 3 | --format documentation 4 | -------------------------------------------------------------------------------- /server/ch-dpr-server/Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'sinatra' 4 | gem 'rack-test' 5 | gem 'rspec' 6 | -------------------------------------------------------------------------------- /server/ch-dpr-server/Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | diff-lcs (1.2.4) 5 | rack (1.5.2) 6 | rack-protection (1.5.1) 7 | rack 8 | rack-test (0.5.7) 9 | rack (>= 1.0) 10 | rspec (2.14.1) 11 | rspec-core (~> 2.14.0) 12 | rspec-expectations (~> 2.14.0) 13 | rspec-mocks (~> 2.14.0) 14 | rspec-core (2.14.6) 15 | rspec-expectations (2.14.3) 16 | diff-lcs (>= 1.1.3, < 2.0) 17 | rspec-mocks (2.14.4) 18 | sinatra (1.4.4) 19 | rack (~> 1.4) 20 | rack-protection (~> 1.4) 21 | tilt (~> 1.3, >= 1.3.4) 22 | tilt (1.4.1) 23 | 24 | PLATFORMS 25 | ruby 26 | 27 | DEPENDENCIES 28 | rack-test 29 | rspec 30 | sinatra 31 | -------------------------------------------------------------------------------- /server/ch-dpr-server/Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler" 2 | require "rspec/core" 3 | require "rspec/core/rake_task" 4 | 5 | task :default => :spec 6 | 7 | desc "Run all specs in spec directory (excluding plugin specs)" 8 | RSpec::Core::RakeTask.new(:spec) 9 | -------------------------------------------------------------------------------- /server/ch-dpr-server/assets/photo-1.0x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igrigorik/http-client-hints/7d18e9e7a859673f109721ba9ad593ca2d3a4e4e/server/ch-dpr-server/assets/photo-1.0x.jpg -------------------------------------------------------------------------------- /server/ch-dpr-server/assets/photo-1.5x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igrigorik/http-client-hints/7d18e9e7a859673f109721ba9ad593ca2d3a4e4e/server/ch-dpr-server/assets/photo-1.5x.jpg -------------------------------------------------------------------------------- /server/ch-dpr-server/assets/photo-2.0x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igrigorik/http-client-hints/7d18e9e7a859673f109721ba9ad593ca2d3a4e4e/server/ch-dpr-server/assets/photo-2.0x.jpg -------------------------------------------------------------------------------- /server/ch-dpr-server/assets/photo-340-1.0x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igrigorik/http-client-hints/7d18e9e7a859673f109721ba9ad593ca2d3a4e4e/server/ch-dpr-server/assets/photo-340-1.0x.jpg -------------------------------------------------------------------------------- /server/ch-dpr-server/assets/photo-510-1.5x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igrigorik/http-client-hints/7d18e9e7a859673f109721ba9ad593ca2d3a4e4e/server/ch-dpr-server/assets/photo-510-1.5x.jpg -------------------------------------------------------------------------------- /server/ch-dpr-server/assets/photo-680-2.0x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/igrigorik/http-client-hints/7d18e9e7a859673f109721ba9ad593ca2d3a4e4e/server/ch-dpr-server/assets/photo-680-2.0x.jpg -------------------------------------------------------------------------------- /server/ch-dpr-server/config.ru: -------------------------------------------------------------------------------- 1 | require './dpr_server' 2 | run Sinatra::Application 3 | -------------------------------------------------------------------------------- /server/ch-dpr-server/dpr_server.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra' 2 | 3 | # Sample CH-DPR aware server. 4 | # 5 | # The server assumes the asset variants are pre-generated (e.g. via a build 6 | # task) and are available on disk. To find the available variants, the server 7 | # performs a wildcard lookup on based on the following filename pattern: 8 | # 9 | # {name}-{dpr}x.{extension} 10 | # 11 | # Note that the wildcard lookup is just one simple strategy. Alternatively, 12 | # a different mechanism can be used to define which breakpoints are available, 13 | # or, a dynamic backend / service can be used to generate the appropriate 14 | # assets on the fly. 15 | # 16 | # For demo purposes, the server also accepts "force_dpr" query param to 17 | # emulate behavior of sending an equivalent "CH-DPR: x.x" request header. 18 | # 19 | 20 | class Image 21 | attr_reader :filename, :ext, :dpr, :name 22 | def initialize(filename) 23 | @filename = filename 24 | @ext = File.extname(filename) 25 | @dpr = filename.match(/(\d\.\d)x/)[1].to_f rescue nil 26 | @name = File.basename(filename).chomp(@ext).chomp("#{@dpr}x") 27 | end 28 | 29 | def nameonly? ; @dpr.nil? ; end 30 | def exactname? ; !@dpr.nil? ; end 31 | end 32 | 33 | 34 | get '/:name' do 35 | # use force_dpr query param to overrie CH-DPR header 36 | dpr = params['force_dpr'] || env['HTTP_CH_DPR'] 37 | 38 | # parse incoming image request and build list of available variants. 39 | img = Image.new('assets/'+params['name']) 40 | variants = Dir["assets/#{img.name}*"].map {|v| Image.new(v) } 41 | return 400 if variants.empty? 42 | 43 | headers \ 44 | "Content-Type" => "image/jpeg", # demo server always returns jpeg's... 45 | "Vary" => "CH-DPR" # use CH-DPR value as part of cache key 46 | 47 | # fallback behavior for requests without CH-DPR header 48 | if dpr.nil? 49 | if img.exactname? 50 | # return exact asset (e.g. /photo-1x.jpg) 51 | return [200, IO.read(img.filename)] 52 | 53 | elsif img.nameonly? 54 | # if DPR is not specified in filename, return lowest available DPR 55 | asset = variants.min_by {|v| v.dpr } 56 | return [200, IO.read(asset.filename)] 57 | end 58 | 59 | return 400 60 | end 61 | 62 | # CH-DPR or force_dpr is present, find the best candidate: 63 | # First, find variants that are equal or less than request DPR. Note that 64 | # you can serve a higher DPR asset, we're just optimizing for number of 65 | # delivered bytes in this case. Second, find the closest variant that 66 | # matches the request DPR from filtered list. 67 | asset = variants. 68 | find_all {|v| v.dpr <= dpr.to_f }. 69 | max_by {|v| v.dpr } 70 | 71 | # Set the server selection header to indicate the DPR of selected asset, 72 | # such that the client adjust its logic when calculating the display size. 73 | headers "DPR" => asset.dpr.to_s 74 | 75 | # Return the actual asset! 76 | return [200, IO.read(asset.filename)] 77 | end 78 | -------------------------------------------------------------------------------- /server/ch-dpr-server/spec/dpr_server_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rspec' 2 | require 'rack/test' 3 | require_relative '../dpr_server' 4 | 5 | SMALL = IO.read('assets/photo-1.0x.jpg').size 6 | MEDIUM = IO.read('assets/photo-1.5x.jpg').size 7 | LARGE = IO.read('assets/photo-2.0x.jpg').size 8 | 9 | describe 'Client-Hints image server' do 10 | include Rack::Test::Methods 11 | 12 | def app 13 | Sinatra::Application 14 | end 15 | 16 | context 'GET {name} without CH-DPR hint' do 17 | it 'should return 400 on missing file' do 18 | get '/missing.jpg' 19 | last_response.status.should eq 400 20 | end 21 | 22 | it 'may return low-res asset if filename is just the name' do 23 | get '/photo.jpg' 24 | last_response.body.size.should eq SMALL 25 | end 26 | 27 | it 'may return exact asset if filename is an exact match' do 28 | get '/photo-453-1.5x.jpg' 29 | last_response.body.size.should eq MEDIUM 30 | end 31 | end 32 | 33 | context 'GET {name} with CH-DPR hint' do 34 | before(:each) do 35 | get '/photo.jpg', {}, {'HTTP_CH_DPR' => 2.2} 36 | end 37 | 38 | it 'should round down to closest available DPR breakpoint' do 39 | last_response.body.size.should eq LARGE 40 | end 41 | 42 | it 'should confirm selected asset via DPR header' do 43 | last_response.headers['DPR'].should eq '2.0' 44 | end 45 | 46 | it 'should return Vary: DPR header' do 47 | last_response.headers['Vary'].should match('DPR') 48 | end 49 | 50 | it 'should allow force_dpr query string override' do 51 | get '/photo.jpg', {}, { 52 | 'HTTP_CH_DPR' => 2.2, 53 | 'QUERY_STRING' => 'force_dpr=1.8' 54 | } 55 | 56 | last_response.body.size.should eq MEDIUM 57 | last_response.headers['DPR'].should eq '1.5' 58 | end 59 | end 60 | end 61 | -------------------------------------------------------------------------------- /server/nginx-ch-dpr.conf: -------------------------------------------------------------------------------- 1 | # 2 | # Example live @ http://www.igvita.com/downloads/ch/ 3 | # 4 | http { 5 | # - capture the DPR resolution from CH-DPR header 6 | # - if no CH-DPR header is present, default to 1.0 7 | # - round DPR ranges to nearest available DPR asset (1.0, 1.5, 2.0) 8 | # -- we're assuming these are available on disk.. {name}-{dpr}.{ext} 9 | map $http_ch_dpr $dpr { 10 | default "1.0"; 11 | ~1\.[01234] "1.0"; 12 | ~1\.[56789] "1.5"; 13 | ~2\.[0123456789] "2.0"; 14 | ~3\.[0123456789] "2.0"; 15 | } 16 | 17 | server { 18 | # - check for appropriate DPR asset 19 | # - if appropriate DPR is not available, fallback to original 20 | location ~* /images/(?.*)\.(?.*)$ { 21 | add_header DPR $dpr; 22 | add_header Vary CH-DPR; 23 | 24 | try_files /images/$name-$dpr.$ext $uri =404; 25 | } 26 | 27 | # ... regular nginx configuration 28 | } 29 | } 30 | --------------------------------------------------------------------------------