How to Implement Bigpipe with HTTP Chunked Transfer in Node.js, PHP, and Java
This article explores the Bigpipe technique for accelerating first‑screen rendering by leveraging HTTP 1.1 chunked transfer, comparing implementations in PHP, Java, Node.js (including Express and Koa), and demonstrating parallel module flushing with async patterns such as callbacks, async parallel, co, and async/await.
After the previous article describing the principles of the new Seller Center Bigpipe, this follow‑up shares practical experiences, code snippets, and lessons learned while implementing chunked transfer for parallel module rendering.
Core Problem
Traditional synchronous loading of first‑screen modules forces the browser to wait for the slowest module, causing user‑perceived latency.
Synchronously loading multiple modules blocks the browser.
Asynchronous scrolling loads modules one by one, reducing initial wait but still incurring request overhead.
Facebook’s idea: a single request where the server generates each module in parallel and streams the chunks to the client for immediate rendering.
Bigpipe draws inspiration from CPU pipeline processing.
Technical Breakthrough
The seller center is modular; the key question becomes: can a single request stream dynamically generated chunks to the client for real‑time rendering until the response ends?
Concept
Use HTTP/1.1 chunked transfer encoding.
When the Transfer‑Encoding header is chunked, the message body consists of an indefinite number of chunks terminated by a zero‑length chunk.
This mechanism lets the server and browser establish a pipeline that delivers content in stages.
Implementation
PHP Approach
<html>
<head>
<title>php chunked</title>
</head>
<body>
<?php sleep(1); ?>
<div id="moduleA"><?php echo 'moduleA' ?></div>
<?php ob_flush(); flush(); ?>
<?php sleep(3); ?>
<div id="moduleB"><?php echo 'moduleB' ?></div>
<?php ob_flush(); flush(); ?>
<?php sleep(2); ?>
<div id="moduleC"><?php echo 'moduleC' ?></div>
<?php ob_flush(); flush(); ?>
</body>
</html>PHP uses ob_flush() and flush() to send chunks to the browser; the response header shows Transfer‑Encoding: chunked.
PHP lacks native threading, so true parallel generation is not possible without additional extensions.
Java Approach
Java provides similar flush capabilities and supports multithreading, making parallel module generation easier.
Flush Considerations
Yahoo’s performance rules suggest flushing early to let the browser start downloading CSS/JS.
Prioritize flushing content that users care about most (e.g., search box).
Split large chunks into smaller pieces for smoother delivery.
Node.js Implementation
Node’s asynchronous nature makes parallel processing straightforward.
Full control over the view layer.
Node’s HTTP API natively supports many advanced HTTP features.
Basic HelloWorld Example
var http = require('http');
http.createServer(function (request, response){
response.writeHead(200, {'Content-Type': 'text/html'});
response.write('hello');
response.write(' world ');
response.write('~ ');
response.end();
}).listen(8080, "127.0.0.1");The response automatically includes Transfer‑Encoding: chunked.
Calling response.write repeatedly flushes data before response.end().
Full Layout Example
layout.html
<!DOCTYPE html>
<html>
<head>
<!-- css and js tags -->
<link rel="stylesheet" href="index.css" />
<script>
function renderFlushCon(selector, html) {
document.querySelector(selector).innerHTML = html;
}
</script>
</head>
<body>
<div id="A"></div>
<div id="B"></div>
<div id="C"></div>The layout provides placeholders for modules A, B, and C.
var http = require('http');
var fs = require('fs');
http.createServer(function(request, response) {
response.writeHead(200, { 'Content-Type': 'text/html' });
// flush layout and assets
var layoutHtml = fs.readFileSync(__dirname + "/layout.html").toString();
response.write(layoutHtml);
// render modules
response.write('<script>renderFlushCon("#A","moduleA");</script>');
response.write('<script>renderFlushCon("#C","moduleC");</script>');
response.write('<script>renderFlushCon("#B","moduleB");</script>');
// close tags
response.write('</body></html>');
response.end();
}).listen(8080, "127.0.0.1");The output streams moduleA, moduleB, moduleC as they become ready.
Express Implementation
var express = require('express');
var app = express();
var fs = require('fs');
app.get('/', function (req, res) {
var layoutHtml = fs.readFileSync(__dirname + "/layout.html").toString();
res.write(layoutHtml);
res.write('<script>renderFlushCon("#A","moduleA");</script>');
res.write('<script>renderFlushCon("#C","moduleC");</script>');
res.write('<script>renderFlushCon("#B","moduleB");</script>');
res.write('</body></html>');
res.end();
});
app.listen(3000);Koa Implementation
var koa = require('koa');
var app = koa();
app.use(function *() {
this.body = 'Hello world';
});
app.listen(3000);Koa does not expose the raw res object; response handling must use the framework’s abstractions.
Importance of Streams
Streaming enables the server to push chunks as soon as they are ready, reducing perceived latency.
Callback Hell
Deeply nested callbacks make asynchronous code hard to maintain.
Using mature libraries (e.g., async) mitigates this problem.
Async Library
async.parallelruns multiple functions concurrently and invokes a final callback when all have finished.
var async = require('async');
async.parallel([
function(cb) { setTimeout(function(){ context.push('<script>renderFlushCon("#A","moduleA");</script>'); cb(); }, 1000); },
function(cb) { context.push('<script>renderFlushCon("#C","moduleC");</script>'); cb(); },
function(cb) { setTimeout(function(){ context.push('<script>renderFlushCon("#B","moduleB");</script>'); cb(); }, 2000); }
], function (err) {
context.push('</body></html>');
context.push(null);
});Co + Promise
Combining co with native promises yields a cleaner parallel flow.
var exec = options.map(function(item){ return new Promise(function (resolve) {
setTimeout(function(){
context.push('<script>renderFlushCon("#'+item.id+'","'+item.html+'");</script>');
resolve();
}, item.delay);
}); });
co(function* () {
yield exec;
}).then(function(){
context.push('</body></html>');
context.push(null);
});ES7 async/await
Future standard syntax can further simplify the logic:
async function flush() {
await Promise.all([moduleA.flush(), moduleB.flush(), moduleC.flush()]);
context.push('</body></html>');
context.push(null);
}Midway Framework
Midway builds on Koa to provide a lightweight MVC layer, making Bigpipe integration easier for front‑end developers.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Node Underground
No language is immortal—Node.js isn’t either—but thoughtful reflection is priceless. This underground community for Node.js enthusiasts was started by Taobao’s Front‑End Team (FED) to share our original insights and viewpoints from working with Node.js. Follow us. BTW, we’re hiring.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
