Skip to content

Commit 794f394

Browse files
author
Walle Cyril
committed
html streaming
1 parent 63d1f16 commit 794f394

8 files changed

Lines changed: 925 additions & 3 deletions

File tree

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,3 +62,9 @@ pip-log.txt
6262
#Mr Developer
6363
.mr.developer.cfg
6464
svg/svg_icons/iconmonstr-idea-2.svg
65+
general/html-streaming/api.js
66+
general/html-streaming/api-2.js
67+
general/html-streaming/readme.md.html
68+
general/html-streaming/last-of-type-test-2.js
69+
general/html-streaming/last-of-type-test.html
70+
general/html-streaming/so.txt
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<title>HTML TEMPLATE</title>
6+
<!-- <link rel="icon" href="favicon.png"> -->
7+
<meta name="viewport" content="width=device-width">
8+
<!-- <link rel="stylesheet" href="css.css"> -->
9+
<script type="module" src="controller.js"></script>
10+
</head>
11+
<body>
12+
<h1>Controller</h1>
13+
<p>
14+
<a href="/viewer" target="_blank">Open viewer in another tab to experience this demo</a>
15+
</p>
16+
<input type="number" data-variable="number" data-function="numberChange">
17+
</body>
18+
</html>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import d from "./dom99.js";
2+
3+
d.functions.numberChange = function (event) {
4+
fetch(`/updateNumber`, {
5+
method: "PUT",
6+
body: String(d.variables.number),
7+
"content-type": "application/json",
8+
headers: {
9+
'Content-Type': 'application/json'
10+
}
11+
});
12+
};
13+
14+
d.activate();

general/html-streaming/index.js

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
"use strict";
2+
const http = require(`http`);
3+
4+
const PORT = 8080;
5+
6+
const htmlStart = `<!doctype html>
7+
<html lang="en">
8+
<head>
9+
<meta charset="utf-8">
10+
<title>html streaming</title>
11+
<meta name="viewport" content="width=device-width">
12+
<script>
13+
var headLoaded = Date.now();
14+
document.addEventListener("DOMContentLoaded", function() {
15+
console.log((Date.now() - headLoaded) / 1000);
16+
});
17+
</script>
18+
</head>
19+
<body>`;
20+
21+
const htmlEnd = `
22+
</body>
23+
</html>`;
24+
25+
const htmlInsideBody = `<h1 id="html-streaming">HTML Streaming</h1>
26+
<p>HTML streaming is a browser feature. Streaming in programming is a gerneral purpose word to define a continous process as opposed to a long stand alone blocking one. Streaming with video means playing the first seconds while the rest is still downloading. In HTML it means that while the HTML document is still being downloaded, the first parts are already parsed and executed. That is why it is generally a good idea to put <code>&lt;style&gt;</code> as early as possible in the HTML document. From an user's persective, that is very convenient, the user can already read the first paragraph, while the rest of the document is still being downloaded. Nowadays, to experience the benefits of this feature, visit a page with a big document on a low brandwidth connection.</p>
27+
<h2 id="downloading-is-a-multy-party-process">Downloading is a Multy Party Process</h2>
28+
<p>There is a sender and a receiver. The receiver, in our case, a web browser can process received information before having received all of it. It can also pause, resume and cancel the process. The sender, a web server, can also pause, resume and cancel the process. The implication is that the sender can precisely control the timing.</p>
29+
<h2 id="timing-controls">Timing Controls</h2>
30+
<p>The web server can control the timings, in other words, control when a HTML tag is being displayed. To demonstrade this capability: <code>npm run demo1</code>. In this example the response is split into individual letters with <code>.split(\`\`)</code>. They are then send one by one with a delay using <code>setInterval()</code>. The visual effect is living text being written, streamed.</p>
31+
<h2 id="leveraging-html-streaming-for-real-time-web-apps">Leveraging HTML Streaming for Real Time Web Apps</h2>
32+
<p>Extending the previous example, it is possible to display any real time data that the server receives while the connection is open. To demonstrate this, two routes are created: <code>controller</code> and <code>viewer</code>. Controller contains an <code>&lt;input type="number"&gt;</code> that uses web sockets to send its last value when it change. Viewer, displays that last value using HTML streaming. The problem is that old values are still in there, because old nodes are not removed.</p>
33+
<h2 id="using-css-to-hide-previous-values">Using CSS to hide previous values</h2>
34+
<p>The following CSS hides all but the last paragraph:</p>
35+
<pre class="editor-colors lang-"><div class="line"><span class="syntax--text syntax--plain syntax--null-grammar"><span>p:not(:last-of-type)&nbsp;{</span></span></div><div class="line"><span class="syntax--text syntax--plain syntax--null-grammar"><span>&nbsp;&nbsp;&nbsp;&nbsp;display:&nbsp;none;</span></span></div><div class="line"><span class="syntax--text syntax--plain syntax--null-grammar"><span>}</span></span></div></pre><h2 id="html-streaming-to-send-data-as-soon-as-possible">HTML Streaming to send data as soon as possible</h2>
36+
<p>In a typical dynamic website a request to a page from a logged user shows a page with static content, dynamic content and user specific content. Without HTML streaming all has to be loaded before anything has to be sent. With HTML streaming, the early parts of HTML can be sent while a database query is going on. The rest is then sent as soon as possible. This can reduce the time the user sees a blank page.</p>
37+
<p>With html streaming <img alt="with" src="C:\files\github\JavaScript-Set-Up\general\html-streaming\diagrams\with.svg"></p>
38+
<p>Without html streaming <img alt="without" src="C:\files\github\JavaScript-Set-Up\general\html-streaming\diagrams\without.svg"></p>
39+
<h2 id="details">Details</h2>
40+
<h3 id="iframes-for-multiple-html-streaming-sources"><code>&lt;iframe&gt;</code>s for multiple HTML Streaming sources</h3>
41+
<h3 id="td-as-an-escape-hatch-to-html-streaming"><code>&lt;td&gt;</code> as an Escape Hatch to HTML Streaming</h3>
42+
<p>The content of <code>&lt;td&gt;</code> is not rendered at all until the entire row has been received.</p>
43+
<h2 id="limitation">Limitation</h2>
44+
<p>The page loading indicator keeps spinning.</p>
45+
<p>Service worker pass through is limited.</p>
46+
<h3 id="timeouts">Timeouts</h3>
47+
<p>HTTP streaming stops after a certain amount of time, for example after 120 seconds after the last TCP frame was received. So this is a problem if there are long pauses in between two <code>response.write</code></p>
48+
<p>One solution could be to listen for the document loaded event and when it occurs do refresh with js <code>location.href = location.href;</code>, or with html <code>&lt;meta http-equiv="refresh" content="230"</code>.</p>
49+
<p>In NodeJS the HTTP timeout can be extedend with <code>server.timeout = TIME_OUT_LIMIT;</code></p>
50+
<h2 id="sources">Sources</h2>
51+
<p><a href="https://www.ebayinc.com/stories/blogs/tech/async-fragments-rediscovering-progressive-html-rendering-with-marko/">https://www.ebayinc.com/stories/blogs/tech/async-fragments-rediscovering-progressive-html-rendering-with-marko/</a>
52+
<a href="https://stackoverflow.com/questions/42589522/why-is-facebooks-html-wrapped-inside-a-table-mobile-login-page">https://stackoverflow.com/questions/42589522/why-is-facebooks-html-wrapped-inside-a-table-mobile-login-page</a></p>`.split(``);
53+
54+
const INTERVAL = 25; // ms
55+
const server = http.createServer((request, response) => {
56+
response.setHeader(`Content-Type`, `text/html`);
57+
response.writeHead(200);
58+
response.write(htmlStart);
59+
let i = 0;
60+
let intervalId = setInterval(function () {
61+
response.write(htmlInsideBody[i]);
62+
i += 1;
63+
if (i === htmlInsideBody.length) {
64+
clearInterval(intervalId);
65+
response.end(htmlEnd);
66+
}
67+
}, INTERVAL);
68+
});
69+
70+
server.listen(PORT);
71+
console.log(`Listening on ${PORT}`);

0 commit comments

Comments
 (0)