Angular CLI Behind the Scenes, Part two

Last time, I walked through the Angular CLI tool ng and its relationship to npm and webpack. When I left off, I noted that if you run ng serve --open, a browser is opened (or a tab in an existing browser is open) to render your code and if you make changes, those changes are automatically reflected in the browser. What's actually going on here?

Angular's ng serve is a wrapper over webpack-dev-server, which you can use and run independently of Angular. You can install it easily via npm:

$ npm install webpack
$ npm install webpack-cli
$ npm install webpack-dev-server

(Yes, believe it or not, webpack itself isn't installed transitively, although 259 other packages are!). Most people go ahead and install these globally, but in this post I'll keep things local. The simplest webpack "site" is just a simple src/index.js like:

$ cat src/index.js
console.log("simplest thing I can think of");

And a corresponding index.html:

$ cat index.html
<script src="main.js"></script>

main.js? What's that? By default, webpack looks in a source directory named src and produces a bundled version named main.js. That's all configurable, of course, but I won't bother changing things around here.

Now, you can run the webpack-dev-server:

$ node_modules/.bin/webpack-dev-server

By default (again), this will start up a web server on port 8080. If you navigate to it in Chrome with devtools open, you'll see two familiar/expected GET requests:

GET http://localhost:8080/
GET http://localhost:8080/main.js

As expected, and two other less familiar requests:

GET ws://localhost:8080/sockjs-node/029/sklgh3l2/websocket
GET http://localhost:8080/sockjs-node/info?t=1546968058570

The first is a web socket request - this allows client-side code to exchange arbitrary, non-HTTP data with a server. If you've been working with Javascript for long, you're probably familiar with the XMLHttpRequest "AJAX" means of client/server communication. WebSockets are similar, but rather than being strictly request/response, they're designed to just transfer arbitrary bytes back and forth between a Javascript application and the server.

Digging a little deeper, it's interesting to look into the behavior of websockets. Web Sockets are supported by Node.JS as well as all modern browsers, so it's easy enough to set up a test web socket:

$ npm install ws
$ cat echo.js
const WebSocket = require('ws');

const wss = new WebSocket.Server({
  port:8099
});

wss.on('connection', function connection(ws)    {
  ws.on('message', function incoming(message)     {
    console.log(message);
    ws.send(message);
  });

  ws.send('ready');
});

Listing 1: WebSocket server

var conn = new WebSocket('ws://localhost:8099');
conn.send('abc123');

Listing 2: WebSocket client

Chrome's developer tools give you an overview of what's going on here, but it can be a little bit muddled to piece together exactly what happens when. A "web" socket is, of course, a regular TCP socket with additional protocol support above it. Actually the first thing the client does when the socket is opened is to send an ordinary HTTP request:

GET / HTTP/1.1
Host: localhost:8099
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36
Upgrade: websocket
Origin: file://
Sec-WebSocket-Version: 13
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Sec-WebSocket-Key: dRBa5g3AFGKYmWQbi9rdRg==
Sec-WebSocket-Extenstions: permessage-deflate; client_max_window_bits

Example 3: WebSocket connect request

To which the server replies with:

HTTP/1.1 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: 9M164RanLF04Uf8bFMq3I9R5qSf=
Origin: file://

So far, this doesn't seem very "lightweight" compared to an XMLHttpRequest - however, once the protocol switch has taken place, the overhead goes way down. Notice on line 13 of listing 1, I have the server posting the string "ready". This is transmitted as soon as the WebSocket protocol takes over and consists of a two-byte header, a "masking key" and a payload. The payload is the original data, byte-for- byte, XOR-ed with the masking key. This is done to prevent replay attacks, not to secure the traffic - it can be easily decoded by anybody with a packet sniffer. If you need security, upgrade to wss.

Once you understand what's going on, you can make sense of Chrome's Network tab view - it displays the request and response headers that make up the HTTP portion of the exchange in the left-most tab, followed by Frames in the second tab. When the server sends data to the browser, a red arrow is displayed; when the client sends data back to the server a green arrow is displayed.

The final GET request that pops up is the sockjs-node/info request which returns a JSON response like:

{"websocket":true,"origins":["*:*"],"cookie_needed":false,"entropy":3718846906}

It's pretty clear what this is for - just describing to the infrastructure what it needs to do.

The webpack-dev-server takes advantage of node.js's filesystem support to monitor changes to files under the src directory and update the held-open web socket whenever any of the files change. Now, with the websocket held open on the client side, if you make a change to the underlying source code (anything that webpack considers part of its bundle), you see a flurry of activity in the network tab. First, the websocket reports that the hash has changed, and then the whole page is reloaded at the behest of webpack-dev-server's client.js applyReload function:

  function applyReload(rootWindow, intervalId) {
    clearInterval(intervalId);
    log.info('[WDS] App updated. Reloading...');
    rootWindow.location.reload();
  }

Add a comment:

Completely off-topic or spam comments will be removed at the discretion of the moderator.

You may preserve formatting (e.g. a code sample) by indenting with four spaces preceding the formatted line(s)

Name: Name is required
Email (will not be displayed publicly):
Comment:
Comment is required
My Book

I'm the author of the book "Implementing SSL/TLS Using Cryptography and PKI". Like the title says, this is a from-the-ground-up examination of the SSL protocol that provides security, integrity and privacy to most application-level internet protocols, most notably HTTP. I include the source code to a complete working SSL implementation, including the most popular cryptographic algorithms (DES, 3DES, RC4, AES, RSA, DSA, Diffie-Hellman, HMAC, MD5, SHA-1, SHA-256, and ECC), and show how they all fit together to provide transport-layer security.

My Picture

Joshua Davies

Past Posts