diff --git a/core/MAINTAINERS.txt b/core/MAINTAINERS.txt index 021f5a4..18caa1d 100644 --- a/core/MAINTAINERS.txt +++ b/core/MAINTAINERS.txt @@ -267,6 +267,10 @@ Basic Auth module - Klaus Purer 'klausi' https://www.drupal.org/u/klausi - Juampy Novillo Requena 'juampy' https://www.drupal.org/u/juampy +BigPipe module +- Wim Leers 'Wim Leers' https://www.drupal.org/u/wim-leers +- Fabian Franz 'Fabianx' https://www.drupal.org/u/fabianx + Block module - Tim Plunkett 'tim.plunkett' https://www.drupal.org/u/tim.plunkett - Ben Dougherty 'benjy' https://www.drupal.org/u/benjy diff --git a/core/composer.json b/core/composer.json index e8120c6..dbe1db9 100644 --- a/core/composer.json +++ b/core/composer.json @@ -48,6 +48,7 @@ "drupal/bartik": "self.version", "drupal/ban": "self.version", "drupal/basic_auth": "self.version", + "drupal/big_pipe": "self.version", "drupal/block": "self.version", "drupal/block_content": "self.version", "drupal/book": "self.version", diff --git a/core/modules/big_pipe/README.md b/core/modules/big_pipe/README.md new file mode 100644 index 0000000..1448be5 --- /dev/null +++ b/core/modules/big_pipe/README.md @@ -0,0 +1,113 @@ +# Installation + +Install like any other Drupal module. + + +# Recommendations + +It is strongly recommended to also enable the Dynamic Page Cache module that is included with Drupal 8 core. + + +# Relation to Page Cache & Dynamic Page Cache modules in Drupal 8 core + +- Page Cache (`page_cache`): no relation to BigPipe. +- Dynamic Page Cache (`dynamic_page_cache`): if a page is cached in the Dynamic Page Cache, BigPipe is able to send the main content much faster. It contains exactly the things that BigPipe still needs to do + + +# Documentation + +- During rendering, the personalized parts are turned into placeholders. +- By default, we use the Single Flush strategy for replacing the placeholders. i.e. we don't send a response until we've replaced them all. +- BigPipe introduces a new strategy, that allows us to flush the initial page first, and then _stream_ the replacements for the placeholders. +- This results in hugely improved front-end/perceived performance (watch the 40-second on the project page). + +There is no detailed documentation about BigPipe yet, but all of the following documentation is relevant, because it covers the principles/architecture that the BigPipe module is built upon. + +- +- +- +- +- + + + +--- + + + +# Environment requirements + +- BigPipe uses streaming, this means any proxy in between should not buffer the response: the origin needs to stream directly to the end user. +- Hence the web server and any proxies should not buffer the response, or otherwise the end result is still a single flush, which means worse performance again. +- BigPipe responses contain the header `Surrogate-Control: no-store, content="BigPipe/1.0"`. For more information about this header, see . + +Note that this version number (`BigPipe/1.0`) is not expected to increase, since all that is necessary for a proxy to support BigPipe, is the absence of buffering. No additional proxy requirements are expected to ever be added. + + +## Apache + +When using Apache, there is nothing to do: no buffering by default. + + +## FastCGI + +When using FastCGI, you must disable its buffering. + +- When using Apache+`mod_fcgid`, [set `FcgidOutputBufferSize` to `0`](https://httpd.apache.org/mod_fcgid/mod/mod_fcgid.html#fcgidoutputbuffersize): +``` + + FcgidOutputBufferSize 0 + +``` +- When using Apache+`mod_fastcgi`, [add the `-flush` option to the `FastCGIExternalServer` directive](http://www.fastcgi.com/mod_fastcgi/docs/mod_fastcgi.html#FastCgiServer): +``` + + FastCGIExternalServer /usr/sbin/php5-fpm -flush -socket /var/run/php5-fpm.sock + +``` +- When using Nginx+FastCGI, [set `fastcgi_buffering` to `off`](http://nginx.org/en/docs/http/ngx_http_fastcgi_module.html#fastcgi_buffering). + + +## IIS + +When using IIS, you must [disable its buffering](https://support.microsoft.com/en-us/kb/2321250). + +## Varnish + +When using Varnish, the following VCL disables buffering only for BigPipe responses: + +``` +vcl_backend_response { + if (beresp.Surrogate-Control ~ "BigPipe/1.0") { + set beresp.do_stream = true; + set beresp.ttl = 0s; + } +} +``` + +and for Varnish <4: + +``` +vcl_fetch { + if (beresp.Surrogate-Control ~ "BigPipe/1.0") { + set beresp.do_stream = true; + set beresp.ttl = 0; + } +} +``` + +Note that the `big_pipe_nojs` cookie does *not* break caching. Varnish should let that cookie pass through. + + +## Nginx + +When using Nginx, the BigPipe module already sends a `X-Accel-Buffering: no` header for BigPipe responses, which disables buffering. + +Alternatively, it is possible to [disable proxy buffering explicitly](http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffering). + + +## Other web servers and (reverse) proxies + +Other web servers and (reverse) proxies, including CDNs, need to be configured in a similar way. + +Buffering will nullify the improved front-end performance. This means that users accessing the site via a ISP-installed proxy will not benefit. But the site won't break either. diff --git a/core/modules/big_pipe/big_pipe.info.yml b/core/modules/big_pipe/big_pipe.info.yml new file mode 100644 index 0000000..0108e1f --- /dev/null +++ b/core/modules/big_pipe/big_pipe.info.yml @@ -0,0 +1,6 @@ +name: BigPipe +type: module +description: 'Sends pages in a way that allows browsers to show them much faster. Uses the BigPipe technique.' +package: Core (Experimental) +version: VERSION +core: 8.x diff --git a/core/modules/big_pipe/big_pipe.libraries.yml b/core/modules/big_pipe/big_pipe.libraries.yml new file mode 100644 index 0000000..6da49b7 --- /dev/null +++ b/core/modules/big_pipe/big_pipe.libraries.yml @@ -0,0 +1,11 @@ +big_pipe: + version: VERSION + js: + js/big_pipe.js: {} + drupalSettings: + bigPipePlaceholderIds: [] + dependencies: + - core/jquery + - core/jquery.once + - core/drupal.ajax + - core/drupalSettings diff --git a/core/modules/big_pipe/big_pipe.module b/core/modules/big_pipe/big_pipe.module new file mode 100644 index 0000000..bcad52c --- /dev/null +++ b/core/modules/big_pipe/big_pipe.module @@ -0,0 +1,61 @@ +' . t('About') . ''; + $output .= '

' . t('The BigPipe module sends pages with dynamic content in a way that allows browsers to show them much faster. For more information, see the online documentation for the BigPipe module.', [':big_pipe-documentation' => 'https://www.drupal.org/documentation/modules/big_pipe']) . '

'; + $output .= '

' . t('Uses') . '

'; + $output .= '
'; + $output .= '
' . t('Speeding up your site') . '
'; + $output .= '
' . t('The module requires no configuration. Every part of the page contains metadata that allows BigPipe to figure this out on its own.') . '
'; + $output .= '
'; + + return $output; + } +} + +/** + * Implements hook_page_attachments(). + * + * @see \Drupal\big_pipe\Controller\BigPipeController::setNoJsCookie() + */ +function big_pipe_page_attachments(array &$page) { + $request = \Drupal::request(); + // BigPipe is only used when there is an actual session, so only add the no-JS + // detection when there actually is a session. + // @see \Drupal\big_pipe\Render\Placeholder\BigPipeStrategy. + $session_exists = \Drupal::service('session_configuration')->hasSession($request); + $page['#cache']['contexts'][] = 'session.exists'; + // Only do the no-JS detection while we don't know if there's no JS support: + // avoid endless redirect loops. + $has_big_pipe_nojs_cookie = $request->cookies->has(BigPipeStrategy::NOJS_COOKIE); + $page['#cache']['contexts'][] = 'cookies:' . BigPipeStrategy::NOJS_COOKIE; + if ($session_exists && !$has_big_pipe_nojs_cookie) { + $page['#attached']['html_head'][] = [ + [ + // Redirect through a 'Refresh' meta tag if JavaScript is disabled. + '#tag' => 'meta', + '#noscript' => TRUE, + '#attributes' => [ + 'http-equiv' => 'Refresh', + // @todo: Switch to Url::fromRoute() once https://www.drupal.org/node/2589967 is resolved. + 'content' => '0; URL=' . Url::fromUri('internal:/big_pipe/no-js', ['query' => \Drupal::service('redirect.destination')->getAsArray()])->toString(), + ], + ], + 'big_pipe_detect_nojs', + ]; + } +} diff --git a/core/modules/big_pipe/big_pipe.routing.yml b/core/modules/big_pipe/big_pipe.routing.yml new file mode 100644 index 0000000..c7981dc --- /dev/null +++ b/core/modules/big_pipe/big_pipe.routing.yml @@ -0,0 +1,9 @@ +big_pipe.nojs: + path: '/big_pipe/no-js' + defaults: + _controller: '\Drupal\big_pipe\Controller\BigPipeController:setNoJsCookie' + _title: 'BigPipe no-JS check' + options: + no_cache: TRUE + requirements: + _access: 'TRUE' diff --git a/core/modules/big_pipe/big_pipe.services.yml b/core/modules/big_pipe/big_pipe.services.yml new file mode 100644 index 0000000..b8ff9d4 --- /dev/null +++ b/core/modules/big_pipe/big_pipe.services.yml @@ -0,0 +1,20 @@ +services: + html_response.big_pipe_subscriber: + class: Drupal\big_pipe\EventSubscriber\HtmlResponseBigPipeSubscriber + tags: + - { name: event_subscriber } + arguments: ['@big_pipe'] + placeholder_strategy.big_pipe: + class: Drupal\big_pipe\Render\Placeholder\BigPipeStrategy + arguments: ['@session_configuration', '@request_stack'] + tags: + - { name: placeholder_strategy, priority: 0 } + big_pipe: + class: Drupal\big_pipe\Render\BigPipe + arguments: ['@renderer', '@session', '@request_stack', '@http_kernel', '@event_dispatcher'] + html_response.attachments_processor.big_pipe: + public: false + class: \Drupal\big_pipe\Render\BigPipeResponseAttachmentsProcessor + decorates: html_response.attachments_processor + decoration_inner_name: html_response.attachments_processor.original + arguments: ['@html_response.attachments_processor.original', '@asset.resolver', '@config.factory', '@asset.css.collection_renderer', '@asset.js.collection_renderer', '@request_stack', '@renderer', '@module_handler'] diff --git a/core/modules/big_pipe/js/big_pipe.js b/core/modules/big_pipe/js/big_pipe.js new file mode 100644 index 0000000..41dbc67 --- /dev/null +++ b/core/modules/big_pipe/js/big_pipe.js @@ -0,0 +1,107 @@ +/** + * @file + * Renders BigPipe placeholders using Drupal's Ajax system. + */ + +(function ($, Drupal, drupalSettings) { + + 'use strict'; + + /** + * Executes Ajax commands in ' . "\n"; + flush(); + + // A BigPipe response consists of a HTML response plus multiple embedded + // AJAX responses. To process the attachments of those AJAX responses, we + // need a fake request that is identical to the master request, but with + // one change: it must have the right Accept header, otherwise the work- + // around for a bug in IE9 will cause not JSON, but