��������,��,>Xe/� �,hg����,�,p�,o�t����d��+��P��_���,0�,0�,Xe��,�t���������� �P��_��,�,�,����HC��$����h]���� ���������,����^��������X�,��,����h]K����������,����`fK���,HC���������HC�(���������HC�H$���������45P5�6.this_function]�H�,�s�p�p����x���8�8������ ��
+���`�����(�h�(�P�`�`� �
+`:�:�:�<�< ?@C0�YXY�Y \p^�``i�qX��� ��
+@�x���
��}��9�@:��:��;��>�B��F��H�J�b� e�
+(��(�HL��L��L��M� Q��U�����z�8{��{������@��`��@��ؤ�0�� �-�0.�x.� 0�h0�7�<�XF��V��i� l�
+h|�H��p��
@�����������P��p3��3�4�5��@��I� �h���������x������������� ���
+���@@�Ї�H����0n�xn��n��o�@p�0w�{�}��~���� ���
+�M�-�X-��-��.��1�@4�8�:�`<��C� ���Ѝ��������Ȥ�����0����� ��x��p��h�� h��
+�(��(P�(
��(��(�(�))P)�!)�#)P%)P-)�6)@o)�o)�o)�p)Xw)|)��)��)0�)0�)�)X�)��)��)�)h�)��)�)H�)��)��)��)��)(�)8�)h�) (*
+P*�*�
+*
hN*�R*@a*p�*��*�*�*h�*��*��*��*��*��*��*��*��*@+��*��*0�*h�*��*P�*(�* �*��*��* ��*
+X�+�~+�~+(+(�+@�+P�+(�+ �+0�+��+ �+h�+��+��+0�+x�+��+��+��+�,�,�,�,�, �,
+�,�#,&,
�!x"#�' (h(0*x*�/Ȗ p�
+�5�R)
��,0�,x�,��,�' (h(0*x*�/Ȗ p�
+�5�R)
��,�,0�,x�,��,
diff --git a/Chapter03/working-with-files/incremental-processing/file.dat b/Chapter03/working-with-files/incremental-processing/file.dat
new file mode 100644
index 0000000..6a97696
Binary files /dev/null and b/Chapter03/working-with-files/incremental-processing/file.dat differ
diff --git a/Chapter03/working-with-files/incremental-processing/log.txt b/Chapter03/working-with-files/incremental-processing/log.txt
new file mode 100644
index 0000000..d210d7f
--- /dev/null
+++ b/Chapter03/working-with-files/incremental-processing/log.txt
@@ -0,0 +1,4 @@
+Wed Jun 08 2016 23:48:13 GMT+0100 (BST) 896977 bytes removed
+Wed Jun 08 2016 23:48:17 GMT+0100 (BST) 896977 bytes removed
+Wed Jun 08 2016 23:49:25 GMT+0100 (BST) 896977 bytes removed
+Wed Jun 08 2016 23:50:17 GMT+0100 (BST) 896977 bytes removed
diff --git a/Chapter03/working-with-files/incremental-processing/null-byte-remover.js b/Chapter03/working-with-files/incremental-processing/null-byte-remover.js
new file mode 100644
index 0000000..9a57267
--- /dev/null
+++ b/Chapter03/working-with-files/incremental-processing/null-byte-remover.js
@@ -0,0 +1,23 @@
+'use strict'
+
+setInterval(() => process.stdout.write('.'), 10).unref()
+
+const fs = require('fs')
+const path = require('path')
+const cwd = process.cwd()
+
+const sbs = require('strip-bytes-stream')
+
+fs.createReadStream(path.join(cwd, 'file.dat'))
+ .pipe(sbs((n) => n))
+ .on('end', function () { log(this.total) })
+ .pipe(fs.createWriteStream(path.join(cwd, 'clean.dat')))
+
+function log(total) {
+ fs.appendFile(
+ path.join(cwd, 'log.txt'),
+ (new Date) + ' ' + total + ' bytes removed\n'
+ )
+}
+
+
diff --git a/Chapter03/working-with-files/incremental-processing/package.json b/Chapter03/working-with-files/incremental-processing/package.json
new file mode 100644
index 0000000..859e5c4
--- /dev/null
+++ b/Chapter03/working-with-files/incremental-processing/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "piping",
+ "version": "1.0.0",
+ "description": "",
+ "main": "null-byte-remover.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "David Mark Clements",
+ "license": "MIT",
+ "dependencies": {
+ "strip-bytes-stream": "^1.1.0"
+ }
+}
diff --git a/Chapter03/working-with-files/log.txt b/Chapter03/working-with-files/log.txt
new file mode 100644
index 0000000..4706c31
--- /dev/null
+++ b/Chapter03/working-with-files/log.txt
@@ -0,0 +1,6 @@
+Wed Jun 08 2016 22:56:41 GMT+0100 (BST) 100000000 bytes removed
+Wed Jun 08 2016 22:56:54 GMT+0100 (BST) 897090 bytes removed
+Wed Jun 08 2016 22:58:02 GMT+0100 (BST) 897090 bytes removed
+Wed Jun 08 2016 22:58:03 GMT+0100 (BST) 897090 bytes removed
+Wed Jun 08 2016 22:58:15 GMT+0100 (BST) 897090 bytes removed
+Wed Jun 08 2016 22:59:08 GMT+0100 (BST) 897090 bytes removed
diff --git a/Chapter03/working-with-files/null-byte-remover.js b/Chapter03/working-with-files/null-byte-remover.js
new file mode 100644
index 0000000..e891808
--- /dev/null
+++ b/Chapter03/working-with-files/null-byte-remover.js
@@ -0,0 +1,15 @@
+'use strict'
+
+const fs = require('fs')
+const path = require('path')
+const cwd = process.cwd()
+const bytes = fs.readFileSync(path.join(cwd, 'file.dat'))
+
+const clean = bytes.filter(n => n)
+fs.writeFileSync(path.join(cwd, 'clean.dat'), clean)
+
+fs.appendFileSync(
+ path.join(cwd, 'log.txt'),
+ (new Date) + ' ' + (bytes.length - clean.length) + ' bytes removed\n'
+)
+
diff --git a/Chapter04/creating-readable-writable-streams/composing-duplex-streams/index.js b/Chapter04/creating-readable-writable-streams/composing-duplex-streams/index.js
new file mode 100644
index 0000000..d5004e7
--- /dev/null
+++ b/Chapter04/creating-readable-writable-streams/composing-duplex-streams/index.js
@@ -0,0 +1,19 @@
+'use strict'
+
+const from = require('from2')
+const to = require('to2')
+const duplexify = require('duplexify')
+
+const rs = from(() => {
+ rs.push(Buffer.from('Hello, World!'))
+ rs.push(null)
+})
+
+const ws = to((data, enc, cb) => {
+ console.log(`Data written: ${data.toString()}`)
+ cb()
+})
+
+const stream = duplexify(ws, rs)
+
+stream.pipe(stream)
diff --git a/Chapter04/creating-readable-writable-streams/composing-duplex-streams/package.json b/Chapter04/creating-readable-writable-streams/composing-duplex-streams/package.json
new file mode 100644
index 0000000..1f07635
--- /dev/null
+++ b/Chapter04/creating-readable-writable-streams/composing-duplex-streams/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "composing-duplex-streams",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "from2": "^2.3.0",
+ "to2": "^1.0.0"
+ }
+}
diff --git a/Chapter04/creating-readable-writable-streams/core-streams/index.js b/Chapter04/creating-readable-writable-streams/core-streams/index.js
new file mode 100644
index 0000000..987d020
--- /dev/null
+++ b/Chapter04/creating-readable-writable-streams/core-streams/index.js
@@ -0,0 +1,19 @@
+'use strict'
+
+const { Readable, Writable } = require('readable-stream')
+
+const rs = Readable({
+ read: () => {
+ rs.push(Buffer.from('Hello, World!'))
+ rs.push(null)
+ }
+})
+
+const ws = Writable({
+ write: (data, enc, cb) => {
+ console.log(`Data written: ${data.toString()}`)
+ cb()
+ }
+})
+
+rs.pipe(ws)
diff --git a/Chapter04/creating-readable-writable-streams/from2-to2-streams/index.js b/Chapter04/creating-readable-writable-streams/from2-to2-streams/index.js
new file mode 100644
index 0000000..41c3085
--- /dev/null
+++ b/Chapter04/creating-readable-writable-streams/from2-to2-streams/index.js
@@ -0,0 +1,20 @@
+'use strict'
+
+const from = require('from2')
+const to = require('to2')
+
+const rs = from(() => {
+ rs.push(Buffer.from('Hello, World!'))
+ rs.push(null)
+})
+
+// rs.on('data', (data) => {
+// console.log(data.toString())
+// })
+
+const ws = to((data, enc, cb) => {
+ console.log(`Data written: ${data.toString()}`)
+ cb()
+})
+
+rs.pipe(ws)
diff --git a/Chapter04/creating-readable-writable-streams/from2-to2-streams/package.json b/Chapter04/creating-readable-writable-streams/from2-to2-streams/package.json
new file mode 100644
index 0000000..623ea3d
--- /dev/null
+++ b/Chapter04/creating-readable-writable-streams/from2-to2-streams/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "from2-to2-streams",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "from2": "^2.3.0",
+ "to2": "^1.0.0"
+ }
+}
diff --git a/Chapter04/creating-readable-writable-streams/readable-flow-control/expected-behavior.js b/Chapter04/creating-readable-writable-streams/readable-flow-control/expected-behavior.js
new file mode 100644
index 0000000..cb43560
--- /dev/null
+++ b/Chapter04/creating-readable-writable-streams/readable-flow-control/expected-behavior.js
@@ -0,0 +1,16 @@
+'use strict'
+
+const from = require('from2')
+const rs = from((size, cb) => {
+ setTimeout(() => {
+ rs.push('Data 0')
+ setTimeout(() => {
+ rs.push('Data 1')
+ cb()
+ }, 50)
+ }, 100)
+})
+
+rs.on('data', (data) => {
+ console.log(data.toString())
+})
\ No newline at end of file
diff --git a/Chapter04/creating-readable-writable-streams/readable-flow-control/package.json b/Chapter04/creating-readable-writable-streams/readable-flow-control/package.json
new file mode 100644
index 0000000..23c8fe2
--- /dev/null
+++ b/Chapter04/creating-readable-writable-streams/readable-flow-control/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "readable-flow-control",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "from2": "^2.3.0",
+ "readable-stream": "^2.2.6"
+ }
+}
diff --git a/Chapter04/creating-readable-writable-streams/readable-flow-control/undefined-behavior.js b/Chapter04/creating-readable-writable-streams/readable-flow-control/undefined-behavior.js
new file mode 100644
index 0000000..012f9a3
--- /dev/null
+++ b/Chapter04/creating-readable-writable-streams/readable-flow-control/undefined-behavior.js
@@ -0,0 +1,18 @@
+'use strict'
+
+// WARNING: DOES NOT WORK AS EXPECTED
+const { Readable } = require('readable-stream')
+const rs = Readable({
+ read: () => {
+ setTimeout(() => {
+ rs.push('Data 0')
+ setTimeout(() => {
+ rs.push('Data 1')
+ }, 50)
+ }, 100)
+ }
+})
+
+rs.on('data', (data) => {
+ console.log(data.toString())
+})
diff --git a/Chapter04/creating-transform-streams/core-transform-streams/classical.js b/Chapter04/creating-transform-streams/core-transform-streams/classical.js
new file mode 100644
index 0000000..388c97f
--- /dev/null
+++ b/Chapter04/creating-transform-streams/core-transform-streams/classical.js
@@ -0,0 +1,13 @@
+'use strict'
+
+const { Transform } = require('readable-stream')
+
+class MyTransform extends Transform {
+ _transform (chunk, enc, cb) {
+ cb(null, chunk.toString().toUpperCase())
+ }
+}
+
+const upper = new MyTransform()
+
+process.stdin.pipe(upper).pipe(process.stdout)
\ No newline at end of file
diff --git a/Chapter04/creating-transform-streams/core-transform-streams/modern.js b/Chapter04/creating-transform-streams/core-transform-streams/modern.js
new file mode 100644
index 0000000..6a1d7a8
--- /dev/null
+++ b/Chapter04/creating-transform-streams/core-transform-streams/modern.js
@@ -0,0 +1,11 @@
+'use strict'
+
+const { Transform } = require('readable-stream')
+
+const upper = Transform({
+ transform: (chunk, enc, cb) => {
+ cb(null, chunk.toString().toUpperCase())
+ }
+})
+
+process.stdin.pipe(upper).pipe(process.stdout)
\ No newline at end of file
diff --git a/Chapter04/creating-transform-streams/core-transform-streams/package.json b/Chapter04/creating-transform-streams/core-transform-streams/package.json
new file mode 100644
index 0000000..9be97b0
--- /dev/null
+++ b/Chapter04/creating-transform-streams/core-transform-streams/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "core-transform-streams",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC"
+}
diff --git a/Chapter04/creating-transform-streams/core-transform-streams/prototypal.js b/Chapter04/creating-transform-streams/core-transform-streams/prototypal.js
new file mode 100644
index 0000000..6ed8bd3
--- /dev/null
+++ b/Chapter04/creating-transform-streams/core-transform-streams/prototypal.js
@@ -0,0 +1,18 @@
+'use strict'
+
+const stream = require('readable-stream')
+const util = require('util')
+
+function MyTransform(opts) {
+ stream.Transform.call(this, opts)
+}
+
+util.inherits(MyTransform, stream.Transform)
+
+MyTransform.prototype._transform = function (chunk, enc, cb) {
+ cb(null, chunk.toString().toUpperCase())
+}
+
+const upper = new MyTransform()
+
+process.stdin.pipe(upper).pipe(process.stdout)
\ No newline at end of file
diff --git a/Chapter04/creating-transform-streams/object-streams/core.js b/Chapter04/creating-transform-streams/object-streams/core.js
new file mode 100644
index 0000000..4b72bea
--- /dev/null
+++ b/Chapter04/creating-transform-streams/object-streams/core.js
@@ -0,0 +1,15 @@
+'use strict'
+
+const { Transform } = require('readable-stream')
+const { serialize } = require('ndjson')
+
+const xyz = Transform({
+ objectMode: true,
+ transform: ({x, y}, enc, cb) => { cb(null, {z: x + y}) }
+})
+
+xyz.pipe(serialize()).pipe(process.stdout)
+
+xyz.write({x: 199, y: 3})
+
+xyz.write({x: 10, y: 12})
diff --git a/Chapter04/creating-transform-streams/object-streams/index.js b/Chapter04/creating-transform-streams/object-streams/index.js
new file mode 100644
index 0000000..2977a38
--- /dev/null
+++ b/Chapter04/creating-transform-streams/object-streams/index.js
@@ -0,0 +1,14 @@
+'use strict'
+
+const through = require('through2')
+const { serialize } = require('ndjson')
+
+const xyz = through.obj(({x, y}, enc, cb) => {
+ cb(null, {z: x + y})
+})
+
+xyz.pipe(serialize()).pipe(process.stdout)
+
+xyz.write({x: 199, y: 3})
+
+xyz.write({x: 10, y: 12})
diff --git a/Chapter04/creating-transform-streams/object-streams/package.json b/Chapter04/creating-transform-streams/object-streams/package.json
new file mode 100644
index 0000000..bf2082d
--- /dev/null
+++ b/Chapter04/creating-transform-streams/object-streams/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "object-streams",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "ndjson": "^1.5.0",
+ "readable-stream": "^2.2.6",
+ "through2": "^2.0.3"
+ }
+}
diff --git a/Chapter04/creating-transform-streams/through-streams/index.js b/Chapter04/creating-transform-streams/through-streams/index.js
new file mode 100644
index 0000000..988ac66
--- /dev/null
+++ b/Chapter04/creating-transform-streams/through-streams/index.js
@@ -0,0 +1,9 @@
+'use strict'
+
+const through = require('through2')
+
+const upper = through((chunk, enc, cb) => {
+ cb(null, chunk.toString().toUpperCase())
+})
+
+process.stdin.pipe(upper).pipe(process.stdout)
\ No newline at end of file
diff --git a/Chapter04/creating-transform-streams/through-streams/package.json b/Chapter04/creating-transform-streams/through-streams/package.json
new file mode 100644
index 0000000..bddf549
--- /dev/null
+++ b/Chapter04/creating-transform-streams/through-streams/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "through-streams",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "through2": "^2.0.3"
+ }
+}
diff --git a/Chapter04/decoupling-io/backpressure/backpressure-with-pipe.js b/Chapter04/decoupling-io/backpressure/backpressure-with-pipe.js
new file mode 100644
index 0000000..e4a876b
--- /dev/null
+++ b/Chapter04/decoupling-io/backpressure/backpressure-with-pipe.js
@@ -0,0 +1,22 @@
+'use strict'
+
+const { Readable, Writable } = require('readable-stream')
+
+var i = 20
+
+const rs = Readable({
+ read: (size) => {
+ setImmediate(function () {
+ rs.push(i-- ? Buffer.alloc(size) : null)
+ })
+ }
+})
+
+const ws = Writable({
+ write: (chunk, enc, cb) => {
+ console.log(ws._writableState.length)
+ setTimeout(cb, 1)
+ }
+})
+
+rs.pipe(ws)
\ No newline at end of file
diff --git a/Chapter04/decoupling-io/backpressure/direct-write-no-backpressure.js b/Chapter04/decoupling-io/backpressure/direct-write-no-backpressure.js
new file mode 100644
index 0000000..66e7276
--- /dev/null
+++ b/Chapter04/decoupling-io/backpressure/direct-write-no-backpressure.js
@@ -0,0 +1,22 @@
+'use strict'
+
+const { Readable, Writable } = require('readable-stream')
+
+var i = 20
+
+const rs = Readable({
+ read: (size) => {
+ setImmediate(function () {
+ rs.push(i-- ? Buffer.alloc(size) : null)
+ })
+ }
+})
+
+const ws = Writable({
+ write: (chunk, enc, cb) => {
+ console.log(ws._writableState.length)
+ setTimeout(cb, 1)
+ }
+})
+
+rs.on('data', (chunk) => ws.write(chunk))
\ No newline at end of file
diff --git a/Chapter04/decoupling-io/backpressure/direct-write-with-backpressure-pull.js b/Chapter04/decoupling-io/backpressure/direct-write-with-backpressure-pull.js
new file mode 100644
index 0000000..906b885
--- /dev/null
+++ b/Chapter04/decoupling-io/backpressure/direct-write-with-backpressure-pull.js
@@ -0,0 +1,40 @@
+'use strict'
+
+const { Readable, Writable } = require('readable-stream')
+
+var i = 20
+
+const rs = Readable({
+ read: (size, cb) => {
+ setImmediate(function () {
+ rs.push(i-- ? Buffer.alloc(size) : null)
+ })
+ }
+})
+
+const ws = Writable({
+ write: (chunk, enc, cb) => {
+ console.log(ws._writableState.length)
+ setTimeout(cb, 1)
+ }
+})
+
+function write (chunk, cb) {
+ const writable = ws.write(chunk)
+ if (writable === false) {
+ ws.once('drain', cb)
+ return
+ }
+ process.nextTick(cb)
+}
+
+function read () {
+ const chunk = rs.read()
+ if (chunk === null) {
+ rs.once('readable', read)
+ return
+ }
+ write(chunk, read)
+}
+
+rs.once('readable', read)
\ No newline at end of file
diff --git a/Chapter04/decoupling-io/backpressure/direct-write-with-backpressure.js b/Chapter04/decoupling-io/backpressure/direct-write-with-backpressure.js
new file mode 100644
index 0000000..412544e
--- /dev/null
+++ b/Chapter04/decoupling-io/backpressure/direct-write-with-backpressure.js
@@ -0,0 +1,28 @@
+'use strict'
+
+const { Readable, Writable } = require('readable-stream')
+
+var i = 20
+
+const rs = Readable({
+ read: (size) => {
+ setImmediate(function () {
+ rs.push(i-- ? Buffer.alloc(size) : null)
+ })
+ }
+})
+
+const ws = Writable({
+ write: (chunk, enc, cb) => {
+ console.log(ws._writableState.length)
+ setTimeout(cb, 1)
+ }
+})
+
+rs.on('data', (chunk) => {
+ const writable = ws.write(chunk)
+ if (writable === false) {
+ rs.pause()
+ ws.once('drain', () => rs.resume())
+ }
+})
\ No newline at end of file
diff --git a/Chapter04/decoupling-io/ping-protocol-stream/index.js b/Chapter04/decoupling-io/ping-protocol-stream/index.js
new file mode 100644
index 0000000..ac74021
--- /dev/null
+++ b/Chapter04/decoupling-io/ping-protocol-stream/index.js
@@ -0,0 +1,22 @@
+'use strict'
+
+const through = require('through2')
+const split = require('split2')
+const pumpify = require('pumpify')
+
+function pingProtocol() {
+ const ping = /Ping:\s+(.*)/
+ const protocol = through(each)
+
+ function each (line, enc, cb) {
+ if (ping.test(line)) {
+ cb(null, `Pong: ${line.toString().match(ping)[1]}\n`)
+ return
+ }
+ cb(null, 'Not Implemented\n')
+ }
+
+ return pumpify(split(), protocol)
+}
+
+module.exports = pingProtocol
diff --git a/Chapter04/decoupling-io/ping-protocol-stream/package.json b/Chapter04/decoupling-io/ping-protocol-stream/package.json
new file mode 100644
index 0000000..1326d68
--- /dev/null
+++ b/Chapter04/decoupling-io/ping-protocol-stream/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "foo-protocol-stream",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "pumpify": "^1.3.5",
+ "split2": "^2.1.1",
+ "through2": "^2.0.3"
+ }
+}
diff --git a/Chapter04/decoupling-io/stream-destruction/index.js b/Chapter04/decoupling-io/stream-destruction/index.js
new file mode 100644
index 0000000..52e9807
--- /dev/null
+++ b/Chapter04/decoupling-io/stream-destruction/index.js
@@ -0,0 +1,25 @@
+'use strict'
+
+var from = require('from2')
+
+function createInfiniteTickStream () {
+ var tick = 0
+ return from.obj((size, cb) => {
+ setImmediate(() => cb(null, {tick: tick++}))
+ })
+}
+
+var stream = createInfiniteTickStream()
+
+stream.on('data', (data) => {
+ console.log(data)
+})
+
+stream.on('close', () => {
+ console.log('(stream destroyed)')
+})
+
+setTimeout(() => {
+ stream.destroy()
+}, 1000)
+
diff --git a/Chapter04/decoupling-io/stream-destruction/package.json b/Chapter04/decoupling-io/stream-destruction/package.json
new file mode 100644
index 0000000..7c53e2c
--- /dev/null
+++ b/Chapter04/decoupling-io/stream-destruction/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "stream-destruction",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "from2": "^2.3.0"
+ }
+}
diff --git a/Chapter04/decoupling-io/tcp-server/index.js b/Chapter04/decoupling-io/tcp-server/index.js
new file mode 100644
index 0000000..0448480
--- /dev/null
+++ b/Chapter04/decoupling-io/tcp-server/index.js
@@ -0,0 +1,17 @@
+'use strict'
+
+const net = require('net')
+const pump = require('pump')
+const ping = require('../ping-protocol-stream')
+
+const server = net.createServer((socket) => {
+ const protocol = ping()
+ pump(socket, protocol, socket, closed)
+})
+
+function closed (err) {
+ if (err) console.error('connection closed with error', err)
+ else console.log('connection closed')
+}
+
+server.listen(3000)
diff --git a/Chapter04/decoupling-io/tcp-server/package.json b/Chapter04/decoupling-io/tcp-server/package.json
new file mode 100644
index 0000000..94f473b
--- /dev/null
+++ b/Chapter04/decoupling-io/tcp-server/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "tcp-server",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "pump": "^1.0.2"
+ }
+}
diff --git a/Chapter04/piping-streams-in-production/big-file-server/index.js b/Chapter04/piping-streams-in-production/big-file-server/index.js
new file mode 100644
index 0000000..e7d287c
--- /dev/null
+++ b/Chapter04/piping-streams-in-production/big-file-server/index.js
@@ -0,0 +1,19 @@
+'use strict'
+
+const fs = require('fs')
+const http = require('http')
+const pump = require('pump')
+
+const server = http.createServer((req, res) => {
+ const stream = fs.createReadStream('big.file')
+ pump(stream, res, done)
+})
+
+function done (err) {
+ if (err) {
+ return console.error('File was not fully streamed to the user', err)
+ }
+ console.log('File was fully streamed to the user')
+}
+
+server.listen(3000)
\ No newline at end of file
diff --git a/Chapter04/piping-streams-in-production/big-file-server/package.json b/Chapter04/piping-streams-in-production/big-file-server/package.json
new file mode 100644
index 0000000..7f8b3de
--- /dev/null
+++ b/Chapter04/piping-streams-in-production/big-file-server/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "big-file-server",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "pump": "^1.0.2"
+ }
+}
diff --git a/Chapter04/piping-streams-in-production/pumpified-pipeline/big.file b/Chapter04/piping-streams-in-production/pumpified-pipeline/big.file
new file mode 100644
index 0000000..e69de29
diff --git a/Chapter04/piping-streams-in-production/pumpified-pipeline/index.js b/Chapter04/piping-streams-in-production/pumpified-pipeline/index.js
new file mode 100644
index 0000000..2351650
--- /dev/null
+++ b/Chapter04/piping-streams-in-production/pumpified-pipeline/index.js
@@ -0,0 +1,25 @@
+'use strict'
+
+const { createGzip } = require('zlib')
+const { createCipher } = require('crypto')
+const pumpify = require('pumpify')
+const base64 = require('base64-encode-stream')
+
+function pipeline () {
+ const stream1 = createGzip()
+ const stream2 = createCipher('aes192', 'secretz')
+ const stream3 = base64()
+ return pumpify(stream1, stream2, stream3)
+}
+
+const pipe = pipeline()
+
+pipe.end('written to stream1')
+
+pipe.on('data', (data) => {
+ console.log('stream3 says: ', data.toString())
+})
+
+pipe.on('finish', () => {
+ console.log('all data was successfully flushed to stream3')
+})
\ No newline at end of file
diff --git a/Chapter04/piping-streams-in-production/pumpified-pipeline/package.json b/Chapter04/piping-streams-in-production/pumpified-pipeline/package.json
new file mode 100644
index 0000000..e702c0e
--- /dev/null
+++ b/Chapter04/piping-streams-in-production/pumpified-pipeline/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "pumpified-pipeline",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "base64-encode-stream": "^1.0.0",
+ "pumpify": "^1.3.5"
+ }
+}
diff --git a/Chapter04/processing-big-data/infinite-read/index.js b/Chapter04/processing-big-data/infinite-read/index.js
new file mode 100644
index 0000000..9c46ee9
--- /dev/null
+++ b/Chapter04/processing-big-data/infinite-read/index.js
@@ -0,0 +1,9 @@
+'use strict'
+
+const rs = fs.createReadStream('/dev/urandom')
+var size = 0
+
+rs.on('data', (data) => {
+ size += data.length
+ console.log('File size:', size)
+})
\ No newline at end of file
diff --git a/Chapter04/processing-big-data/self-read-pull/index.js b/Chapter04/processing-big-data/self-read-pull/index.js
new file mode 100644
index 0000000..b521fec
--- /dev/null
+++ b/Chapter04/processing-big-data/self-read-pull/index.js
@@ -0,0 +1,17 @@
+'use strict'
+
+const fs = require('fs')
+const rs = fs.createReadStream(__filename)
+
+rs.on('readable', () => {
+ var data = rs.read()
+ while (data !== null) {
+ console.log('Read chunk:', data)
+ data = rs.read()
+ }
+})
+
+rs.on('end', () => {
+ console.log('No more data')
+})
+
diff --git a/Chapter04/processing-big-data/self-read/index.js b/Chapter04/processing-big-data/self-read/index.js
new file mode 100644
index 0000000..bafb97d
--- /dev/null
+++ b/Chapter04/processing-big-data/self-read/index.js
@@ -0,0 +1,12 @@
+'use strict'
+const fs = require('fs')
+const rs = fs.createReadStream(__filename)
+
+rs.on('data', (data) => {
+ console.log('Read chunk:', data)
+})
+
+rs.on('end', () => {
+ console.log('No more data')
+})
+
diff --git a/Chapter04/using-the-pipe-method/pipe-without-end/broken.js b/Chapter04/using-the-pipe-method/pipe-without-end/broken.js
new file mode 100644
index 0000000..080ce16
--- /dev/null
+++ b/Chapter04/using-the-pipe-method/pipe-without-end/broken.js
@@ -0,0 +1,10 @@
+const net = require('net')
+const fs = require('fs')
+
+net.createServer((socket) => {
+ const content = fs.createReadStream(__filename)
+ content.pipe(socket)
+ content.on('end', () => {
+ socket.end('\n======= Footer =======\n')
+ })
+}).listen(3000)
\ No newline at end of file
diff --git a/Chapter04/using-the-pipe-method/pipe-without-end/index.js b/Chapter04/using-the-pipe-method/pipe-without-end/index.js
new file mode 100644
index 0000000..c9f3de0
--- /dev/null
+++ b/Chapter04/using-the-pipe-method/pipe-without-end/index.js
@@ -0,0 +1,10 @@
+const net = require('net')
+const fs = require('fs')
+
+net.createServer((socket) => {
+ const content = fs.createReadStream(__filename)
+ content.pipe(socket, {end: false})
+ content.on('end', () => {
+ socket.end('\n======= Footer =======\n')
+ })
+}).listen(3000)
\ No newline at end of file
diff --git a/Chapter04/using-the-pipe-method/piper/index.js b/Chapter04/using-the-pipe-method/piper/index.js
new file mode 100644
index 0000000..1902aaf
--- /dev/null
+++ b/Chapter04/using-the-pipe-method/piper/index.js
@@ -0,0 +1,20 @@
+'use strict'
+
+const zlib = require('zlib')
+const map = require('tar-map-stream')
+const decompress = zlib.createGunzip()
+const whoami = process.env.USER || process.env.USERNAME
+const convert = map((header) => {
+ header.uname = whoami
+ header.mtime = new Date()
+ header.name = header.name.replace('node-v0.1.100', 'edon-v0.0.0')
+ return header
+})
+const compress = zlib.createGzip()
+
+process.stdin
+ .pipe(decompress)
+ .pipe(convert)
+ .pipe(compress)
+ .pipe(process.stdout)
+
diff --git a/Chapter04/using-the-pipe-method/piper/package.json b/Chapter04/using-the-pipe-method/piper/package.json
new file mode 100644
index 0000000..0f92f54
--- /dev/null
+++ b/Chapter04/using-the-pipe-method/piper/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "piper",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "tar-map-stream": "^1.0.0"
+ }
+}
diff --git a/Chapter05/communicating-with-websockets/websocket-app/package.json b/Chapter05/communicating-with-websockets/websocket-app/package.json
new file mode 100644
index 0000000..51478aa
--- /dev/null
+++ b/Chapter05/communicating-with-websockets/websocket-app/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "websocket-app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "server.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1",
+ "start": "node server.js"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "ws": "^1.1.1"
+ }
+}
diff --git a/Chapter05/communicating-with-websockets/websocket-app/public/index.html b/Chapter05/communicating-with-websockets/websocket-app/public/index.html
new file mode 100644
index 0000000..b1e79dc
--- /dev/null
+++ b/Chapter05/communicating-with-websockets/websocket-app/public/index.html
@@ -0,0 +1,31 @@
+Send
+
+
\ No newline at end of file
diff --git a/Chapter05/communicating-with-websockets/websocket-app/server.js b/Chapter05/communicating-with-websockets/websocket-app/server.js
new file mode 100644
index 0000000..5b0cd4e
--- /dev/null
+++ b/Chapter05/communicating-with-websockets/websocket-app/server.js
@@ -0,0 +1,22 @@
+'use strict'
+
+const http = require('http')
+const fs = require('fs')
+const ws = require('ws')
+
+const app = fs.readFileSync('public/index.html')
+const server = http.createServer((req, res) => {
+ res.setHeader('Content-Type', 'text/html')
+ res.end(app)
+})
+const wss = new ws.Server({server})
+
+wss.on('connection', (socket) => {
+ socket.on('message', (msg) => {
+ console.log(`Received: ${msg}`)
+ console.log(`From IP: ${socket.upgradeReq.connection.remoteAddress}`)
+ if (msg === 'Hello') socket.send('Websockets!')
+ })
+})
+
+server.listen(8080)
\ No newline at end of file
diff --git a/Chapter05/communicating-with-websockets/websocket-client/index.js b/Chapter05/communicating-with-websockets/websocket-client/index.js
new file mode 100644
index 0000000..975d1af
--- /dev/null
+++ b/Chapter05/communicating-with-websockets/websocket-client/index.js
@@ -0,0 +1,39 @@
+'use strict'
+
+const WebSocket = require('ws')
+const readline = require('readline')
+const ws = new WebSocket(process.argv[2] || 'ws://localhost:8080')
+const rl = readline.createInterface({
+ input: process.stdin,
+ output: process.stdout,
+ prompt: '-> '
+})
+const gray = '\u001b[90m'
+const red = '\u001b[31m'
+const reset = '\u001b[39m'
+ws.on('open', () => {
+ rl.output.write(`${gray}-- Connected --${reset}\n\n`)
+ rl.prompt()
+})
+rl.on('line', (msg) => {
+ ws.send(msg, () => {
+ rl.output.write(`${gray}<= ${msg}${reset}\n\n`)
+ rl.prompt()
+ })
+})
+ws.on('message', function (msg) {
+ readline.clearLine(rl.output)
+ readline.moveCursor(rl.output, -3 - rl.line.length, -1)
+ rl.output.write(`${gray}=> ${msg}${reset}\n\n`)
+ rl.prompt(true)
+})
+ws.on('close', () => {
+ readline.cursorTo(rl.output, 0)
+ rl.output.write(`${gray}-- Disconnected --${reset}\n\n`)
+ process.exit()
+})
+ws.on('error', (err) => {
+ readline.cursorTo(rl.output, 0)
+ rl.output.write(`${red}-- Error --${reset}\n`)
+ rl.output.write(`${red}${err.stack}${reset}\n`)
+})
\ No newline at end of file
diff --git a/Chapter05/communicating-with-websockets/websocket-client/package.json b/Chapter05/communicating-with-websockets/websocket-client/package.json
new file mode 100644
index 0000000..b0bd1dd
--- /dev/null
+++ b/Chapter05/communicating-with-websockets/websocket-client/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "websocket-client",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "ws": "^1.1.1"
+ }
+}
diff --git a/Chapter05/creating-an-http-server/rest-server-dynamic-content/index.js b/Chapter05/creating-an-http-server/rest-server-dynamic-content/index.js
new file mode 100644
index 0000000..42effc9
--- /dev/null
+++ b/Chapter05/creating-an-http-server/rest-server-dynamic-content/index.js
@@ -0,0 +1,39 @@
+'use strict'
+
+const http = require('http')
+const qs = require('querystring')
+const url = require('url')
+
+const host = process.env.HOST || '0.0.0.0'
+const port = process.env.PORT || 8080
+
+const userList = [
+ {'id': 1, 'first_name': 'Bob', 'second_name': 'Smith', type: 'red'},
+ {'id': 2, 'first_name': 'David', 'second_name': 'Clements', type: 'blue'}
+]
+
+const server = http.createServer((req, res) => {
+ if (req.method !== 'GET') return error(res, 405)
+ if (req.url === '/') return index(res)
+ const {pathname, query} = url.parse(req.url)
+ if (pathname === '/users') return users(query, res)
+
+ error(res, 404)
+})
+
+function error (res, code) {
+ res.statusCode = code
+ res.end(`{"error": "${http.STATUS_CODES[code]}"}`)
+}
+
+function users (query, res) {
+ const {type} = qs.parse(query)
+ const list = !type ? userList : userList.filter((user) => user.type === type)
+ res.end(`{"data": ${JSON.stringify(list)}}`)
+}
+
+function index (res) {
+ res.end('{"name": "my-rest-server", "version": 0}')
+}
+
+server.listen(port, host)
\ No newline at end of file
diff --git a/Chapter05/creating-an-http-server/rest-server-dynamic-content/terser-index.js b/Chapter05/creating-an-http-server/rest-server-dynamic-content/terser-index.js
new file mode 100644
index 0000000..3257808
--- /dev/null
+++ b/Chapter05/creating-an-http-server/rest-server-dynamic-content/terser-index.js
@@ -0,0 +1,37 @@
+'use strict'
+
+const http = require('http')
+const url = require('url')
+
+const host = process.env.HOST || '0.0.0.0'
+const port = process.env.PORT || 8080
+
+const userList = [
+ {'id': 1, 'first_name': 'Bob', 'second_name': 'Smith', type: 'red'},
+ {'id': 2, 'first_name': 'David', 'second_name': 'Clements', type: 'blue'}
+]
+
+const server = http.createServer((req, res) => {
+ if (req.method !== 'GET') return error(res, 405)
+ if (req.url === '/') return index(res)
+ const {pathname, query} = url.parse(req.url, true)
+ if (pathname === '/users') return users(query, res)
+
+ error(res, 404)
+})
+
+function error (res, code) {
+ res.statusCode = code
+ res.end(`{"error": "${http.STATUS_CODES[code]}"}`)
+}
+
+function users ({type}, res) {
+ const list = !type ? userList : userList.filter((user) => user.type === type)
+ res.end(`{"data": ${JSON.stringify(list)}}`)
+}
+
+function index (res) {
+ res.end('{"name": "my-rest-server", "version": 0}')
+}
+
+server.listen(port, host)
\ No newline at end of file
diff --git a/Chapter05/creating-an-http-server/rest-server-random-port/index.js b/Chapter05/creating-an-http-server/rest-server-random-port/index.js
new file mode 100644
index 0000000..f7ccb11
--- /dev/null
+++ b/Chapter05/creating-an-http-server/rest-server-random-port/index.js
@@ -0,0 +1,29 @@
+'use strict'
+
+const http = require('http')
+
+const host = process.env.HOST || '0.0.0.0'
+const port = process.env.PORT || 0
+
+const server = http.createServer((req, res) => {
+ if (req.method !== 'GET') return error(res, 405)
+ if (req.url === '/users') return users(res)
+ if (req.url === '/') return index(res)
+ error(res, 404)
+})
+
+function error (res, code) {
+ res.statusCode = code
+ res.end(`{"error": "${http.STATUS_CODES[code]}"}`)
+}
+
+function users (res) {
+ res.end('{"data": [{"id": 1, "first_name": "Bob", "second_name": "Smith"}]}')
+}
+
+function index (res) {
+ res.end('{"name": "my-rest-server", "version": 0}')
+}
+
+server.listen(port, host, () => console.log(JSON.stringify(server.address())))
+
diff --git a/Chapter05/creating-an-http-server/rest-server/index.js b/Chapter05/creating-an-http-server/rest-server/index.js
new file mode 100644
index 0000000..d93377d
--- /dev/null
+++ b/Chapter05/creating-an-http-server/rest-server/index.js
@@ -0,0 +1,28 @@
+'use strict'
+
+const http = require('http')
+
+const host = process.env.HOST || '0.0.0.0'
+const port = process.env.PORT || 8080
+
+const server = http.createServer((req, res) => {
+ if (req.method !== 'GET') return error(res, 405)
+ if (req.url === '/users') return users(res)
+ if (req.url === '/') return index(res)
+ error(res, 404)
+})
+
+function error (res, code) {
+ res.statusCode = code
+ res.end(`{"error": "${http.STATUS_CODES[code]}"}`)
+}
+
+function users (res) {
+ res.end('{"data": [{"id": 1, "first_name": "Bob", "second_name": "Smith"}]}')
+}
+
+function index (res) {
+ res.end('{"name": "my-rest-server", "version": 0}')
+}
+
+server.listen(port, host)
\ No newline at end of file
diff --git a/Chapter05/creating-an-smtp-server/smtp-client/index.js b/Chapter05/creating-an-smtp-server/smtp-client/index.js
new file mode 100644
index 0000000..3ed1c56
--- /dev/null
+++ b/Chapter05/creating-an-smtp-server/smtp-client/index.js
@@ -0,0 +1,61 @@
+'use strict'
+
+const os = require('os')
+const readline = require('readline')
+const smtp = require('smtp-protocol')
+
+const rl = readline.createInterface({
+ input: process.stdin,
+ output: process.stdout,
+ prompt: ''
+})
+
+const cfg = {
+ host: 'localhost',
+ port: 2525,
+ email: 'me@me.com',
+ hostname: os.hostname()
+}
+
+rl.on('SIGINT', () => {
+ console.log('... cancelled ...')
+ process.exit()
+})
+
+smtp.connect(cfg.host, cfg.port, (mail) => {
+ mail.helo(cfg.hostname)
+ mail.from(cfg.email)
+ rl.question('To: ', (to) => {
+ to.split(/;|,/gm).forEach((rcpt) => {
+ rcpt = rcpt.trim()
+ mail.to(rcpt, (err, code, lines) => {
+ exitOnFail(err, code, lines, {rcpt: rcpt})
+ })
+ })
+ rl.write('===== Message (^D to send) =====\n')
+ mail.data(exitOnFail)
+ const body = []
+ rl.on('line', (line) => body.push(`${line}\r\n`))
+ rl.on('close', () => send(mail, body))
+ })
+})
+
+function send (mail, body) {
+ console.log('... sending ...')
+ const message = mail.message()
+ body.forEach(message.write, message)
+ message.end()
+ mail.quit()
+}
+
+function exitOnFail (err, code, lines, info) {
+ if (code === 550) {
+ err = Error(`No Mailbox for Recipient "${info.rcpt}"`)
+ }
+ if (!err && code !== 354 && code !== 250 && code !== 220 && code !== 200) {
+ err = Error(`Protocol Error: ${code} ${lines.join('')}`)
+ }
+ if (!err) return
+ console.error(err.message)
+ process.exit(1)
+}
diff --git a/Chapter05/creating-an-smtp-server/smtp-client/package.json b/Chapter05/creating-an-smtp-server/smtp-client/package.json
new file mode 100644
index 0000000..a29f3d5
--- /dev/null
+++ b/Chapter05/creating-an-smtp-server/smtp-client/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "smtp-client",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "smtp-protocol": "^2.4.7"
+ }
+}
diff --git a/Chapter05/creating-an-smtp-server/smtp-server/index.js b/Chapter05/creating-an-smtp-server/smtp-server/index.js
new file mode 100644
index 0000000..30e1d89
--- /dev/null
+++ b/Chapter05/creating-an-smtp-server/smtp-server/index.js
@@ -0,0 +1,47 @@
+'use strict'
+
+const fs = require('fs')
+const path = require('path')
+const smtp = require('smtp-protocol')
+const hosts = new Set(['localhost', 'example.com'])
+const users = new Set(['you', 'another'])
+const mailDir = path.join(__dirname, 'mail')
+
+function ensureDir (dir, cb) {
+ try { fs.mkdirSync(dir) } catch (e) {
+ if (e.code !== 'EEXIST') throw e
+ }
+}
+
+ensureDir(mailDir)
+for (let user of users) ensureDir(path.join(mailDir, user))
+
+const server = smtp.createServer((req) => {
+ req.on('to', filter)
+ req.on('message', (stream, ack) => save(req, stream, ack))
+ req.on('error', (err) => console.error(err))
+})
+
+server.listen(2525)
+
+function filter (to, {accept, reject}) {
+ const [user, host] = to.split('@')
+ if (hosts.has(host) && users.has(user)) {
+ accept()
+ return
+ }
+ reject(550, 'mailbox not available')
+}
+
+function save (req, stream, {accept}) {
+ const {from, to} = req
+ accept()
+ to.forEach((rcpt) => {
+ const [user] = rcpt.split('@')
+ const dest = path.join(mailDir, user, `${from}-${Date.now()}`)
+ const mail = fs.createWriteStream(dest)
+ mail.write(`From: ${from} \n`)
+ mail.write(`To: ${rcpt} \n\n`)
+ stream.pipe(mail, {end: false})
+ })
+}
\ No newline at end of file
diff --git a/Chapter05/creating-an-smtp-server/smtp-server/package.json b/Chapter05/creating-an-smtp-server/smtp-server/package.json
new file mode 100644
index 0000000..84d2bf8
--- /dev/null
+++ b/Chapter05/creating-an-smtp-server/smtp-server/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "smtp",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "David Mark Clements",
+ "license": "MIT",
+ "dependencies": {
+ "smtp-protocol": "^2.4.7"
+ }
+}
diff --git a/Chapter05/handling-file-uploads/processing-all-types/package.json b/Chapter05/handling-file-uploads/processing-all-types/package.json
new file mode 100644
index 0000000..f995ab2
--- /dev/null
+++ b/Chapter05/handling-file-uploads/processing-all-types/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "uploading-a-file",
+ "version": "1.0.0",
+ "description": "",
+ "main": "server.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1",
+ "start": "node server.js"
+ },
+ "keywords": [],
+ "author": "David Mark Clements",
+ "license": "MIT",
+ "dependencies": {
+ "multipart-read-stream": "^1.0.1",
+ "pump": "^1.0.1"
+ }
+}
diff --git a/Chapter05/handling-file-uploads/processing-all-types/public/form.html b/Chapter05/handling-file-uploads/processing-all-types/public/form.html
new file mode 100644
index 0000000..d61957b
--- /dev/null
+++ b/Chapter05/handling-file-uploads/processing-all-types/public/form.html
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/Chapter05/handling-file-uploads/processing-all-types/server.js b/Chapter05/handling-file-uploads/processing-all-types/server.js
new file mode 100644
index 0000000..0de798a
--- /dev/null
+++ b/Chapter05/handling-file-uploads/processing-all-types/server.js
@@ -0,0 +1,65 @@
+'use strict'
+
+const http = require('http')
+const fs = require('fs')
+const path = require('path')
+const mrs = require('multipart-read-stream')
+const pump = require('pump')
+const form = fs.readFileSync(path.join(__dirname, 'public', 'form.html'))
+
+http.createServer((req, res) => {
+ if (req.method === 'GET') {
+ get(res)
+ return
+ }
+ if (req.method === 'POST') {
+ post(req, res)
+ return
+ }
+ reject(405, 'Method Not Allowed', res)
+}).listen(8080)
+
+function get (res) {
+ res.writeHead(200, {'Content-Type': 'text/html'})
+ res.end(form)
+}
+
+function reject (code, msg, res) {
+ res.statusCode = code
+ res.end(msg)
+}
+
+function post (req, res) {
+ if (!/multipart\/form-data/.test(req.headers['content-type'])) {
+ reject(415, 'Unsupported Media Type', res)
+ return
+ }
+ console.log('parsing multipart data')
+ const parser = mrs(req, res, part, () => {
+ console.log('finished parsing')
+ })
+ parser.on('field', (field, value) => {
+ console.log(`${field}: ${value}`)
+ res.write(`processed "${field}" input.\n`)
+ })
+ var total = 0
+ pump(req, parser)
+
+ function part (field, file, name) {
+ if (!name) {
+ file.resume()
+ return
+ }
+ total += 1
+ const filename = `${field}-${Date.now()}-${name}`
+ const dest = fs.createWriteStream(path.join(__dirname, 'uploads', filename))
+ pump(file, dest, (err) => {
+ total -= 1
+ res.write(err
+ ? `Error saving ${name}!\n`
+ : `${name} successfully saved!\n`
+ )
+ if (total === 0) res.end('All files processed!')
+ })
+ }
+}
diff --git a/Chapter05/handling-file-uploads/processing-all-types/uploads/userfile1-1482182870159-fail b/Chapter05/handling-file-uploads/processing-all-types/uploads/userfile1-1482182870159-fail
new file mode 100644
index 0000000..9b26b27
Binary files /dev/null and b/Chapter05/handling-file-uploads/processing-all-types/uploads/userfile1-1482182870159-fail differ
diff --git a/Chapter05/handling-file-uploads/processing-all-types/uploads/userfile2-1482182881308-form.html b/Chapter05/handling-file-uploads/processing-all-types/uploads/userfile2-1482182881308-form.html
new file mode 100644
index 0000000..3781ba4
--- /dev/null
+++ b/Chapter05/handling-file-uploads/processing-all-types/uploads/userfile2-1482182881308-form.html
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/Chapter05/handling-file-uploads/processing-all-types/uploads/userfile2-1482182899079-form.html b/Chapter05/handling-file-uploads/processing-all-types/uploads/userfile2-1482182899079-form.html
new file mode 100644
index 0000000..3781ba4
--- /dev/null
+++ b/Chapter05/handling-file-uploads/processing-all-types/uploads/userfile2-1482182899079-form.html
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/package.json b/Chapter05/handling-file-uploads/uploading-a-file-with-put/package.json
new file mode 100644
index 0000000..97477e7
--- /dev/null
+++ b/Chapter05/handling-file-uploads/uploading-a-file-with-put/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "uploading-a-file-with-put",
+ "version": "1.0.0",
+ "description": "",
+ "main": "server.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1",
+ "start": "node server.js"
+ },
+ "keywords": [],
+ "author": "David Mark Clements",
+ "license": "MIT",
+ "dependencies": {
+ "pump": "^1.0.1",
+ "through2": "^2.0.3"
+ }
+}
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/public/form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/public/form.html
new file mode 100644
index 0000000..547d463
--- /dev/null
+++ b/Chapter05/handling-file-uploads/uploading-a-file-with-put/public/form.html
@@ -0,0 +1,35 @@
+
+
+
\ No newline at end of file
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/server.js b/Chapter05/handling-file-uploads/uploading-a-file-with-put/server.js
new file mode 100644
index 0000000..e8649be
--- /dev/null
+++ b/Chapter05/handling-file-uploads/uploading-a-file-with-put/server.js
@@ -0,0 +1,64 @@
+'use strict'
+
+const http = require('http')
+const fs = require('fs')
+const path = require('path')
+const pump = require('pump')
+const through = require('through2')
+const form = fs.readFileSync('public/form.html')
+const maxFileSize = 51200
+
+http.createServer((req, res) => {
+ if (req.method === 'GET') {
+ get(res)
+ return
+ }
+ if (req.method === 'PUT') {
+ put(req, res)
+ return
+ }
+ reject(405, 'Method Not Allowed', res)
+}).listen(8080)
+
+function get (res) {
+ res.writeHead(200, {'Content-Type': 'text/html'})
+ res.end(form)
+}
+
+function reject (code, msg, res) {
+ res.statusCode = code
+ res.end(msg)
+}
+
+function put (req, res) {
+ const size = parseInt(req.headers['content-length'], 10)
+ if (isNaN(size)) {
+ reject(400, 'Bad Request', res)
+ return
+ }
+ if (size > maxFileSize) {
+ reject(413, 'Too Large', res)
+ return
+ }
+
+ const name = req.headers['x-filename']
+ const field = req.headers['x-field']
+ const filename = `${field}-${Date.now()}-${name}`
+ const dest = fs.createWriteStream(path.join(__dirname, 'uploads', filename))
+ const counter = through(function (chunk, enc, cb) {
+ this.bytes += chunk.length
+ if (this.bytes > maxFileSize) {
+ cb(Error('size'))
+ return
+ }
+ cb(null, chunk)
+ })
+ counter.bytes = 0
+ counter.on('error', (err) => {
+ if (err.message === 'size') reject(413, 'Too Large', res)
+ })
+ pump(req, counter, dest, (err) => {
+ if (err) return reject(500, `Error saving ${name}!\n`, res)
+ res.end(`${name} successfully saved!\n`)
+ })
+}
\ No newline at end of file
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482186468980-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482186468980-form.html
new file mode 100644
index 0000000..3781ba4
--- /dev/null
+++ b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482186468980-form.html
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187169479-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187169479-form.html
new file mode 100644
index 0000000..3781ba4
--- /dev/null
+++ b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187169479-form.html
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187236095-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187236095-form.html
new file mode 100644
index 0000000..3781ba4
--- /dev/null
+++ b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187236095-form.html
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187293258-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187293258-form.html
new file mode 100644
index 0000000..3781ba4
--- /dev/null
+++ b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187293258-form.html
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187315712-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187315712-form.html
new file mode 100644
index 0000000..3781ba4
--- /dev/null
+++ b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187315712-form.html
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187329845-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187329845-form.html
new file mode 100644
index 0000000..3781ba4
--- /dev/null
+++ b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187329845-form.html
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187475067-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187475067-form.html
new file mode 100644
index 0000000..e69de29
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187475087-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187475087-form.html
new file mode 100644
index 0000000..e69de29
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187494081-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187494081-form.html
new file mode 100644
index 0000000..e69de29
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187494102-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187494102-form.html
new file mode 100644
index 0000000..e69de29
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187512136-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187512136-form.html
new file mode 100644
index 0000000..e69de29
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187512159-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187512159-form.html
new file mode 100644
index 0000000..e69de29
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187536131-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187536131-form.html
new file mode 100644
index 0000000..e69de29
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187536150-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187536150-form.html
new file mode 100644
index 0000000..e69de29
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187574174-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187574174-form.html
new file mode 100644
index 0000000..e69de29
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187574193-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187574193-form.html
new file mode 100644
index 0000000..e69de29
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187595672-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187595672-form.html
new file mode 100644
index 0000000..e69de29
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187595694-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187595694-form.html
new file mode 100644
index 0000000..e69de29
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187654049-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187654049-form.html
new file mode 100644
index 0000000..e69de29
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187654070-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187654070-form.html
new file mode 100644
index 0000000..e69de29
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187784838-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187784838-form.html
new file mode 100644
index 0000000..e69de29
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187784860-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187784860-form.html
new file mode 100644
index 0000000..e69de29
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187810822-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187810822-form.html
new file mode 100644
index 0000000..e69de29
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187810841-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482187810841-form.html
new file mode 100644
index 0000000..e69de29
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482188024803-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482188024803-form.html
new file mode 100644
index 0000000..e69de29
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482188024815-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482188024815-form.html
new file mode 100644
index 0000000..e69de29
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482188044994-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482188044994-form.html
new file mode 100644
index 0000000..e69de29
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482188070596-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482188070596-form.html
new file mode 100644
index 0000000..3781ba4
--- /dev/null
+++ b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482188070596-form.html
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482188280841-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482188280841-form.html
new file mode 100644
index 0000000..e69de29
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482188280865-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482188280865-form.html
new file mode 100644
index 0000000..e69de29
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482188280869-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482188280869-form.html
new file mode 100644
index 0000000..e69de29
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482188280870-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482188280870-form.html
new file mode 100644
index 0000000..e69de29
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482188280872-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482188280872-form.html
new file mode 100644
index 0000000..e69de29
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482188280874-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482188280874-form.html
new file mode 100644
index 0000000..e69de29
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482188280875-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482188280875-form.html
new file mode 100644
index 0000000..e69de29
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482188377570-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482188377570-form.html
new file mode 100644
index 0000000..e69de29
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482188377588-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482188377588-form.html
new file mode 100644
index 0000000..e69de29
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482188457913-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482188457913-form.html
new file mode 100644
index 0000000..e69de29
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482188555384-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482188555384-form.html
new file mode 100644
index 0000000..e69de29
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482188719238-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482188719238-form.html
new file mode 100644
index 0000000..3781ba4
--- /dev/null
+++ b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482188719238-form.html
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482188723899-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482188723899-form.html
new file mode 100644
index 0000000..3781ba4
--- /dev/null
+++ b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482188723899-form.html
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482188941187-form.html b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482188941187-form.html
new file mode 100644
index 0000000..3781ba4
--- /dev/null
+++ b/Chapter05/handling-file-uploads/uploading-a-file-with-put/uploads/userfile1-1482188941187-form.html
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/Chapter05/handling-file-uploads/uploading-a-file/package.json b/Chapter05/handling-file-uploads/uploading-a-file/package.json
new file mode 100644
index 0000000..a1a4a3a
--- /dev/null
+++ b/Chapter05/handling-file-uploads/uploading-a-file/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "uploading-a-file",
+ "version": "1.0.0",
+ "description": "",
+ "main": "server.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1",
+ "start": "node server.js"
+ },
+ "keywords": [],
+ "author": "David Mark Clements",
+ "license": "MIT",
+ "dependencies": {
+ "multipart-read-stream": "^3.0.0",
+ "pump": "^1.0.1"
+ }
+}
diff --git a/Chapter05/handling-file-uploads/uploading-a-file/public/form.html b/Chapter05/handling-file-uploads/uploading-a-file/public/form.html
new file mode 100644
index 0000000..3781ba4
--- /dev/null
+++ b/Chapter05/handling-file-uploads/uploading-a-file/public/form.html
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/Chapter05/handling-file-uploads/uploading-a-file/server.js b/Chapter05/handling-file-uploads/uploading-a-file/server.js
new file mode 100644
index 0000000..783b9b7
--- /dev/null
+++ b/Chapter05/handling-file-uploads/uploading-a-file/server.js
@@ -0,0 +1,59 @@
+'use strict'
+
+const http = require('http')
+const fs = require('fs')
+const path = require('path')
+const mrs = require('multipart-read-stream')
+const pump = require('pump')
+const form = fs.readFileSync(path.join(__dirname, 'public', 'form.html'))
+
+http.createServer((req, res) => {
+ if (req.method === 'GET') {
+ get(res)
+ return
+ }
+ if (req.method === 'POST') {
+ post(req, res)
+ return
+ }
+ reject(405, 'Method Not Allowed', res)
+}).listen(8080)
+
+function get (res) {
+ res.writeHead(200, {'Content-Type': 'text/html'})
+ res.end(form)
+}
+
+function reject (code, msg, res) {
+ res.statusCode = code
+ res.end(msg)
+}
+
+function post (req, res) {
+ if (!/multipart\/form-data/.test(req.headers['content-type'])) {
+ reject(415, 'Unsupported Media Type', res)
+ return
+ }
+ console.log('parsing multipart data')
+ const parser = mrs(req.headers, part)
+ var total = 0
+ pump(req, parser)
+
+ function part (field, file, name) {
+ if (!name) {
+ file.resume()
+ return
+ }
+ total += 1
+ const filename = `${field}-${Date.now()}-${name}`
+ const dest = fs.createWriteStream(path.join(__dirname, 'uploads', filename))
+ pump(file, dest, (err) => {
+ total -= 1
+ res.write(err
+ ? `Error saving ${name}!\n`
+ : `${name} successfully saved!\n`
+ )
+ if (total === 0) res.end('All files processed!')
+ })
+ }
+}
diff --git a/Chapter05/making-an-http-post-request/buffering-a-request/index.js b/Chapter05/making-an-http-post-request/buffering-a-request/index.js
new file mode 100644
index 0000000..3613a42
--- /dev/null
+++ b/Chapter05/making-an-http-post-request/buffering-a-request/index.js
@@ -0,0 +1,19 @@
+'use strict'
+
+const http = require('http')
+const assert = require('assert')
+const url = 'http://www.davidmarkclements.com/ncb3/some.json'
+
+http.get(url, (res) => {
+ const size = parseInt(res.headers['content-length'], 10)
+ const buffer = Buffer.allocUnsafe(size)
+ var index = 0
+ res.on('data', (chunk) => {
+ chunk.copy(buffer, index)
+ index += chunk.length
+ })
+ res.on('end', () => {
+ assert.equal(size, buffer.length)
+ console.log('GUID:', JSON.parse(buffer).guid)
+ })
+})
\ No newline at end of file
diff --git a/Chapter05/making-an-http-post-request/multipart-post-uploads/index.js b/Chapter05/making-an-http-post-request/multipart-post-uploads/index.js
new file mode 100644
index 0000000..4cf2063
--- /dev/null
+++ b/Chapter05/making-an-http-post-request/multipart-post-uploads/index.js
@@ -0,0 +1,47 @@
+'use strict'
+
+const http = require('http')
+const fs = require('fs')
+const path = require('path')
+const steed = require('steed')()
+const files = process.argv.slice(2)
+const boundary = Date.now()
+const opts = {
+ method: 'POST',
+ hostname: 'localhost',
+ port: 8080,
+ path: '/',
+ headers: {
+ 'Content-Type': 'multipart/form-data; boundary="' + boundary + '"',
+ 'Transfer-Encoding': 'chunked'
+ }
+}
+
+const req = http.request(opts, (res) => {
+ console.log('\n Status: ' + res.statusCode)
+ process.stdout.write(' Body: ')
+ res.pipe(process.stdout)
+ res.on('end', () => console.log('\n'))
+})
+
+req.on('error', (err) => console.error('Error: ', err))
+
+const parts = files.map((file, i) => (cb) => {
+ const stream = fs.createReadStream(file)
+ stream.once('open', () => {
+ req.write(
+ `\r\n--${boundary}\r\n` +
+ 'Content-Disposition: ' +
+ `form-data; name="userfile${i}";` +
+ `filename="${path.basename(file)}"\r\n` +
+ 'Content-Type: application/octet-stream\r\n' +
+ 'Content-Transfer-Encoding: binary\r\n' +
+ '\r\n'
+ )
+ })
+ stream.pipe(req, {end: false})
+ stream.on('data', (chunk) => req.write(chunk))
+ stream.on('end', cb)
+})
+
+steed.series(parts, () => req.end(`\r\n--${boundary}--\r\n`))
diff --git a/Chapter05/making-an-http-post-request/multipart-post-uploads/package.json b/Chapter05/making-an-http-post-request/multipart-post-uploads/package.json
new file mode 100644
index 0000000..44a25ff
--- /dev/null
+++ b/Chapter05/making-an-http-post-request/multipart-post-uploads/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "multipart-post-uploads",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "steed": "^1.1.3"
+ }
+}
diff --git a/Chapter05/making-an-http-post-request/post-request/index.js b/Chapter05/making-an-http-post-request/post-request/index.js
new file mode 100644
index 0000000..f8da0b4
--- /dev/null
+++ b/Chapter05/making-an-http-post-request/post-request/index.js
@@ -0,0 +1,28 @@
+'use strict'
+
+const http = require('http')
+const payload = `{
+ "name": "Cian Ó Maidín",
+ "company": "nearForm"
+}`
+const opts = {
+ method: 'POST',
+ hostname: 'reqres.in',
+ port: 80,
+ path: '/api/users',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Content-Length': Buffer.byteLength(payload)
+ }
+}
+
+const req = http.request(opts, (res) => {
+ console.log('\n Status: ' + res.statusCode)
+ process.stdout.write(' Body: ')
+ res.pipe(process.stdout)
+ res.on('end', () => console.log('\n'))
+})
+
+req.on('error', (err) => console.error('Error: ', err))
+
+req.end(payload)
\ No newline at end of file
diff --git a/Chapter05/making-an-http-post-request/streaming-payloads/index.js b/Chapter05/making-an-http-post-request/streaming-payloads/index.js
new file mode 100644
index 0000000..aab6759
--- /dev/null
+++ b/Chapter05/making-an-http-post-request/streaming-payloads/index.js
@@ -0,0 +1,26 @@
+'use strict'
+
+const http = require('http')
+const opts = {
+ method: 'POST',
+ hostname: 'reqres.in',
+ port: 80,
+ path: '/api/users',
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Transfer-Encoding': 'chunked'
+ }
+}
+
+const req = http.request(opts, (res) => {
+ console.log('\n Status: ' + res.statusCode)
+ process.stdout.write(' Body: ')
+ res.pipe(process.stdout)
+ res.on('end', () => console.log('\n'))
+})
+
+req.on('error', (err) => console.error('Error: ', err))
+
+http.get('http://reqres.in/api/users', (res) => {
+ res.pipe(req)
+})
\ No newline at end of file
diff --git a/Chapter05/receiving-post-data/accepting-json/package.json b/Chapter05/receiving-post-data/accepting-json/package.json
new file mode 100644
index 0000000..fc7edd7
--- /dev/null
+++ b/Chapter05/receiving-post-data/accepting-json/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "accepting-json",
+ "version": "1.0.0",
+ "description": "",
+ "main": "server.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1",
+ "start": "node server.js"
+ },
+ "keywords": [],
+ "author": "David Mark Clements",
+ "license": "MIT",
+ "dependencies": {
+ "fast-json-parse": "^1.0.2"
+ }
+}
diff --git a/Chapter05/receiving-post-data/accepting-json/public/form.html b/Chapter05/receiving-post-data/accepting-json/public/form.html
new file mode 100644
index 0000000..dcb95e3
--- /dev/null
+++ b/Chapter05/receiving-post-data/accepting-json/public/form.html
@@ -0,0 +1,29 @@
+
+
\ No newline at end of file
diff --git a/Chapter05/receiving-post-data/accepting-json/server.js b/Chapter05/receiving-post-data/accepting-json/server.js
new file mode 100644
index 0000000..8eae20a
--- /dev/null
+++ b/Chapter05/receiving-post-data/accepting-json/server.js
@@ -0,0 +1,73 @@
+'use strict'
+
+const http = require('http')
+const fs = require('fs')
+const path = require('path')
+const parse = require('fast-json-parse')
+const form = fs.readFileSync(path.join(__dirname, 'public', 'form.html'))
+const maxData = 2 * 1024 * 1024 // 2mb
+
+http.createServer((req, res) => {
+ if (req.method === 'GET') {
+ get(res)
+ return
+ }
+ if (req.method === 'POST') {
+ post(req, res)
+ return
+ }
+ reject(405, 'Method Not Allowed', res)
+}).listen(8080)
+
+function get (res) {
+ res.writeHead(200, {'Content-Type': 'text/html'})
+ res.end(form)
+}
+
+function post (req, res) {
+ if (req.headers['content-type'] !== 'application/json') {
+ reject(415, 'Unsupported Media Type', res)
+ return
+ }
+ const size = parseInt(req.headers['content-length'], 10)
+ if (isNaN(size)) {
+ reject(400, 'Bad Request', res)
+ return
+ }
+ if (size > maxData) {
+ reject(413, 'Too Large', res)
+ return
+ }
+ const buffer = Buffer.allocUnsafe(size)
+ var pos = 0
+
+ req
+ .on('data', (chunk) => {
+ const offset = pos + chunk.length
+ if (offset > size) {
+ reject(413, 'Too Large', res)
+ return
+ }
+ chunk.copy(buffer, pos)
+ pos = offset
+ })
+ .on('end', () => {
+ if (pos !== size) {
+ reject(400, 'Bad Request', res)
+ return
+ }
+ const data = buffer.toString()
+ const parsed = parse(data)
+ if (parsed.err) {
+ reject(400, 'Bad Request', res)
+ return
+ }
+ console.log('User Posted: ', parsed.value)
+ res.end('{"data": ' + data + "}")
+ })
+}
+
+function reject (code, msg, res) {
+ res.statusCode = code
+ res.end(msg)
+}
\ No newline at end of file
diff --git a/Chapter05/receiving-post-data/process-post-data/public/form.html b/Chapter05/receiving-post-data/process-post-data/public/form.html
new file mode 100644
index 0000000..fac28d7
--- /dev/null
+++ b/Chapter05/receiving-post-data/process-post-data/public/form.html
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/Chapter05/receiving-post-data/process-post-data/server.js b/Chapter05/receiving-post-data/process-post-data/server.js
new file mode 100644
index 0000000..dec8d8c
--- /dev/null
+++ b/Chapter05/receiving-post-data/process-post-data/server.js
@@ -0,0 +1,68 @@
+'use strict'
+
+const http = require('http')
+const fs = require('fs')
+const path = require('path')
+const form = fs.readFileSync(path.join(__dirname, 'public', 'form.html'))
+const qs = require('querystring')
+const maxData = 2 * 1024 * 1024 // 2mb
+
+http.createServer((req, res) => {
+ if (req.method === 'GET') {
+ get(res)
+ return
+ }
+ if (req.method === 'POST') {
+ post(req, res)
+ return
+ }
+ reject(405, 'Method Not Allowed', res)
+}).listen(8080)
+
+function get (res) {
+ res.writeHead(200, {'Content-Type': 'text/html'})
+ res.end(form)
+}
+
+function post (req, res) {
+ if (req.headers['content-type'] !== 'application/x-www-form-urlencoded') {
+ reject(415, 'Unsupported Media Type', res)
+ return
+ }
+ const size = parseInt(req.headers['content-length'], 10)
+ if (isNaN(size)) {
+ reject(400, 'Bad Request', res)
+ return
+ }
+ if (size > maxData) {
+ reject(413, 'Too Large', res)
+ return
+ }
+ const buffer = Buffer.allocUnsafe(size)
+ var pos = 0
+
+ req
+ .on('data', (chunk) => {
+ const offset = pos + chunk.length
+ if (offset > size) {
+ reject(413, 'Too Large', res)
+ return
+ }
+ chunk.copy(buffer, pos)
+ pos = offset
+ })
+ .on('end', () => {
+ if (pos !== size) {
+ reject(400, 'Bad Request', res)
+ return
+ }
+ const data = qs.parse(buffer.toString())
+ console.log('User Posted: ', data)
+ res.end('You Posted: ' + JSON.stringify(data))
+ })
+}
+
+function reject (code, msg, res) {
+ res.statusCode = code
+ res.end(msg)
+}
\ No newline at end of file
diff --git a/Chapter06/level/level-app/index.js b/Chapter06/level/level-app/index.js
new file mode 100644
index 0000000..50b8e43
--- /dev/null
+++ b/Chapter06/level/level-app/index.js
@@ -0,0 +1,45 @@
+const {hash} = require('xxhash')
+const through = require('through2')
+const eos = require('end-of-stream')
+const level = require('level')
+
+const db = level('./data')
+
+const params = {
+ author: process.argv[2],
+ quote: process.argv[3]
+}
+
+if (params.author && params.quote) {
+ add(params, (err) => {
+ if (err) console.error(err)
+ list(params.author)
+ })
+ return
+}
+
+if (params.author) {
+ list(params.author)
+ return
+}
+
+function add({quote, author}, cb) {
+ const key = author + hash(Buffer.from(quote), 0xDAF1DC)
+ db.put(key, quote, cb)
+}
+
+function list (author) {
+ if (!author) db.close()
+ const quotes = db.createValueStream({
+ gte: author,
+ lt: String.fromCharCode(author.charCodeAt(0) + 1)
+ })
+ const format = through((quote, enc, cb) => {
+ cb(null, `${author} ${quote}`)
+ })
+ quotes.pipe(format).pipe(process.stdout)
+ eos(format, () => {
+ db.close()
+ console.log()
+ })
+}
\ No newline at end of file
diff --git a/Chapter06/level/level-app/package.json b/Chapter06/level/level-app/package.json
new file mode 100644
index 0000000..7df9426
--- /dev/null
+++ b/Chapter06/level/level-app/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "level-app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "end-of-stream": "^1.1.0",
+ "level": "^1.6.0",
+ "through2": "^2.0.3",
+ "xxhash": "^0.2.4"
+ }
+}
diff --git a/Chapter06/level/level-sql-app/index.js b/Chapter06/level/level-sql-app/index.js
new file mode 100644
index 0000000..32c880e
--- /dev/null
+++ b/Chapter06/level/level-sql-app/index.js
@@ -0,0 +1,45 @@
+const {hash} = require('xxhash')
+const through = require('through2')
+const eos = require('end-of-stream')
+const levelup = require('levelup')
+const sqldown = require('sqldown')
+const db = levelup('./data', {db: sqldown})
+
+const params = {
+ author: process.argv[2],
+ quote: process.argv[3]
+}
+
+if (params.author && params.quote) {
+ add(params, (err) => {
+ if (err) console.error(err)
+ list(params.author)
+ })
+ return
+}
+
+if (params.author) {
+ list(params.author)
+ return
+}
+
+function add({quote, author}, cb) {
+ const key = author + hash(Buffer.from(quote), 0xDAF1DC)
+ db.put(key, quote, cb)
+}
+
+function list (author) {
+ if (!author) db.close()
+ const quotes = db.createValueStream({
+ gte: author,
+ lt: String.fromCharCode(author.charCodeAt(0) + 1)
+ })
+ const format = through((quote, enc, cb) => {
+ cb(null, `${author} ${quote}`)
+ })
+ quotes.pipe(format).pipe(process.stdout)
+ eos(format, () => {
+ db.close()
+ console.log()
+ })
+}
\ No newline at end of file
diff --git a/Chapter06/level/level-sql-app/package.json b/Chapter06/level/level-sql-app/package.json
new file mode 100644
index 0000000..39e43e3
--- /dev/null
+++ b/Chapter06/level/level-sql-app/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "level-app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "end-of-stream": "^1.1.0",
+ "levelup": "^1.3.3",
+ "sqldown": "^2.1.0",
+ "sqlite": "^2.3.0",
+ "through2": "^2.0.3",
+ "xxhash": "^0.2.4"
+ }
+}
diff --git a/Chapter06/mongodb/mongo-app/authors.js b/Chapter06/mongodb/mongo-app/authors.js
new file mode 100644
index 0000000..a0fff32
--- /dev/null
+++ b/Chapter06/mongodb/mongo-app/authors.js
@@ -0,0 +1,17 @@
+const {MongoClient} = require('mongodb')
+const client = new MongoClient()
+
+client.connect('mongodb://localhost:27017/quotes', ready)
+
+function ready (err, db) {
+ if (err) throw err
+ const collection = db.collection('quotes')
+ collection.ensureIndex('author', (err) => {
+ if (err) throw err
+ collection.distinct('author', (err, result) => {
+ if (err) throw err
+ console.log(result.join('\n'))
+ db.close()
+ })
+ })
+}
diff --git a/Chapter06/mongodb/mongo-app/index.js b/Chapter06/mongodb/mongo-app/index.js
new file mode 100644
index 0000000..23cfd5f
--- /dev/null
+++ b/Chapter06/mongodb/mongo-app/index.js
@@ -0,0 +1,39 @@
+const {MongoClient} = require('mongodb')
+const client = new MongoClient()
+
+const params = {
+ author: process.argv[2],
+ quote: process.argv[3]
+}
+
+client.connect('mongodb://localhost:27017/quotes', ready)
+
+function ready (err, db) {
+ if (err) throw err
+ const collection = db.collection('quotes')
+
+ if (params.author && params.quote) {
+ collection.insert({
+ author: params.author,
+ quote: params.quote
+ }, (err) => {
+ if (err) throw err
+ })
+ }
+
+ if (params.author) {
+ collection.find({
+ author: params.author
+ }).each((err, doc) => {
+ if (err) throw err
+ if (!doc) {
+ db.close()
+ return
+ }
+ console.log('%s: %s \n', doc.author, doc.quote)
+ })
+ return
+ }
+
+ db.close()
+}
diff --git a/Chapter06/mongodb/mongo-app/package.json b/Chapter06/mongodb/mongo-app/package.json
new file mode 100644
index 0000000..4b8fadd
--- /dev/null
+++ b/Chapter06/mongodb/mongo-app/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "mongo-app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "mongodb": "^2.2.16"
+ }
+}
diff --git a/Chapter06/mongodb/mongo-app/votes.js b/Chapter06/mongodb/mongo-app/votes.js
new file mode 100644
index 0000000..e2f2069
--- /dev/null
+++ b/Chapter06/mongodb/mongo-app/votes.js
@@ -0,0 +1,52 @@
+const {MongoClient, ObjectID} = require('mongodb')
+const client = new MongoClient()
+const params = {id: process.argv[2]}
+
+client.connect('mongodb://localhost:27017/quotes', ready)
+
+function ready (err, db) {
+ if (err) throw err
+ const collection = db.collection('quotes')
+
+ if (!params.id) {
+ showIds(collection, db)
+ return
+ }
+
+ vote(params.id, db, collection)
+}
+
+function showIds (collection, db) {
+ collection.find().each((err, doc) => {
+ if (err) throw err
+ if (doc) {
+ console.log(doc._id, doc.quote)
+ return
+ }
+ db.close()
+ })
+}
+
+function vote (id, db, collection) {
+ const query = {
+ _id: ObjectID(id)
+ }
+ const action = {$inc: {votes: 1}}
+ const opts = {safe: true}
+ collection.update(query, action, opts, (err) => {
+ if (err) throw err
+ console.log('1 vote added to %s by %s', params.id)
+ const by = {votes: 1}
+ const max = 10
+ collection.find().sort(by).limit(max).each((err, doc) => {
+ if (err) throw err
+ if (doc) {
+ const votes = doc.votes || 0
+ console.log(`${votes} | ${doc.author}: ${doc.quote.substr(0, 30)}...`)
+ return
+ }
+ db.close()
+ })
+ })
+ return
+}
diff --git a/Chapter06/mysql/insert-quotes/index.js b/Chapter06/mysql/insert-quotes/index.js
new file mode 100644
index 0000000..d5f34d3
--- /dev/null
+++ b/Chapter06/mysql/insert-quotes/index.js
@@ -0,0 +1,42 @@
+const mysql = require('mysql')
+
+const params = {
+ author: process.argv[2],
+ quote: process.argv[3]
+}
+
+const db = mysql.createConnection({
+ user: 'root',
+ //password: 'pw-if-set',
+ //debug: true
+})
+
+db.query('CREATE DATABASE quotes')
+db.query('USE quotes')
+
+db.query(`
+ CREATE TABLE quotes.quotes (
+ id INT NOT NULL AUTO_INCREMENT,
+ author VARCHAR ( 128 ) NOT NULL,
+ quote TEXT NOT NULL, PRIMARY KEY ( id )
+ )
+`)
+
+const ignore = new Set([
+ 'ER_DB_CREATE_EXISTS',
+ 'ER_TABLE_EXISTS_ERROR'
+])
+
+db.on('error', (err) => {
+ if (ignore.has(err.code)) return
+ throw err
+})
+
+if (params.author && params.quote) {
+ db.query(`
+ INSERT INTO quotes.quotes (author, quote)
+ VALUES (?, ?);
+ `, [params.author, params.quote])
+}
+
+db.end()
\ No newline at end of file
diff --git a/Chapter06/mysql/insert-quotes/package.json b/Chapter06/mysql/insert-quotes/package.json
new file mode 100644
index 0000000..d3f9465
--- /dev/null
+++ b/Chapter06/mysql/insert-quotes/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "mysql-app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "mysql": "^2.12.0"
+ }
+}
diff --git a/Chapter06/mysql/mysql-app/index.js b/Chapter06/mysql/mysql-app/index.js
new file mode 100644
index 0000000..48ebe12
--- /dev/null
+++ b/Chapter06/mysql/mysql-app/index.js
@@ -0,0 +1,34 @@
+const mysql = require('mysql')
+const db = mysql.createConnection({
+ user: 'root',
+ //password: 'pw-if-set',
+ //debug: true
+})
+
+db.query('CREATE DATABASE quotes')
+db.query('USE quotes')
+
+db.query(`
+ CREATE TABLE quotes.quotes (
+ id INT NOT NULL AUTO_INCREMENT,
+ author VARCHAR ( 128 ) NOT NULL,
+ quote TEXT NOT NULL, PRIMARY KEY ( id )
+ )
+`)
+
+const ignore = new Set([
+ 'ER_DB_CREATE_EXISTS',
+ 'ER_TABLE_EXISTS_ERROR'
+])
+
+db.on('error', (err) => {
+ if (ignore.has(err.code)) return
+ throw err
+})
+
+db.query(`
+ INSERT INTO quotes.quotes (author, quote)
+ VALUES ("Bjarne Stroustrup", "Proof by analogy is fraud.");
+`)
+
+db.end()
\ No newline at end of file
diff --git a/Chapter06/mysql/mysql-app/package.json b/Chapter06/mysql/mysql-app/package.json
new file mode 100644
index 0000000..d3f9465
--- /dev/null
+++ b/Chapter06/mysql/mysql-app/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "mysql-app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "mysql": "^2.12.0"
+ }
+}
diff --git a/Chapter06/mysql/quotes-app/index.js b/Chapter06/mysql/quotes-app/index.js
new file mode 100644
index 0000000..da7bc0f
--- /dev/null
+++ b/Chapter06/mysql/quotes-app/index.js
@@ -0,0 +1,51 @@
+const mysql = require('mysql')
+
+const params = {
+ author: process.argv[2],
+ quote: process.argv[3]
+}
+
+const db = mysql.createConnection({
+ user: 'root',
+ //password: 'pw-if-set',
+ //debug: true
+})
+
+db.query('CREATE DATABASE quotes')
+db.query('USE quotes')
+
+db.query(`
+ CREATE TABLE quotes.quotes (
+ id INT NOT NULL AUTO_INCREMENT,
+ author VARCHAR ( 128 ) NOT NULL,
+ quote TEXT NOT NULL, PRIMARY KEY ( id )
+ )
+`)
+
+const ignore = new Set([
+ 'ER_DB_CREATE_EXISTS',
+ 'ER_TABLE_EXISTS_ERROR'
+])
+
+db.on('error', (err) => {
+ if (ignore.has(err.code)) return
+ throw err
+})
+
+if (params.author && params.quote) {
+ db.query(`
+ INSERT INTO quotes.quotes (author, quote)
+ VALUES (?, ?);
+ `, [params.author, params.quote])
+}
+
+if (params.author) {
+ db.query(`
+ SELECT * FROM quotes
+ WHERE author LIKE ${db.escape(params.author)}
+ `).on('result', ({author, quote}) => {
+ console.log(`${author} ${quote}`)
+ })
+}
+
+db.end()
\ No newline at end of file
diff --git a/Chapter06/mysql/quotes-app/package.json b/Chapter06/mysql/quotes-app/package.json
new file mode 100644
index 0000000..d3f9465
--- /dev/null
+++ b/Chapter06/mysql/quotes-app/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "mysql-app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "mysql": "^2.12.0"
+ }
+}
diff --git a/Chapter06/postgres/postgres-app/index.js b/Chapter06/postgres/postgres-app/index.js
new file mode 100644
index 0000000..4a6b835
--- /dev/null
+++ b/Chapter06/postgres/postgres-app/index.js
@@ -0,0 +1,46 @@
+const pg = require('pg')
+const db = new pg.Client()
+const params = {
+ author: process.argv[2],
+ quote: process.argv[3]
+}
+
+db.connect((err) => {
+ if (err) throw err
+ db.query(`
+ CREATE TABLE IF NOT EXISTS quotes (
+ id SERIAL,
+ author VARCHAR ( 128 ) NOT NULL,
+ quote TEXT NOT NULL, PRIMARY KEY ( id )
+ )
+ `, (err) => {
+ if (err) throw err
+
+ if (params.author && params.quote) {
+ db.query(`
+ INSERT INTO quotes (author, quote)
+ VALUES ($1, $2);
+ `, [params.author, params.quote], (err) => {
+ if (err) throw err
+ list(db, params)
+ })
+ }
+
+ if (!params.quote) list(db, params)
+ })
+})
+
+function list (db, params) {
+ if (!params.author) return db.end()
+ db.query(`
+ SELECT * FROM quotes
+ WHERE author LIKE ${db.escapeLiteral(params.author)}
+ `, (err, results) => {
+ if (err) throw err
+ results.rows.forEach(({author, quote}) => {
+ console.log(`${author} ${quote}`)
+ })
+ db.end()
+ })
+}
+
diff --git a/Chapter06/postgres/postgres-app/package.json b/Chapter06/postgres/postgres-app/package.json
new file mode 100644
index 0000000..890e38e
--- /dev/null
+++ b/Chapter06/postgres/postgres-app/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "mysql-app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "mysql": "^2.12.0",
+ "pg": "^6.1.2"
+ }
+}
diff --git a/Chapter06/postgres/postgres-native-app/index.js b/Chapter06/postgres/postgres-native-app/index.js
new file mode 100644
index 0000000..248cdc5
--- /dev/null
+++ b/Chapter06/postgres/postgres-native-app/index.js
@@ -0,0 +1,46 @@
+const pg = require('pg').native || require('pg')
+const db = new pg.Client()
+const params = {
+ author: process.argv[2],
+ quote: process.argv[3]
+}
+
+db.connect((err) => {
+ if (err) throw err
+ db.query(`
+ CREATE TABLE IF NOT EXISTS quotes (
+ id SERIAL,
+ author VARCHAR ( 128 ) NOT NULL,
+ quote TEXT NOT NULL, PRIMARY KEY ( id )
+ )
+ `, (err) => {
+ if (err) throw err
+
+ if (params.author && params.quote) {
+ db.query(`
+ INSERT INTO quotes (author, quote)
+ VALUES ($1, $2);
+ `, [params.author, params.quote], (err) => {
+ if (err) throw err
+ list(db, params)
+ })
+ }
+
+ if (!params.quote) list(db, params)
+ })
+})
+
+function list (db, params) {
+ if (!params.author) return db.end()
+ db.query(`
+ SELECT * FROM quotes
+ WHERE author LIKE $1
+ `, [params.author], (err, results) => {
+ if (err) throw err
+ results.rows.forEach(({author, quote}) => {
+ console.log(`${author} ${quote}`)
+ })
+ db.end()
+ })
+}
+
diff --git a/Chapter06/postgres/postgres-native-app/package.json b/Chapter06/postgres/postgres-native-app/package.json
new file mode 100644
index 0000000..f38d01c
--- /dev/null
+++ b/Chapter06/postgres/postgres-native-app/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "mysql-app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "mysql": "^2.12.0",
+ "pg": "^6.1.2"
+ },
+ "optionalDependencies": {
+ "pg-native": "^1.10.0"
+ }
+}
diff --git a/Chapter06/postgres/postgres-object-app/index.js b/Chapter06/postgres/postgres-object-app/index.js
new file mode 100644
index 0000000..d531174
--- /dev/null
+++ b/Chapter06/postgres/postgres-object-app/index.js
@@ -0,0 +1,51 @@
+const pg = require('pg')
+const db = new pg.Client()
+const params = {
+ author: process.argv[2],
+ quote: process.argv[3]
+}
+
+db.connect((err) => {
+ if (err) throw err
+ db.query(`
+ CREATE TABLE IF NOT EXISTS quote_docs (
+ id SERIAL,
+ doc jsonb,
+ CONSTRAINT author CHECK (length(doc->>'author') > 0 AND (doc->>'author') IS NOT NULL),
+ CONSTRAINT quote CHECK (length(doc->>'quote') > 0 AND (doc->>'quote') IS NOT NULL)
+ )
+ `, (err) => {
+ if (err) throw err
+
+ if (params.author && params.quote) {
+ db.query(`
+ INSERT INTO quote_docs (doc)
+ VALUES ($1);
+ `, [params], (err) => {
+ if (err) throw err
+ list(db, params)
+ })
+ }
+
+ if (!params.quote) list(db, params)
+
+ })
+})
+
+function list (db, params) {
+ if (!params.author) return db.end()
+ db.query(`
+ SELECT * FROM quote_docs
+ WHERE doc ->> 'author' LIKE ${db.escapeLiteral(params.author)}
+ `, (err, results) => {
+ if (err) throw err
+ results.rows
+ .map(({doc}) => doc)
+ .forEach(({author, quote}) => {
+ console.log(`${author} ${quote}`)
+ })
+ db.end()
+ })
+}
+
+
diff --git a/Chapter06/postgres/postgres-object-app/package.json b/Chapter06/postgres/postgres-object-app/package.json
new file mode 100644
index 0000000..890e38e
--- /dev/null
+++ b/Chapter06/postgres/postgres-object-app/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "mysql-app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "mysql": "^2.12.0",
+ "pg": "^6.1.2"
+ }
+}
diff --git a/Chapter06/redis/redis-app/index.js b/Chapter06/redis/redis-app/index.js
new file mode 100644
index 0000000..35d66fe
--- /dev/null
+++ b/Chapter06/redis/redis-app/index.js
@@ -0,0 +1,46 @@
+const uuid = require('uuid')
+const steed = require('steed')()
+const redis = require('redis')
+const client = redis.createClient()
+const params = {
+ author: process.argv[2],
+ quote: process.argv[3]
+}
+
+if (params.author && params.quote) {
+ add(params)
+ list((err) => {
+ if (err) console.error(err)
+ client.quit()
+ })
+ return
+}
+
+if (params.author) {
+ list((err) => {
+ if (err) console.error(err)
+ client.quit()
+ })
+ return
+}
+
+client.quit()
+
+function add ({author, quote}) {
+ const key = `Quotes: ${uuid()}`
+ client.hmset(key, {author, quote})
+ client.sadd(`Author: ${params.author}`, key)
+}
+
+function list (cb) {
+ client.smembers(`Author: ${params.author}`, (err, keys) => {
+ if (err) return cb(err)
+ steed.each(keys, (key, next) => {
+ client.hgetall(key, (err, {author, quote}) => {
+ if (err) return next(err)
+ console.log(`${author} ${quote} \n`)
+ next()
+ })
+ }, cb)
+ })
+}
\ No newline at end of file
diff --git a/Chapter06/redis/redis-app/package.json b/Chapter06/redis/redis-app/package.json
new file mode 100644
index 0000000..a013205
--- /dev/null
+++ b/Chapter06/redis/redis-app/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "redis-app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "redis": "^2.7.1",
+ "steed": "^1.1.3",
+ "uuid": "^3.0.1"
+ }
+}
diff --git a/Chapter06/redis/redis-batch-app/index.js b/Chapter06/redis/redis-batch-app/index.js
new file mode 100644
index 0000000..8ba8d2d
--- /dev/null
+++ b/Chapter06/redis/redis-batch-app/index.js
@@ -0,0 +1,56 @@
+const uuid = require('uuid')
+const steed = require('steed')()
+const redis = require('redis')
+const client = redis.createClient(),
+const params = {
+ author: process.argv[2],
+ quote: process.argv[3]
+}
+
+if (params.author && params.quote) {
+ add(params, (err) => {
+ if (err) throw err
+ list((err) => {
+ if (err) console.error(err)
+ client.quit()
+ })
+ })
+ return
+}
+
+if (params.author) {
+ list((err) => {
+ if (err) console.error(err)
+ client.quit()
+ })
+ return
+}
+
+client.quit()
+
+
+function add ({author, quote}, cb) {
+ const key = `Quotes: ${uuid()}`
+ client
+ .multi()
+ .hmset(key, {author, quote})
+ .sadd(`Author: ${params.author}`, key)
+ .exec((err, replies) => {
+ if (err) return cb(err)
+ if (replies[0] === "OK") console.log('Added...\n')
+ cb()
+ })
+}
+
+function list (cb) {
+ client.smembers(`Author: ${params.author}`, (err, keys) => {
+ if (err) return cb(err)
+ steed.each(keys, (key, next) => {
+ client.hgetall(key, (err, {author, quote}) => {
+ if (err) return next(err)
+ console.log(`${author} ${quote} \n`)
+ next()
+ })
+ }, cb)
+ })
+}
\ No newline at end of file
diff --git a/Chapter06/redis/redis-batch-app/package.json b/Chapter06/redis/redis-batch-app/package.json
new file mode 100644
index 0000000..a013205
--- /dev/null
+++ b/Chapter06/redis/redis-batch-app/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "redis-app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "redis": "^2.7.1",
+ "steed": "^1.1.3",
+ "uuid": "^3.0.1"
+ }
+}
diff --git a/Chapter07/adding-a-view-layer/express-template-strings/index.js b/Chapter07/adding-a-view-layer/express-template-strings/index.js
new file mode 100644
index 0000000..a698855
--- /dev/null
+++ b/Chapter07/adding-a-view-layer/express-template-strings/index.js
@@ -0,0 +1,19 @@
+'use strict'
+
+const express = require('express')
+const {join} = require('path')
+const index = require('./routes/index')
+
+const app = express()
+const dev = process.env.NODE_ENV !== 'production'
+const port = process.env.PORT || 3000
+
+if (dev) {
+ app.use(express.static(join(__dirname, 'public')))
+}
+
+app.use('/', index)
+
+app.listen(port, () => {
+ console.log(`Server listening on port ${port}`)
+})
\ No newline at end of file
diff --git a/Chapter07/adding-a-view-layer/express-template-strings/package.json b/Chapter07/adding-a-view-layer/express-template-strings/package.json
new file mode 100644
index 0000000..8711bc5
--- /dev/null
+++ b/Chapter07/adding-a-view-layer/express-template-strings/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "express": "^4.15.2"
+ }
+}
diff --git a/Chapter07/adding-a-view-layer/express-template-strings/public/styles.css b/Chapter07/adding-a-view-layer/express-template-strings/public/styles.css
new file mode 100644
index 0000000..28e6f72
--- /dev/null
+++ b/Chapter07/adding-a-view-layer/express-template-strings/public/styles.css
@@ -0,0 +1,4 @@
+body {
+ padding: 50px;
+ font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
+}
\ No newline at end of file
diff --git a/Chapter07/adding-a-view-layer/express-template-strings/routes/index.js b/Chapter07/adding-a-view-layer/express-template-strings/routes/index.js
new file mode 100644
index 0000000..3101e79
--- /dev/null
+++ b/Chapter07/adding-a-view-layer/express-template-strings/routes/index.js
@@ -0,0 +1,15 @@
+'use strict'
+
+const {Router} = require('express')
+const router = Router()
+const views = {
+ index: require('../views/index')
+}
+
+router.get('/', function(req, res, next()) {
+ const title = 'Express'
+ res.send(views.index({title}))
+ next()
+})
+
+module.exports = router
diff --git a/Chapter07/adding-a-view-layer/express-template-strings/views/index.js b/Chapter07/adding-a-view-layer/express-template-strings/views/index.js
new file mode 100644
index 0000000..0f8e2c4
--- /dev/null
+++ b/Chapter07/adding-a-view-layer/express-template-strings/views/index.js
@@ -0,0 +1,14 @@
+'use strict'
+
+module.exports = function indexView ({title}) {
+ return `
+
+ ${title}
+
+
+
+ ${title}
+ Welcome to ${title}
+
+ `
+}
\ No newline at end of file
diff --git a/Chapter07/adding-a-view-layer/express-views/index.js b/Chapter07/adding-a-view-layer/express-views/index.js
new file mode 100644
index 0000000..7cffd67
--- /dev/null
+++ b/Chapter07/adding-a-view-layer/express-views/index.js
@@ -0,0 +1,22 @@
+'use strict'
+
+const express = require('express')
+const {join} = require('path')
+const index = require('./routes/index')
+
+const app = express()
+const dev = process.env.NODE_ENV !== 'production'
+const port = process.env.PORT || 3000
+
+app.set('views', join(__dirname, 'views'))
+app.set('view engine', 'ejs')
+
+if (dev) {
+ app.use(express.static(join(__dirname, 'public')))
+}
+
+app.use('/', index)
+
+app.listen(port, () => {
+ console.log(`Server listening on port ${port}`)
+})
\ No newline at end of file
diff --git a/Chapter07/adding-a-view-layer/express-views/package.json b/Chapter07/adding-a-view-layer/express-views/package.json
new file mode 100644
index 0000000..fa7f4f9
--- /dev/null
+++ b/Chapter07/adding-a-view-layer/express-views/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "ejs": "^2.5.6",
+ "express": "^4.15.2"
+ }
+}
diff --git a/Chapter07/adding-a-view-layer/express-views/public/styles.css b/Chapter07/adding-a-view-layer/express-views/public/styles.css
new file mode 100644
index 0000000..28e6f72
--- /dev/null
+++ b/Chapter07/adding-a-view-layer/express-views/public/styles.css
@@ -0,0 +1,4 @@
+body {
+ padding: 50px;
+ font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
+}
\ No newline at end of file
diff --git a/Chapter07/adding-a-view-layer/express-views/routes/index.js b/Chapter07/adding-a-view-layer/express-views/routes/index.js
new file mode 100644
index 0000000..0a8ec96
--- /dev/null
+++ b/Chapter07/adding-a-view-layer/express-views/routes/index.js
@@ -0,0 +1,11 @@
+'use strict'
+
+const {Router} = require('express')
+const router = Router()
+
+router.get('/', function(req, res) {
+ const title = 'Express'
+ res.render('index', {title: 'Express'})
+})
+
+module.exports = router
diff --git a/Chapter07/adding-a-view-layer/express-views/views/index.ejs b/Chapter07/adding-a-view-layer/express-views/views/index.ejs
new file mode 100644
index 0000000..02aec40
--- /dev/null
+++ b/Chapter07/adding-a-view-layer/express-views/views/index.ejs
@@ -0,0 +1,10 @@
+
+
+ <%= title %>
+
+
+
+ <%= title %>
+ Welcome to <%= title %>
+
+
\ No newline at end of file
diff --git a/Chapter07/adding-a-view-layer/hapi-views/index.js b/Chapter07/adding-a-view-layer/hapi-views/index.js
new file mode 100644
index 0000000..0c6dc74
--- /dev/null
+++ b/Chapter07/adding-a-view-layer/hapi-views/index.js
@@ -0,0 +1,42 @@
+'use strict'
+
+const hapi = require('hapi')
+const inert = require('inert')
+const vision = require('vision')
+const ejs = require('ejs')
+const routes = {
+ index: require('./routes/index'),
+ devStatic: require('./routes/dev-static')
+}
+
+const dev = process.env.NODE_ENV !== 'production'
+const port = process.env.PORT || 3000
+
+const server = new hapi.Server()
+
+server.connection({
+ host: 'localhost',
+ port: port
+})
+
+const plugins = dev ? [vision, inert] : [vision]
+server.register(plugins, start)
+
+function start (err) {
+ if (err) throw err
+
+ server.views({
+ engines: { ejs },
+ relativeTo: __dirname,
+ path: 'views'
+ })
+
+ routes.index(server)
+
+ if (dev) routes.devStatic(server)
+
+ server.start((err) => {
+ if (err) throw err
+ console.log(`Server listening on port ${port}`)
+ })
+}
\ No newline at end of file
diff --git a/Chapter07/adding-a-view-layer/hapi-views/package.json b/Chapter07/adding-a-view-layer/hapi-views/package.json
new file mode 100644
index 0000000..2af54dc
--- /dev/null
+++ b/Chapter07/adding-a-view-layer/hapi-views/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "ejs": "^2.5.6",
+ "hapi": "^16.1.1",
+ "inert": "^4.2.0",
+ "vision": "^4.1.1"
+ }
+}
diff --git a/Chapter07/adding-a-view-layer/hapi-views/public/styles.css b/Chapter07/adding-a-view-layer/hapi-views/public/styles.css
new file mode 100644
index 0000000..28e6f72
--- /dev/null
+++ b/Chapter07/adding-a-view-layer/hapi-views/public/styles.css
@@ -0,0 +1,4 @@
+body {
+ padding: 50px;
+ font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
+}
\ No newline at end of file
diff --git a/Chapter07/adding-a-view-layer/hapi-views/routes/dev-static.js b/Chapter07/adding-a-view-layer/hapi-views/routes/dev-static.js
new file mode 100644
index 0000000..ccc1c7f
--- /dev/null
+++ b/Chapter07/adding-a-view-layer/hapi-views/routes/dev-static.js
@@ -0,0 +1,15 @@
+'use strict'
+
+module.exports = devStatic
+
+function devStatic (server) {
+ server.route({
+ method: 'GET',
+ path: '/{param*}',
+ handler: {
+ directory: {
+ path: 'public'
+ }
+ }
+ })
+}
diff --git a/Chapter07/adding-a-view-layer/hapi-views/routes/index.js b/Chapter07/adding-a-view-layer/hapi-views/routes/index.js
new file mode 100644
index 0000000..8cf3b92
--- /dev/null
+++ b/Chapter07/adding-a-view-layer/hapi-views/routes/index.js
@@ -0,0 +1,14 @@
+'use strict'
+
+module.exports = index
+
+function index (server) {
+ server.route({
+ method: 'GET',
+ path: '/',
+ handler: function (request, reply) {
+ const title = 'Hapi'
+ reply.view('index', {title})
+ }
+ })
+}
diff --git a/Chapter07/adding-a-view-layer/hapi-views/views/index.ejs b/Chapter07/adding-a-view-layer/hapi-views/views/index.ejs
new file mode 100644
index 0000000..02aec40
--- /dev/null
+++ b/Chapter07/adding-a-view-layer/hapi-views/views/index.ejs
@@ -0,0 +1,10 @@
+
+
+ <%= title %>
+
+
+
+ <%= title %>
+ Welcome to <%= title %>
+
+
\ No newline at end of file
diff --git a/Chapter07/adding-a-view-layer/koa-views/index.js b/Chapter07/adding-a-view-layer/koa-views/index.js
new file mode 100644
index 0000000..f1c07fb
--- /dev/null
+++ b/Chapter07/adding-a-view-layer/koa-views/index.js
@@ -0,0 +1,28 @@
+'use strict'
+
+const {join} = require('path')
+const Koa = require('koa')
+const serve = require('koa-static')
+const views = require('koa-views')
+const router = require('koa-router')()
+const index = require('./routes/index')
+
+const app = new Koa()
+const dev = process.env.NODE_ENV !== 'production'
+const port = process.env.PORT || 3000
+
+app.use(views(join(__dirname, 'views'), {
+ extension: 'ejs'
+}))
+
+if (dev) {
+ app.use(serve(join(__dirname, 'public')))
+}
+
+router.use('/', index.routes())
+
+app.use(router.routes())
+
+app.listen(port, () => {
+ console.log(`Server listening on port ${port}`)
+})
\ No newline at end of file
diff --git a/Chapter07/adding-a-view-layer/koa-views/package.json b/Chapter07/adding-a-view-layer/koa-views/package.json
new file mode 100644
index 0000000..1c44556
--- /dev/null
+++ b/Chapter07/adding-a-view-layer/koa-views/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "ejs": "^2.5.6",
+ "koa": "^2.2.0",
+ "koa-router": "^7.1.1",
+ "koa-static": "^3.0.0",
+ "koa-views": "^6.0.1"
+ }
+}
diff --git a/Chapter07/adding-a-view-layer/koa-views/public/styles.css b/Chapter07/adding-a-view-layer/koa-views/public/styles.css
new file mode 100644
index 0000000..28e6f72
--- /dev/null
+++ b/Chapter07/adding-a-view-layer/koa-views/public/styles.css
@@ -0,0 +1,4 @@
+body {
+ padding: 50px;
+ font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
+}
\ No newline at end of file
diff --git a/Chapter07/adding-a-view-layer/koa-views/routes/index.js b/Chapter07/adding-a-view-layer/koa-views/routes/index.js
new file mode 100644
index 0000000..290efe2
--- /dev/null
+++ b/Chapter07/adding-a-view-layer/koa-views/routes/index.js
@@ -0,0 +1,17 @@
+'use strict'
+
+const router = require('koa-router')()
+
+router.get('/', async function (ctx, next) {
+ await next()
+ await ctx.render('index')
+}, async (ctx) => ctx.state = {title: 'Koa'})
+
+
+// simplified:
+// router.get('/', async (ctx, next) => {
+// await ctx.render('index', {title: 'Koa'})
+// })
+
+module.exports = router
+
diff --git a/Chapter07/adding-a-view-layer/koa-views/views/index.ejs b/Chapter07/adding-a-view-layer/koa-views/views/index.ejs
new file mode 100644
index 0000000..02aec40
--- /dev/null
+++ b/Chapter07/adding-a-view-layer/koa-views/views/index.ejs
@@ -0,0 +1,10 @@
+
+
+ <%= title %>
+
+
+
+ <%= title %>
+ Welcome to <%= title %>
+
+
\ No newline at end of file
diff --git a/Chapter07/adding-logging/express-logging/index.js b/Chapter07/adding-logging/express-logging/index.js
new file mode 100644
index 0000000..ddaca05
--- /dev/null
+++ b/Chapter07/adding-logging/express-logging/index.js
@@ -0,0 +1,28 @@
+'use strict'
+
+const {join} = require('path')
+const express = require('express')
+const pino = require('pino')()
+const logger = require('express-pino-logger')({
+ instance: pino
+})
+const index = require('./routes/index')
+
+const app = express()
+const dev = process.env.NODE_ENV !== 'production'
+const port = process.env.PORT || 3000
+
+app.set('views', join(__dirname, 'views'))
+app.set('view engine', 'ejs')
+
+app.use(logger)
+
+if (dev) {
+ app.use(express.static(join(__dirname, 'public')))
+}
+
+app.use('/', index)
+
+app.listen(port, () => {
+ pino.info(`Server listening on port ${port}`)
+})
\ No newline at end of file
diff --git a/Chapter07/adding-logging/express-logging/package.json b/Chapter07/adding-logging/express-logging/package.json
new file mode 100644
index 0000000..cd17c71
--- /dev/null
+++ b/Chapter07/adding-logging/express-logging/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "ejs": "^2.5.6",
+ "express": "^4.15.2",
+ "express-pino-logger": "^2.0.0",
+ "pino": "^4.2.4"
+ }
+}
diff --git a/Chapter07/adding-logging/express-logging/public/styles.css b/Chapter07/adding-logging/express-logging/public/styles.css
new file mode 100644
index 0000000..28e6f72
--- /dev/null
+++ b/Chapter07/adding-logging/express-logging/public/styles.css
@@ -0,0 +1,4 @@
+body {
+ padding: 50px;
+ font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
+}
\ No newline at end of file
diff --git a/Chapter07/adding-logging/express-logging/routes/index.js b/Chapter07/adding-logging/express-logging/routes/index.js
new file mode 100644
index 0000000..8f84469
--- /dev/null
+++ b/Chapter07/adding-logging/express-logging/routes/index.js
@@ -0,0 +1,12 @@
+'use strict'
+
+const {Router} = require('express')
+const router = Router()
+
+router.get('/', function (req, res) {
+ const title = 'Express'
+ req.log.info(`rendering index view with ${title}`)
+ res.render('index', {title: 'Express'})
+})
+
+module.exports = router
diff --git a/Chapter07/adding-logging/express-logging/views/index.ejs b/Chapter07/adding-logging/express-logging/views/index.ejs
new file mode 100644
index 0000000..02aec40
--- /dev/null
+++ b/Chapter07/adding-logging/express-logging/views/index.ejs
@@ -0,0 +1,10 @@
+
+
+ <%= title %>
+
+
+
+ <%= title %>
+ Welcome to <%= title %>
+
+
\ No newline at end of file
diff --git a/Chapter07/adding-logging/express-morgan-logging/index.js b/Chapter07/adding-logging/express-morgan-logging/index.js
new file mode 100644
index 0000000..f14bbcc
--- /dev/null
+++ b/Chapter07/adding-logging/express-morgan-logging/index.js
@@ -0,0 +1,25 @@
+'use strict'
+
+const {join} = require('path')
+const express = require('express')
+const morgan = require('morgan')
+const index = require('./routes/index')
+
+const app = express()
+const dev = process.env.NODE_ENV !== 'production'
+const port = process.env.PORT || 3000
+
+app.set('views', join(__dirname, 'views'))
+app.set('view engine', 'ejs')
+
+app.use(morgan('common'))
+
+if (dev) {
+ app.use(express.static(join(__dirname, 'public')))
+}
+
+app.use('/', index)
+
+app.listen(port, () => {
+ console.log(`Server listening on port ${port}`)
+})
\ No newline at end of file
diff --git a/Chapter07/adding-logging/express-morgan-logging/package.json b/Chapter07/adding-logging/express-morgan-logging/package.json
new file mode 100644
index 0000000..2b282f0
--- /dev/null
+++ b/Chapter07/adding-logging/express-morgan-logging/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "ejs": "^2.5.6",
+ "express": "^4.15.2",
+ "morgan": "^1.8.1"
+ }
+}
diff --git a/Chapter07/adding-logging/express-morgan-logging/public/styles.css b/Chapter07/adding-logging/express-morgan-logging/public/styles.css
new file mode 100644
index 0000000..28e6f72
--- /dev/null
+++ b/Chapter07/adding-logging/express-morgan-logging/public/styles.css
@@ -0,0 +1,4 @@
+body {
+ padding: 50px;
+ font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
+}
\ No newline at end of file
diff --git a/Chapter07/adding-logging/express-morgan-logging/routes/index.js b/Chapter07/adding-logging/express-morgan-logging/routes/index.js
new file mode 100644
index 0000000..64a4c62
--- /dev/null
+++ b/Chapter07/adding-logging/express-morgan-logging/routes/index.js
@@ -0,0 +1,11 @@
+'use strict'
+
+const {Router} = require('express')
+const router = Router()
+
+router.get('/', function (req, res) {
+ const title = 'Express'
+ res.render('index', {title: 'Express'})
+})
+
+module.exports = router
diff --git a/Chapter07/adding-logging/express-morgan-logging/views/index.ejs b/Chapter07/adding-logging/express-morgan-logging/views/index.ejs
new file mode 100644
index 0000000..02aec40
--- /dev/null
+++ b/Chapter07/adding-logging/express-morgan-logging/views/index.ejs
@@ -0,0 +1,10 @@
+
+
+ <%= title %>
+
+
+
+ <%= title %>
+ Welcome to <%= title %>
+
+
\ No newline at end of file
diff --git a/Chapter07/adding-logging/express-pino-debug-logging/index.js b/Chapter07/adding-logging/express-pino-debug-logging/index.js
new file mode 100644
index 0000000..ddaca05
--- /dev/null
+++ b/Chapter07/adding-logging/express-pino-debug-logging/index.js
@@ -0,0 +1,28 @@
+'use strict'
+
+const {join} = require('path')
+const express = require('express')
+const pino = require('pino')()
+const logger = require('express-pino-logger')({
+ instance: pino
+})
+const index = require('./routes/index')
+
+const app = express()
+const dev = process.env.NODE_ENV !== 'production'
+const port = process.env.PORT || 3000
+
+app.set('views', join(__dirname, 'views'))
+app.set('view engine', 'ejs')
+
+app.use(logger)
+
+if (dev) {
+ app.use(express.static(join(__dirname, 'public')))
+}
+
+app.use('/', index)
+
+app.listen(port, () => {
+ pino.info(`Server listening on port ${port}`)
+})
\ No newline at end of file
diff --git a/Chapter07/adding-logging/express-pino-debug-logging/package.json b/Chapter07/adding-logging/express-pino-debug-logging/package.json
new file mode 100644
index 0000000..0b3400b
--- /dev/null
+++ b/Chapter07/adding-logging/express-pino-debug-logging/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "ejs": "^2.5.6",
+ "express": "^4.15.2",
+ "express-pino-logger": "^2.0.0",
+ "pino": "^4.2.4",
+ "pino-debug": "^1.0.3"
+ }
+}
diff --git a/Chapter07/adding-logging/express-pino-debug-logging/public/styles.css b/Chapter07/adding-logging/express-pino-debug-logging/public/styles.css
new file mode 100644
index 0000000..28e6f72
--- /dev/null
+++ b/Chapter07/adding-logging/express-pino-debug-logging/public/styles.css
@@ -0,0 +1,4 @@
+body {
+ padding: 50px;
+ font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
+}
\ No newline at end of file
diff --git a/Chapter07/adding-logging/express-pino-debug-logging/routes/index.js b/Chapter07/adding-logging/express-pino-debug-logging/routes/index.js
new file mode 100644
index 0000000..8f84469
--- /dev/null
+++ b/Chapter07/adding-logging/express-pino-debug-logging/routes/index.js
@@ -0,0 +1,12 @@
+'use strict'
+
+const {Router} = require('express')
+const router = Router()
+
+router.get('/', function (req, res) {
+ const title = 'Express'
+ req.log.info(`rendering index view with ${title}`)
+ res.render('index', {title: 'Express'})
+})
+
+module.exports = router
diff --git a/Chapter07/adding-logging/express-pino-debug-logging/views/index.ejs b/Chapter07/adding-logging/express-pino-debug-logging/views/index.ejs
new file mode 100644
index 0000000..02aec40
--- /dev/null
+++ b/Chapter07/adding-logging/express-pino-debug-logging/views/index.ejs
@@ -0,0 +1,10 @@
+
+
+ <%= title %>
+
+
+
+ <%= title %>
+ Welcome to <%= title %>
+
+
\ No newline at end of file
diff --git a/Chapter07/adding-logging/express-winston-logging/index.js b/Chapter07/adding-logging/express-winston-logging/index.js
new file mode 100644
index 0000000..9db1c91
--- /dev/null
+++ b/Chapter07/adding-logging/express-winston-logging/index.js
@@ -0,0 +1,35 @@
+'use strict'
+
+const express = require('express')
+const {join} = require('path')
+const winston = require('winston')
+const expressWinston = require('express-winston')
+const index = require('./routes/index')
+const logger = new winston.Logger({
+ transports: [
+ new winston.transports.Console({
+ json: true
+ })
+ ]
+})
+
+const app = express()
+const dev = process.env.NODE_ENV !== 'production'
+const port = process.env.PORT || 3000
+
+app.set('views', join(__dirname, 'views'))
+app.set('view engine', 'ejs')
+
+app.use(expressWinston.logger({
+ winstonInstance: logger
+}))
+
+if (dev) {
+ app.use(express.static(join(__dirname, 'public')))
+}
+
+app.use('/', index)
+
+app.listen(port, () => {
+ logger.info(`Server listening on port ${port}`)
+})
\ No newline at end of file
diff --git a/Chapter07/adding-logging/express-winston-logging/package.json b/Chapter07/adding-logging/express-winston-logging/package.json
new file mode 100644
index 0000000..92654b3
--- /dev/null
+++ b/Chapter07/adding-logging/express-winston-logging/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "ejs": "^2.5.6",
+ "express": "^4.15.2",
+ "express-winston": "^2.3.0",
+ "winston": "^2.3.1"
+ }
+}
diff --git a/Chapter07/adding-logging/express-winston-logging/public/styles.css b/Chapter07/adding-logging/express-winston-logging/public/styles.css
new file mode 100644
index 0000000..28e6f72
--- /dev/null
+++ b/Chapter07/adding-logging/express-winston-logging/public/styles.css
@@ -0,0 +1,4 @@
+body {
+ padding: 50px;
+ font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
+}
\ No newline at end of file
diff --git a/Chapter07/adding-logging/express-winston-logging/routes/index.js b/Chapter07/adding-logging/express-winston-logging/routes/index.js
new file mode 100644
index 0000000..0a8ec96
--- /dev/null
+++ b/Chapter07/adding-logging/express-winston-logging/routes/index.js
@@ -0,0 +1,11 @@
+'use strict'
+
+const {Router} = require('express')
+const router = Router()
+
+router.get('/', function(req, res) {
+ const title = 'Express'
+ res.render('index', {title: 'Express'})
+})
+
+module.exports = router
diff --git a/Chapter07/adding-logging/express-winston-logging/views/index.ejs b/Chapter07/adding-logging/express-winston-logging/views/index.ejs
new file mode 100644
index 0000000..02aec40
--- /dev/null
+++ b/Chapter07/adding-logging/express-winston-logging/views/index.ejs
@@ -0,0 +1,10 @@
+
+
+ <%= title %>
+
+
+
+ <%= title %>
+ Welcome to <%= title %>
+
+
\ No newline at end of file
diff --git a/Chapter07/adding-logging/hapi-logging/index.js b/Chapter07/adding-logging/hapi-logging/index.js
new file mode 100644
index 0000000..3ef5961
--- /dev/null
+++ b/Chapter07/adding-logging/hapi-logging/index.js
@@ -0,0 +1,50 @@
+'use strict'
+
+const hapi = require('hapi')
+const inert = require('inert')
+const vision = require('vision')
+const ejs = require('ejs')
+const pino = require('pino')()
+const hapiPino = require('hapi-pino')
+const routes = {
+ index: require('./routes/index'),
+ devStatic: require('./routes/dev-static')
+}
+
+const dev = process.env.NODE_ENV !== 'production'
+const port = process.env.PORT || 3000
+
+const server = new hapi.Server()
+
+server.connection({
+ host: 'localhost',
+ port: port
+})
+
+const plugins = dev ? [{
+ register: hapiPino,
+ options: {instance: pino}
+}, vision, inert] : [{
+ register: hapiPino,
+ options: {instance: pino}
+}, vision]
+
+server.register(plugins, start)
+
+function start (err) {
+ if (err) throw err
+ server.views({
+ engines: { ejs },
+ relativeTo: __dirname,
+ path: 'views'
+ })
+
+ routes.index(server)
+
+ if (dev) routes.devStatic(server)
+
+ server.start((err) => {
+ if (err) throw err
+ server.log(`Server listening on port ${port}`)
+ })
+}
\ No newline at end of file
diff --git a/Chapter07/adding-logging/hapi-logging/package.json b/Chapter07/adding-logging/hapi-logging/package.json
new file mode 100644
index 0000000..2ed7f05
--- /dev/null
+++ b/Chapter07/adding-logging/hapi-logging/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "ejs": "^2.5.6",
+ "hapi": "^16.1.1",
+ "hapi-pino": "^1.4.1",
+ "inert": "^4.2.0",
+ "pino": "^4.2.4",
+ "vision": "^4.1.1"
+ }
+}
diff --git a/Chapter07/adding-logging/hapi-logging/public/styles.css b/Chapter07/adding-logging/hapi-logging/public/styles.css
new file mode 100644
index 0000000..28e6f72
--- /dev/null
+++ b/Chapter07/adding-logging/hapi-logging/public/styles.css
@@ -0,0 +1,4 @@
+body {
+ padding: 50px;
+ font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
+}
\ No newline at end of file
diff --git a/Chapter07/adding-logging/hapi-logging/routes/dev-static.js b/Chapter07/adding-logging/hapi-logging/routes/dev-static.js
new file mode 100644
index 0000000..ccc1c7f
--- /dev/null
+++ b/Chapter07/adding-logging/hapi-logging/routes/dev-static.js
@@ -0,0 +1,15 @@
+'use strict'
+
+module.exports = devStatic
+
+function devStatic (server) {
+ server.route({
+ method: 'GET',
+ path: '/{param*}',
+ handler: {
+ directory: {
+ path: 'public'
+ }
+ }
+ })
+}
diff --git a/Chapter07/adding-logging/hapi-logging/routes/index.js b/Chapter07/adding-logging/hapi-logging/routes/index.js
new file mode 100644
index 0000000..8c61027
--- /dev/null
+++ b/Chapter07/adding-logging/hapi-logging/routes/index.js
@@ -0,0 +1,15 @@
+'use strict'
+
+module.exports = index
+
+function index (server) {
+ server.route({
+ method: 'GET',
+ path: '/',
+ handler: function (request, reply) {
+ const title = 'Hapi'
+ request.logger.info(`rendering index view with ${title}`)
+ reply.view('index', {title})
+ }
+ })
+}
diff --git a/Chapter07/adding-logging/hapi-logging/views/index.ejs b/Chapter07/adding-logging/hapi-logging/views/index.ejs
new file mode 100644
index 0000000..02aec40
--- /dev/null
+++ b/Chapter07/adding-logging/hapi-logging/views/index.ejs
@@ -0,0 +1,10 @@
+
+
+ <%= title %>
+
+
+
+ <%= title %>
+ Welcome to <%= title %>
+
+
\ No newline at end of file
diff --git a/Chapter07/adding-logging/koa-logging/index.js b/Chapter07/adding-logging/koa-logging/index.js
new file mode 100644
index 0000000..1ea62b8
--- /dev/null
+++ b/Chapter07/adding-logging/koa-logging/index.js
@@ -0,0 +1,34 @@
+'use strict'
+
+const {join} = require('path')
+const Koa = require('koa')
+const serve = require('koa-static')
+const views = require('koa-views')
+const router = require('koa-router')()
+const pino = require('pino')()
+const logger = require('koa-pino-logger')({
+ instance: pino
+})
+const index = require('./routes/index')
+
+const app = new Koa()
+const dev = process.env.NODE_ENV !== 'production'
+const port = process.env.PORT || 3000
+
+app.use(views(join(__dirname, 'views'), {
+ extension: 'ejs'
+}))
+
+app.use(logger)
+
+if (dev) {
+ app.use(serve(join(__dirname, 'public')))
+}
+
+router.use('/', index.routes())
+
+app.use(router.routes())
+
+app.listen(port, () => {
+ pino.info(`Server listening on port ${port}`)
+})
\ No newline at end of file
diff --git a/Chapter07/adding-logging/koa-logging/package.json b/Chapter07/adding-logging/koa-logging/package.json
new file mode 100644
index 0000000..c609071
--- /dev/null
+++ b/Chapter07/adding-logging/koa-logging/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "ejs": "^2.5.6",
+ "koa": "^2.2.0",
+ "koa-pino-logger": "^2.1.0",
+ "koa-router": "^7.1.1",
+ "koa-static": "^3.0.0",
+ "koa-views": "^6.0.1",
+ "pino": "^4.2.4"
+ }
+}
diff --git a/Chapter07/adding-logging/koa-logging/public/styles.css b/Chapter07/adding-logging/koa-logging/public/styles.css
new file mode 100644
index 0000000..28e6f72
--- /dev/null
+++ b/Chapter07/adding-logging/koa-logging/public/styles.css
@@ -0,0 +1,4 @@
+body {
+ padding: 50px;
+ font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
+}
\ No newline at end of file
diff --git a/Chapter07/adding-logging/koa-logging/routes/index.js b/Chapter07/adding-logging/koa-logging/routes/index.js
new file mode 100644
index 0000000..fc5a7f6
--- /dev/null
+++ b/Chapter07/adding-logging/koa-logging/routes/index.js
@@ -0,0 +1,19 @@
+'use strict'
+
+const router = require('koa-router')()
+
+router.get('/', async function (ctx, next) {
+ await next()
+ ctx.log.info(`rendering index view with ${ctx.state.title}`)
+ await ctx.render('index')
+}, async (ctx) => ctx.state = {title: 'Koa'})
+
+
+// simplified:
+// router.get('/', async (ctx, next) => {
+// . ctx.log.info(`rendering index view with ${ctx.state.title}`)
+// await ctx.render('index', {title: 'Koa'})
+// })
+
+module.exports = router
+
diff --git a/Chapter07/adding-logging/koa-logging/views/index.ejs b/Chapter07/adding-logging/koa-logging/views/index.ejs
new file mode 100644
index 0000000..02aec40
--- /dev/null
+++ b/Chapter07/adding-logging/koa-logging/views/index.ejs
@@ -0,0 +1,10 @@
+
+
+ <%= title %>
+
+
+
+ <%= title %>
+ Welcome to <%= title %>
+
+
\ No newline at end of file
diff --git a/Chapter07/creating-a-hapi-web-app/app/index.js b/Chapter07/creating-a-hapi-web-app/app/index.js
new file mode 100644
index 0000000..c4d5568
--- /dev/null
+++ b/Chapter07/creating-a-hapi-web-app/app/index.js
@@ -0,0 +1,34 @@
+'use strict'
+
+const hapi = require('hapi')
+const inert = require('inert')
+const routes = {
+ index: require('./routes/index'),
+ devStatic: require('./routes/dev-static')
+}
+
+const dev = process.env.NODE_ENV !== 'production'
+const port = process.env.PORT || 3000
+
+const server = new hapi.Server()
+
+server.connection({
+ host: 'localhost',
+ port: port
+})
+
+if (dev) server.register(inert, start)
+else start()
+
+function start (err) {
+ if (err) throw err
+
+ routes.index(server)
+
+ if (dev) routes.devStatic(server)
+
+ server.start((err) => {
+ if (err) throw err
+ console.log(`Server listening on port ${port}`)
+ })
+}
diff --git a/Chapter07/creating-a-hapi-web-app/app/package.json b/Chapter07/creating-a-hapi-web-app/app/package.json
new file mode 100644
index 0000000..68f1cb3
--- /dev/null
+++ b/Chapter07/creating-a-hapi-web-app/app/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "hapi": "^16.1.1",
+ "inert": "^4.2.0"
+ }
+}
diff --git a/Chapter07/creating-a-hapi-web-app/app/public/styles.css b/Chapter07/creating-a-hapi-web-app/app/public/styles.css
new file mode 100644
index 0000000..28e6f72
--- /dev/null
+++ b/Chapter07/creating-a-hapi-web-app/app/public/styles.css
@@ -0,0 +1,4 @@
+body {
+ padding: 50px;
+ font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
+}
\ No newline at end of file
diff --git a/Chapter07/creating-a-hapi-web-app/app/routes/dev-static.js b/Chapter07/creating-a-hapi-web-app/app/routes/dev-static.js
new file mode 100644
index 0000000..ccc1c7f
--- /dev/null
+++ b/Chapter07/creating-a-hapi-web-app/app/routes/dev-static.js
@@ -0,0 +1,15 @@
+'use strict'
+
+module.exports = devStatic
+
+function devStatic (server) {
+ server.route({
+ method: 'GET',
+ path: '/{param*}',
+ handler: {
+ directory: {
+ path: 'public'
+ }
+ }
+ })
+}
diff --git a/Chapter07/creating-a-hapi-web-app/app/routes/index.js b/Chapter07/creating-a-hapi-web-app/app/routes/index.js
new file mode 100644
index 0000000..cfd5646
--- /dev/null
+++ b/Chapter07/creating-a-hapi-web-app/app/routes/index.js
@@ -0,0 +1,25 @@
+'use strict'
+
+module.exports = index
+
+function index (server) {
+ server.route({
+ method: 'GET',
+ path: '/',
+ handler: function (request, reply) {
+ const title = 'Hapi'
+ reply(`
+
+
+ ${title}
+
+
+
+ ${title}
+ Welcome to ${title}
+
+
+ `)
+ }
+ })
+}
diff --git a/Chapter07/creating-a-hapi-web-app/custom-plugin-app/index.js b/Chapter07/creating-a-hapi-web-app/custom-plugin-app/index.js
new file mode 100644
index 0000000..c969d81
--- /dev/null
+++ b/Chapter07/creating-a-hapi-web-app/custom-plugin-app/index.js
@@ -0,0 +1,36 @@
+'use strict'
+
+const hapi = require('hapi')
+const inert = require('inert')
+const answer = require('./plugins/answer')
+const routes = {
+ index: require('./routes/index'),
+ devStatic: require('./routes/dev-static')
+}
+
+const dev = process.env.NODE_ENV !== 'production'
+const port = process.env.PORT || 3000
+
+const server = new hapi.Server()
+
+server.connection({
+ host: 'localhost',
+ port: port
+})
+
+const plugins = dev ? [answer, inert] : [answer]
+
+server.register(plugins, start)
+
+function start (err) {
+ if (err) throw err
+
+ routes.index(server)
+
+ if (dev) routes.devStatic(server)
+
+ server.start((err) => {
+ if (err) throw err
+ console.log(`Server listening on port ${port}`)
+ })
+}
\ No newline at end of file
diff --git a/Chapter07/creating-a-hapi-web-app/custom-plugin-app/package.json b/Chapter07/creating-a-hapi-web-app/custom-plugin-app/package.json
new file mode 100644
index 0000000..68f1cb3
--- /dev/null
+++ b/Chapter07/creating-a-hapi-web-app/custom-plugin-app/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "hapi": "^16.1.1",
+ "inert": "^4.2.0"
+ }
+}
diff --git a/Chapter07/creating-a-hapi-web-app/custom-plugin-app/plugins/answer.js b/Chapter07/creating-a-hapi-web-app/custom-plugin-app/plugins/answer.js
new file mode 100644
index 0000000..38b4473
--- /dev/null
+++ b/Chapter07/creating-a-hapi-web-app/custom-plugin-app/plugins/answer.js
@@ -0,0 +1,13 @@
+'use strict'
+
+module.exports = answer
+
+function answer (server, options, next) {
+ server.ext('response', (request, reply) => {
+ request.response.header('X-Answer', 42)
+ reply.continue()
+ })
+ next()
+}
+
+answer.attributes = {name: 'answer'}
\ No newline at end of file
diff --git a/Chapter07/creating-a-hapi-web-app/custom-plugin-app/public/styles.css b/Chapter07/creating-a-hapi-web-app/custom-plugin-app/public/styles.css
new file mode 100644
index 0000000..28e6f72
--- /dev/null
+++ b/Chapter07/creating-a-hapi-web-app/custom-plugin-app/public/styles.css
@@ -0,0 +1,4 @@
+body {
+ padding: 50px;
+ font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
+}
\ No newline at end of file
diff --git a/Chapter07/creating-a-hapi-web-app/custom-plugin-app/routes/dev-static.js b/Chapter07/creating-a-hapi-web-app/custom-plugin-app/routes/dev-static.js
new file mode 100644
index 0000000..ccc1c7f
--- /dev/null
+++ b/Chapter07/creating-a-hapi-web-app/custom-plugin-app/routes/dev-static.js
@@ -0,0 +1,15 @@
+'use strict'
+
+module.exports = devStatic
+
+function devStatic (server) {
+ server.route({
+ method: 'GET',
+ path: '/{param*}',
+ handler: {
+ directory: {
+ path: 'public'
+ }
+ }
+ })
+}
diff --git a/Chapter07/creating-a-hapi-web-app/custom-plugin-app/routes/index.js b/Chapter07/creating-a-hapi-web-app/custom-plugin-app/routes/index.js
new file mode 100644
index 0000000..cfd5646
--- /dev/null
+++ b/Chapter07/creating-a-hapi-web-app/custom-plugin-app/routes/index.js
@@ -0,0 +1,25 @@
+'use strict'
+
+module.exports = index
+
+function index (server) {
+ server.route({
+ method: 'GET',
+ path: '/',
+ handler: function (request, reply) {
+ const title = 'Hapi'
+ reply(`
+
+
+ ${title}
+
+
+
+ ${title}
+ Welcome to ${title}
+
+
+ `)
+ }
+ })
+}
diff --git a/Chapter07/creating-a-hapi-web-app/label-app/index.js b/Chapter07/creating-a-hapi-web-app/label-app/index.js
new file mode 100644
index 0000000..dd84a28
--- /dev/null
+++ b/Chapter07/creating-a-hapi-web-app/label-app/index.js
@@ -0,0 +1,46 @@
+'use strict'
+
+const hapi = require('hapi')
+const inert = require('inert')
+const routes = {
+ index: require('./routes/index'),
+ devStatic: require('./routes/dev-static')
+}
+
+const devPort = process.env.DEV_PORT || 3000
+const prodPort = process.env.PORT || 8080
+
+const server = new hapi.Server()
+
+const dev = process.env.NODE_ENV !== 'production'
+
+if (dev) server.connection({
+ host: 'localhost',
+ port: devPort,
+ labels: ['dev', 'staging']
+})
+
+if (!dev) server.connection({
+ host: '0.0.0.0',
+ port: prodPort,
+ labels: 'prod'
+})
+
+server.register({
+ register: inert,
+ select: ['dev', 'staging']
+}, start)
+
+function start (err) {
+ if (err) throw err
+
+ routes.index(server)
+
+ routes.devStatic(server)
+
+ server.start((err) => {
+ if (err) throw err
+ console.log(`Dev/Staging server listening on port ${devPort}`)
+ console.log(`Prod server listening on port ${prodPort}`)
+ })
+}
\ No newline at end of file
diff --git a/Chapter07/creating-a-hapi-web-app/label-app/package.json b/Chapter07/creating-a-hapi-web-app/label-app/package.json
new file mode 100644
index 0000000..68f1cb3
--- /dev/null
+++ b/Chapter07/creating-a-hapi-web-app/label-app/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "hapi": "^16.1.1",
+ "inert": "^4.2.0"
+ }
+}
diff --git a/Chapter07/creating-a-hapi-web-app/label-app/public/styles.css b/Chapter07/creating-a-hapi-web-app/label-app/public/styles.css
new file mode 100644
index 0000000..28e6f72
--- /dev/null
+++ b/Chapter07/creating-a-hapi-web-app/label-app/public/styles.css
@@ -0,0 +1,4 @@
+body {
+ padding: 50px;
+ font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
+}
\ No newline at end of file
diff --git a/Chapter07/creating-a-hapi-web-app/label-app/routes/dev-static.js b/Chapter07/creating-a-hapi-web-app/label-app/routes/dev-static.js
new file mode 100644
index 0000000..de9d6fc
--- /dev/null
+++ b/Chapter07/creating-a-hapi-web-app/label-app/routes/dev-static.js
@@ -0,0 +1,15 @@
+'use strict'
+
+module.exports = devStatic
+
+function devStatic (server) {
+ server.select(['dev', 'staging']).route({
+ method: 'GET',
+ path: '/{param*}',
+ handler: {
+ directory: {
+ path: 'public'
+ }
+ }
+ })
+}
diff --git a/Chapter07/creating-a-hapi-web-app/label-app/routes/index.js b/Chapter07/creating-a-hapi-web-app/label-app/routes/index.js
new file mode 100644
index 0000000..cfd5646
--- /dev/null
+++ b/Chapter07/creating-a-hapi-web-app/label-app/routes/index.js
@@ -0,0 +1,25 @@
+'use strict'
+
+module.exports = index
+
+function index (server) {
+ server.route({
+ method: 'GET',
+ path: '/',
+ handler: function (request, reply) {
+ const title = 'Hapi'
+ reply(`
+
+
+ ${title}
+
+
+
+ ${title}
+ Welcome to ${title}
+
+
+ `)
+ }
+ })
+}
diff --git a/Chapter07/creating-a-koa-web-app/app/index.js b/Chapter07/creating-a-koa-web-app/app/index.js
new file mode 100644
index 0000000..caa4ee2
--- /dev/null
+++ b/Chapter07/creating-a-koa-web-app/app/index.js
@@ -0,0 +1,23 @@
+'use strict'
+
+const {join} = require('path')
+const Koa = require('koa')
+const serve = require('koa-static')
+const router = require('koa-router')()
+const index = require('./routes/index')
+
+const app = new Koa()
+const dev = process.env.NODE_ENV !== 'production'
+const port = process.env.PORT || 3000
+
+if (dev) {
+ app.use(serve(join(__dirname, 'public')))
+}
+
+router.use('/', index.routes())
+
+app.use(router.routes())
+
+app.listen(port, () => {
+ console.log(`Server listening on port ${port}`)
+})
diff --git a/Chapter07/creating-a-koa-web-app/app/package.json b/Chapter07/creating-a-koa-web-app/app/package.json
new file mode 100644
index 0000000..bc15d27
--- /dev/null
+++ b/Chapter07/creating-a-koa-web-app/app/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "koa": "^2.2.0",
+ "koa-router": "^7.1.1",
+ "koa-static": "^3.0.0"
+ }
+}
diff --git a/Chapter07/creating-a-koa-web-app/app/public/styles.css b/Chapter07/creating-a-koa-web-app/app/public/styles.css
new file mode 100644
index 0000000..28e6f72
--- /dev/null
+++ b/Chapter07/creating-a-koa-web-app/app/public/styles.css
@@ -0,0 +1,4 @@
+body {
+ padding: 50px;
+ font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
+}
\ No newline at end of file
diff --git a/Chapter07/creating-a-koa-web-app/app/routes/index.js b/Chapter07/creating-a-koa-web-app/app/routes/index.js
new file mode 100644
index 0000000..76287b4
--- /dev/null
+++ b/Chapter07/creating-a-koa-web-app/app/routes/index.js
@@ -0,0 +1,22 @@
+'use strict'
+
+const router = require('koa-router')()
+
+router.get('/', async function (ctx, next) {
+ await next()
+ const { title } = ctx.state
+ ctx.body = `
+
+
+ ${title}
+
+
+
+ ${title}
+ Welcome to ${title}
+
+
+ `
+}, async (ctx) => ctx.state = {title: 'Koa'})
+
+module.exports = router
diff --git a/Chapter07/creating-a-koa-web-app/async-ops-app/index.js b/Chapter07/creating-a-koa-web-app/async-ops-app/index.js
new file mode 100644
index 0000000..caa4ee2
--- /dev/null
+++ b/Chapter07/creating-a-koa-web-app/async-ops-app/index.js
@@ -0,0 +1,23 @@
+'use strict'
+
+const {join} = require('path')
+const Koa = require('koa')
+const serve = require('koa-static')
+const router = require('koa-router')()
+const index = require('./routes/index')
+
+const app = new Koa()
+const dev = process.env.NODE_ENV !== 'production'
+const port = process.env.PORT || 3000
+
+if (dev) {
+ app.use(serve(join(__dirname, 'public')))
+}
+
+router.use('/', index.routes())
+
+app.use(router.routes())
+
+app.listen(port, () => {
+ console.log(`Server listening on port ${port}`)
+})
diff --git a/Chapter07/creating-a-koa-web-app/async-ops-app/package.json b/Chapter07/creating-a-koa-web-app/async-ops-app/package.json
new file mode 100644
index 0000000..bc15d27
--- /dev/null
+++ b/Chapter07/creating-a-koa-web-app/async-ops-app/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "koa": "^2.2.0",
+ "koa-router": "^7.1.1",
+ "koa-static": "^3.0.0"
+ }
+}
diff --git a/Chapter07/creating-a-koa-web-app/async-ops-app/public/styles.css b/Chapter07/creating-a-koa-web-app/async-ops-app/public/styles.css
new file mode 100644
index 0000000..28e6f72
--- /dev/null
+++ b/Chapter07/creating-a-koa-web-app/async-ops-app/public/styles.css
@@ -0,0 +1,4 @@
+body {
+ padding: 50px;
+ font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
+}
\ No newline at end of file
diff --git a/Chapter07/creating-a-koa-web-app/async-ops-app/routes/index.js b/Chapter07/creating-a-koa-web-app/async-ops-app/routes/index.js
new file mode 100644
index 0000000..513100f
--- /dev/null
+++ b/Chapter07/creating-a-koa-web-app/async-ops-app/routes/index.js
@@ -0,0 +1,25 @@
+'use strict'
+
+const router = require('koa-router')()
+
+router.get('/', async function (ctx, next) {
+ const title = await pretendDbLookup('title')
+ ctx.body = `
+
+
+ ${title}
+
+
+
+ ${title}
+ Welcome to ${title}
+
+
+ `
+})
+
+function pretendDbLookup () {
+ return Promise.resolve('Koa')
+}
+
+module.exports = router
diff --git a/Chapter07/creating-a-koa-web-app/custom-middleware-app/index.js b/Chapter07/creating-a-koa-web-app/custom-middleware-app/index.js
new file mode 100644
index 0000000..c77a427
--- /dev/null
+++ b/Chapter07/creating-a-koa-web-app/custom-middleware-app/index.js
@@ -0,0 +1,27 @@
+'use strict'
+
+const Koa = require('koa')
+const serve = require('koa-static')
+const router = require('koa-router')()
+const {join} = require('path')
+const index = require('./routes/index')
+const answer = require('./middleware/answer')
+
+const app = new Koa()
+const dev = process.env.NODE_ENV !== 'production'
+const port = process.env.PORT || 3000
+
+app.use(answer())
+
+if (dev) {
+ app.use(serve(join(__dirname, 'public')))
+}
+
+router.use('/', index.routes(), index.allowedMethods())
+
+app.use(router.routes())
+app.use(router.allowedMethods())
+
+app.listen(port, () => {
+ console.log(`Server listening on port ${port}`)
+})
\ No newline at end of file
diff --git a/Chapter07/creating-a-koa-web-app/custom-middleware-app/middleware/answer.js b/Chapter07/creating-a-koa-web-app/custom-middleware-app/middleware/answer.js
new file mode 100644
index 0000000..7766dd6
--- /dev/null
+++ b/Chapter07/creating-a-koa-web-app/custom-middleware-app/middleware/answer.js
@@ -0,0 +1,10 @@
+'use strict'
+
+module.exports = answer
+
+function answer () {
+ return async (ctx, next) => {
+ ctx.set('X-Answer', 42)
+ await next()
+ }
+}
diff --git a/Chapter07/creating-a-koa-web-app/custom-middleware-app/package.json b/Chapter07/creating-a-koa-web-app/custom-middleware-app/package.json
new file mode 100644
index 0000000..bc15d27
--- /dev/null
+++ b/Chapter07/creating-a-koa-web-app/custom-middleware-app/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "koa": "^2.2.0",
+ "koa-router": "^7.1.1",
+ "koa-static": "^3.0.0"
+ }
+}
diff --git a/Chapter07/creating-a-koa-web-app/custom-middleware-app/public/styles.css b/Chapter07/creating-a-koa-web-app/custom-middleware-app/public/styles.css
new file mode 100644
index 0000000..28e6f72
--- /dev/null
+++ b/Chapter07/creating-a-koa-web-app/custom-middleware-app/public/styles.css
@@ -0,0 +1,4 @@
+body {
+ padding: 50px;
+ font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
+}
\ No newline at end of file
diff --git a/Chapter07/creating-a-koa-web-app/custom-middleware-app/routes/index.js b/Chapter07/creating-a-koa-web-app/custom-middleware-app/routes/index.js
new file mode 100644
index 0000000..76287b4
--- /dev/null
+++ b/Chapter07/creating-a-koa-web-app/custom-middleware-app/routes/index.js
@@ -0,0 +1,22 @@
+'use strict'
+
+const router = require('koa-router')()
+
+router.get('/', async function (ctx, next) {
+ await next()
+ const { title } = ctx.state
+ ctx.body = `
+
+
+ ${title}
+
+
+
+ ${title}
+ Welcome to ${title}
+
+
+ `
+}, async (ctx) => ctx.state = {title: 'Koa'})
+
+module.exports = router
diff --git a/Chapter07/creating-an-express-web-app/app/index.js b/Chapter07/creating-an-express-web-app/app/index.js
new file mode 100644
index 0000000..75ee4cb
--- /dev/null
+++ b/Chapter07/creating-an-express-web-app/app/index.js
@@ -0,0 +1,19 @@
+'use strict'
+
+const {join} = require('path')
+const express = require('express')
+const index = require('./routes/index')
+
+const app = express()
+const dev = process.env.NODE_ENV !== 'production'
+const port = process.env.PORT || 3000
+
+if (dev) {
+ app.use(express.static(join(__dirname, 'public')))
+}
+
+app.use('/', index)
+
+app.listen(port, () => {
+ console.log(`Server listening on port ${port}`)
+})
\ No newline at end of file
diff --git a/Chapter07/creating-an-express-web-app/app/package.json b/Chapter07/creating-an-express-web-app/app/package.json
new file mode 100644
index 0000000..8711bc5
--- /dev/null
+++ b/Chapter07/creating-an-express-web-app/app/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "express": "^4.15.2"
+ }
+}
diff --git a/Chapter07/creating-an-express-web-app/app/public/styles.css b/Chapter07/creating-an-express-web-app/app/public/styles.css
new file mode 100644
index 0000000..28e6f72
--- /dev/null
+++ b/Chapter07/creating-an-express-web-app/app/public/styles.css
@@ -0,0 +1,4 @@
+body {
+ padding: 50px;
+ font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
+}
\ No newline at end of file
diff --git a/Chapter07/creating-an-express-web-app/app/routes/index.js b/Chapter07/creating-an-express-web-app/app/routes/index.js
new file mode 100644
index 0000000..a33053d
--- /dev/null
+++ b/Chapter07/creating-an-express-web-app/app/routes/index.js
@@ -0,0 +1,22 @@
+'use strict'
+
+const {Router} = require('express')
+const router = Router()
+
+router.get('/', function (req, res) {
+ const title = 'Express'
+ res.send(`
+
+
+ ${title}
+
+
+
+ ${title}
+ Welcome to ${title}
+
+
+ `)
+})
+
+module.exports = router
diff --git a/Chapter07/creating-an-express-web-app/custom-middleware-app/index.js b/Chapter07/creating-an-express-web-app/custom-middleware-app/index.js
new file mode 100644
index 0000000..753a31f
--- /dev/null
+++ b/Chapter07/creating-an-express-web-app/custom-middleware-app/index.js
@@ -0,0 +1,26 @@
+'use strict'
+
+const express = require('express')
+const {join} = require('path')
+const index = require('./routes/index')
+const answer = require('./middleware/answer')
+
+const app = express()
+const dev = process.env.NODE_ENV !== 'production'
+const port = process.env.PORT || 3000
+
+app.use(answer())
+
+if (dev) {
+ app.use(express.static(join(__dirname, 'public')))
+}
+
+app.use('/', index)
+
+app.use((req, res, next) => {
+ next(Object.assign(Error('Not Found'), {status: 404}))
+})
+
+app.listen(port, () => {
+ console.log(`Server listening on port ${port}`)
+})
\ No newline at end of file
diff --git a/Chapter07/creating-an-express-web-app/custom-middleware-app/middleware/answer.js b/Chapter07/creating-an-express-web-app/custom-middleware-app/middleware/answer.js
new file mode 100644
index 0000000..e7dd3d2
--- /dev/null
+++ b/Chapter07/creating-an-express-web-app/custom-middleware-app/middleware/answer.js
@@ -0,0 +1,10 @@
+'use strict'
+
+module.exports = answer
+
+function answer () {
+ return (req, res, next) => {
+ res.setHeader('X-Answer', 42)
+ next()
+ }
+}
\ No newline at end of file
diff --git a/Chapter07/creating-an-express-web-app/custom-middleware-app/package.json b/Chapter07/creating-an-express-web-app/custom-middleware-app/package.json
new file mode 100644
index 0000000..8711bc5
--- /dev/null
+++ b/Chapter07/creating-an-express-web-app/custom-middleware-app/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "express": "^4.15.2"
+ }
+}
diff --git a/Chapter07/creating-an-express-web-app/custom-middleware-app/public/styles.css b/Chapter07/creating-an-express-web-app/custom-middleware-app/public/styles.css
new file mode 100644
index 0000000..28e6f72
--- /dev/null
+++ b/Chapter07/creating-an-express-web-app/custom-middleware-app/public/styles.css
@@ -0,0 +1,4 @@
+body {
+ padding: 50px;
+ font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
+}
\ No newline at end of file
diff --git a/Chapter07/creating-an-express-web-app/custom-middleware-app/routes/index.js b/Chapter07/creating-an-express-web-app/custom-middleware-app/routes/index.js
new file mode 100644
index 0000000..c1ab84d
--- /dev/null
+++ b/Chapter07/creating-an-express-web-app/custom-middleware-app/routes/index.js
@@ -0,0 +1,20 @@
+const {Router} = require('express')
+const router = Router()
+
+router.get('/', function(req, res, next) {
+ const title = 'Express'
+ res.send(`
+
+
+ ${title}
+
+
+
+ ${title}
+ Welcome to ${title}
+
+
+ `)
+})
+
+module.exports = router
diff --git a/Chapter07/creating-an-express-web-app/params-postable-app/index.js b/Chapter07/creating-an-express-web-app/params-postable-app/index.js
new file mode 100644
index 0000000..555a95d
--- /dev/null
+++ b/Chapter07/creating-an-express-web-app/params-postable-app/index.js
@@ -0,0 +1,22 @@
+'use strict'
+
+const {join} = require('path')
+const express = require('express')
+const bodyParser = require('body-parser')
+const index = require('./routes/index')
+
+const app = express()
+const dev = process.env.NODE_ENV !== 'production'
+const port = process.env.PORT || 3000
+
+app.use(bodyParser.urlencoded({extended: false}))
+
+if (dev) {
+ app.use(express.static(join(__dirname, 'public')))
+}
+
+app.use('/', index)
+
+app.listen(port, () => {
+ console.log(`Server listening on port ${port}`)
+})
\ No newline at end of file
diff --git a/Chapter07/creating-an-express-web-app/params-postable-app/package.json b/Chapter07/creating-an-express-web-app/params-postable-app/package.json
new file mode 100644
index 0000000..6be7c1c
--- /dev/null
+++ b/Chapter07/creating-an-express-web-app/params-postable-app/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "body-parser": "^1.17.1",
+ "express": "^4.15.2"
+ }
+}
diff --git a/Chapter07/creating-an-express-web-app/params-postable-app/public/styles.css b/Chapter07/creating-an-express-web-app/params-postable-app/public/styles.css
new file mode 100644
index 0000000..28e6f72
--- /dev/null
+++ b/Chapter07/creating-an-express-web-app/params-postable-app/public/styles.css
@@ -0,0 +1,4 @@
+body {
+ padding: 50px;
+ font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
+}
\ No newline at end of file
diff --git a/Chapter07/creating-an-express-web-app/params-postable-app/routes/index.js b/Chapter07/creating-an-express-web-app/params-postable-app/routes/index.js
new file mode 100644
index 0000000..d33985f
--- /dev/null
+++ b/Chapter07/creating-an-express-web-app/params-postable-app/routes/index.js
@@ -0,0 +1,35 @@
+'use strict'
+
+const {Router} = require('express')
+const router = Router()
+
+router.get('/:name?', function (req, res) {
+ const title = 'Express'
+ // CAUTION: never place user input
+ // directly into HTML output in production
+ // without sanitizing it first. Otherwise, we make
+ // ourselves vulnerable to XSS attacks.
+ // See Chapter 8 Dealing with Security for details
+ const name = req.params.name
+ res.send(`
+
+
+ ${title}
+
+
+
+ ${title}
+ Welcome to ${title}${name ? `, ${name}.` : ''}
+
+
+
+ `)
+})
+
+router.post('/data', function (req, res) {
+ res.redirect(`/${req.body.name}`)
+})
+
+module.exports = router
diff --git a/Chapter07/implementing-authentication/express-authentication/index.js b/Chapter07/implementing-authentication/express-authentication/index.js
new file mode 100644
index 0000000..4fd5bb9
--- /dev/null
+++ b/Chapter07/implementing-authentication/express-authentication/index.js
@@ -0,0 +1,40 @@
+'use strict'
+
+const {join} = require('path')
+const express = require('express')
+const pino = require('pino')()
+const logger = require('express-pino-logger')({
+ instance: pino
+})
+const session = require('express-session')
+const bodyParser = require('body-parser')
+const index = require('./routes/index')
+const auth = require('./routes/auth')
+
+const app = express()
+const dev = process.env.NODE_ENV !== 'production'
+const port = process.env.PORT || 3000
+
+app.set('views', join(__dirname, 'views'))
+app.set('view engine', 'ejs')
+if (!dev) app.set('trust proxy', 1)
+
+app.use(logger)
+app.use(session({
+ secret: 'I like pies',
+ resave: false,
+ saveUninitialized: false,
+ cookie: {secure: !dev}
+}))
+app.use(bodyParser.urlencoded({extended: false}))
+
+if (dev) {
+ app.use(express.static(join(__dirname, 'public')))
+}
+
+app.use('/', index)
+app.use('/auth', auth)
+
+app.listen(port, () => {
+ pino.info(`Server listening on port ${port}`)
+})
\ No newline at end of file
diff --git a/Chapter07/implementing-authentication/express-authentication/package.json b/Chapter07/implementing-authentication/express-authentication/package.json
new file mode 100644
index 0000000..20becd0
--- /dev/null
+++ b/Chapter07/implementing-authentication/express-authentication/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "body-parser": "^1.17.1",
+ "ejs": "^2.5.6",
+ "express": "^4.15.2",
+ "express-pino-logger": "^2.0.0",
+ "express-session": "^1.15.2",
+ "pino": "^4.2.4"
+ }
+}
diff --git a/Chapter07/implementing-authentication/express-authentication/public/styles.css b/Chapter07/implementing-authentication/express-authentication/public/styles.css
new file mode 100644
index 0000000..28e6f72
--- /dev/null
+++ b/Chapter07/implementing-authentication/express-authentication/public/styles.css
@@ -0,0 +1,4 @@
+body {
+ padding: 50px;
+ font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
+}
\ No newline at end of file
diff --git a/Chapter07/implementing-authentication/express-authentication/routes/auth.js b/Chapter07/implementing-authentication/express-authentication/routes/auth.js
new file mode 100644
index 0000000..ee99e82
--- /dev/null
+++ b/Chapter07/implementing-authentication/express-authentication/routes/auth.js
@@ -0,0 +1,34 @@
+'use strict'
+
+const { Router } = require('express')
+const router = Router()
+
+router.get('/login', function (req, res, next) {
+ res.render('login', {fail: false})
+ next()
+})
+
+router.post('/login', function (req, res, next) {
+ if (req.session.user) {
+ res.redirect('/')
+ next()
+ return
+ }
+ if (req.body.un === 'dave' && req.body.pw === 'ncb') {
+ req.session.user = {name: req.body.un}
+ res.redirect('/')
+ next()
+ return
+ }
+
+ res.render('login', {fail: true})
+
+ next()
+})
+
+router.get('/logout', function (req, res, next) {
+ req.session.user = null
+ res.redirect('/')
+})
+
+module.exports = router
diff --git a/Chapter07/implementing-authentication/express-authentication/routes/index.js b/Chapter07/implementing-authentication/express-authentication/routes/index.js
new file mode 100644
index 0000000..e1ddd04
--- /dev/null
+++ b/Chapter07/implementing-authentication/express-authentication/routes/index.js
@@ -0,0 +1,13 @@
+'use strict'
+
+const {Router} = require('express')
+const router = Router()
+
+router.get('/', function (req, res) {
+ const title = 'Express'
+ req.log.info(`rendering index view with ${title}`)
+ const user = req.session.user
+ res.render('index', {title, user})
+})
+
+module.exports = router
diff --git a/Chapter07/implementing-authentication/express-authentication/views/index.ejs b/Chapter07/implementing-authentication/express-authentication/views/index.ejs
new file mode 100644
index 0000000..6d52625
--- /dev/null
+++ b/Chapter07/implementing-authentication/express-authentication/views/index.ejs
@@ -0,0 +1,16 @@
+
+
+ <%= title %>
+
+
+
+ <%= title %>
+ Welcome to <%= title %>
+ <% if (user) { %>
+ Hi <%= user.name %>!
+ Logout
+ <% } else { %>
+ Login
+ <% } %>
+
+
\ No newline at end of file
diff --git a/Chapter07/implementing-authentication/express-authentication/views/login.ejs b/Chapter07/implementing-authentication/express-authentication/views/login.ejs
new file mode 100644
index 0000000..3adbd80
--- /dev/null
+++ b/Chapter07/implementing-authentication/express-authentication/views/login.ejs
@@ -0,0 +1,17 @@
+
+
+ Login
+
+
+
+ Login
+ <% if (fail) { %>
+ Try Again
+ <% } %>
+
+
+
\ No newline at end of file
diff --git a/Chapter07/implementing-authentication/hapi-authentication/index.js b/Chapter07/implementing-authentication/hapi-authentication/index.js
new file mode 100644
index 0000000..dceebc8
--- /dev/null
+++ b/Chapter07/implementing-authentication/hapi-authentication/index.js
@@ -0,0 +1,69 @@
+'use strict'
+
+const hapi = require('hapi')
+const inert = require('inert')
+const vision = require('vision')
+const ejs = require('ejs')
+const pino = require('pino')()
+const hapiPino = require('hapi-pino')
+const yar = require('yar')
+const routes = {
+ index: require('./routes/index'),
+ auth: require('./routes/auth'),
+ devStatic: require('./routes/dev-static')
+}
+
+const dev = process.env.NODE_ENV !== 'production'
+const port = process.env.PORT || 3000
+
+const server = new hapi.Server()
+
+server.connection({
+ host: '127.0.0.1',
+ port: port
+})
+
+const plugins = dev ? [{
+ register: hapiPino,
+ options: {instance: pino}
+}, {
+ register: yar,
+ options: {
+ cookieOptions: {
+ password: 'I really really really like pies',
+ isSecure: false
+ }
+ }
+}, vision, inert] : [{
+ register: hapiPino,
+ options: {instance: pino}
+}, {
+ register: yar,
+ options: {
+ cookieOptions: {
+ password: 'something more secure than a bit about pies',
+ isSecure: true
+ }
+ }
+}, vision]
+
+server.register(plugins, start)
+
+function start (err) {
+ if (err) throw err
+ server.views({
+ engines: { ejs },
+ relativeTo: __dirname,
+ path: 'views'
+ })
+
+ routes.index(server)
+ routes.auth(server)
+
+ if (dev) routes.devStatic(server)
+
+ server.start((err) => {
+ if (err) throw err
+ server.log(`Server listening on port ${port}`)
+ })
+}
\ No newline at end of file
diff --git a/Chapter07/implementing-authentication/hapi-authentication/package.json b/Chapter07/implementing-authentication/hapi-authentication/package.json
new file mode 100644
index 0000000..77e03f9
--- /dev/null
+++ b/Chapter07/implementing-authentication/hapi-authentication/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "ejs": "^2.5.6",
+ "hapi": "^16.1.1",
+ "hapi-pino": "^1.4.1",
+ "inert": "^4.2.0",
+ "pino": "^4.2.4",
+ "vision": "^4.1.1",
+ "yar": "^8.1.2"
+ }
+}
diff --git a/Chapter07/implementing-authentication/hapi-authentication/public/styles.css b/Chapter07/implementing-authentication/hapi-authentication/public/styles.css
new file mode 100644
index 0000000..28e6f72
--- /dev/null
+++ b/Chapter07/implementing-authentication/hapi-authentication/public/styles.css
@@ -0,0 +1,4 @@
+body {
+ padding: 50px;
+ font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
+}
\ No newline at end of file
diff --git a/Chapter07/implementing-authentication/hapi-authentication/routes/auth.js b/Chapter07/implementing-authentication/hapi-authentication/routes/auth.js
new file mode 100644
index 0000000..aad7c02
--- /dev/null
+++ b/Chapter07/implementing-authentication/hapi-authentication/routes/auth.js
@@ -0,0 +1,41 @@
+'use strict'
+
+module.exports = auth
+
+function auth (server) {
+
+ server.route({
+ method: ['GET', 'POST'],
+ path: '/auth/login',
+ handler: function (request, reply) {
+ if (request.auth.isAuthenticated) {
+ reply.redirect('/');
+ return
+ }
+
+ if (request.method === 'get') {
+ reply.view('login', {fail: false})
+ return
+ }
+
+ if (request.method === 'post') {
+ if (request.payload.un === 'dave' && request.payload.pw === 'ncb') {
+ request.yar.set('user', {name: request.payload.un})
+ reply.redirect('/')
+ } else {
+ reply.view('login', {fail: true})
+ }
+ }
+ }
+ })
+
+
+ server.route({
+ method: 'GET',
+ path: '/auth/logout',
+ handler: function (request, reply) {
+ request.yar.reset()
+ reply.redirect('/')
+ }
+ })
+}
\ No newline at end of file
diff --git a/Chapter07/implementing-authentication/hapi-authentication/routes/dev-static.js b/Chapter07/implementing-authentication/hapi-authentication/routes/dev-static.js
new file mode 100644
index 0000000..ccc1c7f
--- /dev/null
+++ b/Chapter07/implementing-authentication/hapi-authentication/routes/dev-static.js
@@ -0,0 +1,15 @@
+'use strict'
+
+module.exports = devStatic
+
+function devStatic (server) {
+ server.route({
+ method: 'GET',
+ path: '/{param*}',
+ handler: {
+ directory: {
+ path: 'public'
+ }
+ }
+ })
+}
diff --git a/Chapter07/implementing-authentication/hapi-authentication/routes/index.js b/Chapter07/implementing-authentication/hapi-authentication/routes/index.js
new file mode 100644
index 0000000..314c961
--- /dev/null
+++ b/Chapter07/implementing-authentication/hapi-authentication/routes/index.js
@@ -0,0 +1,16 @@
+'use strict'
+
+module.exports = index
+
+function index (server) {
+ server.route({
+ method: 'GET',
+ path: '/',
+ handler: function (request, reply) {
+ const title = 'Hapi'
+ const user = request.yar.get('user')
+ request.logger.info(`rendering index view with ${title}`)
+ reply.view('index', {title, user})
+ }
+ })
+}
diff --git a/Chapter07/implementing-authentication/hapi-authentication/views/index.ejs b/Chapter07/implementing-authentication/hapi-authentication/views/index.ejs
new file mode 100644
index 0000000..6d52625
--- /dev/null
+++ b/Chapter07/implementing-authentication/hapi-authentication/views/index.ejs
@@ -0,0 +1,16 @@
+
+
+ <%= title %>
+
+
+
+ <%= title %>
+ Welcome to <%= title %>
+ <% if (user) { %>
+ Hi <%= user.name %>!
+ Logout
+ <% } else { %>
+ Login
+ <% } %>
+
+
\ No newline at end of file
diff --git a/Chapter07/implementing-authentication/hapi-authentication/views/login.ejs b/Chapter07/implementing-authentication/hapi-authentication/views/login.ejs
new file mode 100644
index 0000000..3adbd80
--- /dev/null
+++ b/Chapter07/implementing-authentication/hapi-authentication/views/login.ejs
@@ -0,0 +1,17 @@
+
+
+ Login
+
+
+
+ Login
+ <% if (fail) { %>
+ Try Again
+ <% } %>
+
+
+
\ No newline at end of file
diff --git a/Chapter07/implementing-authentication/koa-authentication/index.js b/Chapter07/implementing-authentication/koa-authentication/index.js
new file mode 100644
index 0000000..3ef5e03
--- /dev/null
+++ b/Chapter07/implementing-authentication/koa-authentication/index.js
@@ -0,0 +1,43 @@
+'use strict'
+
+const {join} = require('path')
+const Koa = require('koa')
+const serve = require('koa-static')
+const views = require('koa-views')
+const router = require('koa-router')()
+const bodyParser = require('koa-bodyparser')
+const session = require('koa-generic-session')
+const pino = require('pino')()
+const logger = require('koa-pino-logger')({
+ instance: pino
+})
+const index = require('./routes/index')
+const auth = require('./routes/auth')
+
+const app = new Koa()
+const dev = process.env.NODE_ENV !== 'production'
+const port = process.env.PORT || 3000
+
+app.keys = ['koa has integrated secret management']
+
+app.use(views(join(__dirname, 'views'), {
+ extension: 'ejs'
+}))
+
+app.use(logger)
+app.use(session())
+app.use(bodyParser())
+
+
+if (dev) {
+ app.use(serve(join(__dirname, 'public')))
+}
+
+router.use('/', index.routes())
+router.use('/auth', auth.routes())
+
+app.use(router.routes())
+
+app.listen(port, () => {
+ pino.info(`Server listening on port ${port}`)
+})
\ No newline at end of file
diff --git a/Chapter07/implementing-authentication/koa-authentication/package.json b/Chapter07/implementing-authentication/koa-authentication/package.json
new file mode 100644
index 0000000..e341bf8
--- /dev/null
+++ b/Chapter07/implementing-authentication/koa-authentication/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "ejs": "^2.5.6",
+ "koa": "^2.2.0",
+ "koa-bodyparser": "^4.2.0",
+ "koa-generic-session": "^1.11.5",
+ "koa-pino-logger": "^2.1.0",
+ "koa-router": "^7.1.1",
+ "koa-static": "^3.0.0",
+ "koa-views": "^6.0.1",
+ "pino": "^4.2.4"
+ }
+}
diff --git a/Chapter07/implementing-authentication/koa-authentication/public/styles.css b/Chapter07/implementing-authentication/koa-authentication/public/styles.css
new file mode 100644
index 0000000..28e6f72
--- /dev/null
+++ b/Chapter07/implementing-authentication/koa-authentication/public/styles.css
@@ -0,0 +1,4 @@
+body {
+ padding: 50px;
+ font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
+}
\ No newline at end of file
diff --git a/Chapter07/implementing-authentication/koa-authentication/routes/auth.js b/Chapter07/implementing-authentication/koa-authentication/routes/auth.js
new file mode 100644
index 0000000..be7d8a2
--- /dev/null
+++ b/Chapter07/implementing-authentication/koa-authentication/routes/auth.js
@@ -0,0 +1,30 @@
+'use strict'
+
+const router = require('koa-router')()
+
+router.get('/login', async (ctx) => {
+ await ctx.render('login', {fail: false})
+})
+
+router.post('/login', async (ctx) => {
+ const { session, request } = ctx
+ const { body } = request
+ if (session.user) {
+ ctx.redirect('/')
+ return
+ }
+ if (body.un === 'dave' && body.pw === 'ncb') {
+ session.user = {name: body.un}
+ ctx.redirect('/')
+ return
+ }
+
+ await ctx.render('login', {fail: true})
+})
+
+router.get('/logout', async (ctx, next) => {
+ ctx.session.user = null
+ ctx.redirect('/')
+})
+
+module.exports = router
\ No newline at end of file
diff --git a/Chapter07/implementing-authentication/koa-authentication/routes/index.js b/Chapter07/implementing-authentication/koa-authentication/routes/index.js
new file mode 100644
index 0000000..5edb7c5
--- /dev/null
+++ b/Chapter07/implementing-authentication/koa-authentication/routes/index.js
@@ -0,0 +1,13 @@
+'use strict'
+
+const router = require('koa-router')()
+
+router.get('/', async function (ctx) {
+ const title = 'Koa'
+ ctx.log.info(`rendering index view with ${title}`)
+ const user = ctx.session.user
+ await ctx.render('index', {title, user})
+})
+
+module.exports = router
+
diff --git a/Chapter07/implementing-authentication/koa-authentication/views/index.ejs b/Chapter07/implementing-authentication/koa-authentication/views/index.ejs
new file mode 100644
index 0000000..6d52625
--- /dev/null
+++ b/Chapter07/implementing-authentication/koa-authentication/views/index.ejs
@@ -0,0 +1,16 @@
+
+
+ <%= title %>
+
+
+
+ <%= title %>
+ Welcome to <%= title %>
+ <% if (user) { %>
+ Hi <%= user.name %>!
+ Logout
+ <% } else { %>
+ Login
+ <% } %>
+
+
\ No newline at end of file
diff --git a/Chapter07/implementing-authentication/koa-authentication/views/login.ejs b/Chapter07/implementing-authentication/koa-authentication/views/login.ejs
new file mode 100644
index 0000000..3adbd80
--- /dev/null
+++ b/Chapter07/implementing-authentication/koa-authentication/views/login.ejs
@@ -0,0 +1,17 @@
+
+
+ Login
+
+
+
+ Login
+ <% if (fail) { %>
+ Try Again
+ <% } %>
+
+
+
\ No newline at end of file
diff --git a/Chapter08/anticipating-malicious-input/app/index-fixed.js b/Chapter08/anticipating-malicious-input/app/index-fixed.js
new file mode 100644
index 0000000..1de4afa
--- /dev/null
+++ b/Chapter08/anticipating-malicious-input/app/index-fixed.js
@@ -0,0 +1,21 @@
+'use strict'
+
+const express = require('express')
+const app = express()
+
+app.get('/', (req, res) => {
+ pretendDbQuery(() => {
+ var msg = req.query.msg
+
+ if (Array.isArray(msg)) msg = msg.pop()
+
+ const yelling = (msg || '').toUpperCase()
+ res.send(yelling)
+ })
+})
+
+app.listen(3000)
+
+function pretendDbQuery (cb) {
+ setTimeout(cb, 0)
+}
diff --git a/Chapter08/anticipating-malicious-input/app/index.js b/Chapter08/anticipating-malicious-input/app/index.js
new file mode 100644
index 0000000..120c6ae
--- /dev/null
+++ b/Chapter08/anticipating-malicious-input/app/index.js
@@ -0,0 +1,17 @@
+'use strict'
+
+const express = require('express')
+const app = express()
+
+app.get('/', (req, res) => {
+ pretendDbQuery(() => {
+ const yelling = (req.query.msg || '').toUpperCase()
+ res.send(yelling)
+ })
+})
+
+app.listen(3000)
+
+function pretendDbQuery (cb) {
+ setTimeout(cb, 0)
+}
diff --git a/Chapter08/anticipating-malicious-input/app/package.json b/Chapter08/anticipating-malicious-input/app/package.json
new file mode 100644
index 0000000..8711bc5
--- /dev/null
+++ b/Chapter08/anticipating-malicious-input/app/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "express": "^4.15.2"
+ }
+}
diff --git a/Chapter08/anticipating-malicious-input/buffer-safety/index-fixed.js b/Chapter08/anticipating-malicious-input/buffer-safety/index-fixed.js
new file mode 100644
index 0000000..d40f2c2
--- /dev/null
+++ b/Chapter08/anticipating-malicious-input/buffer-safety/index-fixed.js
@@ -0,0 +1,87 @@
+'use strict'
+
+const http = require('http')
+
+const server = http.createServer((req, res) => {
+ if (req.method === 'GET') {
+ res.setHeader('Content-Type', 'text/html')
+ if (req.url === '/') return res.end(html())
+ res.setHeader('Content-Type', 'application/json')
+ if (req.url === '/friends') return res.end(friends())
+
+ return
+ }
+ if (req.method === 'POST') {
+ if (req.url === '/') return action(req, res)
+ }
+})
+
+function html (res) {
+ return `
+
+
+
+ `
+}
+
+function friends () {
+ return JSON.stringify(friends.list)
+}
+friends.list = [Buffer.from('Dave').toString('base64')]
+friends.add = (friend) => {
+ friends.list.push(Buffer.from(friend).toString('base64'))
+}
+
+function action (req, res) {
+ var data = ''
+ req.on('data', (chunk) => data += chunk)
+ req.on('end', () => {
+ try {
+ data = JSON.parse(data)
+ } catch (e) {
+ console.error(e)
+ res.end('{"ok": false}')
+ return
+ }
+ if (data.cmd === 'add') {
+ try {
+ friends.add(data.friend)
+ } catch (e) {
+ console.error(e)
+ res.end('{"ok": false}')
+ }
+ }
+ })
+}
+
+server.listen(3000)
\ No newline at end of file
diff --git a/Chapter08/anticipating-malicious-input/buffer-safety/index.js b/Chapter08/anticipating-malicious-input/buffer-safety/index.js
new file mode 100644
index 0000000..a155ac8
--- /dev/null
+++ b/Chapter08/anticipating-malicious-input/buffer-safety/index.js
@@ -0,0 +1,80 @@
+'use strict'
+
+const http = require('http')
+
+const server = http.createServer((req, res) => {
+ if (req.method === 'GET') {
+ res.setHeader('Content-Type', 'text/html')
+ if (req.url === '/') return res.end(html())
+ res.setHeader('Content-Type', 'application/json')
+ if (req.url === '/friends') return res.end(friends())
+
+ return
+ }
+ if (req.method === 'POST') {
+ if (req.url === '/') return action(req, res)
+ }
+})
+
+function html (res) {
+ return `
+
+
+
+ `
+}
+
+function friends () {
+ return JSON.stringify(friends.list)
+}
+friends.list = [Buffer('Dave').toString('base64')]
+friends.add = (friend) => friends.list.push(Buffer(friend).toString('base64'))
+
+function action (req, res) {
+ var data = ''
+ req.on('data', (chunk) => data += chunk)
+ req.on('end', () => {
+ try {
+ data = JSON.parse(data)
+ } catch (e) {
+ res.end('{"ok": false}')
+ return
+ }
+ if (data.cmd === 'add') {
+ friends.add(data.friend)
+ }
+ res.end('{"ok": true}')
+ })
+}
+
+server.listen(3000)
\ No newline at end of file
diff --git a/Chapter08/anticipating-malicious-input/json-validation/index-fixed.js b/Chapter08/anticipating-malicious-input/json-validation/index-fixed.js
new file mode 100644
index 0000000..2ccb7f6
--- /dev/null
+++ b/Chapter08/anticipating-malicious-input/json-validation/index-fixed.js
@@ -0,0 +1,78 @@
+'use strict'
+
+const http = require('http')
+const Ajv = require('ajv')
+const ajv = new Ajv
+const schema = {
+ title: 'UserReg',
+ properties: {
+ id: {type: 'integer'},
+ name: {type: 'string'},
+ privileges: {
+ anyOf: [
+ {type: 'string'},
+ {type: 'boolean'},
+ {type: 'array', items: {type: 'string'}},
+ {type: 'object'}
+ ]
+ }
+ },
+ additionalProperties: false,
+ required: ['id', 'name']
+}
+const validate = ajv.compile(schema)
+const {STATUS_CODES} = http
+
+const server = http.createServer((req, res) => {
+
+ if (req.method !== 'POST') {
+ res.statusCode = 404
+ res.end(STATUS_CODES[res.statusCode])
+ return
+ }
+ if (req.url === '/register') {
+ register(req, res)
+ return
+ }
+ res.statusCode = 404
+ res.end(STATUS_CODES[res.statusCode])
+
+})
+
+function register (req, res) {
+ var data = ''
+ req.on('data', (chunk) => data += chunk)
+ req.on('end', () => {
+ try {
+ data = JSON.parse(data)
+ } catch (e) {
+ res.end('{"ok": false}')
+ return
+ }
+ const valid = validate(data, schema)
+ if (!valid) {
+ console.error(validate.errors)
+ res.end('{"ok": false}')
+ return
+ }
+
+ if (data.hasOwnProperty('privileges')) {
+ createAdminUser(data)
+ res.end('{"ok": true, "admin": true}')
+ } else {
+ createUser(data)
+ res.end('{"ok": true, "admin": false}')
+ }
+ })
+}
+
+function createAdminUser (user) {
+ const key = user.id + user.name
+ // ...
+}
+
+function createUser (user) {
+ // ...
+}
+
+server.listen(3000)
\ No newline at end of file
diff --git a/Chapter08/anticipating-malicious-input/json-validation/index.js b/Chapter08/anticipating-malicious-input/json-validation/index.js
new file mode 100644
index 0000000..9b39327
--- /dev/null
+++ b/Chapter08/anticipating-malicious-input/json-validation/index.js
@@ -0,0 +1,53 @@
+'use strict'
+
+const http = require('http')
+const {STATUS_CODES} = http
+
+const server = http.createServer((req, res) => {
+
+ if (req.method !== 'POST') {
+ res.statusCode = 404
+ res.end(STATUS_CODES[res.statusCode])
+ return
+ }
+ if (req.url === '/register') {
+ register(req, res)
+ return
+ }
+ res.statusCode = 404
+ res.end(STATUS_CODES[res.statusCode])
+
+})
+
+function register (req, res) {
+ var data = ''
+ req.on('data', (chunk) => data += chunk)
+ req.on('end', () => {
+ try {
+ data = JSON.parse(data)
+ } catch (e) {
+ res.end('{"ok": false}')
+ return
+ }
+ // privileges can be multiple types, boolean, array, object, string,
+ // but the presence of the key means the user is an admin
+ if (data.hasOwnProperty('privileges')) {
+ createAdminUser(data)
+ res.end('{"ok": true, "admin": true}')
+ } else {
+ createUser(data)
+ res.end('{"ok": true, "admin": false}')
+ }
+ })
+}
+
+function createAdminUser (user) {
+ const key = user.id + user.name
+ // ...
+}
+
+function createUser (user) {
+ // ...
+}
+
+server.listen(3000)
\ No newline at end of file
diff --git a/Chapter08/anticipating-malicious-input/json-validation/package.json b/Chapter08/anticipating-malicious-input/json-validation/package.json
new file mode 100644
index 0000000..23855f0
--- /dev/null
+++ b/Chapter08/anticipating-malicious-input/json-validation/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "json-validation",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "ajv": "^4.11.5"
+ }
+}
diff --git a/Chapter08/detecting-dependency-vulnerabilities/app/.snyk b/Chapter08/detecting-dependency-vulnerabilities/app/.snyk
new file mode 100644
index 0000000..127718e
--- /dev/null
+++ b/Chapter08/detecting-dependency-vulnerabilities/app/.snyk
@@ -0,0 +1,4 @@
+# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
+version: v1.7.0
+ignore: {}
+patch: {}
diff --git a/Chapter08/detecting-dependency-vulnerabilities/app/package.json b/Chapter08/detecting-dependency-vulnerabilities/app/package.json
new file mode 100644
index 0000000..e449c98
--- /dev/null
+++ b/Chapter08/detecting-dependency-vulnerabilities/app/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1",
+ "audit": "auditjs"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "express": "^4.15.2"
+ },
+ "devDependencies": {
+ "auditjs": "^2.0.2"
+ }
+}
diff --git a/Chapter08/guarding-against-xss/app/index.js b/Chapter08/guarding-against-xss/app/index.js
new file mode 100644
index 0000000..5bd2a69
--- /dev/null
+++ b/Chapter08/guarding-against-xss/app/index.js
@@ -0,0 +1,32 @@
+'use strict'
+
+const express = require('express')
+const app = express()
+
+app.get('/', (req, res) => {
+ const {prev = '', handoverToken = '', lang = 'en'} = req.query
+ pretendDbQuery((err, status) => {
+ if (err) {
+ res.sendStatus(500)
+ return
+ }
+ res.send(`
+ Current Status
+
+ ${status}
+
+
+ `)
+ })
+
+})
+
+function pretendDbQuery (cb) {
+ const status = 'ON FIRE!!! HELP!!!'
+ cb(null, status)
+}
+
+
+app.listen(3000)
\ No newline at end of file
diff --git a/Chapter08/guarding-against-xss/app/package.json b/Chapter08/guarding-against-xss/app/package.json
new file mode 100644
index 0000000..8711bc5
--- /dev/null
+++ b/Chapter08/guarding-against-xss/app/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "express": "^4.15.2"
+ }
+}
diff --git a/Chapter08/guarding-against-xss/fixed-app/index.js b/Chapter08/guarding-against-xss/fixed-app/index.js
new file mode 100644
index 0000000..5bc4145
--- /dev/null
+++ b/Chapter08/guarding-against-xss/fixed-app/index.js
@@ -0,0 +1,34 @@
+'use strict'
+
+const express = require('express')
+const he = require('he')
+const app = express()
+
+
+app.get('/', (req, res) => {
+ const {prev = '', handoverToken = '', lang = 'en'} = req.query
+ pretendDbQuery((err, status) => {
+ if (err) {
+ res.sendStatus(500)
+ return
+ }
+ const href = he.encode(`${prev}${handoverToken}/${lang}`)
+ res.send(`
+ Current Status
+
+ ${he.escape(status)}
+
+
+ Back to Control HQ
+ `)
+ })
+
+})
+
+function pretendDbQuery (cb) {
+ const status = 'ON FIRE!!! HELP!!!'
+ cb(null, status)
+}
+
+
+app.listen(3000)
\ No newline at end of file
diff --git a/Chapter08/guarding-against-xss/fixed-app/package.json b/Chapter08/guarding-against-xss/fixed-app/package.json
new file mode 100644
index 0000000..f6f3dbc
--- /dev/null
+++ b/Chapter08/guarding-against-xss/fixed-app/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "express": "^4.15.2",
+ "he": "^1.1.1"
+ }
+}
diff --git a/Chapter08/guarding-against-xss/fully-escaped-app/index.js b/Chapter08/guarding-against-xss/fully-escaped-app/index.js
new file mode 100644
index 0000000..36251e0
--- /dev/null
+++ b/Chapter08/guarding-against-xss/fully-escaped-app/index.js
@@ -0,0 +1,33 @@
+'use strict'
+
+const express = require('express')
+const escapeHtml = require('escape-html')
+const app = express()
+
+
+app.get('/', (req, res) => {
+ const {prev = '', handoverToken = '', lang = 'en'} = req.query
+ pretendDbQuery((err, status) => {
+ if (err) {
+ res.sendStatus(500)
+ return
+ }
+ const href = escapeHtml(`/${prev}${handoverToken}/${lang}`)
+ res.send(`
+ Current Status
+
+ ${escapeHtml(status)}
+
+
+ Back to Control HQ
+ `)
+ })
+})
+
+function pretendDbQuery (cb) {
+ const status = 'ON FIRE!!! HELP!!!'
+ cb(null, status)
+}
+
+
+app.listen(3000)
\ No newline at end of file
diff --git a/Chapter08/guarding-against-xss/fully-escaped-app/package.json b/Chapter08/guarding-against-xss/fully-escaped-app/package.json
new file mode 100644
index 0000000..c18daca
--- /dev/null
+++ b/Chapter08/guarding-against-xss/fully-escaped-app/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "escape-html": "^1.0.3",
+ "express": "^4.15.2",
+ "he": "^1.1.1"
+ }
+}
diff --git a/Chapter08/guarding-against-xss/param-constraints-app/index.js b/Chapter08/guarding-against-xss/param-constraints-app/index.js
new file mode 100644
index 0000000..955a110
--- /dev/null
+++ b/Chapter08/guarding-against-xss/param-constraints-app/index.js
@@ -0,0 +1,46 @@
+'use strict'
+
+const express = require('express')
+const app = express()
+
+function validate ({prev, handoverToken, lang}, query) {
+ var valid = Object.keys(query).length <= 3
+ valid = valid && typeof lang === 'string' && lang.length === 2
+ valid = valid && typeof handoverToken === 'string' && handoverToken.length === 16
+ valid = valid && typeof prev === 'string' && prev.length < 10
+ return valid
+}
+
+app.get('/', (req, res) => {
+ const {prev = '', handoverToken = '', lang = 'en'} = req.query
+
+ if (!validate({prev, handoverToken, lang}, req.query)) {
+ res.sendStatus(422)
+ return
+ }
+
+ pretendDbQuery((err, status) => {
+ if (err) {
+ res.sendStatus(500)
+ return
+ }
+ res.send(`
+ Current Status
+
+ ${status}
+
+
+ `)
+ })
+
+})
+
+function pretendDbQuery (cb) {
+ const status = 'ON FIRE!!! HELP!!!'
+ cb(null, status)
+}
+
+
+app.listen(3000)
\ No newline at end of file
diff --git a/Chapter08/guarding-against-xss/param-constraints-app/package.json b/Chapter08/guarding-against-xss/param-constraints-app/package.json
new file mode 100644
index 0000000..a09118c
--- /dev/null
+++ b/Chapter08/guarding-against-xss/param-constraints-app/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "escape-html": "^1.0.3",
+ "express": "^4.15.2"
+ }
+}
diff --git a/Chapter08/guarding-against-xss/protocol-safe-app/index.js b/Chapter08/guarding-against-xss/protocol-safe-app/index.js
new file mode 100644
index 0000000..36251e0
--- /dev/null
+++ b/Chapter08/guarding-against-xss/protocol-safe-app/index.js
@@ -0,0 +1,33 @@
+'use strict'
+
+const express = require('express')
+const escapeHtml = require('escape-html')
+const app = express()
+
+
+app.get('/', (req, res) => {
+ const {prev = '', handoverToken = '', lang = 'en'} = req.query
+ pretendDbQuery((err, status) => {
+ if (err) {
+ res.sendStatus(500)
+ return
+ }
+ const href = escapeHtml(`/${prev}${handoverToken}/${lang}`)
+ res.send(`
+ Current Status
+
+ ${escapeHtml(status)}
+
+
+ Back to Control HQ
+ `)
+ })
+})
+
+function pretendDbQuery (cb) {
+ const status = 'ON FIRE!!! HELP!!!'
+ cb(null, status)
+}
+
+
+app.listen(3000)
\ No newline at end of file
diff --git a/Chapter08/guarding-against-xss/protocol-safe-app/package.json b/Chapter08/guarding-against-xss/protocol-safe-app/package.json
new file mode 100644
index 0000000..a09118c
--- /dev/null
+++ b/Chapter08/guarding-against-xss/protocol-safe-app/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "escape-html": "^1.0.3",
+ "express": "^4.15.2"
+ }
+}
diff --git a/Chapter08/preventing-cross-site-request-forgery/app/index.js b/Chapter08/preventing-cross-site-request-forgery/app/index.js
new file mode 100644
index 0000000..c541738
--- /dev/null
+++ b/Chapter08/preventing-cross-site-request-forgery/app/index.js
@@ -0,0 +1,80 @@
+'use strict'
+
+const express = require('express')
+const bodyParser = require('body-parser')
+const session = require('express-session')
+const he = require('he')
+const app = express()
+
+const pretendData = {
+ dave: {
+ ac: '12345678',
+ sc: '88-26-26'
+ }
+}
+
+app.use(session({
+ secret: 'AI overlords are coming',
+ name: 'SESSIONID',
+ resave: false,
+ saveUninitialized: false
+}))
+
+app.use(bodyParser.urlencoded({extended: false}))
+
+app.get('/', (req, res) => {
+ if (req.session.user) return res.redirect('/profile')
+ res.send(`
+ Login
+
+ `)
+})
+
+app.post('/', (req, res) => {
+ if (req.body.user === 'dave' && req.body.pass === 'ncb') {
+ req.session.user = req.body.user
+ }
+ if (req.session.user) res.redirect('/profile')
+ else res.redirect('/')
+})
+
+app.get('/profile', (req, res) => {
+ if (!req.session.user) return res.redirect('/')
+ const {prev = '', handoverToken = '', lang = 'en'} = req.query
+ pretendDbQuery(req.session.user, (err, {sc, ac}) => {
+ if (err) {
+ res.sendStatus(500)
+ return
+ }
+ sc = he.encode(sc)
+ ac = he.encode(ac)
+ res.send(`
+ Employee Payment Profile
+
+ `)
+ })
+})
+
+app.post('/update', (req, res) => {
+ if (!req.session.user) return res.sendStatus(403)
+ pretendData[req.session.user].ac = req.body.ac
+ pretendData[req.session.user].sc = req.body.sc
+ res.send(`
+ updated
+
+ `)
+})
+
+function pretendDbQuery (user, cb) {
+ cb(null, pretendData[user])
+}
+
+app.listen(3000)
diff --git a/Chapter08/preventing-cross-site-request-forgery/app/package.json b/Chapter08/preventing-cross-site-request-forgery/app/package.json
new file mode 100644
index 0000000..b7d89f0
--- /dev/null
+++ b/Chapter08/preventing-cross-site-request-forgery/app/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "body-parser": "^1.17.1",
+ "express": "^4.15.2",
+ "express-session": "^1.15.2",
+ "he": "^1.1.1"
+ }
+}
diff --git a/Chapter08/preventing-cross-site-request-forgery/attacker/index.js b/Chapter08/preventing-cross-site-request-forgery/attacker/index.js
new file mode 100644
index 0000000..747e3e8
--- /dev/null
+++ b/Chapter08/preventing-cross-site-request-forgery/attacker/index.js
@@ -0,0 +1,19 @@
+const http = require('http')
+
+const attackerAc = '87654321'
+const attackerSc = '11-11-11'
+const attackerMsg = 'Everything you could ever want is only one click away'
+
+const server = http.createServer((req, res) => {
+ res.writeHead(200, {'Content-Type': 'text/html'})
+ res.end(`
+
+
+ `)
+})
+
+server.listen(3001)
\ No newline at end of file
diff --git a/Chapter08/preventing-cross-site-request-forgery/fixed-app/index.js b/Chapter08/preventing-cross-site-request-forgery/fixed-app/index.js
new file mode 100644
index 0000000..36aa5c9
--- /dev/null
+++ b/Chapter08/preventing-cross-site-request-forgery/fixed-app/index.js
@@ -0,0 +1,83 @@
+'use strict'
+
+const express = require('express')
+const bodyParser = require('body-parser')
+const session = require('express-session')
+const he = require('he')
+const app = express()
+
+const pretendData = {
+ dave: {
+ ac: '12345678',
+ sc: '88-26-26'
+ }
+}
+
+app.use(session({
+ secret: 'AI overlords are coming',
+ name: 'SESSIONID',
+ resave: false,
+ saveUninitialized: false,
+ cookie: {
+ sameSite: true
+ }
+}))
+
+app.use(bodyParser.urlencoded({extended: false}))
+
+app.get('/', (req, res) => {
+ if (req.session.user) return res.redirect('/profile')
+ res.send(`
+ Login
+
+ `)
+})
+
+app.post('/', (req, res) => {
+ if (req.body.user === 'dave' && req.body.pass === 'ncb') {
+ req.session.user = req.body.user
+ }
+ if (req.session.user) res.redirect('/profile')
+ else res.redirect('/')
+})
+
+app.get('/profile', (req, res) => {
+ if (!req.session.user) return res.redirect('/')
+ const {prev = '', handoverToken = '', lang = 'en'} = req.query
+ pretendDbQuery(req.session.user, (err, {sc, ac}) => {
+ if (err) {
+ res.sendStatus(500)
+ return
+ }
+ sc = he.encode(sc)
+ ac = he.encode(ac)
+ res.send(`
+ Employee Payment Profile
+
+ `)
+ })
+})
+
+app.post('/update', (req, res) => {
+ if (!req.session.user) return res.sendStatus(403)
+ pretendData[req.session.user].ac = req.body.ac
+ pretendData[req.session.user].sc = req.body.sc
+ res.send(`
+ updated
+
+ `)
+})
+
+function pretendDbQuery (user, cb) {
+ cb(null, pretendData[user])
+}
+
+app.listen(3000)
diff --git a/Chapter08/preventing-cross-site-request-forgery/fixed-app/package.json b/Chapter08/preventing-cross-site-request-forgery/fixed-app/package.json
new file mode 100644
index 0000000..b7d89f0
--- /dev/null
+++ b/Chapter08/preventing-cross-site-request-forgery/fixed-app/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "body-parser": "^1.17.1",
+ "express": "^4.15.2",
+ "express-session": "^1.15.2",
+ "he": "^1.1.1"
+ }
+}
diff --git a/Chapter08/preventing-cross-site-request-forgery/secured-app/index.js b/Chapter08/preventing-cross-site-request-forgery/secured-app/index.js
new file mode 100644
index 0000000..90a8a22
--- /dev/null
+++ b/Chapter08/preventing-cross-site-request-forgery/secured-app/index.js
@@ -0,0 +1,86 @@
+'use strict'
+
+const express = require('express')
+const bodyParser = require('body-parser')
+const session = require('express-session')
+const he = require('he')
+const csurf = require('csurf')
+const app = express()
+const csrf = csurf()
+
+const pretendData = {
+ dave: {
+ ac: '12345678',
+ sc: '88-26-26'
+ }
+}
+
+app.use(session({
+ secret: 'AI overlords are coming',
+ name: 'SESSIONID',
+ resave: false,
+ saveUninitialized: false,
+ cookie: {
+ sameSite: true
+ }
+}))
+
+app.use(bodyParser.urlencoded({extended: false}))
+
+app.get('/', (req, res) => {
+ if (req.session.user) return res.redirect('/profile')
+ res.send(`
+ Login
+
+ `)
+})
+
+app.post('/', (req, res) => {
+ if (req.body.user === 'dave' && req.body.pass === 'ncb') {
+ req.session.user = req.body.user
+ }
+ if (req.session.user) res.redirect('/profile')
+ else res.redirect('/')
+})
+
+app.get('/profile', csrf, (req, res) => {
+ if (!req.session.user) return res.redirect('/')
+ const {prev = '', handoverToken = '', lang = 'en'} = req.query
+ pretendDbQuery(req.session.user, (err, {sc, ac}) => {
+ if (err) {
+ res.sendStatus(500)
+ return
+ }
+ sc = he.encode(sc)
+ ac = he.encode(ac)
+ res.send(`
+ Employee Payment Profile
+
+ `)
+ })
+})
+
+app.post('/update', csrf, (req, res) => {
+ if (!req.session.user) return res.sendStatus(403)
+ pretendData[req.session.user].ac = req.body.ac
+ pretendData[req.session.user].sc = req.body.sc
+ res.send(`
+ updated
+
+ `)
+})
+
+function pretendDbQuery (user, cb) {
+ cb(null, pretendData[user])
+}
+
+app.listen(3000)
diff --git a/Chapter08/preventing-cross-site-request-forgery/secured-app/package.json b/Chapter08/preventing-cross-site-request-forgery/secured-app/package.json
new file mode 100644
index 0000000..86cf66b
--- /dev/null
+++ b/Chapter08/preventing-cross-site-request-forgery/secured-app/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "app",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "body-parser": "^1.17.1",
+ "csurf": "^1.9.0",
+ "express": "^4.15.2",
+ "express-session": "^1.15.2",
+ "he": "^1.1.1"
+ }
+}
diff --git a/Chapter08/web-server-hardening/app/app.js b/Chapter08/web-server-hardening/app/app.js
new file mode 100644
index 0000000..1e9ad86
--- /dev/null
+++ b/Chapter08/web-server-hardening/app/app.js
@@ -0,0 +1,47 @@
+var express = require('express')
+var path = require('path')
+var favicon = require('serve-favicon')
+var logger = require('morgan')
+var cookieParser = require('cookie-parser')
+var bodyParser = require('body-parser')
+var helmet = require('helmet')
+var index = require('./routes/index')
+var users = require('./routes/users')
+
+var app = express()
+
+// view engine setup
+app.set('views', path.join(__dirname, 'views'))
+app.set('view engine', 'jade')
+
+// uncomment after placing your favicon in /public
+// app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
+app.use(helmet())
+app.use(logger('dev'))
+app.use(bodyParser.json())
+app.use(bodyParser.urlencoded({ extended: false }))
+app.use(cookieParser())
+app.use(express.static(path.join(__dirname, 'public')))
+
+app.use('/', index)
+app.use('/users', users)
+
+// catch 404 and forward to error handler
+app.use(function (req, res, next) {
+ var err = new Error('Not Found')
+ err.status = 404
+ next(err)
+})
+
+// error handler
+app.use(function (err, req, res, next) {
+ // set locals, only providing error in development
+ res.locals.message = err.message
+ res.locals.error = req.app.get('env') === 'development' ? err : {}
+
+ // render the error page
+ res.status(err.status || 500)
+ res.render('error')
+})
+
+module.exports = app
diff --git a/Chapter08/web-server-hardening/app/bin/www b/Chapter08/web-server-hardening/app/bin/www
new file mode 100644
index 0000000..a8c2d36
--- /dev/null
+++ b/Chapter08/web-server-hardening/app/bin/www
@@ -0,0 +1,90 @@
+#!/usr/bin/env node
+
+/**
+ * Module dependencies.
+ */
+
+var app = require('../app');
+var debug = require('debug')('app:server');
+var http = require('http');
+
+/**
+ * Get port from environment and store in Express.
+ */
+
+var port = normalizePort(process.env.PORT || '3000');
+app.set('port', port);
+
+/**
+ * Create HTTP server.
+ */
+
+var server = http.createServer(app);
+
+/**
+ * Listen on provided port, on all network interfaces.
+ */
+
+server.listen(port);
+server.on('error', onError);
+server.on('listening', onListening);
+
+/**
+ * Normalize a port into a number, string, or false.
+ */
+
+function normalizePort(val) {
+ var port = parseInt(val, 10);
+
+ if (isNaN(port)) {
+ // named pipe
+ return val;
+ }
+
+ if (port >= 0) {
+ // port number
+ return port;
+ }
+
+ return false;
+}
+
+/**
+ * Event listener for HTTP server "error" event.
+ */
+
+function onError(error) {
+ if (error.syscall !== 'listen') {
+ throw error;
+ }
+
+ var bind = typeof port === 'string'
+ ? 'Pipe ' + port
+ : 'Port ' + port;
+
+ // handle specific listen errors with friendly messages
+ switch (error.code) {
+ case 'EACCES':
+ console.error(bind + ' requires elevated privileges');
+ process.exit(1);
+ break;
+ case 'EADDRINUSE':
+ console.error(bind + ' is already in use');
+ process.exit(1);
+ break;
+ default:
+ throw error;
+ }
+}
+
+/**
+ * Event listener for HTTP server "listening" event.
+ */
+
+function onListening() {
+ var addr = server.address();
+ var bind = typeof addr === 'string'
+ ? 'pipe ' + addr
+ : 'port ' + addr.port;
+ debug('Listening on ' + bind);
+}
diff --git a/Chapter08/web-server-hardening/app/package.json b/Chapter08/web-server-hardening/app/package.json
new file mode 100644
index 0000000..35569d5
--- /dev/null
+++ b/Chapter08/web-server-hardening/app/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "app",
+ "version": "0.0.0",
+ "private": true,
+ "scripts": {
+ "start": "node ./bin/www"
+ },
+ "dependencies": {
+ "body-parser": "~1.16.0",
+ "cookie-parser": "~1.4.3",
+ "debug": "~2.6.0",
+ "express": "~4.14.1",
+ "helmet": "^3.5.0",
+ "jade": "~1.11.0",
+ "morgan": "~1.7.0",
+ "serve-favicon": "~2.3.2"
+ }
+}
diff --git a/Chapter08/web-server-hardening/app/public/stylesheets/style.css b/Chapter08/web-server-hardening/app/public/stylesheets/style.css
new file mode 100644
index 0000000..9453385
--- /dev/null
+++ b/Chapter08/web-server-hardening/app/public/stylesheets/style.css
@@ -0,0 +1,8 @@
+body {
+ padding: 50px;
+ font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
+}
+
+a {
+ color: #00B7FF;
+}
diff --git a/Chapter08/web-server-hardening/app/routes/index.js b/Chapter08/web-server-hardening/app/routes/index.js
new file mode 100644
index 0000000..956680b
--- /dev/null
+++ b/Chapter08/web-server-hardening/app/routes/index.js
@@ -0,0 +1,9 @@
+var express = require('express')
+var router = express.Router()
+
+/* GET home page. */
+router.get('/', function (req, res, next) {
+ res.render('index', { title: 'Express' })
+})
+
+module.exports = router
diff --git a/Chapter08/web-server-hardening/app/routes/users.js b/Chapter08/web-server-hardening/app/routes/users.js
new file mode 100644
index 0000000..8cfe88f
--- /dev/null
+++ b/Chapter08/web-server-hardening/app/routes/users.js
@@ -0,0 +1,9 @@
+var express = require('express')
+var router = express.Router()
+
+/* GET users listing. */
+router.get('/', function (req, res, next) {
+ res.send('respond with a resource')
+})
+
+module.exports = router
diff --git a/Chapter08/web-server-hardening/app/views/error.jade b/Chapter08/web-server-hardening/app/views/error.jade
new file mode 100644
index 0000000..51ec12c
--- /dev/null
+++ b/Chapter08/web-server-hardening/app/views/error.jade
@@ -0,0 +1,6 @@
+extends layout
+
+block content
+ h1= message
+ h2= error.status
+ pre #{error.stack}
diff --git a/Chapter08/web-server-hardening/app/views/index.jade b/Chapter08/web-server-hardening/app/views/index.jade
new file mode 100644
index 0000000..3d63b9a
--- /dev/null
+++ b/Chapter08/web-server-hardening/app/views/index.jade
@@ -0,0 +1,5 @@
+extends layout
+
+block content
+ h1= title
+ p Welcome to #{title}
diff --git a/Chapter08/web-server-hardening/app/views/layout.jade b/Chapter08/web-server-hardening/app/views/layout.jade
new file mode 100644
index 0000000..15af079
--- /dev/null
+++ b/Chapter08/web-server-hardening/app/views/layout.jade
@@ -0,0 +1,7 @@
+doctype html
+html
+ head
+ title= title
+ link(rel='stylesheet', href='/stylesheets/style.css')
+ body
+ block content
diff --git a/Chapter08/web-server-hardening/hapi-app/.gitignore b/Chapter08/web-server-hardening/hapi-app/.gitignore
new file mode 100644
index 0000000..40b878d
--- /dev/null
+++ b/Chapter08/web-server-hardening/hapi-app/.gitignore
@@ -0,0 +1 @@
+node_modules/
\ No newline at end of file
diff --git a/Chapter08/web-server-hardening/hapi-app/LICENSE b/Chapter08/web-server-hardening/hapi-app/LICENSE
new file mode 100644
index 0000000..b253f47
--- /dev/null
+++ b/Chapter08/web-server-hardening/hapi-app/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2017 Azaritech
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Chapter08/web-server-hardening/hapi-app/README.md b/Chapter08/web-server-hardening/hapi-app/README.md
new file mode 100644
index 0000000..1fb3729
--- /dev/null
+++ b/Chapter08/web-server-hardening/hapi-app/README.md
@@ -0,0 +1,21 @@
+# hapi-starter-kit
+Starter kit for Hapi
+
+## Install
+```bash
+git clone git@github.com:azaritech/hapi-starter-kit.git
+cd hapi-starter-kit/
+yarn install
+# or
+npm install
+```
+
+## Start
+```bash
+npm run start
+```
+
+### Open browser and go to
+[http://localhost:3000/](http://localhost:3000/)
+
+[http://localhost:3000/hello](http://localhost:3000/hello)
diff --git a/Chapter08/web-server-hardening/hapi-app/config/default.json b/Chapter08/web-server-hardening/hapi-app/config/default.json
new file mode 100644
index 0000000..cc83a42
--- /dev/null
+++ b/Chapter08/web-server-hardening/hapi-app/config/default.json
@@ -0,0 +1,12 @@
+{
+ "connections":
+ [
+ {
+ "server": {
+ "port": 3000,
+ "host": "0.0.0.0",
+ "labels": ["server"]
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Chapter08/web-server-hardening/hapi-app/lib/index.js b/Chapter08/web-server-hardening/hapi-app/lib/index.js
new file mode 100644
index 0000000..5b9dab6
--- /dev/null
+++ b/Chapter08/web-server-hardening/hapi-app/lib/index.js
@@ -0,0 +1,34 @@
+'use strict';
+
+const util = require('util');
+const hapi = require('hapi');
+const config = require('getconfig');
+
+const init = require('./init');
+const server = new hapi.Server();
+
+// init connections before registering plugins
+init.connections(server, config);
+server.ext('onPreResponse', (request, reply) => {
+ var response = request.response.isBoom ?
+ request.response.output :
+ request.response;
+ response.headers['X-DNS-Prefetch-Control'] = 'off';
+ response.headers['X-DNS-Prefetch-Control'] = 'off';
+ response.headers['X-Frame-Options'] = 'SAMEORIGIN';
+ response.headers['X-Download-Options'] = 'noopen';
+ response.headers['X-Content-Type-Options'] = 'nosniff';
+ response.headers['X-XSS-Protection'] = '1; mode=block';
+ reply.continue();
+});
+// register plugins
+init.registers(server);
+// loading views
+init.views(server);
+
+server.start((err) => {
+ if (err) {
+ throw err;
+ }
+ server.log('info', 'Server running');
+});
\ No newline at end of file
diff --git a/Chapter08/web-server-hardening/hapi-app/lib/init.js b/Chapter08/web-server-hardening/hapi-app/lib/init.js
new file mode 100644
index 0000000..071838a
--- /dev/null
+++ b/Chapter08/web-server-hardening/hapi-app/lib/init.js
@@ -0,0 +1,39 @@
+'use strict';
+
+module.exports.connections = function(server, config) {
+ require('./server').connection(server, config);
+};
+
+module.exports.registers = function(server) {
+ server.register([{
+ register: require('./logs').register,
+ options: require('./logs').options
+ }, {
+ register: require('nes')
+ }, {
+ register: require('vision')
+ }, {
+ register: require('inert')
+ }, {
+ register: require('./server').register,
+ select: ['server']
+ }], (err) => {
+ if (err) {
+ throw err;
+ }
+ });
+};
+
+module.exports.views = function(server) {
+ server.views({
+ engines: {
+ html: require('handlebars')
+ },
+ path: __dirname + '/views',
+ layout: false
+ //layoutPath: 'views/layout',
+ //layout: 'default',
+ //helpersPath: 'views/helpers',
+ //partialsPath: 'views/partials'
+ });
+}
\ No newline at end of file
diff --git a/Chapter08/web-server-hardening/hapi-app/lib/logs.js b/Chapter08/web-server-hardening/hapi-app/lib/logs.js
new file mode 100644
index 0000000..3e0d146
--- /dev/null
+++ b/Chapter08/web-server-hardening/hapi-app/lib/logs.js
@@ -0,0 +1,21 @@
+'use strict';
+
+// logging to console
+const logs = {
+ register: require('good'),
+ options: {
+ ops: {
+ interval: 60 * 1000
+ },
+ reporters: {
+ console: [{
+ module: 'good-console',
+ args: [ { log: '*', response: '*', request: '*' } ]
+ },
+ 'stdout'
+ ]
+ }
+ }
+};
+
+module.exports = logs;
\ No newline at end of file
diff --git a/Chapter08/web-server-hardening/hapi-app/lib/server/index.js b/Chapter08/web-server-hardening/hapi-app/lib/server/index.js
new file mode 100644
index 0000000..8d3b4ef
--- /dev/null
+++ b/Chapter08/web-server-hardening/hapi-app/lib/server/index.js
@@ -0,0 +1,19 @@
+'use strict';
+
+module.exports.connection = function(server, config) {
+ server.connection({
+ host: config.connections[0].server.host,
+ port: config.connections[0].server.port,
+ labels: config.connections[0].server.labels
+ });
+};
+
+// register plugin
+module.exports.register = function (server, options, next) {
+ require('./routes')(server);
+ next();
+};
+
+module.exports.register.attributes = {
+ pkg: require('./package.json')
+};
\ No newline at end of file
diff --git a/Chapter08/web-server-hardening/hapi-app/lib/server/package.json b/Chapter08/web-server-hardening/hapi-app/lib/server/package.json
new file mode 100644
index 0000000..69a979e
--- /dev/null
+++ b/Chapter08/web-server-hardening/hapi-app/lib/server/package.json
@@ -0,0 +1,4 @@
+{
+ "name": "server",
+ "version": "1.0.0"
+}
\ No newline at end of file
diff --git a/Chapter08/web-server-hardening/hapi-app/lib/server/routes.js b/Chapter08/web-server-hardening/hapi-app/lib/server/routes.js
new file mode 100644
index 0000000..2eb3f5d
--- /dev/null
+++ b/Chapter08/web-server-hardening/hapi-app/lib/server/routes.js
@@ -0,0 +1,22 @@
+'use strict';
+
+module.exports = function(server) {
+ server.route({
+ method: 'GET',
+ path: '/',
+ handler: function (request, reply) {
+ reply.view('index');
+ }
+ });
+ server.route({
+ method: 'GET',
+ path: '/hello',
+ handler: function (request, reply) {
+ const data = {
+ message1: 'Hello',
+ message2: 'Paris, France'
+ };
+ reply.view('hello', data);
+ }
+ });
+};
diff --git a/Chapter08/web-server-hardening/hapi-app/lib/views/hello.html b/Chapter08/web-server-hardening/hapi-app/lib/views/hello.html
new file mode 100644
index 0000000..e5ea604
--- /dev/null
+++ b/Chapter08/web-server-hardening/hapi-app/lib/views/hello.html
@@ -0,0 +1 @@
+{{message1}} from {{message2}}.
\ No newline at end of file
diff --git a/Chapter08/web-server-hardening/hapi-app/lib/views/index.html b/Chapter08/web-server-hardening/hapi-app/lib/views/index.html
new file mode 100644
index 0000000..a403e71
--- /dev/null
+++ b/Chapter08/web-server-hardening/hapi-app/lib/views/index.html
@@ -0,0 +1 @@
+Hapi Starter Kit
\ No newline at end of file
diff --git a/Chapter08/web-server-hardening/hapi-app/package.json b/Chapter08/web-server-hardening/hapi-app/package.json
new file mode 100644
index 0000000..8ae93a3
--- /dev/null
+++ b/Chapter08/web-server-hardening/hapi-app/package.json
@@ -0,0 +1,24 @@
+{
+ "name": "hapi-starter-kit",
+ "version": "1.0.2",
+ "description": "Starter kit for Hapi",
+ "main": "lib/index.js",
+ "repository": "git@github.com:azaritech/hapi-starter-kit.git",
+ "author": "Nicolas Azari",
+ "license": "MIT",
+ "scripts": {
+ "start": "node lib/index.js",
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "dependencies": {
+ "getconfig": "^3.0.0",
+ "good": "^7.1.0",
+ "good-console": "^6.4.0",
+ "handlebars": "^4.0.6",
+ "hapi": "^16.1.0",
+ "inert": "^4.1.0",
+ "nes": "^6.4.0",
+ "vision": "^4.1.1"
+ },
+ "devDependencies": {}
+}
diff --git a/Chapter08/web-server-hardening/hapi-app/yarn.lock b/Chapter08/web-server-hardening/hapi-app/yarn.lock
new file mode 100644
index 0000000..8ef51d4
--- /dev/null
+++ b/Chapter08/web-server-hardening/hapi-app/yarn.lock
@@ -0,0 +1,551 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+accept@2.x.x:
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/accept/-/accept-2.1.3.tgz#ab0f5bda4c449bbe926aea607b3522562f5acf86"
+ dependencies:
+ boom "4.x.x"
+ hoek "4.x.x"
+
+align-text@^0.1.1, align-text@^0.1.3:
+ version "0.1.4"
+ resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117"
+ dependencies:
+ kind-of "^3.0.2"
+ longest "^1.0.1"
+ repeat-string "^1.5.2"
+
+amdefine@>=0.0.4:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5"
+
+ammo@2.x.x:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/ammo/-/ammo-2.0.3.tgz#914bbcf65b043ed0f58a8a9d0196e250ec51e6a7"
+ dependencies:
+ boom "4.x.x"
+ hoek "4.x.x"
+
+async@^1.4.0:
+ version "1.5.2"
+ resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
+
+async@~0.2.6:
+ version "0.2.10"
+ resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
+
+b64@3.x.x:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/b64/-/b64-3.0.2.tgz#7a9d60466adf7b8de114cbdf651a5fdfcc90894d"
+
+boom@4.x.x:
+ version "4.2.0"
+ resolved "https://registry.yarnpkg.com/boom/-/boom-4.2.0.tgz#c1a74174b11fbba223f6162d4fd8851a1b82a536"
+ dependencies:
+ hoek "4.x.x"
+
+buffer-shims@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/buffer-shims/-/buffer-shims-1.0.0.tgz#9978ce317388c649ad8793028c3477ef044a8b51"
+
+call@3.x.x:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/call/-/call-3.0.4.tgz#e380f2f2a491330aa79085355f8be080877d559e"
+ dependencies:
+ boom "4.x.x"
+ hoek "4.x.x"
+
+call@4.x.x:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/call/-/call-4.0.0.tgz#cd29381a98046a132db26e2628e70bd8321a1ddf"
+ dependencies:
+ boom "4.x.x"
+ hoek "4.x.x"
+
+camelcase@^1.0.2:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39"
+
+catbox-memory@2.x.x:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/catbox-memory/-/catbox-memory-2.0.4.tgz#433e255902caf54233d1286429c8f4df14e822d5"
+ dependencies:
+ hoek "4.x.x"
+
+catbox@7.x.x:
+ version "7.1.3"
+ resolved "https://registry.yarnpkg.com/catbox/-/catbox-7.1.3.tgz#9817edec5a921743282addfc9c45ace52847eebb"
+ dependencies:
+ boom "4.x.x"
+ hoek "4.x.x"
+ joi "10.x.x"
+
+center-align@^0.1.1:
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad"
+ dependencies:
+ align-text "^0.1.3"
+ lazy-cache "^1.0.3"
+
+cliui@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1"
+ dependencies:
+ center-align "^0.1.1"
+ right-align "^0.1.1"
+ wordwrap "0.0.2"
+
+content@3.x.x:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/content/-/content-3.0.3.tgz#000f8a01371b95c66afe99be9390fa6cb91aa87a"
+ dependencies:
+ boom "4.x.x"
+
+core-util-is@~1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
+
+cryptiles@3.x.x:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-3.1.1.tgz#86a9203f7367a0e9324bc7555ff0fcf5f81979ee"
+ dependencies:
+ boom "4.x.x"
+
+decamelize@^1.0.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
+
+duplexify@^3.1.2:
+ version "3.5.0"
+ resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.5.0.tgz#1aa773002e1578457e9d9d4a50b0ccaaebcbd604"
+ dependencies:
+ end-of-stream "1.0.0"
+ inherits "^2.0.1"
+ readable-stream "^2.0.0"
+ stream-shift "^1.0.0"
+
+end-of-stream@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.0.0.tgz#d4596e702734a93e40e9af864319eabd99ff2f0e"
+ dependencies:
+ once "~1.3.0"
+
+end-of-stream@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.1.0.tgz#e9353258baa9108965efc41cb0ef8ade2f3cfb07"
+ dependencies:
+ once "~1.3.0"
+
+getconfig@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/getconfig/-/getconfig-3.0.0.tgz#e74ca2581d5ec805f8778e1236569453a0f0ae1d"
+
+good-console@^6.4.0:
+ version "6.4.0"
+ resolved "https://registry.yarnpkg.com/good-console/-/good-console-6.4.0.tgz#7294c9d90c4c9f059a082e180625495966d2ba59"
+ dependencies:
+ hoek "4.x.x"
+ joi "8.1.x"
+ json-stringify-safe "5.0.x"
+ moment "2.15.x"
+
+good@^7.1.0:
+ version "7.1.0"
+ resolved "https://registry.yarnpkg.com/good/-/good-7.1.0.tgz#9e05ad24c58a11b71cf5081700f3778db0b22c1c"
+ dependencies:
+ hoek "4.x.x"
+ joi "10.x.x"
+ oppsy "1.x.x"
+ pumpify "1.3.x"
+
+handlebars@^4.0.6:
+ version "4.0.6"
+ resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.6.tgz#2ce4484850537f9c97a8026d5399b935c4ed4ed7"
+ dependencies:
+ async "^1.4.0"
+ optimist "^0.6.1"
+ source-map "^0.4.4"
+ optionalDependencies:
+ uglify-js "^2.6"
+
+hapi@^16.1.0:
+ version "16.1.0"
+ resolved "https://registry.yarnpkg.com/hapi/-/hapi-16.1.0.tgz#419dd86347588821eb5a0a5f493bce019802d33b"
+ dependencies:
+ accept "2.x.x"
+ ammo "2.x.x"
+ boom "4.x.x"
+ call "4.x.x"
+ catbox "7.x.x"
+ catbox-memory "2.x.x"
+ cryptiles "3.x.x"
+ heavy "4.x.x"
+ hoek "4.x.x"
+ iron "4.x.x"
+ items "2.x.x"
+ joi "10.x.x"
+ mimos "3.x.x"
+ podium "^1.2.x"
+ shot "3.x.x"
+ statehood "5.x.x"
+ subtext "^4.3.x"
+ topo "2.x.x"
+
+heavy@4.x.x:
+ version "4.0.3"
+ resolved "https://registry.yarnpkg.com/heavy/-/heavy-4.0.3.tgz#976bba118b011b15fe904aa4f292a168bfc6232f"
+ dependencies:
+ boom "4.x.x"
+ hoek "4.x.x"
+ joi "10.x.x"
+
+hoek@4.x.x:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.1.0.tgz#4a4557460f69842ed463aa00628cc26d2683afa7"
+
+inert@^4.1.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/inert/-/inert-4.1.0.tgz#e68df9fb0b87d8ad688e3428daaf35d623b64f5d"
+ dependencies:
+ ammo "2.x.x"
+ boom "4.x.x"
+ hoek "4.x.x"
+ items "2.x.x"
+ joi "10.x.x"
+ lru-cache "4.0.x"
+
+inherits@^2.0.1, inherits@~2.0.1:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
+
+iron@4.x.x:
+ version "4.0.4"
+ resolved "https://registry.yarnpkg.com/iron/-/iron-4.0.4.tgz#c1f8cc4c91454194ab8920d9247ba882e528061a"
+ dependencies:
+ boom "4.x.x"
+ cryptiles "3.x.x"
+ hoek "4.x.x"
+
+is-buffer@^1.0.2:
+ version "1.1.4"
+ resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.4.tgz#cfc86ccd5dc5a52fa80489111c6920c457e2d98b"
+
+isarray@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
+
+isemail@2.x.x:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/isemail/-/isemail-2.2.1.tgz#0353d3d9a62951080c262c2aa0a42b8ea8e9e2a6"
+
+items@2.x.x, items@^2.1.x:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/items/-/items-2.1.1.tgz#8bd16d9c83b19529de5aea321acaada78364a198"
+
+joi@10.x.x:
+ version "10.2.2"
+ resolved "https://registry.yarnpkg.com/joi/-/joi-10.2.2.tgz#dc5a792b7b4c6fffa562242a95b55d9d3f077e24"
+ dependencies:
+ hoek "4.x.x"
+ isemail "2.x.x"
+ items "2.x.x"
+ topo "2.x.x"
+
+joi@8.1.x:
+ version "8.1.1"
+ resolved "https://registry.yarnpkg.com/joi/-/joi-8.1.1.tgz#2d8b52a5d909d217ed47248577eefe8b1798f48f"
+ dependencies:
+ hoek "4.x.x"
+ isemail "2.x.x"
+ moment "2.x.x"
+ topo "2.x.x"
+
+json-stringify-safe@5.0.x:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
+
+kind-of@^3.0.2:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.1.0.tgz#475d698a5e49ff5e53d14e3e732429dc8bf4cf47"
+ dependencies:
+ is-buffer "^1.0.2"
+
+lazy-cache@^1.0.3:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e"
+
+longest@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097"
+
+lru-cache@4.0.x:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.0.2.tgz#1d17679c069cda5d040991a09dbc2c0db377e55e"
+ dependencies:
+ pseudomap "^1.0.1"
+ yallist "^2.0.0"
+
+mime-db@1.x.x:
+ version "1.26.0"
+ resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.26.0.tgz#eaffcd0e4fc6935cf8134da246e2e6c35305adff"
+
+mimos@3.x.x:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/mimos/-/mimos-3.0.3.tgz#b9109072ad378c2b72f6a0101c43ddfb2b36641f"
+ dependencies:
+ hoek "4.x.x"
+ mime-db "1.x.x"
+
+minimist@~0.0.1:
+ version "0.0.10"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf"
+
+moment@2.15.x, moment@2.x.x:
+ version "2.15.2"
+ resolved "https://registry.yarnpkg.com/moment/-/moment-2.15.2.tgz#1bfdedf6a6e345f322fe956d5df5bd08a8ce84dc"
+
+nes@^6.4.0:
+ version "6.4.0"
+ resolved "https://registry.yarnpkg.com/nes/-/nes-6.4.0.tgz#35831781df19cbe5a8014169bbd70887bc5c63d0"
+ dependencies:
+ boom "4.x.x"
+ call "3.x.x"
+ cryptiles "3.x.x"
+ hoek "4.x.x"
+ iron "4.x.x"
+ items "^2.1.x"
+ joi "10.x.x"
+ ws "1.x.x"
+
+nigel@2.x.x:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/nigel/-/nigel-2.0.2.tgz#93a1866fb0c52d87390aa75e2b161f4b5c75e5b1"
+ dependencies:
+ hoek "4.x.x"
+ vise "2.x.x"
+
+once@^1.3.1:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
+ dependencies:
+ wrappy "1"
+
+once@~1.3.0:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/once/-/once-1.3.3.tgz#b2e261557ce4c314ec8304f3fa82663e4297ca20"
+ dependencies:
+ wrappy "1"
+
+oppsy@1.x.x:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/oppsy/-/oppsy-1.0.2.tgz#98014cd6967653a83cfffa554226dc90050baad4"
+ dependencies:
+ hoek "4.x.x"
+ items "2.x.x"
+
+optimist@^0.6.1:
+ version "0.6.1"
+ resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686"
+ dependencies:
+ minimist "~0.0.1"
+ wordwrap "~0.0.2"
+
+options@>=0.0.5:
+ version "0.0.6"
+ resolved "https://registry.yarnpkg.com/options/-/options-0.0.6.tgz#ec22d312806bb53e731773e7cdaefcf1c643128f"
+
+pez@2.x.x:
+ version "2.1.4"
+ resolved "https://registry.yarnpkg.com/pez/-/pez-2.1.4.tgz#73f822fa62d599d65c4606f490d54d345191bc7c"
+ dependencies:
+ b64 "3.x.x"
+ boom "4.x.x"
+ content "3.x.x"
+ hoek "4.x.x"
+ nigel "2.x.x"
+
+podium@^1.2.x:
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/podium/-/podium-1.2.5.tgz#87c566c2f0365bcf0a1ec7602c4d01948cdd2ad5"
+ dependencies:
+ hoek "4.x.x"
+ items "2.x.x"
+ joi "10.x.x"
+
+process-nextick-args@~1.0.6:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3"
+
+pseudomap@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
+
+pump@^1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/pump/-/pump-1.0.2.tgz#3b3ee6512f94f0e575538c17995f9f16990a5d51"
+ dependencies:
+ end-of-stream "^1.1.0"
+ once "^1.3.1"
+
+pumpify@1.3.x:
+ version "1.3.5"
+ resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.3.5.tgz#1b671c619940abcaeac0ad0e3a3c164be760993b"
+ dependencies:
+ duplexify "^3.1.2"
+ inherits "^2.0.1"
+ pump "^1.0.0"
+
+readable-stream@^2.0.0:
+ version "2.2.2"
+ resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.2.tgz#a9e6fec3c7dda85f8bb1b3ba7028604556fc825e"
+ dependencies:
+ buffer-shims "^1.0.0"
+ core-util-is "~1.0.0"
+ inherits "~2.0.1"
+ isarray "~1.0.0"
+ process-nextick-args "~1.0.6"
+ string_decoder "~0.10.x"
+ util-deprecate "~1.0.1"
+
+repeat-string@^1.5.2:
+ version "1.6.1"
+ resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
+
+right-align@^0.1.1:
+ version "0.1.3"
+ resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef"
+ dependencies:
+ align-text "^0.1.1"
+
+shot@3.x.x:
+ version "3.4.0"
+ resolved "https://registry.yarnpkg.com/shot/-/shot-3.4.0.tgz#e7125ee72575ae5218349e933636808d790d4b92"
+ dependencies:
+ hoek "4.x.x"
+ joi "10.x.x"
+
+source-map@^0.4.4:
+ version "0.4.4"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b"
+ dependencies:
+ amdefine ">=0.0.4"
+
+source-map@~0.5.1:
+ version "0.5.6"
+ resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412"
+
+statehood@5.x.x:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/statehood/-/statehood-5.0.1.tgz#fc13c97b37751c18e70513d2b97e896ac8b73005"
+ dependencies:
+ boom "4.x.x"
+ cryptiles "3.x.x"
+ hoek "4.x.x"
+ iron "4.x.x"
+ items "2.x.x"
+ joi "10.x.x"
+
+stream-shift@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952"
+
+string_decoder@~0.10.x:
+ version "0.10.31"
+ resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
+
+subtext@^4.3.x:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/subtext/-/subtext-4.3.0.tgz#dfac90492ec35669fd6e00c6e5d938b06d7ccfbb"
+ dependencies:
+ boom "4.x.x"
+ content "3.x.x"
+ hoek "4.x.x"
+ pez "2.x.x"
+ wreck "10.x.x"
+
+topo@2.x.x:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/topo/-/topo-2.0.2.tgz#cd5615752539057c0dc0491a621c3bc6fbe1d182"
+ dependencies:
+ hoek "4.x.x"
+
+uglify-js@^2.6:
+ version "2.7.5"
+ resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.7.5.tgz#4612c0c7baaee2ba7c487de4904ae122079f2ca8"
+ dependencies:
+ async "~0.2.6"
+ source-map "~0.5.1"
+ uglify-to-browserify "~1.0.0"
+ yargs "~3.10.0"
+
+uglify-to-browserify@~1.0.0:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7"
+
+ultron@1.0.x:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.0.2.tgz#ace116ab557cd197386a4e88f4685378c8b2e4fa"
+
+util-deprecate@~1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
+
+vise@2.x.x:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/vise/-/vise-2.0.2.tgz#6b08e8fb4cb76e3a50cd6dd0ec37338e811a0d39"
+ dependencies:
+ hoek "4.x.x"
+
+vision@^4.1.1:
+ version "4.1.1"
+ resolved "https://registry.yarnpkg.com/vision/-/vision-4.1.1.tgz#e1b612b2d2e2f20310a039290fd49d51248f82da"
+ dependencies:
+ boom "4.x.x"
+ hoek "4.x.x"
+ items "2.x.x"
+ joi "10.x.x"
+
+window-size@0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d"
+
+wordwrap@0.0.2:
+ version "0.0.2"
+ resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"
+
+wordwrap@~0.0.2:
+ version "0.0.3"
+ resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107"
+
+wrappy@1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
+
+wreck@10.x.x:
+ version "10.0.0"
+ resolved "https://registry.yarnpkg.com/wreck/-/wreck-10.0.0.tgz#98ab882f85e16a526332507f101f5a7841162278"
+ dependencies:
+ boom "4.x.x"
+ hoek "4.x.x"
+
+ws@1.x.x:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/ws/-/ws-1.1.2.tgz#8a244fa052401e08c9886cf44a85189e1fd4067f"
+ dependencies:
+ options ">=0.0.5"
+ ultron "1.0.x"
+
+yallist@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.0.0.tgz#306c543835f09ee1a4cb23b7bce9ab341c91cdd4"
+
+yargs@~3.10.0:
+ version "3.10.0"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1"
+ dependencies:
+ camelcase "^1.0.2"
+ cliui "^2.1.0"
+ decamelize "^1.0.0"
+ window-size "0.1.0"
diff --git a/Chapter08/web-server-hardening/http-app/index.js b/Chapter08/web-server-hardening/http-app/index.js
new file mode 100644
index 0000000..f6a2a09
--- /dev/null
+++ b/Chapter08/web-server-hardening/http-app/index.js
@@ -0,0 +1,25 @@
+const http = require('http')
+
+const server = http.createServer((req, res) => {
+ secureHeaders(res)
+ switch (req.url) {
+ case '/': return res.end('hello world')
+ case '/users': return res.end('oh, some users!')
+ default: return error('404', res)
+ }
+})
+
+function secureHeaders (res) {
+ res.setHeader('X-DNS-Prefetch-Control', 'off')
+ res.setHeader('X-Frame-Options', 'SAMEORIGIN')
+ res.setHeader('X-Download-Options', 'noopen')
+ res.setHeader('X-Content-Type-Options', 'nosniff')
+ res.setHeader('X-XSS-Protection', '1; mode=block')
+}
+
+function error(code, res) {
+ res.statusCode = code
+ res.end(http.STATUS_CODES[code])
+}
+
+server.listen(3000)
\ No newline at end of file
diff --git a/Chapter08/web-server-hardening/koa-app/app.js b/Chapter08/web-server-hardening/koa-app/app.js
new file mode 100644
index 0000000..569f219
--- /dev/null
+++ b/Chapter08/web-server-hardening/koa-app/app.js
@@ -0,0 +1,61 @@
+const Koa = require('koa')
+const helmet = require('koa-helmet')
+const app = new Koa()
+const router = require('koa-router')()
+const views = require('koa-views')
+const co = require('co')
+const json = require('koa-json')
+const onerror = require('koa-onerror')
+const bodyparser = require('koa-bodyparser')
+const serve = require('koa-static')
+const path = require('path')
+const log4js = require('koa-log4')
+const logger = log4js.getLogger('app')
+
+const index = require('./routes/index')
+const users = require('./routes/users')
+
+// middlewares
+app.use(helmet())
+app.use(bodyparser())
+app.use(json())
+app.use(log4js.koaLogger(log4js.getLogger('http'), { level: 'auto' }))
+app.use(serve(path.join(__dirname, 'public')))
+
+// handle error
+onerror(app)
+
+// setup view
+app.use(views(path.join(__dirname, 'views'), {
+ extension: 'jade'
+}))
+
+// logger
+// app.use(async (ctx, next) => {
+// const start = new Date()
+// await next()
+// const ms = new Date() - start
+// logger.info(`${ctx.method} ${ctx.url} - ${ms}ms`)
+// })
+app.use(co.wrap(function * (ctx, next) {
+ const start = new Date()
+ yield next()
+ const ms = new Date() - start
+ logger.info(`${ctx.method} ${ctx.url} - ${ms}ms`)
+}))
+
+// routes definition
+router.use('/', index.routes(), index.allowedMethods())
+router.use('/users', users.routes(), users.allowedMethods())
+
+// mount root routes
+app.use(router.routes())
+ .use(router.allowedMethods())
+
+// log error
+app.on('error', function (err, ctx) {
+ logger.error(err)
+ logger.error('server error', err, ctx)
+})
+
+module.exports = app
diff --git a/Chapter08/web-server-hardening/koa-app/bin/www b/Chapter08/web-server-hardening/koa-app/bin/www
new file mode 100644
index 0000000..d8f1386
--- /dev/null
+++ b/Chapter08/web-server-hardening/koa-app/bin/www
@@ -0,0 +1,106 @@
+#!/usr/bin/env node
+
+/**
+ * Module dependencies.
+ */
+
+const app = require('../app')
+const http = require('http')
+const path = require('path')
+
+const appDir = path.resolve(__dirname, '..')
+const logDir = path.join(appDir, 'logs')
+/**
+ * make a log directory, just in case it isn't there.
+ */
+try {
+ require('fs').mkdirSync(logDir)
+} catch (e) {
+ if (e.code !== 'EEXIST') {
+ console.error('Could not set up log directory, error was: ', e)
+ process.exit(1)
+ }
+}
+const log4js = require('koa-log4')
+log4js.configure(path.join(appDir, 'log4js.json'), { cwd: logDir })
+const logger = log4js.getLogger('startup')
+/**
+ * Get port from environment and store in Express.
+ */
+
+var port = normalizePort(process.env.PORT || '3000')
+// app.set('port', port)
+
+/**
+ * Create HTTP server.
+ */
+
+var server = http.createServer(app.callback())
+
+/**
+ * Listen on provided port, on all network interfaces.
+ */
+
+server.listen(port)
+server.on('error', onError)
+server.on('listening', onListening)
+
+/**
+ * Normalize a port into a number, string, or false.
+ */
+
+function normalizePort (val) {
+ var port = parseInt(val, 10)
+
+ if (isNaN(port)) {
+ // named pipe
+ return val
+ }
+
+ if (port >= 0) {
+ // port number
+ return port
+ }
+
+ return false
+}
+
+/**
+ * Event listener for HTTP server "error" event.
+ */
+
+function onError (error) {
+ if (error.syscall !== 'listen') {
+ throw error
+ }
+
+ var bind = typeof port === 'string'
+ ? 'Pipe ' + port
+ : 'Port ' + port
+
+ // handle specific listen errors with friendly messages
+ switch (error.code) {
+ case 'EACCES':
+ logger.error(bind + ' requires elevated privileges')
+ process.exit(1)
+ break
+ case 'EADDRINUSE':
+ logger.error(bind + ' is already in use')
+ process.exit(1)
+ break
+ default:
+ throw error
+ }
+}
+
+/**
+ * Event listener for HTTP server "listening" event.
+ */
+
+function onListening () {
+ var addr = server.address()
+ var bind = typeof addr === 'string'
+ ? 'pipe ' + addr
+ : 'port ' + addr.port
+ logger.info('Listening on ' + bind)
+}
diff --git a/Chapter08/web-server-hardening/koa-app/gulpfile.js b/Chapter08/web-server-hardening/koa-app/gulpfile.js
new file mode 100644
index 0000000..c54a01a
--- /dev/null
+++ b/Chapter08/web-server-hardening/koa-app/gulpfile.js
@@ -0,0 +1,66 @@
+var gulp = require('gulp')
+var nodemon = require('gulp-nodemon')
+var sourcemaps = require('gulp-sourcemaps')
+var concat = require('gulp-concat')
+var uglify = require('gulp-uglify')
+var cssmin = require('gulp-cssmin')
+var del = require('del')
+var path = require('path')
+
+var client = {
+ js: {
+ src: 'client/js',
+ dest: 'public/js'
+ },
+ css: {
+ src: 'client/css',
+ dest: 'public/css'
+ }
+}
+
+gulp.task('default', ['jsmin', 'cssmin', 'start'])
+
+gulp.task('start', function () {
+ nodemon({
+ script: 'bin/www',
+ ext: 'js css ejs',
+ ignore: ['public', 'logs'],
+ tasks: function (files) {
+ var tasks = []
+ files.forEach(function (file) {
+ if (path.relative(client.js.src, file).substr(0, 2) !== '..'
+ && !~tasks.indexOf('jsmin')) {
+ tasks.push('jsmin')
+ }
+ if (path.relative(client.css.src, file).substr(0, 2) !== '..'
+ && !~tasks.indexOf('cssmin')) {
+ tasks.push('cssmin')
+ }
+ })
+ return tasks
+ }
+ })
+})
+
+gulp.task('cleanjs', function () {
+ return del([client.js.dest])
+})
+
+gulp.task('cleancss', function () {
+ return del([client.css.dest])
+})
+
+gulp.task('jsmin', ['cleanjs'], function () {
+ return gulp.src(client.js.src + '/**/*.js')
+ .pipe(sourcemaps.init())
+ .pipe(concat('graph.js'))
+ .pipe(uglify())
+ .pipe(gulp.dest(client.js.dest))
+})
+
+gulp.task('cssmin', ['cleancss'], function () {
+ return gulp.src(client.css.src + '/**/*.css')
+ .pipe(concat('style.css'))
+ .pipe(cssmin())
+ .pipe(gulp.dest(client.css.dest))
+})
diff --git a/Chapter08/web-server-hardening/koa-app/log4js.json b/Chapter08/web-server-hardening/koa-app/log4js.json
new file mode 100644
index 0000000..febe3b5
--- /dev/null
+++ b/Chapter08/web-server-hardening/koa-app/log4js.json
@@ -0,0 +1,38 @@
+{
+ "appenders": [
+ {
+ "type": "console"
+ },
+ {
+ "type": "clustered",
+ "appenders": [
+ {
+ "type": "dateFile",
+ "filename": "startup.log",
+ "pattern": "-yyyy-MM-dd",
+ "category": "startup"
+ },
+ {
+ "type": "dateFile",
+ "filename": "http.log",
+ "pattern": "-yyyy-MM-dd",
+ "category": "http"
+ },
+ {
+ "type": "file",
+ "filename": "app.log",
+ "maxLogSize": 10485760,
+ "numBackups": 5
+ },
+ {
+ "type": "logLevelFilter",
+ "level": "ERROR",
+ "appender": {
+ "type": "file",
+ "filename": "errors.log"
+ }
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/Chapter08/web-server-hardening/koa-app/package.json b/Chapter08/web-server-hardening/koa-app/package.json
new file mode 100644
index 0000000..8b18f50
--- /dev/null
+++ b/Chapter08/web-server-hardening/koa-app/package.json
@@ -0,0 +1,31 @@
+{
+ "name": "koa-app",
+ "version": "0.0.0",
+ "private": true,
+ "scripts": {
+ "start": "node ./bin/www",
+ "test": "gulp"
+ },
+ "dependencies": {
+ "co": "^4.6.0",
+ "jade": "~1.11.0",
+ "koa": "^2.0.0",
+ "koa-bodyparser": "^3.0.0",
+ "koa-helmet": "^3.1.0",
+ "koa-json": "^2.0.0",
+ "koa-log4": "^2.0.1",
+ "koa-onerror": "^1.2.1",
+ "koa-router": "^7.0.0",
+ "koa-static": "^3.0.0",
+ "koa-views": "^5.0.1"
+ },
+ "devDependencies": {
+ "del": "^2.2.0",
+ "gulp": "^3.9.1",
+ "gulp-concat": "^2.6.0",
+ "gulp-cssmin": "^0.1.7",
+ "gulp-nodemon": "^2.0.6",
+ "gulp-sourcemaps": "^1.6.0",
+ "gulp-uglify": "^1.5.3"
+ }
+}
diff --git a/Chapter08/web-server-hardening/koa-app/public/stylesheets/style.css b/Chapter08/web-server-hardening/koa-app/public/stylesheets/style.css
new file mode 100644
index 0000000..9453385
--- /dev/null
+++ b/Chapter08/web-server-hardening/koa-app/public/stylesheets/style.css
@@ -0,0 +1,8 @@
+body {
+ padding: 50px;
+ font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
+}
+
+a {
+ color: #00B7FF;
+}
diff --git a/Chapter08/web-server-hardening/koa-app/routes/index.js b/Chapter08/web-server-hardening/koa-app/routes/index.js
new file mode 100644
index 0000000..b0bbe62
--- /dev/null
+++ b/Chapter08/web-server-hardening/koa-app/routes/index.js
@@ -0,0 +1,19 @@
+const co = require('co')
+const router = require('koa-router')()
+
+router.get('/', co.wrap(function *(ctx, next) {
+ ctx.state = {
+ title: 'Welcome to Koa'
+ }
+ yield ctx.render('index', {})
+}))
+
+// router.get('/', async function (ctx, next) {
+// ctx.state = {
+// title: 'koa2 title'
+// }
+//
+// await ctx.render('index', {
+// })
+// })
+module.exports = router
diff --git a/Chapter08/web-server-hardening/koa-app/routes/users.js b/Chapter08/web-server-hardening/koa-app/routes/users.js
new file mode 100644
index 0000000..eae6d06
--- /dev/null
+++ b/Chapter08/web-server-hardening/koa-app/routes/users.js
@@ -0,0 +1,7 @@
+const router = require('koa-router')()
+
+router.get('/', function (ctx, next) {
+ ctx.body = 'this a users response!'
+})
+
+module.exports = router
diff --git a/Chapter08/web-server-hardening/koa-app/views/error.jade b/Chapter08/web-server-hardening/koa-app/views/error.jade
new file mode 100644
index 0000000..51ec12c
--- /dev/null
+++ b/Chapter08/web-server-hardening/koa-app/views/error.jade
@@ -0,0 +1,6 @@
+extends layout
+
+block content
+ h1= message
+ h2= error.status
+ pre #{error.stack}
diff --git a/Chapter08/web-server-hardening/koa-app/views/index.jade b/Chapter08/web-server-hardening/koa-app/views/index.jade
new file mode 100644
index 0000000..3d63b9a
--- /dev/null
+++ b/Chapter08/web-server-hardening/koa-app/views/index.jade
@@ -0,0 +1,5 @@
+extends layout
+
+block content
+ h1= title
+ p Welcome to #{title}
diff --git a/Chapter08/web-server-hardening/koa-app/views/layout.jade b/Chapter08/web-server-hardening/koa-app/views/layout.jade
new file mode 100644
index 0000000..15af079
--- /dev/null
+++ b/Chapter08/web-server-hardening/koa-app/views/layout.jade
@@ -0,0 +1,7 @@
+doctype html
+html
+ head
+ title= title
+ link(rel='stylesheet', href='/stylesheets/style.css')
+ body
+ block content
diff --git a/Chapter09/async-opt/calculate-average.js b/Chapter09/async-opt/calculate-average.js
new file mode 100644
index 0000000..302b357
--- /dev/null
+++ b/Chapter09/async-opt/calculate-average.js
@@ -0,0 +1,23 @@
+'use strict'
+
+const MongoClient = require('mongodb').MongoClient
+const url = 'mongodb://localhost:27017/test';
+var count = 0
+var max = 1000
+
+MongoClient.connect(url, function(err, db) {
+ if (err) { throw err }
+ const collection = db.collection('data')
+ const average = db.collection('averages')
+
+ collection.find({}).toArray(function (err, data) {
+ if (err) { throw err }
+ average.insert({
+ value: data.reduce((acc, v) => acc + v, 0) / data.length
+ }, function (err) {
+ if (err) { throw err }
+ db.close()
+ })
+ })
+})
+
diff --git a/Chapter09/async-opt/load.js b/Chapter09/async-opt/load.js
new file mode 100644
index 0000000..d1c2285
--- /dev/null
+++ b/Chapter09/async-opt/load.js
@@ -0,0 +1,26 @@
+'use strict'
+
+const MongoClient = require('mongodb').MongoClient
+const url = 'mongodb://localhost:27017/test';
+var count = 0
+var max = 1000
+
+MongoClient.connect(url, function(err, db) {
+ if (err) { throw err }
+ const collection = db.collection('data')
+
+ function insert (err) {
+ if (err) throw err
+
+ if (count++ === max) {
+ return db.close()
+ }
+
+ collection.insert({
+ value: Math.random() * 1000000
+ }, insert)
+ }
+
+ insert()
+})
+
diff --git a/Chapter09/async-opt/package.json b/Chapter09/async-opt/package.json
new file mode 100644
index 0000000..fc82b7b
--- /dev/null
+++ b/Chapter09/async-opt/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "async-opt",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "express": "^4.14.0",
+ "fastq": "^1.4.1",
+ "lru-cache": "^4.0.1",
+ "mongodb": "^2.1.18"
+ }
+}
diff --git a/Chapter09/async-opt/server-average.js b/Chapter09/async-opt/server-average.js
new file mode 100644
index 0000000..863a0de
--- /dev/null
+++ b/Chapter09/async-opt/server-average.js
@@ -0,0 +1,19 @@
+'use strict'
+
+const MongoClient = require('mongodb').MongoClient
+const express = require('express')
+const app = express()
+
+var url = 'mongodb://localhost:27017/test';
+
+MongoClient.connect(url, function(err, db) {
+ if (err) { throw err }
+ const collection = db.collection('data')
+ app.get('/hello', (req, res) => {
+ collection.findOne({}, function sum (err, data) {
+ res.send('' + data.value)
+ })
+ })
+
+ app.listen(3000)
+})
diff --git a/Chapter09/async-opt/server-cache.js b/Chapter09/async-opt/server-cache.js
new file mode 100644
index 0000000..61e0818
--- /dev/null
+++ b/Chapter09/async-opt/server-cache.js
@@ -0,0 +1,56 @@
+'use strict'
+
+const MongoClient = require('mongodb').MongoClient
+const express = require('express')
+const LRU = require('lru-cache')
+const fastq = require('fastq')
+const app = express()
+
+var url = 'mongodb://localhost:27017/test';
+
+function sum (data) {
+ var sum = 0
+ const l = data.length
+ for (var i = 0; i < l; i++) {
+ sum += data[i].value
+ }
+ return sum
+}
+
+MongoClient.connect(url, function(err, db) {
+ if (err) { throw err }
+ const collection = db.collection('data')
+ const queue = fastq(work)
+ const cache = LRU({
+ maxAge: 1000 * 5 // 5 seconds
+ })
+
+ function work (req, done) {
+ const elem = cache.get('average')
+ if (elem) {
+ done(null, elem)
+ return
+ }
+ collection.find({}).toArray(function (err, data) {
+ if (err) {
+ done(err)
+ return
+ }
+ const result = sum(data) / data.length
+ cache.set('average', result)
+ done(null, result)
+ })
+ }
+
+ app.get('/hello', (req, res) => {
+ queue.push(req, function (err, result) {
+ if (err) {
+ res.send(err.message)
+ return
+ }
+ res.send('' + result)
+ })
+ })
+
+ app.listen(3000)
+})
diff --git a/Chapter09/async-opt/server-no-reduce.js b/Chapter09/async-opt/server-no-reduce.js
new file mode 100644
index 0000000..f0f5db3
--- /dev/null
+++ b/Chapter09/async-opt/server-no-reduce.js
@@ -0,0 +1,29 @@
+'use strict'
+
+const MongoClient = require('mongodb').MongoClient
+const express = require('express')
+const app = express()
+
+var url = 'mongodb://localhost:27017/test'
+
+MongoClient.connect(url, function(err, db) {
+ if (err) { throw err }
+ const collection = db.collection('data')
+ app.get('/hello', (req, res) => {
+ collection.find({}).toArray(function sum (err, data) {
+ if (err) {
+ res.send(err)
+ return
+ }
+ var sum = 0
+ const l = data.length
+ for (var i = 0; i < l; i++) {
+ sum += data[i].value
+ }
+ const result = sum / data.length
+ res.send('' + result)
+ })
+ })
+
+ app.listen(3000)
+})
\ No newline at end of file
diff --git a/Chapter09/async-opt/server-one-sum-fn.js b/Chapter09/async-opt/server-one-sum-fn.js
new file mode 100644
index 0000000..d828303
--- /dev/null
+++ b/Chapter09/async-opt/server-one-sum-fn.js
@@ -0,0 +1,33 @@
+'use strict'
+
+const MongoClient = require('mongodb').MongoClient
+const express = require('express')
+const app = express()
+
+var url = 'mongodb://localhost:27017/test';
+
+function sum (data) {
+ var sum = 0
+ const l = data.length
+ for (var i = 0; i < l; i++) {
+ sum += data[i].value
+ }
+ return sum
+}
+
+MongoClient.connect(url, function(err, db) {
+ if (err) { throw err }
+ const collection = db.collection('data')
+ app.get('/hello', (req, res) => {
+ collection.find({}).toArray(function (err, data) {
+ if (err) {
+ res.send(err)
+ return
+ }
+ const result = sum(data) / data.length
+ res.send('' + result)
+ })
+ })
+
+ app.listen(3000)
+})
diff --git a/Chapter09/async-opt/server.js b/Chapter09/async-opt/server.js
new file mode 100644
index 0000000..26c8f62
--- /dev/null
+++ b/Chapter09/async-opt/server.js
@@ -0,0 +1,26 @@
+'use strict'
+
+const MongoClient = require('mongodb').MongoClient
+const express = require('express')
+const app = express()
+
+var url = 'mongodb://localhost:27017/test';
+
+
+MongoClient.connect(url, function(err, db) {
+ if (err) { throw err }
+ const collection = db.collection('data')
+ app.get('/hello', (req, res) => {
+ collection.find({}).toArray(function sum (err, data) {
+ if (err) {
+ res.send(err)
+ return
+ }
+ const total = data.reduce((acc, d) => acc + d.value, 0)
+ const result = total / data.length
+ res.send('' + result)
+ })
+ })
+
+ app.listen(3000)
+})
diff --git a/Chapter09/async-opt/server2.js b/Chapter09/async-opt/server2.js
new file mode 100644
index 0000000..85b1ec4
--- /dev/null
+++ b/Chapter09/async-opt/server2.js
@@ -0,0 +1,27 @@
+'use strict'
+
+const MongoClient = require('mongodb').MongoClient
+const express = require('express')
+const app = express()
+
+var url = 'mongodb://localhost:27017/test';
+
+
+MongoClient.connect(url, function(err, db) {
+ if (err) { throw err }
+ const collection = db.collection('data')
+ app.get('/hello', (req, res) => {
+ var count = 0
+ var result = 0
+ collection.find({})
+ .on('data', function (chunk) {
+ count++
+ result += chunk.value
+ })
+ .on('end', function () {
+ res.send('' + (result / count))
+ })
+ })
+
+ app.listen(3000)
+})
diff --git a/Chapter09/bench-http/measuring-post-performance/package.json b/Chapter09/bench-http/measuring-post-performance/package.json
new file mode 100644
index 0000000..3865d17
--- /dev/null
+++ b/Chapter09/bench-http/measuring-post-performance/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "bench-http",
+ "version": "0.0.1",
+ "description": "",
+ "main": "server.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "",
+ "license": "UNLICENSED",
+ "dependencies": {
+ "body-parser": "^1.15.2",
+ "express": "^4.13.4"
+ }
+}
diff --git a/Chapter09/bench-http/measuring-post-performance/server.js b/Chapter09/bench-http/measuring-post-performance/server.js
new file mode 100644
index 0000000..13bdf4e
--- /dev/null
+++ b/Chapter09/bench-http/measuring-post-performance/server.js
@@ -0,0 +1,14 @@
+'use strict'
+
+const express = require('express')
+const bodyParser = require('body-parser')
+const app = express()
+
+app.use(bodyParser.json())
+app.use(bodyParser.urlencoded({extended: false}))
+
+app.post('/echo', (req, res) => {
+ res.send(req.body)
+})
+
+app.listen(3000)
diff --git a/Chapter09/bench-http/package.json b/Chapter09/bench-http/package.json
new file mode 100644
index 0000000..13e3c65
--- /dev/null
+++ b/Chapter09/bench-http/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "bench-http",
+ "version": "0.0.1",
+ "description": "",
+ "main": "server.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "",
+ "license": "UNLICENSED",
+ "dependencies": {
+ "express": "^4.13.4"
+ }
+}
diff --git a/Chapter09/bench-http/profiling-for-production/package.json b/Chapter09/bench-http/profiling-for-production/package.json
new file mode 100644
index 0000000..e7fb00e
--- /dev/null
+++ b/Chapter09/bench-http/profiling-for-production/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "bench-http",
+ "version": "0.0.1",
+ "description": "",
+ "main": "server.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "",
+ "license": "UNLICENSED",
+ "dependencies": {
+ "express": "^4.13.4",
+ "jade": "^1.11.0"
+ }
+}
diff --git a/Chapter09/bench-http/profiling-for-production/server.js b/Chapter09/bench-http/profiling-for-production/server.js
new file mode 100644
index 0000000..2a499b2
--- /dev/null
+++ b/Chapter09/bench-http/profiling-for-production/server.js
@@ -0,0 +1,14 @@
+'use strict'
+
+const express = require('express')
+const path = require('path')
+const app = express()
+
+app.set('views', path.join(__dirname, 'views'));
+app.set('view engine', 'jade');
+
+app.get('/hello', (req, res) => {
+ res.render('hello', { title: 'Express' });
+})
+
+app.listen(3000)
diff --git a/Chapter09/bench-http/profiling-for-production/views/hello.jade b/Chapter09/bench-http/profiling-for-production/views/hello.jade
new file mode 100644
index 0000000..b3dd2e7
--- /dev/null
+++ b/Chapter09/bench-http/profiling-for-production/views/hello.jade
@@ -0,0 +1,7 @@
+doctype html
+html
+ head
+ title= title
+ link(rel='stylesheet', href='/stylesheets/style.css')
+ body
+ h1= title
diff --git a/Chapter09/bench-http/server.js b/Chapter09/bench-http/server.js
new file mode 100644
index 0000000..ad8c0d6
--- /dev/null
+++ b/Chapter09/bench-http/server.js
@@ -0,0 +1,10 @@
+'use strict'
+
+const express = require('express')
+const app = express()
+
+app.get('/hello', (req, res) => {
+ res.send('hello world')
+})
+
+app.listen(3000)
diff --git a/Chapter09/hello-server/package.json b/Chapter09/hello-server/package.json
new file mode 100644
index 0000000..5dfcc8c
--- /dev/null
+++ b/Chapter09/hello-server/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "hello-server",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "David Mark Clements",
+ "license": "MIT",
+ "dependencies": {
+ "express": "^4.14.0",
+ "jade": "^1.11.0"
+ }
+}
diff --git a/Chapter09/hello-server/server.js b/Chapter09/hello-server/server.js
new file mode 100644
index 0000000..413b1dc
--- /dev/null
+++ b/Chapter09/hello-server/server.js
@@ -0,0 +1,12 @@
+const express = require('express')
+const path = require('path')
+const app = express()
+
+app.set('views', path.join(__dirname, 'views'));
+app.set('view engine', 'jade');
+
+app.get('/hello', (req, res) => {
+ res.render('hello', { title: 'Express' });
+})
+
+app.listen(3000)
diff --git a/Chapter09/hello-server/views/hello.jade b/Chapter09/hello-server/views/hello.jade
new file mode 100644
index 0000000..b3dd2e7
--- /dev/null
+++ b/Chapter09/hello-server/views/hello.jade
@@ -0,0 +1,7 @@
+doctype html
+html
+ head
+ title= title
+ link(rel='stylesheet', href='/stylesheets/style.css')
+ body
+ h1= title
diff --git a/Chapter09/name-server/climem-enabled/index.js b/Chapter09/name-server/climem-enabled/index.js
new file mode 100644
index 0000000..45485f8
--- /dev/null
+++ b/Chapter09/name-server/climem-enabled/index.js
@@ -0,0 +1,16 @@
+const http = require('http')
+const starwarsName = require('starwars-names').random
+const names = {}
+
+http.createServer((req, res) => {
+ res.end(`Your unique name is: ${createName(req)} \n`)
+}).listen(8080)
+
+function createName () {
+ var result = starwarsName()
+ if (names[result]) {
+ result += names[result]++
+ }
+ names[result] = 1
+ return result
+}
\ No newline at end of file
diff --git a/Chapter09/name-server/climem-enabled/package.json b/Chapter09/name-server/climem-enabled/package.json
new file mode 100644
index 0000000..4b1d390
--- /dev/null
+++ b/Chapter09/name-server/climem-enabled/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "name-server",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "David Mark Clements",
+ "license": "MIT",
+ "dependencies": {
+ "starwars-names": "^1.6.0"
+ },
+ "devDependencies": {
+ "climem": "^1.0.2"
+ }
+}
diff --git a/Chapter09/name-server/index.js b/Chapter09/name-server/index.js
new file mode 100644
index 0000000..45485f8
--- /dev/null
+++ b/Chapter09/name-server/index.js
@@ -0,0 +1,16 @@
+const http = require('http')
+const starwarsName = require('starwars-names').random
+const names = {}
+
+http.createServer((req, res) => {
+ res.end(`Your unique name is: ${createName(req)} \n`)
+}).listen(8080)
+
+function createName () {
+ var result = starwarsName()
+ if (names[result]) {
+ result += names[result]++
+ }
+ names[result] = 1
+ return result
+}
\ No newline at end of file
diff --git a/Chapter09/name-server/non-leaky/index.js b/Chapter09/name-server/non-leaky/index.js
new file mode 100644
index 0000000..62f7c4a
--- /dev/null
+++ b/Chapter09/name-server/non-leaky/index.js
@@ -0,0 +1,15 @@
+const http = require('http')
+const starwarsName = require('starwars-names').random
+const names = {}
+
+http.createServer((req, res) => {
+ res.end(`Your unique name is: ${createName(req)} \n`)
+}).listen(8080)
+
+function createName () {
+ var result = starwarsName()
+ names[result] = names[result] ?
+ names[result] + 1 :
+ 1
+ return result + names[result]
+}
\ No newline at end of file
diff --git a/Chapter09/name-server/non-leaky/package.json b/Chapter09/name-server/non-leaky/package.json
new file mode 100644
index 0000000..cc9bf3f
--- /dev/null
+++ b/Chapter09/name-server/non-leaky/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "name-server",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "David Mark Clements",
+ "license": "MIT",
+ "dependencies": {
+ "sillyname": "^0.1.0"
+ }
+}
diff --git a/Chapter09/name-server/package.json b/Chapter09/name-server/package.json
new file mode 100644
index 0000000..854c33b
--- /dev/null
+++ b/Chapter09/name-server/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "name-server",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "David Mark Clements",
+ "license": "MIT",
+ "dependencies": {
+ "starwars-names": "^1.6.0"
+ }
+}
diff --git a/Chapter09/placeholder.deleteme b/Chapter09/placeholder.deleteme
new file mode 100644
index 0000000..e69de29
diff --git a/Chapter09/sync-opt/bench.js b/Chapter09/sync-opt/bench.js
new file mode 100644
index 0000000..3b9d63c
--- /dev/null
+++ b/Chapter09/sync-opt/bench.js
@@ -0,0 +1,33 @@
+'use strict'
+
+const benchmark = require('benchmark')
+const slow = require('./slow')
+const noCollection = require('./no-collections')
+
+const suite = new benchmark.Suite()
+
+const numbers = []
+
+for (let i = 0; i < 1000; i++) {
+ numbers.push(Math.random() * i)
+}
+
+suite.add('slow', function () {
+ slow(12, numbers)
+})
+
+suite.add('no-collections', function () {
+ noCollection(12, numbers)
+})
+
+suite.on('complete', print)
+
+suite.run()
+
+function print () {
+ for (var i = 0; i < this.length; i++) {
+ console.log(this[i].toString())
+ }
+
+ console.log('Fastest is', this.filter('fastest').map('name')[0])
+}
diff --git a/Chapter09/sync-opt/checking-optimization-status/bench.js b/Chapter09/sync-opt/checking-optimization-status/bench.js
new file mode 100644
index 0000000..6562c99
--- /dev/null
+++ b/Chapter09/sync-opt/checking-optimization-status/bench.js
@@ -0,0 +1,42 @@
+'use strict'
+
+const benchmark = require('benchmark')
+const slow = require('./slow')
+const noCollection = require('./no-collections')
+const noTryCatch = require('./no-try-catch')
+const funcStatus = require('./func-status')
+
+const suite = new benchmark.Suite()
+
+const numbers = []
+
+for (let i = 0; i < 1000; i++) {
+ numbers.push(Math.random() * i)
+}
+
+suite.add('slow', function () {
+ slow(12, numbers)
+})
+
+suite.add('no-collections', function () {
+ noCollection(12, numbers)
+})
+
+suite.add('no-try-catch', function () {
+ noTryCatch(12, numbers)
+})
+
+suite.on('complete', print)
+
+suite.run()
+
+function print () {
+ for (var i = 0; i < this.length; i++) {
+ console.log(this[i].toString())
+ }
+ funcStatus('slow', slow)
+ funcStatus('noCollection', noCollection)
+ funcStatus('noTryCatch', noTryCatch)
+
+ console.log('Fastest is', this.filter('fastest').map('name')[0])
+}
diff --git a/Chapter09/sync-opt/checking-optimization-status/func-status.js b/Chapter09/sync-opt/checking-optimization-status/func-status.js
new file mode 100644
index 0000000..0c66940
--- /dev/null
+++ b/Chapter09/sync-opt/checking-optimization-status/func-status.js
@@ -0,0 +1,15 @@
+'use strict'
+
+function printStatus (name, fn) {
+ switch(%GetOptimizationStatus(fn)) {
+ case 1: console.log(`${name} function is optimized`); break;
+ case 2: console.log(`${name} function is not optimized`); break;
+ case 3: console.log(`${name} function is always optimized`); break;
+ case 4: console.log(`${name} function is never optimized`); break;
+ case 6: console.log(`${name} function is maybe deoptimized`); break;
+ case 7: console.log(`${name} function is optimized by TurboFan`); break;
+ default: console.log(`${name} function optimization status unknown`); break;
+ }
+}
+
+module.exports = printStatus
diff --git a/Chapter09/sync-opt/checking-optimization-status/no-collections-vs-no-try-catch.js b/Chapter09/sync-opt/checking-optimization-status/no-collections-vs-no-try-catch.js
new file mode 100644
index 0000000..651a1a7
--- /dev/null
+++ b/Chapter09/sync-opt/checking-optimization-status/no-collections-vs-no-try-catch.js
@@ -0,0 +1,28 @@
+'use strict'
+
+const benchmark = require('benchmark')
+const noCollection = require('./no-collections')
+
+const suite = new benchmark.Suite()
+
+const numbers = []
+
+for (let i = 0; i < 1000; i++) {
+ numbers.push(Math.random() * i)
+}
+
+suite.add('no-collections', function () {
+ noCollection(12, numbers)
+})
+
+suite.on('complete', print)
+
+suite.run()
+
+function print () {
+ for (var i = 0; i < this.length; i++) {
+ console.log(this[i].toString())
+ }
+
+ console.log('Fastest is', this.filter('fastest').map('name')[0])
+}
diff --git a/Chapter09/sync-opt/checking-optimization-status/no-collections.js b/Chapter09/sync-opt/checking-optimization-status/no-collections.js
new file mode 100644
index 0000000..e93243c
--- /dev/null
+++ b/Chapter09/sync-opt/checking-optimization-status/no-collections.js
@@ -0,0 +1,15 @@
+'use strict'
+
+function divideByAndSum (num, array) {
+ var result = 0
+ try {
+ for (var i = 0; i < array.length; i++) {
+ result += array[i] / num
+ }
+ } catch (err) {
+ // to guard for division by zero
+ return 0
+ }
+}
+
+module.exports = divideByAndSum
diff --git a/Chapter09/sync-opt/checking-optimization-status/no-try-catch-bench.js b/Chapter09/sync-opt/checking-optimization-status/no-try-catch-bench.js
new file mode 100644
index 0000000..91ffffd
--- /dev/null
+++ b/Chapter09/sync-opt/checking-optimization-status/no-try-catch-bench.js
@@ -0,0 +1,28 @@
+'use strict'
+
+const benchmark = require('benchmark')
+const noTryCatch = require('./no-try-catch')
+
+const suite = new benchmark.Suite()
+
+const numbers = []
+
+for (let i = 0; i < 1000; i++) {
+ numbers.push(Math.random() * i)
+}
+
+suite.add('no-try-catch', function () {
+ noTryCatch(12, numbers)
+})
+
+suite.on('complete', print)
+
+suite.run()
+
+function print () {
+ for (var i = 0; i < this.length; i++) {
+ console.log(this[i].toString())
+ }
+
+ console.log('Fastest is', this.filter('fastest').map('name')[0])
+}
diff --git a/Chapter09/sync-opt/checking-optimization-status/no-try-catch.js b/Chapter09/sync-opt/checking-optimization-status/no-try-catch.js
new file mode 100644
index 0000000..213026b
--- /dev/null
+++ b/Chapter09/sync-opt/checking-optimization-status/no-try-catch.js
@@ -0,0 +1,17 @@
+'use strict'
+
+function divideByAndSum (num, array) {
+ var result = 0
+
+ if (num === 0) {
+ return 0
+ }
+
+ for (var i = 0; i < array.length; i++) {
+ result += array[i] / num
+ }
+
+ return result
+}
+
+module.exports = divideByAndSum
diff --git a/Chapter09/sync-opt/checking-optimization-status/slow.js b/Chapter09/sync-opt/checking-optimization-status/slow.js
new file mode 100644
index 0000000..f3f38f0
--- /dev/null
+++ b/Chapter09/sync-opt/checking-optimization-status/slow.js
@@ -0,0 +1,16 @@
+'use strict'
+
+function divideByAndSum (num, array) {
+ try {
+ array.map(function (item) {
+ return item / num
+ }).reduce(function (acc, item) {
+ return acc + item
+ }, 0)
+ } catch (err) {
+ // to guard for division by zero
+ return 0
+ }
+}
+
+module.exports = divideByAndSum
diff --git a/Chapter09/sync-opt/function-inlining/bench.js b/Chapter09/sync-opt/function-inlining/bench.js
new file mode 100644
index 0000000..01ec5f7
--- /dev/null
+++ b/Chapter09/sync-opt/function-inlining/bench.js
@@ -0,0 +1,38 @@
+'use strict'
+
+const benchmark = require('benchmark')
+const slow = require('./slow')
+const noCollection = require('./no-collections')
+const noTryCatch = require('./no-try-catch')
+
+const suite = new benchmark.Suite()
+
+const numbers = []
+
+for (let i = 0; i < 1000; i++) {
+ numbers.push(Math.random() * i)
+}
+
+suite.add('slow', function () {
+ slow(12, numbers)
+})
+
+suite.add('no-collections', function () {
+ noCollection(12, numbers)
+})
+
+suite.add('no-try-catch', function () {
+ noTryCatch(12, numbers)
+})
+
+suite.on('complete', print)
+
+suite.run()
+
+function print () {
+ for (var i = 0; i < this.length; i++) {
+ console.log(this[i].toString())
+ }
+
+ console.log('Fastest is', this.filter('fastest').map('name')[0])
+}
diff --git a/Chapter09/sync-opt/function-inlining/initial-bench.js b/Chapter09/sync-opt/function-inlining/initial-bench.js
new file mode 100644
index 0000000..03021cf
--- /dev/null
+++ b/Chapter09/sync-opt/function-inlining/initial-bench.js
@@ -0,0 +1,29 @@
+'use strict'
+
+const benchmark = require('benchmark')
+const noCollection = require('./no-collections')
+const noTryCatch = require('./no-try-catch')
+
+const suite = new benchmark.Suite()
+
+const numbers = []
+
+for (let i = 0; i < 1000; i++) {
+ numbers.push(Math.random() * i)
+}
+
+suite.add('no-collections', function () {
+ noCollection(12, numbers)
+})
+
+suite.on('complete', print)
+
+suite.run()
+
+function print () {
+ for (var i = 0; i < this.length; i++) {
+ console.log(this[i].toString())
+ }
+
+ console.log('Fastest is', this.filter('fastest').map('name')[0])
+}
diff --git a/Chapter09/sync-opt/function-inlining/no-collections-vs-no-try-catch.js b/Chapter09/sync-opt/function-inlining/no-collections-vs-no-try-catch.js
new file mode 100644
index 0000000..651a1a7
--- /dev/null
+++ b/Chapter09/sync-opt/function-inlining/no-collections-vs-no-try-catch.js
@@ -0,0 +1,28 @@
+'use strict'
+
+const benchmark = require('benchmark')
+const noCollection = require('./no-collections')
+
+const suite = new benchmark.Suite()
+
+const numbers = []
+
+for (let i = 0; i < 1000; i++) {
+ numbers.push(Math.random() * i)
+}
+
+suite.add('no-collections', function () {
+ noCollection(12, numbers)
+})
+
+suite.on('complete', print)
+
+suite.run()
+
+function print () {
+ for (var i = 0; i < this.length; i++) {
+ console.log(this[i].toString())
+ }
+
+ console.log('Fastest is', this.filter('fastest').map('name')[0])
+}
diff --git a/Chapter09/sync-opt/function-inlining/no-collections.js b/Chapter09/sync-opt/function-inlining/no-collections.js
new file mode 100644
index 0000000..e93243c
--- /dev/null
+++ b/Chapter09/sync-opt/function-inlining/no-collections.js
@@ -0,0 +1,15 @@
+'use strict'
+
+function divideByAndSum (num, array) {
+ var result = 0
+ try {
+ for (var i = 0; i < array.length; i++) {
+ result += array[i] / num
+ }
+ } catch (err) {
+ // to guard for division by zero
+ return 0
+ }
+}
+
+module.exports = divideByAndSum
diff --git a/Chapter09/sync-opt/function-inlining/no-try-catch-bench.js b/Chapter09/sync-opt/function-inlining/no-try-catch-bench.js
new file mode 100644
index 0000000..91ffffd
--- /dev/null
+++ b/Chapter09/sync-opt/function-inlining/no-try-catch-bench.js
@@ -0,0 +1,28 @@
+'use strict'
+
+const benchmark = require('benchmark')
+const noTryCatch = require('./no-try-catch')
+
+const suite = new benchmark.Suite()
+
+const numbers = []
+
+for (let i = 0; i < 1000; i++) {
+ numbers.push(Math.random() * i)
+}
+
+suite.add('no-try-catch', function () {
+ noTryCatch(12, numbers)
+})
+
+suite.on('complete', print)
+
+suite.run()
+
+function print () {
+ for (var i = 0; i < this.length; i++) {
+ console.log(this[i].toString())
+ }
+
+ console.log('Fastest is', this.filter('fastest').map('name')[0])
+}
diff --git a/Chapter09/sync-opt/function-inlining/no-try-catch.js b/Chapter09/sync-opt/function-inlining/no-try-catch.js
new file mode 100644
index 0000000..213026b
--- /dev/null
+++ b/Chapter09/sync-opt/function-inlining/no-try-catch.js
@@ -0,0 +1,17 @@
+'use strict'
+
+function divideByAndSum (num, array) {
+ var result = 0
+
+ if (num === 0) {
+ return 0
+ }
+
+ for (var i = 0; i < array.length; i++) {
+ result += array[i] / num
+ }
+
+ return result
+}
+
+module.exports = divideByAndSum
diff --git a/Chapter09/sync-opt/function-inlining/slow.js b/Chapter09/sync-opt/function-inlining/slow.js
new file mode 100644
index 0000000..f3f38f0
--- /dev/null
+++ b/Chapter09/sync-opt/function-inlining/slow.js
@@ -0,0 +1,16 @@
+'use strict'
+
+function divideByAndSum (num, array) {
+ try {
+ array.map(function (item) {
+ return item / num
+ }).reduce(function (acc, item) {
+ return acc + item
+ }, 0)
+ } catch (err) {
+ // to guard for division by zero
+ return 0
+ }
+}
+
+module.exports = divideByAndSum
diff --git a/Chapter09/sync-opt/initial-bench.js b/Chapter09/sync-opt/initial-bench.js
new file mode 100644
index 0000000..3801655
--- /dev/null
+++ b/Chapter09/sync-opt/initial-bench.js
@@ -0,0 +1,28 @@
+'use strict'
+
+const benchmark = require('benchmark')
+const slow = require('./slow')
+
+const suite = new benchmark.Suite()
+
+const numbers = []
+
+for (let i = 0; i < 1000; i++) {
+ numbers.push(Math.random() * i)
+}
+
+suite.add('slow', function () {
+ slow(12, numbers)
+})
+
+suite.on('complete', print)
+
+suite.run()
+
+function print () {
+ for (var i = 0; i < this.length; i++) {
+ console.log(this[i].toString())
+ }
+
+ console.log('Fastest is', this.filter('fastest').map('name')[0])
+}
diff --git a/Chapter09/sync-opt/no-collections.js b/Chapter09/sync-opt/no-collections.js
new file mode 100644
index 0000000..e93243c
--- /dev/null
+++ b/Chapter09/sync-opt/no-collections.js
@@ -0,0 +1,15 @@
+'use strict'
+
+function divideByAndSum (num, array) {
+ var result = 0
+ try {
+ for (var i = 0; i < array.length; i++) {
+ result += array[i] / num
+ }
+ } catch (err) {
+ // to guard for division by zero
+ return 0
+ }
+}
+
+module.exports = divideByAndSum
diff --git a/Chapter09/sync-opt/package.json b/Chapter09/sync-opt/package.json
new file mode 100644
index 0000000..b0d4c6d
--- /dev/null
+++ b/Chapter09/sync-opt/package.json
@@ -0,0 +1,14 @@
+{
+ "name": "sync-opt",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "benchmark": "^2.1.0"
+ }
+}
diff --git a/Chapter09/sync-opt/slow.js b/Chapter09/sync-opt/slow.js
new file mode 100644
index 0000000..f3f38f0
--- /dev/null
+++ b/Chapter09/sync-opt/slow.js
@@ -0,0 +1,16 @@
+'use strict'
+
+function divideByAndSum (num, array) {
+ try {
+ array.map(function (item) {
+ return item / num
+ }).reduce(function (acc, item) {
+ return acc + item
+ }, 0)
+ } catch (err) {
+ // to guard for division by zero
+ return 0
+ }
+}
+
+module.exports = divideByAndSum
diff --git a/Chapter10/adding-a-queue-based-service/micro/adderservice/index.js b/Chapter10/adding-a-queue-based-service/micro/adderservice/index.js
new file mode 100644
index 0000000..1f2d9cf
--- /dev/null
+++ b/Chapter10/adding-a-queue-based-service/micro/adderservice/index.js
@@ -0,0 +1,7 @@
+'use strict'
+
+const wiring = require('./wiring')
+const service = require('./service')()
+
+
+wiring(service)
diff --git a/Chapter10/adding-a-queue-based-service/micro/adderservice/package.json b/Chapter10/adding-a-queue-based-service/micro/adderservice/package.json
new file mode 100644
index 0000000..7cef23a
--- /dev/null
+++ b/Chapter10/adding-a-queue-based-service/micro/adderservice/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "adderservice",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "tap test"
+ },
+ "keywords": [],
+ "author": "Peter Elger",
+ "license": "ISC",
+ "dependencies": {
+ "restify": "^4.3.0"
+ },
+ "devDependencies": {
+ "tap": "^10.3.1"
+ }
+}
diff --git a/Chapter10/adding-a-queue-based-service/micro/adderservice/service.js b/Chapter10/adding-a-queue-based-service/micro/adderservice/service.js
new file mode 100644
index 0000000..9d0d175
--- /dev/null
+++ b/Chapter10/adding-a-queue-based-service/micro/adderservice/service.js
@@ -0,0 +1,13 @@
+'use strict'
+
+module.exports = service
+
+function service () {
+ function add (args, cb) {
+ const {first, second} = args
+ const result = (parseInt(first, 10) + parseInt(second, 10))
+ cb(null, {result: result.toString()})
+ }
+
+ return { add }
+}
diff --git a/Chapter10/adding-a-queue-based-service/micro/adderservice/test/index.js b/Chapter10/adding-a-queue-based-service/micro/adderservice/test/index.js
new file mode 100644
index 0000000..32c45e5
--- /dev/null
+++ b/Chapter10/adding-a-queue-based-service/micro/adderservice/test/index.js
@@ -0,0 +1,13 @@
+'use strict'
+
+const {test} = require('tap')
+const service = require('../service')()
+
+test('test add', (t) => {
+ t.plan(2)
+
+ service.add({first: 1, second: 2}, (err, answer) => {
+ t.error(err)
+ t.same(answer, {result: 3})
+ })
+})
diff --git a/Chapter10/adding-a-queue-based-service/micro/adderservice/wiring.js b/Chapter10/adding-a-queue-based-service/micro/adderservice/wiring.js
new file mode 100644
index 0000000..0ed018c
--- /dev/null
+++ b/Chapter10/adding-a-queue-based-service/micro/adderservice/wiring.js
@@ -0,0 +1,27 @@
+'use strict'
+
+const restify = require('restify')
+const { ADDERSERVICE_SERVICE_PORT } = process.env
+
+module.exports = wiring
+
+function wiring (service) {
+ const server = restify.createServer()
+
+ server.get('/add/:first/:second', (req, res, next) => {
+ service.add(req.params, (err, result) => {
+ if (err) {
+ res.send(err)
+ next()
+ return
+ }
+ res.send(200, result)
+ next()
+ })
+ })
+
+ server.listen(ADDERSERVICE_SERVICE_PORT, '0.0.0.0', () => {
+ console.log('%s listening at %s', server.name, server.url)
+ })
+}
+
diff --git a/Chapter10/adding-a-queue-based-service/micro/auditservice/index.js b/Chapter10/adding-a-queue-based-service/micro/auditservice/index.js
new file mode 100644
index 0000000..40a4677
--- /dev/null
+++ b/Chapter10/adding-a-queue-based-service/micro/auditservice/index.js
@@ -0,0 +1,6 @@
+'use strict'
+
+const wiring = require('./wiring')
+const service = require('./service')()
+
+wiring(service)
diff --git a/Chapter10/adding-a-queue-based-service/micro/auditservice/package.json b/Chapter10/adding-a-queue-based-service/micro/auditservice/package.json
new file mode 100644
index 0000000..d93b31e
--- /dev/null
+++ b/Chapter10/adding-a-queue-based-service/micro/auditservice/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "auditservice",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "Peter Elger",
+ "license": "ISC",
+ "dependencies": {
+ "concordant": "^0.2.1",
+ "mongo": "^0.1.0",
+ "restify": "^4.3.0"
+ }
+}
diff --git a/Chapter10/adding-a-queue-based-service/micro/auditservice/service.js b/Chapter10/adding-a-queue-based-service/micro/auditservice/service.js
new file mode 100644
index 0000000..c416e4c
--- /dev/null
+++ b/Chapter10/adding-a-queue-based-service/micro/auditservice/service.js
@@ -0,0 +1,74 @@
+'use strict'
+
+const { MongoClient } = require('mongodb')
+const { dns } = require('concordant')()
+
+module.exports = service
+
+function service () {
+
+ var db
+
+ setup()
+
+ function setup () {
+ const mongo = '_main._tcp.mongo.micro.svc.cluster.local'
+
+ dns.resolve(mongo, (err, locs) => {
+ if (err) {
+ console.error(err)
+ return
+ }
+ const { host, port } = locs[0]
+ const url = `mongodb://${host}:${port}/audit`
+ MongoClient.connect(url, (err, client) => {
+ if (err) {
+ console.log('failed to connect to MongoDB retrying in 100ms')
+ setTimeout(setup, 100)
+ return
+ }
+ db = client
+ db.on('close', () => db = null)
+ })
+ })
+ }
+
+ function append (args, cb) {
+ if (!db) {
+ cb(Error('No database connection'))
+ return
+ }
+ const audit = db.collection('audit')
+ const data = {
+ ts: Date.now(),
+ calc: args.calc,
+ result: args.calcResult
+ }
+
+ audit.insert(data, (err, result) => {
+ if (err) {
+ cb(err)
+ return
+ }
+ cb(null, {result: result.toString()})
+ })
+ }
+
+ function list (args, cb) {
+ if (!db) {
+ cb(Error('No database connection'))
+ return
+ }
+ const audit = db.collection('audit')
+ audit.find({}, {limit: 10}).toArray((err, docs) => {
+ if (err) {
+ cb(err)
+ return
+ }
+ cb(null, {list: docs})
+ })
+ }
+
+ return { append, list }
+}
+
diff --git a/Chapter10/adding-a-queue-based-service/micro/auditservice/wiring.js b/Chapter10/adding-a-queue-based-service/micro/auditservice/wiring.js
new file mode 100644
index 0000000..b18dce8
--- /dev/null
+++ b/Chapter10/adding-a-queue-based-service/micro/auditservice/wiring.js
@@ -0,0 +1,39 @@
+'use strict'
+
+const restify = require('restify')
+const { AUDITSERVICE_SERVICE_PORT } = process.env
+
+module.exports = wiring
+
+function wiring (service) {
+ const server = restify.createServer()
+
+ server.use(restify.bodyParser())
+
+ server.post('/append', (req, res, next) => {
+ service.append(req.params, (err, result) => {
+ if (err) {
+ res.send(err)
+ return
+ }
+ res.send(result)
+ next()
+ })
+ })
+
+ server.get('/list', (req, res, next) => {
+ service.list(req.params, (err, result) => {
+ if (err) {
+ res.send(err)
+ return
+ }
+ res.send(200, result)
+ next()
+ })
+ })
+
+ server.listen(AUDITSERVICE_SERVICE_PORT, '0.0.0.0', () => {
+ console.log('%s listening at %s', server.name, server.url)
+ })
+}
+
diff --git a/Chapter10/adding-a-queue-based-service/micro/eventservice/index.js b/Chapter10/adding-a-queue-based-service/micro/eventservice/index.js
new file mode 100644
index 0000000..df3a380
--- /dev/null
+++ b/Chapter10/adding-a-queue-based-service/micro/eventservice/index.js
@@ -0,0 +1,5 @@
+'use strict'
+
+const wiring = require('./wiring')
+const service = require('./service')()
+wiring(service)
diff --git a/Chapter10/adding-a-queue-based-service/micro/eventservice/package.json b/Chapter10/adding-a-queue-based-service/micro/eventservice/package.json
new file mode 100644
index 0000000..d8a7d4b
--- /dev/null
+++ b/Chapter10/adding-a-queue-based-service/micro/eventservice/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "eventservice",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "Peter Elger",
+ "license": "ISC",
+ "dependencies": {
+ "concordant": "^0.2.1",
+ "mongo": "^0.1.0",
+ "redis": "^2.6.5",
+ "restify": "^4.3.0"
+ }
+}
diff --git a/Chapter10/adding-a-queue-based-service/micro/eventservice/service.js b/Chapter10/adding-a-queue-based-service/micro/eventservice/service.js
new file mode 100644
index 0000000..4082d3e
--- /dev/null
+++ b/Chapter10/adding-a-queue-based-service/micro/eventservice/service.js
@@ -0,0 +1,80 @@
+'use strict'
+
+const { MongoClient } = require('mongodb')
+const { dns } = require('concordant')()
+
+module.exports = service
+
+function service () {
+ var db
+
+ setup()
+
+ function setup () {
+ const mongo = '_main._tcp.mongo.micro.svc.cluster.local'
+
+ dns.resolve(mongo, (err, locs) => {
+ if (err) {
+ console.error(err)
+ return
+ }
+ const { host, port } = locs[0]
+ const url = `mongodb://${host}:${port}/events`
+ MongoClient.connect(url, (err, client) => {
+ if (err) {
+ console.log('failed to connect to MongoDB retrying in 100ms')
+ setTimeout(setup, 100)
+ return
+ }
+ db = client
+ db.on('close', () => db = null)
+ })
+ })
+ }
+
+ function record (args, cb) {
+ if (!db) {
+ cb(Error('No database connection'))
+ return
+ }
+ const events = db.collection('events')
+ const data = {
+ ts: Date.now(),
+ eventType: args.type,
+ url: args.url
+ }
+ events.insert(data, (err, result) => {
+ if (err) {
+ cb(err)
+ return
+ }
+ cb(null, result)
+ })
+ }
+
+ function summary (args, cb) {
+ if (!db) {
+ cb(Error('No database connection'))
+ return
+ }
+ const summary = {}
+ const events = db.collection('events')
+ events.find({}).toArray( (err, docs) => {
+ if (err) return cb(err)
+
+ docs.forEach(function (doc) {
+ if (!(summary[doc.url])) {
+ summary[doc.url] = 1
+ } else {
+ summary[doc.url]++
+ }
+ })
+ cb(null, summary)
+ })
+ }
+
+ return {
+ record: record,
+ summary: summary
+ }
+}
diff --git a/Chapter10/adding-a-queue-based-service/micro/eventservice/wiring.js b/Chapter10/adding-a-queue-based-service/micro/eventservice/wiring.js
new file mode 100644
index 0000000..b04ab35
--- /dev/null
+++ b/Chapter10/adding-a-queue-based-service/micro/eventservice/wiring.js
@@ -0,0 +1,53 @@
+'use strict'
+
+const { dns } = require('concordant')()
+const redis = require('redis')
+const QNAME = 'eventservice'
+
+module.exports = wiring
+
+function wiring (service) {
+
+ const endpoint = '_main._tcp.redis.micro.svc.cluster.local'
+
+ dns.resolve(endpoint, (err, locs) => {
+ if (err) {
+ console.log(err)
+ return
+ }
+ const { port, host } = locs[0]
+ pullFromQueue(redis.createClient(port, host))
+ })
+
+ function pullFromQueue (client) {
+ client.brpop(QNAME, 5, function (err, data) {
+ if (err) console.error(err)
+ if (err || !data) {
+ pullFromQueue(client)
+ return
+ }
+ const msg = JSON.parse(data[1])
+ const { action, returnPath } = msg
+ const cmd = service[action]
+ if (typeof cmd !== 'function') {
+ pullFromQueue(client)
+ return
+ }
+ cmd(msg, (err, result) => {
+ if (err) {
+ console.error(err)
+ pullFromQueue(client)
+ return
+ }
+ if (!returnPath) {
+ pullFromQueue(client)
+ return
+ }
+ client.lpush(returnPath, JSON.stringify(result), (err) => {
+ if (err) console.error(err)
+ pullFromQueue(client)
+ })
+ })
+ })
+ }
+}
diff --git a/Chapter10/adding-a-queue-based-service/micro/fuge/fuge.yml b/Chapter10/adding-a-queue-based-service/micro/fuge/fuge.yml
new file mode 100644
index 0000000..839ba34
--- /dev/null
+++ b/Chapter10/adding-a-queue-based-service/micro/fuge/fuge.yml
@@ -0,0 +1,45 @@
+fuge_global:
+ run_containers: true
+ dns_enabled: true
+ dns_host: 127.0.0.1
+ dns_port: 53053
+ dns_suffix: svc.cluster.local
+ dns_namespace: micro
+ tail: true
+ monitor: true
+ monitor_excludes:
+ - '**/node_modules/**'
+ - '**/.git/**'
+ - '**/*.log'
+adderservice:
+ type: node
+ path: ../adderservice
+ run: node index.js
+ ports:
+ - main=8080
+auditservice:
+ type: process
+ path: ../auditservice
+ run: 'node index.js'
+ ports:
+ - main=8081
+eventservice:
+ type: process
+ path: ../eventservice
+ run: 'node index.js'
+webapp:
+ type: process
+ path: ../webapp
+ run: npm start
+ ports:
+ - http=3000
+mongo:
+ image: mongo
+ type: container
+ ports:
+ - main=27017:27017
+redis:
+ image: redis
+ type: container
+ ports:
+ - main=6379:6379
diff --git a/Chapter10/adding-a-queue-based-service/micro/report/env.js b/Chapter10/adding-a-queue-based-service/micro/report/env.js
new file mode 100644
index 0000000..a4d26e9
--- /dev/null
+++ b/Chapter10/adding-a-queue-based-service/micro/report/env.js
@@ -0,0 +1,17 @@
+'use strict'
+
+// provides environment variable setup for concordant
+
+const env = {
+ DNS_NAMESPACE: 'micro',
+ DNS_SUFFIX: 'svc.cluster.local'
+}
+
+if (process.env.NODE_ENV !== 'production') {
+ Object.assign(env, {
+ DNS_HOST: '127.0.0.1',
+ DNS_PORT: '53053'
+ })
+}
+
+Object.assign(process.env, env)
\ No newline at end of file
diff --git a/Chapter10/adding-a-queue-based-service/micro/report/index.js b/Chapter10/adding-a-queue-based-service/micro/report/index.js
new file mode 100644
index 0000000..b65b65a
--- /dev/null
+++ b/Chapter10/adding-a-queue-based-service/micro/report/index.js
@@ -0,0 +1,43 @@
+'use strict'
+require('./env')
+const { dns } = require('concordant')()
+const redis = require('redis')
+const CliTable = require('cli-table')
+const QNAME = 'eventservice'
+const RESPONSE_QUEUE = 'summary'
+const ENDPOINT = '_main._tcp.redis.micro.svc.cluster.local'
+
+dns.resolve(ENDPOINT, report)
+
+function report (err, locs) {
+ if (err) { return console.log(err) }
+ const { port, host } = locs[0]
+ const client = redis.createClient(port, host)
+ const event = JSON.stringify({
+ action: 'summary',
+ returnPath: RESPONSE_QUEUE
+ })
+
+ client.lpush(QNAME, event, (err) => {
+ if (err) {
+ console.error(err)
+ return
+ }
+
+ client.brpop(RESPONSE_QUEUE, 5, (err, data) => {
+ if (err) {
+ console.error(err)
+ return
+ }
+ const summary = JSON.parse(data[1])
+ const cols = Object.keys(summary).map((url) => [url, summary[url]])
+ const table = new CliTable({
+ head: ['url', 'count'],
+ colWidths: [50, 10]
+ })
+ table.push(...cols)
+ console.log(table.toString())
+ client.quit()
+ })
+ })
+}
\ No newline at end of file
diff --git a/Chapter10/adding-a-queue-based-service/micro/report/package.json b/Chapter10/adding-a-queue-based-service/micro/report/package.json
new file mode 100644
index 0000000..f22e52e
--- /dev/null
+++ b/Chapter10/adding-a-queue-based-service/micro/report/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "report",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "Peter Elger",
+ "license": "ISC",
+ "dependencies": {
+ "cli-table": "^0.3.1",
+ "concordant": "^0.2.1",
+ "redis": "^2.6.5"
+ }
+}
diff --git a/Chapter10/adding-a-queue-based-service/micro/webapp/app.js b/Chapter10/adding-a-queue-based-service/micro/webapp/app.js
new file mode 100644
index 0000000..eef315b
--- /dev/null
+++ b/Chapter10/adding-a-queue-based-service/micro/webapp/app.js
@@ -0,0 +1,52 @@
+var express = require('express')
+var path = require('path')
+var favicon = require('serve-favicon')
+var logger = require('morgan')
+var cookieParser = require('cookie-parser')
+var bodyParser = require('body-parser')
+var eventLogger = require('./lib/event-logger')
+
+var index = require('./routes/index')
+var users = require('./routes/users')
+var add = require('./routes/add')
+var audit = require('./routes/audit')
+
+var app = express()
+
+// view engine setup
+app.set('views', path.join(__dirname, 'views'))
+app.set('view engine', 'ejs')
+
+// uncomment after placing your favicon in /public
+// app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
+app.use(eventLogger())
+app.use(logger('dev'))
+app.use(bodyParser.json())
+app.use(bodyParser.urlencoded({ extended: false }))
+app.use(cookieParser())
+app.use(express.static(path.join(__dirname, 'public')))
+
+app.use('/', index)
+app.use('/users', users)
+app.use('/add', add)
+app.use('/audit', audit)
+
+// catch 404 and forward to error handler
+app.use(function (req, res, next) {
+ var err = new Error('Not Found')
+ err.status = 404
+ next(err)
+})
+
+// error handler
+app.use(function (err, req, res, next) {
+ // set locals, only providing error in development
+ res.locals.message = err.message
+ res.locals.error = req.app.get('env') === 'development' ? err : {}
+
+ // render the error page
+ res.status(err.status || 500)
+ res.render('error')
+})
+
+module.exports = app
diff --git a/Chapter10/adding-a-queue-based-service/micro/webapp/bin/www b/Chapter10/adding-a-queue-based-service/micro/webapp/bin/www
new file mode 100644
index 0000000..8715eeb
--- /dev/null
+++ b/Chapter10/adding-a-queue-based-service/micro/webapp/bin/www
@@ -0,0 +1,90 @@
+#!/usr/bin/env node
+
+/**
+ * Module dependencies.
+ */
+
+var app = require('../app');
+var debug = require('debug')('webapp:server');
+var http = require('http');
+
+/**
+ * Get port from environment and store in Express.
+ */
+
+var port = normalizePort(process.env.PORT || '3000');
+app.set('port', port);
+
+/**
+ * Create HTTP server.
+ */
+
+var server = http.createServer(app);
+
+/**
+ * Listen on provided port, on all network interfaces.
+ */
+
+server.listen(port);
+server.on('error', onError);
+server.on('listening', onListening);
+
+/**
+ * Normalize a port into a number, string, or false.
+ */
+
+function normalizePort(val) {
+ var port = parseInt(val, 10);
+
+ if (isNaN(port)) {
+ // named pipe
+ return val;
+ }
+
+ if (port >= 0) {
+ // port number
+ return port;
+ }
+
+ return false;
+}
+
+/**
+ * Event listener for HTTP server "error" event.
+ */
+
+function onError(error) {
+ if (error.syscall !== 'listen') {
+ throw error;
+ }
+
+ var bind = typeof port === 'string'
+ ? 'Pipe ' + port
+ : 'Port ' + port;
+
+ // handle specific listen errors with friendly messages
+ switch (error.code) {
+ case 'EACCES':
+ console.error(bind + ' requires elevated privileges');
+ process.exit(1);
+ break;
+ case 'EADDRINUSE':
+ console.error(bind + ' is already in use');
+ process.exit(1);
+ break;
+ default:
+ throw error;
+ }
+}
+
+/**
+ * Event listener for HTTP server "listening" event.
+ */
+
+function onListening() {
+ var addr = server.address();
+ var bind = typeof addr === 'string'
+ ? 'pipe ' + addr
+ : 'port ' + addr.port;
+ debug('Listening on ' + bind);
+}
diff --git a/Chapter10/adding-a-queue-based-service/micro/webapp/lib/event-logger.js b/Chapter10/adding-a-queue-based-service/micro/webapp/lib/event-logger.js
new file mode 100644
index 0000000..0140d3f
--- /dev/null
+++ b/Chapter10/adding-a-queue-based-service/micro/webapp/lib/event-logger.js
@@ -0,0 +1,40 @@
+'use strict'
+
+const { dns } = require('concordant')()
+const redis = require('redis')
+
+module.exports = eventLogger
+
+function eventLogger () {
+ const QNAME = 'eventservice'
+ var client
+
+ const endpoint = '_main._tcp.redis.micro.svc.cluster.local'
+ dns.resolve(endpoint, (err, locs) => {
+ if (err) {
+ console.error(err)
+ return
+ }
+ const { port, host } = locs[0]
+ client = redis.createClient(port, host)
+ })
+
+ function middleware (req, res, next) {
+ if (!client) {
+ console.log('client not ready, waiting 100ms')
+ setTimeout(middleware, 100, req, res, next)
+ return
+ }
+ const event = {
+ action: 'record',
+ type: 'page',
+ url: `${req.protocol}://${req.get('host')}${req.originalUrl}`
+ }
+ client.lpush(QNAME, JSON.stringify(event), (err) => {
+ if (err) console.error(err)
+ next()
+ })
+ }
+
+ return middleware
+}
diff --git a/Chapter10/adding-a-queue-based-service/micro/webapp/package.json b/Chapter10/adding-a-queue-based-service/micro/webapp/package.json
new file mode 100644
index 0000000..1c45acf
--- /dev/null
+++ b/Chapter10/adding-a-queue-based-service/micro/webapp/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "webapp",
+ "version": "0.0.0",
+ "private": true,
+ "scripts": {
+ "start": "node ./bin/www"
+ },
+ "dependencies": {
+ "body-parser": "~1.16.0",
+ "concordant": "^0.2.1",
+ "cookie-parser": "~1.4.3",
+ "debug": "~2.6.0",
+ "ejs": "~2.5.5",
+ "express": "~4.14.1",
+ "morgan": "~1.7.0",
+ "mu": "^2.1.2",
+ "restify": "^4.3.0",
+ "serve-favicon": "~2.3.2"
+ }
+}
diff --git a/Chapter10/adding-a-queue-based-service/micro/webapp/public/stylesheets/style.css b/Chapter10/adding-a-queue-based-service/micro/webapp/public/stylesheets/style.css
new file mode 100644
index 0000000..9453385
--- /dev/null
+++ b/Chapter10/adding-a-queue-based-service/micro/webapp/public/stylesheets/style.css
@@ -0,0 +1,8 @@
+body {
+ padding: 50px;
+ font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
+}
+
+a {
+ color: #00B7FF;
+}
diff --git a/Chapter10/adding-a-queue-based-service/micro/webapp/routes/add.js b/Chapter10/adding-a-queue-based-service/micro/webapp/routes/add.js
new file mode 100644
index 0000000..3ab03cc
--- /dev/null
+++ b/Chapter10/adding-a-queue-based-service/micro/webapp/routes/add.js
@@ -0,0 +1,68 @@
+'use strict'
+
+const { Router } = require('express')
+const restify = require('restify')
+const { dns } = require('concordant')()
+const router = Router()
+var clients
+
+router.get('/', function (req, res) {
+ res.render('add', { first: 0, second: 0, result: 0 })
+})
+
+router.post('/calculate', resolve, respond)
+
+function resolve (req, res, next) {
+ if (clients) {
+ next()
+ return
+ }
+ const adderservice = `_main._tcp.adderservice.micro.svc.cluster.local`
+ const auditservice = `_main._tcp.auditservice.micro.svc.cluster.local`
+ dns.resolve(adderservice, (err, locs) => {
+ if (err) {
+ next(err)
+ return
+ }
+ const { host, port } = locs[0]
+ const adder = `${host}:${port}`
+ dns.resolve(auditservice, (err, locs) => {
+ if (err) {
+ next(err)
+ return
+ }
+ const { host, port } = locs[0]
+ const audit = `${host}:${port}`
+ clients = {
+ adder: restify.createJSONClient({url: `http://${adder}`}),
+ audit: restify.createJSONClient({url: `http://${audit}`})
+ }
+ next()
+ })
+ })
+}
+
+function respond (req, res, next) {
+ const { first, second } = req.body
+ clients.adder.get(
+ `/add/${first}/${second}`,
+ (err, svcReq, svcRes, data) => {
+ if (err) {
+ next(err)
+ return
+ }
+
+ const { result } = data
+ clients.audit.post('/append', {
+ calc: first + '+' + second,
+ calcResult: result
+ }, (err) => {
+ if (err) console.error(err)
+ })
+
+ res.render('add', { first, second, result })
+ }
+ )
+}
+
+module.exports = router
diff --git a/Chapter10/adding-a-queue-based-service/micro/webapp/routes/audit.js b/Chapter10/adding-a-queue-based-service/micro/webapp/routes/audit.js
new file mode 100644
index 0000000..a083eef
--- /dev/null
+++ b/Chapter10/adding-a-queue-based-service/micro/webapp/routes/audit.js
@@ -0,0 +1,37 @@
+'use strict'
+
+const { Router } = require('express')
+const restify = require('restify')
+const { dns } = require('concordant')()
+const router = Router()
+var client
+
+router.get('/', resolve, respond)
+
+function resolve (req, res, next) {
+ if (client) {
+ next()
+ return
+ }
+ const auditservice = `_main._tcp.auditservice.micro.svc.cluster.local`
+ dns.resolve(auditservice, (err, locs) => {
+ if (err) {
+ next(err)
+ return
+ }
+ const { host, port } = locs[0]
+ client = restify.createJSONClient(`http://${host}:${port}`)
+ })
+}
+
+function respond (req, res, next) {
+ client.get('/list', (err, svcReq, svcRes, data) => {
+ if (err) {
+ next(err)
+ return
+ }
+ res.render('audit', data)
+ })
+}
+
+module.exports = router
diff --git a/Chapter10/adding-a-queue-based-service/micro/webapp/routes/index.js b/Chapter10/adding-a-queue-based-service/micro/webapp/routes/index.js
new file mode 100644
index 0000000..956680b
--- /dev/null
+++ b/Chapter10/adding-a-queue-based-service/micro/webapp/routes/index.js
@@ -0,0 +1,9 @@
+var express = require('express')
+var router = express.Router()
+
+/* GET home page. */
+router.get('/', function (req, res, next) {
+ res.render('index', { title: 'Express' })
+})
+
+module.exports = router
diff --git a/Chapter10/adding-a-queue-based-service/micro/webapp/routes/users.js b/Chapter10/adding-a-queue-based-service/micro/webapp/routes/users.js
new file mode 100644
index 0000000..8cfe88f
--- /dev/null
+++ b/Chapter10/adding-a-queue-based-service/micro/webapp/routes/users.js
@@ -0,0 +1,9 @@
+var express = require('express')
+var router = express.Router()
+
+/* GET users listing. */
+router.get('/', function (req, res, next) {
+ res.send('respond with a resource')
+})
+
+module.exports = router
diff --git a/Chapter10/adding-a-queue-based-service/micro/webapp/views/add.ejs b/Chapter10/adding-a-queue-based-service/micro/webapp/views/add.ejs
new file mode 100644
index 0000000..da8279c
--- /dev/null
+++ b/Chapter10/adding-a-queue-based-service/micro/webapp/views/add.ejs
@@ -0,0 +1,16 @@
+
+
+
+ Add
+
+
+
+ Add it up!
+
+ Submit
+ result = <%= result %>
+
+
diff --git a/Chapter10/adding-a-queue-based-service/micro/webapp/views/audit.ejs b/Chapter10/adding-a-queue-based-service/micro/webapp/views/audit.ejs
new file mode 100644
index 0000000..dd6d71b
--- /dev/null
+++ b/Chapter10/adding-a-queue-based-service/micro/webapp/views/audit.ejs
@@ -0,0 +1,15 @@
+
+
+
+ Audit
+
+
+
+ Calculation History
+
+ <% list.forEach(function (el) { %>
+ at: <%= new Date(el.ts).toLocaleString() %>, calculated: <%= el.calc %>, result: <%= el.result %>
+ <% }) %>
+
+
+
\ No newline at end of file
diff --git a/Chapter10/adding-a-queue-based-service/micro/webapp/views/error.ejs b/Chapter10/adding-a-queue-based-service/micro/webapp/views/error.ejs
new file mode 100644
index 0000000..7cf94ed
--- /dev/null
+++ b/Chapter10/adding-a-queue-based-service/micro/webapp/views/error.ejs
@@ -0,0 +1,3 @@
+<%= message %>
+<%= error.status %>
+<%= error.stack %>
diff --git a/Chapter10/adding-a-queue-based-service/micro/webapp/views/index.ejs b/Chapter10/adding-a-queue-based-service/micro/webapp/views/index.ejs
new file mode 100644
index 0000000..7b7a1d6
--- /dev/null
+++ b/Chapter10/adding-a-queue-based-service/micro/webapp/views/index.ejs
@@ -0,0 +1,11 @@
+
+
+
+ <%= title %>
+
+
+
+ <%= title %>
+ Welcome to <%= title %>
+
+
diff --git a/Chapter10/consuming-a-service/micro/adderservice/package.json b/Chapter10/consuming-a-service/micro/adderservice/package.json
new file mode 100644
index 0000000..dedef88
--- /dev/null
+++ b/Chapter10/consuming-a-service/micro/adderservice/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "adderservice",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "Peter Elger",
+ "license": "ISC",
+ "dependencies": {
+ "restify": "^4.3.0"
+ }
+}
diff --git a/Chapter10/consuming-a-service/micro/adderservice/service.js b/Chapter10/consuming-a-service/micro/adderservice/service.js
new file mode 100644
index 0000000..a6d6e74
--- /dev/null
+++ b/Chapter10/consuming-a-service/micro/adderservice/service.js
@@ -0,0 +1,18 @@
+'use strict'
+
+const restify = require('restify')
+
+function respond (req, res, next) {
+ const result = (parseInt(req.params.first, 10) +
+ parseInt(req.params.second, 10)).toString()
+ res.send(result)
+ next()
+}
+
+const server = restify.createServer()
+server.get('/add/:first/:second', respond)
+
+server.listen(8080, () => {
+ console.log('%s listening at %s', server.name, server.url)
+})
+
diff --git a/Chapter10/consuming-a-service/micro/inttest/addtest.js b/Chapter10/consuming-a-service/micro/inttest/addtest.js
new file mode 100644
index 0000000..10698e3
--- /dev/null
+++ b/Chapter10/consuming-a-service/micro/inttest/addtest.js
@@ -0,0 +1,18 @@
+'use strict'
+
+const request = require('superagent')
+const { test } = require('tap')
+
+test('add test', (t) => {
+ t.plan(2)
+
+ request
+ .post('http://localhost:3000/add/calculate')
+ .send('first=1')
+ .send('second=2')
+ .end((err, res) => {
+ t.equal(err, null)
+ t.ok(/result = 3/ig.test(res.text))
+ })
+})
+
diff --git a/Chapter10/consuming-a-service/micro/inttest/package.json b/Chapter10/consuming-a-service/micro/inttest/package.json
new file mode 100644
index 0000000..672c6b2
--- /dev/null
+++ b/Chapter10/consuming-a-service/micro/inttest/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "inttest",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "Peter Elger",
+ "license": "ISC",
+ "devDependencies": {
+ "superagent": "^3.5.2",
+ "tap": "^10.3.1"
+ }
+}
diff --git a/Chapter10/consuming-a-service/micro/webapp/app.js b/Chapter10/consuming-a-service/micro/webapp/app.js
new file mode 100644
index 0000000..90f0e7a
--- /dev/null
+++ b/Chapter10/consuming-a-service/micro/webapp/app.js
@@ -0,0 +1,48 @@
+var express = require('express')
+var path = require('path')
+var favicon = require('serve-favicon')
+var logger = require('morgan')
+var cookieParser = require('cookie-parser')
+var bodyParser = require('body-parser')
+
+var index = require('./routes/index')
+var users = require('./routes/users')
+var add = require('./routes/add')
+
+var app = express()
+
+// view engine setup
+app.set('views', path.join(__dirname, 'views'))
+app.set('view engine', 'ejs')
+
+// uncomment after placing your favicon in /public
+// app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
+app.use(logger('dev'))
+app.use(bodyParser.json())
+app.use(bodyParser.urlencoded({ extended: false }))
+app.use(cookieParser())
+app.use(express.static(path.join(__dirname, 'public')))
+
+app.use('/', index)
+app.use('/users', users)
+app.use('/add', add)
+
+// catch 404 and forward to error handler
+app.use(function (req, res, next) {
+ var err = new Error('Not Found')
+ err.status = 404
+ next(err)
+})
+
+// error handler
+app.use(function (err, req, res, next) {
+ // set locals, only providing error in development
+ res.locals.message = err.message
+ res.locals.error = req.app.get('env') === 'development' ? err : {}
+
+ // render the error page
+ res.status(err.status || 500)
+ res.render('error')
+})
+
+module.exports = app
diff --git a/Chapter10/consuming-a-service/micro/webapp/bin/www b/Chapter10/consuming-a-service/micro/webapp/bin/www
new file mode 100644
index 0000000..8715eeb
--- /dev/null
+++ b/Chapter10/consuming-a-service/micro/webapp/bin/www
@@ -0,0 +1,90 @@
+#!/usr/bin/env node
+
+/**
+ * Module dependencies.
+ */
+
+var app = require('../app');
+var debug = require('debug')('webapp:server');
+var http = require('http');
+
+/**
+ * Get port from environment and store in Express.
+ */
+
+var port = normalizePort(process.env.PORT || '3000');
+app.set('port', port);
+
+/**
+ * Create HTTP server.
+ */
+
+var server = http.createServer(app);
+
+/**
+ * Listen on provided port, on all network interfaces.
+ */
+
+server.listen(port);
+server.on('error', onError);
+server.on('listening', onListening);
+
+/**
+ * Normalize a port into a number, string, or false.
+ */
+
+function normalizePort(val) {
+ var port = parseInt(val, 10);
+
+ if (isNaN(port)) {
+ // named pipe
+ return val;
+ }
+
+ if (port >= 0) {
+ // port number
+ return port;
+ }
+
+ return false;
+}
+
+/**
+ * Event listener for HTTP server "error" event.
+ */
+
+function onError(error) {
+ if (error.syscall !== 'listen') {
+ throw error;
+ }
+
+ var bind = typeof port === 'string'
+ ? 'Pipe ' + port
+ : 'Port ' + port;
+
+ // handle specific listen errors with friendly messages
+ switch (error.code) {
+ case 'EACCES':
+ console.error(bind + ' requires elevated privileges');
+ process.exit(1);
+ break;
+ case 'EADDRINUSE':
+ console.error(bind + ' is already in use');
+ process.exit(1);
+ break;
+ default:
+ throw error;
+ }
+}
+
+/**
+ * Event listener for HTTP server "listening" event.
+ */
+
+function onListening() {
+ var addr = server.address();
+ var bind = typeof addr === 'string'
+ ? 'pipe ' + addr
+ : 'port ' + addr.port;
+ debug('Listening on ' + bind);
+}
diff --git a/Chapter10/consuming-a-service/micro/webapp/package.json b/Chapter10/consuming-a-service/micro/webapp/package.json
new file mode 100644
index 0000000..1c0bcc8
--- /dev/null
+++ b/Chapter10/consuming-a-service/micro/webapp/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "webapp",
+ "version": "0.0.0",
+ "private": true,
+ "scripts": {
+ "start": "node ./bin/www"
+ },
+ "dependencies": {
+ "body-parser": "~1.16.0",
+ "cookie-parser": "~1.4.3",
+ "debug": "~2.6.0",
+ "ejs": "~2.5.5",
+ "express": "~4.14.1",
+ "morgan": "~1.7.0",
+ "restify": "^4.3.0",
+ "serve-favicon": "~2.3.2"
+ }
+}
diff --git a/Chapter10/consuming-a-service/micro/webapp/public/stylesheets/style.css b/Chapter10/consuming-a-service/micro/webapp/public/stylesheets/style.css
new file mode 100644
index 0000000..9453385
--- /dev/null
+++ b/Chapter10/consuming-a-service/micro/webapp/public/stylesheets/style.css
@@ -0,0 +1,8 @@
+body {
+ padding: 50px;
+ font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
+}
+
+a {
+ color: #00B7FF;
+}
diff --git a/Chapter10/consuming-a-service/micro/webapp/routes/add.js b/Chapter10/consuming-a-service/micro/webapp/routes/add.js
new file mode 100644
index 0000000..3240051
--- /dev/null
+++ b/Chapter10/consuming-a-service/micro/webapp/routes/add.js
@@ -0,0 +1,26 @@
+const { Router } = require('express')
+const restify = require('restify')
+const router = Router()
+
+router.get('/', function (req, res) {
+ res.render('add', { first: 0, second: 0, result: 0 })
+})
+
+router.post('/calculate', function (req, res, next) {
+ const client = restify.createStringClient({
+ url: 'http://localhost:8080'
+ })
+ const {first, second} = req.body
+ client.get(
+ `/add/${first}/${second}`,
+ (err, svcReq, svcRes, result) => {
+ if (err) {
+ next(err)
+ return
+ }
+ res.render('add', { first, second, result })
+ }
+ )
+})
+
+module.exports = router
diff --git a/Chapter10/consuming-a-service/micro/webapp/routes/index.js b/Chapter10/consuming-a-service/micro/webapp/routes/index.js
new file mode 100644
index 0000000..956680b
--- /dev/null
+++ b/Chapter10/consuming-a-service/micro/webapp/routes/index.js
@@ -0,0 +1,9 @@
+var express = require('express')
+var router = express.Router()
+
+/* GET home page. */
+router.get('/', function (req, res, next) {
+ res.render('index', { title: 'Express' })
+})
+
+module.exports = router
diff --git a/Chapter10/consuming-a-service/micro/webapp/routes/users.js b/Chapter10/consuming-a-service/micro/webapp/routes/users.js
new file mode 100644
index 0000000..8cfe88f
--- /dev/null
+++ b/Chapter10/consuming-a-service/micro/webapp/routes/users.js
@@ -0,0 +1,9 @@
+var express = require('express')
+var router = express.Router()
+
+/* GET users listing. */
+router.get('/', function (req, res, next) {
+ res.send('respond with a resource')
+})
+
+module.exports = router
diff --git a/Chapter10/consuming-a-service/micro/webapp/views/add.ejs b/Chapter10/consuming-a-service/micro/webapp/views/add.ejs
new file mode 100644
index 0000000..da8279c
--- /dev/null
+++ b/Chapter10/consuming-a-service/micro/webapp/views/add.ejs
@@ -0,0 +1,16 @@
+
+
+
+ Add
+
+
+
+ Add it up!
+
+ Submit
+ result = <%= result %>
+
+
diff --git a/Chapter10/consuming-a-service/micro/webapp/views/error.ejs b/Chapter10/consuming-a-service/micro/webapp/views/error.ejs
new file mode 100644
index 0000000..7cf94ed
--- /dev/null
+++ b/Chapter10/consuming-a-service/micro/webapp/views/error.ejs
@@ -0,0 +1,3 @@
+<%= message %>
+<%= error.status %>
+<%= error.stack %>
diff --git a/Chapter10/consuming-a-service/micro/webapp/views/index.ejs b/Chapter10/consuming-a-service/micro/webapp/views/index.ejs
new file mode 100644
index 0000000..7b7a1d6
--- /dev/null
+++ b/Chapter10/consuming-a-service/micro/webapp/views/index.ejs
@@ -0,0 +1,11 @@
+
+
+
+ <%= title %>
+
+
+
+ <%= title %>
+ Welcome to <%= title %>
+
+
diff --git a/Chapter10/creating-a-simple-RESTful-microservice/micro-core-http/adderservice/package.json b/Chapter10/creating-a-simple-RESTful-microservice/micro-core-http/adderservice/package.json
new file mode 100644
index 0000000..dedef88
--- /dev/null
+++ b/Chapter10/creating-a-simple-RESTful-microservice/micro-core-http/adderservice/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "adderservice",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "Peter Elger",
+ "license": "ISC",
+ "dependencies": {
+ "restify": "^4.3.0"
+ }
+}
diff --git a/Chapter10/creating-a-simple-RESTful-microservice/micro-core-http/adderservice/service.js b/Chapter10/creating-a-simple-RESTful-microservice/micro-core-http/adderservice/service.js
new file mode 100644
index 0000000..b130c44
--- /dev/null
+++ b/Chapter10/creating-a-simple-RESTful-microservice/micro-core-http/adderservice/service.js
@@ -0,0 +1,30 @@
+'use strict'
+
+const http = require('http')
+
+const server = http.createServer(respond)
+
+server.listen(8080, function () {
+ console.log('listening on port 8080')
+})
+
+function respond (req, res) {
+ const [cmd, first, second] = req.url.split('/').slice(1)
+ const notFound = cmd !== 'add' ||
+ first === undefined ||
+ second === undefined
+
+ if (notFound) {
+ error(404, res)
+ return
+ }
+
+ const result = parseInt(first, 10) + parseInt(second, 10)
+ res.end(result)
+}
+
+function error(code, res) {
+ res.statusCode = code
+ res.end(http.STATUS_CODES[code])
+}
+
diff --git a/Chapter10/creating-a-simple-RESTful-microservice/micro-core-http/curlexample.sh b/Chapter10/creating-a-simple-RESTful-microservice/micro-core-http/curlexample.sh
new file mode 100644
index 0000000..fb9583e
--- /dev/null
+++ b/Chapter10/creating-a-simple-RESTful-microservice/micro-core-http/curlexample.sh
@@ -0,0 +1 @@
+curl http://localhost:8080/add/1/2
diff --git a/Chapter10/creating-a-simple-RESTful-microservice/micro/adderservice/package.json b/Chapter10/creating-a-simple-RESTful-microservice/micro/adderservice/package.json
new file mode 100644
index 0000000..dedef88
--- /dev/null
+++ b/Chapter10/creating-a-simple-RESTful-microservice/micro/adderservice/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "adderservice",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "Peter Elger",
+ "license": "ISC",
+ "dependencies": {
+ "restify": "^4.3.0"
+ }
+}
diff --git a/Chapter10/creating-a-simple-RESTful-microservice/micro/adderservice/service.js b/Chapter10/creating-a-simple-RESTful-microservice/micro/adderservice/service.js
new file mode 100644
index 0000000..a6d6e74
--- /dev/null
+++ b/Chapter10/creating-a-simple-RESTful-microservice/micro/adderservice/service.js
@@ -0,0 +1,18 @@
+'use strict'
+
+const restify = require('restify')
+
+function respond (req, res, next) {
+ const result = (parseInt(req.params.first, 10) +
+ parseInt(req.params.second, 10)).toString()
+ res.send(result)
+ next()
+}
+
+const server = restify.createServer()
+server.get('/add/:first/:second', respond)
+
+server.listen(8080, () => {
+ console.log('%s listening at %s', server.name, server.url)
+})
+
diff --git a/Chapter10/creating-a-simple-RESTful-microservice/micro/curlexample.sh b/Chapter10/creating-a-simple-RESTful-microservice/micro/curlexample.sh
new file mode 100644
index 0000000..fb9583e
--- /dev/null
+++ b/Chapter10/creating-a-simple-RESTful-microservice/micro/curlexample.sh
@@ -0,0 +1 @@
+curl http://localhost:8080/add/1/2
diff --git a/Chapter10/service-discovery-with-dns/micro/adderservice/index.js b/Chapter10/service-discovery-with-dns/micro/adderservice/index.js
new file mode 100644
index 0000000..1f2d9cf
--- /dev/null
+++ b/Chapter10/service-discovery-with-dns/micro/adderservice/index.js
@@ -0,0 +1,7 @@
+'use strict'
+
+const wiring = require('./wiring')
+const service = require('./service')()
+
+
+wiring(service)
diff --git a/Chapter10/service-discovery-with-dns/micro/adderservice/package.json b/Chapter10/service-discovery-with-dns/micro/adderservice/package.json
new file mode 100644
index 0000000..7cef23a
--- /dev/null
+++ b/Chapter10/service-discovery-with-dns/micro/adderservice/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "adderservice",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "tap test"
+ },
+ "keywords": [],
+ "author": "Peter Elger",
+ "license": "ISC",
+ "dependencies": {
+ "restify": "^4.3.0"
+ },
+ "devDependencies": {
+ "tap": "^10.3.1"
+ }
+}
diff --git a/Chapter10/service-discovery-with-dns/micro/adderservice/service.js b/Chapter10/service-discovery-with-dns/micro/adderservice/service.js
new file mode 100644
index 0000000..9d0d175
--- /dev/null
+++ b/Chapter10/service-discovery-with-dns/micro/adderservice/service.js
@@ -0,0 +1,13 @@
+'use strict'
+
+module.exports = service
+
+function service () {
+ function add (args, cb) {
+ const {first, second} = args
+ const result = (parseInt(first, 10) + parseInt(second, 10))
+ cb(null, {result: result.toString()})
+ }
+
+ return { add }
+}
diff --git a/Chapter10/service-discovery-with-dns/micro/adderservice/test/index.js b/Chapter10/service-discovery-with-dns/micro/adderservice/test/index.js
new file mode 100644
index 0000000..32c45e5
--- /dev/null
+++ b/Chapter10/service-discovery-with-dns/micro/adderservice/test/index.js
@@ -0,0 +1,13 @@
+'use strict'
+
+const {test} = require('tap')
+const service = require('../service')()
+
+test('test add', (t) => {
+ t.plan(2)
+
+ service.add({first: 1, second: 2}, (err, answer) => {
+ t.error(err)
+ t.same(answer, {result: 3})
+ })
+})
diff --git a/Chapter10/service-discovery-with-dns/micro/adderservice/wiring.js b/Chapter10/service-discovery-with-dns/micro/adderservice/wiring.js
new file mode 100644
index 0000000..0ed018c
--- /dev/null
+++ b/Chapter10/service-discovery-with-dns/micro/adderservice/wiring.js
@@ -0,0 +1,27 @@
+'use strict'
+
+const restify = require('restify')
+const { ADDERSERVICE_SERVICE_PORT } = process.env
+
+module.exports = wiring
+
+function wiring (service) {
+ const server = restify.createServer()
+
+ server.get('/add/:first/:second', (req, res, next) => {
+ service.add(req.params, (err, result) => {
+ if (err) {
+ res.send(err)
+ next()
+ return
+ }
+ res.send(200, result)
+ next()
+ })
+ })
+
+ server.listen(ADDERSERVICE_SERVICE_PORT, '0.0.0.0', () => {
+ console.log('%s listening at %s', server.name, server.url)
+ })
+}
+
diff --git a/Chapter10/service-discovery-with-dns/micro/auditservice/index.js b/Chapter10/service-discovery-with-dns/micro/auditservice/index.js
new file mode 100644
index 0000000..40a4677
--- /dev/null
+++ b/Chapter10/service-discovery-with-dns/micro/auditservice/index.js
@@ -0,0 +1,6 @@
+'use strict'
+
+const wiring = require('./wiring')
+const service = require('./service')()
+
+wiring(service)
diff --git a/Chapter10/service-discovery-with-dns/micro/auditservice/package.json b/Chapter10/service-discovery-with-dns/micro/auditservice/package.json
new file mode 100644
index 0000000..d93b31e
--- /dev/null
+++ b/Chapter10/service-discovery-with-dns/micro/auditservice/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "auditservice",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "Peter Elger",
+ "license": "ISC",
+ "dependencies": {
+ "concordant": "^0.2.1",
+ "mongo": "^0.1.0",
+ "restify": "^4.3.0"
+ }
+}
diff --git a/Chapter10/service-discovery-with-dns/micro/auditservice/service.js b/Chapter10/service-discovery-with-dns/micro/auditservice/service.js
new file mode 100644
index 0000000..c416e4c
--- /dev/null
+++ b/Chapter10/service-discovery-with-dns/micro/auditservice/service.js
@@ -0,0 +1,74 @@
+'use strict'
+
+const { MongoClient } = require('mongodb')
+const { dns } = require('concordant')()
+
+module.exports = service
+
+function service () {
+
+ var db
+
+ setup()
+
+ function setup () {
+ const mongo = '_main._tcp.mongo.micro.svc.cluster.local'
+
+ dns.resolve(mongo, (err, locs) => {
+ if (err) {
+ console.error(err)
+ return
+ }
+ const { host, port } = locs[0]
+ const url = `mongodb://${host}:${port}/audit`
+ MongoClient.connect(url, (err, client) => {
+ if (err) {
+ console.log('failed to connect to MongoDB retrying in 100ms')
+ setTimeout(setup, 100)
+ return
+ }
+ db = client
+ db.on('close', () => db = null)
+ })
+ })
+ }
+
+ function append (args, cb) {
+ if (!db) {
+ cb(Error('No database connection'))
+ return
+ }
+ const audit = db.collection('audit')
+ const data = {
+ ts: Date.now(),
+ calc: args.calc,
+ result: args.calcResult
+ }
+
+ audit.insert(data, (err, result) => {
+ if (err) {
+ cb(err)
+ return
+ }
+ cb(null, {result: result.toString()})
+ })
+ }
+
+ function list (args, cb) {
+ if (!db) {
+ cb(Error('No database connection'))
+ return
+ }
+ const audit = db.collection('audit')
+ audit.find({}, {limit: 10}).toArray((err, docs) => {
+ if (err) {
+ cb(err)
+ return
+ }
+ cb(null, {list: docs})
+ })
+ }
+
+ return { append, list }
+}
+
diff --git a/Chapter10/service-discovery-with-dns/micro/auditservice/wiring.js b/Chapter10/service-discovery-with-dns/micro/auditservice/wiring.js
new file mode 100644
index 0000000..b18dce8
--- /dev/null
+++ b/Chapter10/service-discovery-with-dns/micro/auditservice/wiring.js
@@ -0,0 +1,39 @@
+'use strict'
+
+const restify = require('restify')
+const { AUDITSERVICE_SERVICE_PORT } = process.env
+
+module.exports = wiring
+
+function wiring (service) {
+ const server = restify.createServer()
+
+ server.use(restify.bodyParser())
+
+ server.post('/append', (req, res, next) => {
+ service.append(req.params, (err, result) => {
+ if (err) {
+ res.send(err)
+ return
+ }
+ res.send(result)
+ next()
+ })
+ })
+
+ server.get('/list', (req, res, next) => {
+ service.list(req.params, (err, result) => {
+ if (err) {
+ res.send(err)
+ return
+ }
+ res.send(200, result)
+ next()
+ })
+ })
+
+ server.listen(AUDITSERVICE_SERVICE_PORT, '0.0.0.0', () => {
+ console.log('%s listening at %s', server.name, server.url)
+ })
+}
+
diff --git a/Chapter10/service-discovery-with-dns/micro/fuge/fuge.yml b/Chapter10/service-discovery-with-dns/micro/fuge/fuge.yml
new file mode 100644
index 0000000..c263ca7
--- /dev/null
+++ b/Chapter10/service-discovery-with-dns/micro/fuge/fuge.yml
@@ -0,0 +1,35 @@
+fuge_global:
+ dns_enabled: true
+ dns_host: 127.0.0.1
+ dns_port: 53053
+ dns_suffix: svc.cluster.local
+ dns_namespace: micro
+ tail: true
+ monitor: true
+ monitor_excludes:
+ - '**/node_modules/**'
+ - '**/.git/**'
+ - '*.log'
+adderservice:
+ type: process
+ path: ../adderservice
+ run: node index.js
+ ports:
+ - main=8080
+webapp:
+ type: process
+ path: ../webapp
+ run: npm start
+ ports:
+ - main=3000
+auditservice:
+ type: process
+ path: ../auditservice
+ run: 'node index.js'
+ ports:
+ - main=8081
+mongo:
+ image: mongo
+ type: container
+ ports:
+ - main=27017:27017
diff --git a/Chapter10/service-discovery-with-dns/micro/webapp/app.js b/Chapter10/service-discovery-with-dns/micro/webapp/app.js
new file mode 100644
index 0000000..ec527f4
--- /dev/null
+++ b/Chapter10/service-discovery-with-dns/micro/webapp/app.js
@@ -0,0 +1,50 @@
+var express = require('express')
+var path = require('path')
+var favicon = require('serve-favicon')
+var logger = require('morgan')
+var cookieParser = require('cookie-parser')
+var bodyParser = require('body-parser')
+
+var index = require('./routes/index')
+var users = require('./routes/users')
+var add = require('./routes/add')
+var audit = require('./routes/audit')
+
+var app = express()
+
+// view engine setup
+app.set('views', path.join(__dirname, 'views'))
+app.set('view engine', 'ejs')
+
+// uncomment after placing your favicon in /public
+// app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
+app.use(logger('dev'))
+app.use(bodyParser.json())
+app.use(bodyParser.urlencoded({ extended: false }))
+app.use(cookieParser())
+app.use(express.static(path.join(__dirname, 'public')))
+
+app.use('/', index)
+app.use('/users', users)
+app.use('/add', add)
+app.use('/audit', audit)
+
+// catch 404 and forward to error handler
+app.use(function (req, res, next) {
+ var err = new Error('Not Found')
+ err.status = 404
+ next(err)
+})
+
+// error handler
+app.use(function (err, req, res, next) {
+ // set locals, only providing error in development
+ res.locals.message = err.message
+ res.locals.error = req.app.get('env') === 'development' ? err : {}
+
+ // render the error page
+ res.status(err.status || 500)
+ res.render('error')
+})
+
+module.exports = app
diff --git a/Chapter10/service-discovery-with-dns/micro/webapp/bin/www b/Chapter10/service-discovery-with-dns/micro/webapp/bin/www
new file mode 100644
index 0000000..8715eeb
--- /dev/null
+++ b/Chapter10/service-discovery-with-dns/micro/webapp/bin/www
@@ -0,0 +1,90 @@
+#!/usr/bin/env node
+
+/**
+ * Module dependencies.
+ */
+
+var app = require('../app');
+var debug = require('debug')('webapp:server');
+var http = require('http');
+
+/**
+ * Get port from environment and store in Express.
+ */
+
+var port = normalizePort(process.env.PORT || '3000');
+app.set('port', port);
+
+/**
+ * Create HTTP server.
+ */
+
+var server = http.createServer(app);
+
+/**
+ * Listen on provided port, on all network interfaces.
+ */
+
+server.listen(port);
+server.on('error', onError);
+server.on('listening', onListening);
+
+/**
+ * Normalize a port into a number, string, or false.
+ */
+
+function normalizePort(val) {
+ var port = parseInt(val, 10);
+
+ if (isNaN(port)) {
+ // named pipe
+ return val;
+ }
+
+ if (port >= 0) {
+ // port number
+ return port;
+ }
+
+ return false;
+}
+
+/**
+ * Event listener for HTTP server "error" event.
+ */
+
+function onError(error) {
+ if (error.syscall !== 'listen') {
+ throw error;
+ }
+
+ var bind = typeof port === 'string'
+ ? 'Pipe ' + port
+ : 'Port ' + port;
+
+ // handle specific listen errors with friendly messages
+ switch (error.code) {
+ case 'EACCES':
+ console.error(bind + ' requires elevated privileges');
+ process.exit(1);
+ break;
+ case 'EADDRINUSE':
+ console.error(bind + ' is already in use');
+ process.exit(1);
+ break;
+ default:
+ throw error;
+ }
+}
+
+/**
+ * Event listener for HTTP server "listening" event.
+ */
+
+function onListening() {
+ var addr = server.address();
+ var bind = typeof addr === 'string'
+ ? 'pipe ' + addr
+ : 'port ' + addr.port;
+ debug('Listening on ' + bind);
+}
diff --git a/Chapter10/service-discovery-with-dns/micro/webapp/package.json b/Chapter10/service-discovery-with-dns/micro/webapp/package.json
new file mode 100644
index 0000000..1c45acf
--- /dev/null
+++ b/Chapter10/service-discovery-with-dns/micro/webapp/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "webapp",
+ "version": "0.0.0",
+ "private": true,
+ "scripts": {
+ "start": "node ./bin/www"
+ },
+ "dependencies": {
+ "body-parser": "~1.16.0",
+ "concordant": "^0.2.1",
+ "cookie-parser": "~1.4.3",
+ "debug": "~2.6.0",
+ "ejs": "~2.5.5",
+ "express": "~4.14.1",
+ "morgan": "~1.7.0",
+ "mu": "^2.1.2",
+ "restify": "^4.3.0",
+ "serve-favicon": "~2.3.2"
+ }
+}
diff --git a/Chapter10/service-discovery-with-dns/micro/webapp/public/stylesheets/style.css b/Chapter10/service-discovery-with-dns/micro/webapp/public/stylesheets/style.css
new file mode 100644
index 0000000..9453385
--- /dev/null
+++ b/Chapter10/service-discovery-with-dns/micro/webapp/public/stylesheets/style.css
@@ -0,0 +1,8 @@
+body {
+ padding: 50px;
+ font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
+}
+
+a {
+ color: #00B7FF;
+}
diff --git a/Chapter10/service-discovery-with-dns/micro/webapp/routes/add.js b/Chapter10/service-discovery-with-dns/micro/webapp/routes/add.js
new file mode 100644
index 0000000..3ab03cc
--- /dev/null
+++ b/Chapter10/service-discovery-with-dns/micro/webapp/routes/add.js
@@ -0,0 +1,68 @@
+'use strict'
+
+const { Router } = require('express')
+const restify = require('restify')
+const { dns } = require('concordant')()
+const router = Router()
+var clients
+
+router.get('/', function (req, res) {
+ res.render('add', { first: 0, second: 0, result: 0 })
+})
+
+router.post('/calculate', resolve, respond)
+
+function resolve (req, res, next) {
+ if (clients) {
+ next()
+ return
+ }
+ const adderservice = `_main._tcp.adderservice.micro.svc.cluster.local`
+ const auditservice = `_main._tcp.auditservice.micro.svc.cluster.local`
+ dns.resolve(adderservice, (err, locs) => {
+ if (err) {
+ next(err)
+ return
+ }
+ const { host, port } = locs[0]
+ const adder = `${host}:${port}`
+ dns.resolve(auditservice, (err, locs) => {
+ if (err) {
+ next(err)
+ return
+ }
+ const { host, port } = locs[0]
+ const audit = `${host}:${port}`
+ clients = {
+ adder: restify.createJSONClient({url: `http://${adder}`}),
+ audit: restify.createJSONClient({url: `http://${audit}`})
+ }
+ next()
+ })
+ })
+}
+
+function respond (req, res, next) {
+ const { first, second } = req.body
+ clients.adder.get(
+ `/add/${first}/${second}`,
+ (err, svcReq, svcRes, data) => {
+ if (err) {
+ next(err)
+ return
+ }
+
+ const { result } = data
+ clients.audit.post('/append', {
+ calc: first + '+' + second,
+ calcResult: result
+ }, (err) => {
+ if (err) console.error(err)
+ })
+
+ res.render('add', { first, second, result })
+ }
+ )
+}
+
+module.exports = router
diff --git a/Chapter10/service-discovery-with-dns/micro/webapp/routes/audit.js b/Chapter10/service-discovery-with-dns/micro/webapp/routes/audit.js
new file mode 100644
index 0000000..a083eef
--- /dev/null
+++ b/Chapter10/service-discovery-with-dns/micro/webapp/routes/audit.js
@@ -0,0 +1,37 @@
+'use strict'
+
+const { Router } = require('express')
+const restify = require('restify')
+const { dns } = require('concordant')()
+const router = Router()
+var client
+
+router.get('/', resolve, respond)
+
+function resolve (req, res, next) {
+ if (client) {
+ next()
+ return
+ }
+ const auditservice = `_main._tcp.auditservice.micro.svc.cluster.local`
+ dns.resolve(auditservice, (err, locs) => {
+ if (err) {
+ next(err)
+ return
+ }
+ const { host, port } = locs[0]
+ client = restify.createJSONClient(`http://${host}:${port}`)
+ })
+}
+
+function respond (req, res, next) {
+ client.get('/list', (err, svcReq, svcRes, data) => {
+ if (err) {
+ next(err)
+ return
+ }
+ res.render('audit', data)
+ })
+}
+
+module.exports = router
diff --git a/Chapter10/service-discovery-with-dns/micro/webapp/routes/index.js b/Chapter10/service-discovery-with-dns/micro/webapp/routes/index.js
new file mode 100644
index 0000000..956680b
--- /dev/null
+++ b/Chapter10/service-discovery-with-dns/micro/webapp/routes/index.js
@@ -0,0 +1,9 @@
+var express = require('express')
+var router = express.Router()
+
+/* GET home page. */
+router.get('/', function (req, res, next) {
+ res.render('index', { title: 'Express' })
+})
+
+module.exports = router
diff --git a/Chapter10/service-discovery-with-dns/micro/webapp/routes/users.js b/Chapter10/service-discovery-with-dns/micro/webapp/routes/users.js
new file mode 100644
index 0000000..8cfe88f
--- /dev/null
+++ b/Chapter10/service-discovery-with-dns/micro/webapp/routes/users.js
@@ -0,0 +1,9 @@
+var express = require('express')
+var router = express.Router()
+
+/* GET users listing. */
+router.get('/', function (req, res, next) {
+ res.send('respond with a resource')
+})
+
+module.exports = router
diff --git a/Chapter10/service-discovery-with-dns/micro/webapp/views/add.ejs b/Chapter10/service-discovery-with-dns/micro/webapp/views/add.ejs
new file mode 100644
index 0000000..da8279c
--- /dev/null
+++ b/Chapter10/service-discovery-with-dns/micro/webapp/views/add.ejs
@@ -0,0 +1,16 @@
+
+
+
+ Add
+
+
+
+ Add it up!
+
+ Submit
+ result = <%= result %>
+
+
diff --git a/Chapter10/service-discovery-with-dns/micro/webapp/views/audit.ejs b/Chapter10/service-discovery-with-dns/micro/webapp/views/audit.ejs
new file mode 100644
index 0000000..dd6d71b
--- /dev/null
+++ b/Chapter10/service-discovery-with-dns/micro/webapp/views/audit.ejs
@@ -0,0 +1,15 @@
+
+
+
+ Audit
+
+
+
+ Calculation History
+
+ <% list.forEach(function (el) { %>
+ at: <%= new Date(el.ts).toLocaleString() %>, calculated: <%= el.calc %>, result: <%= el.result %>
+ <% }) %>
+
+
+
\ No newline at end of file
diff --git a/Chapter10/service-discovery-with-dns/micro/webapp/views/error.ejs b/Chapter10/service-discovery-with-dns/micro/webapp/views/error.ejs
new file mode 100644
index 0000000..7cf94ed
--- /dev/null
+++ b/Chapter10/service-discovery-with-dns/micro/webapp/views/error.ejs
@@ -0,0 +1,3 @@
+<%= message %>
+<%= error.status %>
+<%= error.stack %>
diff --git a/Chapter10/service-discovery-with-dns/micro/webapp/views/index.ejs b/Chapter10/service-discovery-with-dns/micro/webapp/views/index.ejs
new file mode 100644
index 0000000..7b7a1d6
--- /dev/null
+++ b/Chapter10/service-discovery-with-dns/micro/webapp/views/index.ejs
@@ -0,0 +1,11 @@
+
+
+
+ <%= title %>
+
+
+
+ <%= title %>
+ Welcome to <%= title %>
+
+
diff --git a/Chapter10/setting-up-a-development-environment/micro-lil-pids/adderservice/package.json b/Chapter10/setting-up-a-development-environment/micro-lil-pids/adderservice/package.json
new file mode 100644
index 0000000..dedef88
--- /dev/null
+++ b/Chapter10/setting-up-a-development-environment/micro-lil-pids/adderservice/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "adderservice",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "Peter Elger",
+ "license": "ISC",
+ "dependencies": {
+ "restify": "^4.3.0"
+ }
+}
diff --git a/Chapter10/setting-up-a-development-environment/micro-lil-pids/adderservice/service.js b/Chapter10/setting-up-a-development-environment/micro-lil-pids/adderservice/service.js
new file mode 100644
index 0000000..5131b53
--- /dev/null
+++ b/Chapter10/setting-up-a-development-environment/micro-lil-pids/adderservice/service.js
@@ -0,0 +1,19 @@
+'use strict'
+
+const restify = require('restify')
+
+function respond (req, res, next) {
+ const result = (parseInt(req.params.first, 10) +
+ parseInt(req.params.second, 10)).toString()
+ console.log('adding numbers!')
+ res.send(result)
+ next()
+}
+
+const server = restify.createServer()
+server.get('/add/:first/:second', respond)
+
+server.listen(8080, () => {
+ console.log('%s listening at %s', server.name, server.url)
+})
+
diff --git a/Chapter10/setting-up-a-development-environment/micro-lil-pids/services b/Chapter10/setting-up-a-development-environment/micro-lil-pids/services
new file mode 100644
index 0000000..8d17b4a
--- /dev/null
+++ b/Chapter10/setting-up-a-development-environment/micro-lil-pids/services
@@ -0,0 +1,2 @@
+cd webapp && npm start
+cd adderservice && node service
diff --git a/Chapter10/setting-up-a-development-environment/micro-lil-pids/webapp/app.js b/Chapter10/setting-up-a-development-environment/micro-lil-pids/webapp/app.js
new file mode 100644
index 0000000..90f0e7a
--- /dev/null
+++ b/Chapter10/setting-up-a-development-environment/micro-lil-pids/webapp/app.js
@@ -0,0 +1,48 @@
+var express = require('express')
+var path = require('path')
+var favicon = require('serve-favicon')
+var logger = require('morgan')
+var cookieParser = require('cookie-parser')
+var bodyParser = require('body-parser')
+
+var index = require('./routes/index')
+var users = require('./routes/users')
+var add = require('./routes/add')
+
+var app = express()
+
+// view engine setup
+app.set('views', path.join(__dirname, 'views'))
+app.set('view engine', 'ejs')
+
+// uncomment after placing your favicon in /public
+// app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
+app.use(logger('dev'))
+app.use(bodyParser.json())
+app.use(bodyParser.urlencoded({ extended: false }))
+app.use(cookieParser())
+app.use(express.static(path.join(__dirname, 'public')))
+
+app.use('/', index)
+app.use('/users', users)
+app.use('/add', add)
+
+// catch 404 and forward to error handler
+app.use(function (req, res, next) {
+ var err = new Error('Not Found')
+ err.status = 404
+ next(err)
+})
+
+// error handler
+app.use(function (err, req, res, next) {
+ // set locals, only providing error in development
+ res.locals.message = err.message
+ res.locals.error = req.app.get('env') === 'development' ? err : {}
+
+ // render the error page
+ res.status(err.status || 500)
+ res.render('error')
+})
+
+module.exports = app
diff --git a/Chapter10/setting-up-a-development-environment/micro-lil-pids/webapp/bin/www b/Chapter10/setting-up-a-development-environment/micro-lil-pids/webapp/bin/www
new file mode 100644
index 0000000..8715eeb
--- /dev/null
+++ b/Chapter10/setting-up-a-development-environment/micro-lil-pids/webapp/bin/www
@@ -0,0 +1,90 @@
+#!/usr/bin/env node
+
+/**
+ * Module dependencies.
+ */
+
+var app = require('../app');
+var debug = require('debug')('webapp:server');
+var http = require('http');
+
+/**
+ * Get port from environment and store in Express.
+ */
+
+var port = normalizePort(process.env.PORT || '3000');
+app.set('port', port);
+
+/**
+ * Create HTTP server.
+ */
+
+var server = http.createServer(app);
+
+/**
+ * Listen on provided port, on all network interfaces.
+ */
+
+server.listen(port);
+server.on('error', onError);
+server.on('listening', onListening);
+
+/**
+ * Normalize a port into a number, string, or false.
+ */
+
+function normalizePort(val) {
+ var port = parseInt(val, 10);
+
+ if (isNaN(port)) {
+ // named pipe
+ return val;
+ }
+
+ if (port >= 0) {
+ // port number
+ return port;
+ }
+
+ return false;
+}
+
+/**
+ * Event listener for HTTP server "error" event.
+ */
+
+function onError(error) {
+ if (error.syscall !== 'listen') {
+ throw error;
+ }
+
+ var bind = typeof port === 'string'
+ ? 'Pipe ' + port
+ : 'Port ' + port;
+
+ // handle specific listen errors with friendly messages
+ switch (error.code) {
+ case 'EACCES':
+ console.error(bind + ' requires elevated privileges');
+ process.exit(1);
+ break;
+ case 'EADDRINUSE':
+ console.error(bind + ' is already in use');
+ process.exit(1);
+ break;
+ default:
+ throw error;
+ }
+}
+
+/**
+ * Event listener for HTTP server "listening" event.
+ */
+
+function onListening() {
+ var addr = server.address();
+ var bind = typeof addr === 'string'
+ ? 'pipe ' + addr
+ : 'port ' + addr.port;
+ debug('Listening on ' + bind);
+}
diff --git a/Chapter10/setting-up-a-development-environment/micro-lil-pids/webapp/package.json b/Chapter10/setting-up-a-development-environment/micro-lil-pids/webapp/package.json
new file mode 100644
index 0000000..1c0bcc8
--- /dev/null
+++ b/Chapter10/setting-up-a-development-environment/micro-lil-pids/webapp/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "webapp",
+ "version": "0.0.0",
+ "private": true,
+ "scripts": {
+ "start": "node ./bin/www"
+ },
+ "dependencies": {
+ "body-parser": "~1.16.0",
+ "cookie-parser": "~1.4.3",
+ "debug": "~2.6.0",
+ "ejs": "~2.5.5",
+ "express": "~4.14.1",
+ "morgan": "~1.7.0",
+ "restify": "^4.3.0",
+ "serve-favicon": "~2.3.2"
+ }
+}
diff --git a/Chapter10/setting-up-a-development-environment/micro-lil-pids/webapp/public/stylesheets/style.css b/Chapter10/setting-up-a-development-environment/micro-lil-pids/webapp/public/stylesheets/style.css
new file mode 100644
index 0000000..9453385
--- /dev/null
+++ b/Chapter10/setting-up-a-development-environment/micro-lil-pids/webapp/public/stylesheets/style.css
@@ -0,0 +1,8 @@
+body {
+ padding: 50px;
+ font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
+}
+
+a {
+ color: #00B7FF;
+}
diff --git a/Chapter10/setting-up-a-development-environment/micro-lil-pids/webapp/routes/add.js b/Chapter10/setting-up-a-development-environment/micro-lil-pids/webapp/routes/add.js
new file mode 100644
index 0000000..e1c2ebc
--- /dev/null
+++ b/Chapter10/setting-up-a-development-environment/micro-lil-pids/webapp/routes/add.js
@@ -0,0 +1,26 @@
+const { Router } = require('express')
+const restify = require('restify')
+const router = Router()
+
+router.get('/', function (req, res) {
+ res.render('add', { first: 0, second: 0, result: 0 })
+})
+
+router.post('/calculate', function (req, res, next) {
+ const client = restify.createStringClient({
+ url: 'http://localhost:8080'
+ })
+ const {first, second} = req.body
+ client.get(
+ `/add/${first}/${second}`,
+ (err, svcReq, svcRes, result) => {
+ if (err) {
+ next(err)
+ return
+ }
+ res.render('add', { first, second, result })
+ }
+ )
+})
+
+module.exports = router
diff --git a/Chapter10/setting-up-a-development-environment/micro-lil-pids/webapp/routes/index.js b/Chapter10/setting-up-a-development-environment/micro-lil-pids/webapp/routes/index.js
new file mode 100644
index 0000000..956680b
--- /dev/null
+++ b/Chapter10/setting-up-a-development-environment/micro-lil-pids/webapp/routes/index.js
@@ -0,0 +1,9 @@
+var express = require('express')
+var router = express.Router()
+
+/* GET home page. */
+router.get('/', function (req, res, next) {
+ res.render('index', { title: 'Express' })
+})
+
+module.exports = router
diff --git a/Chapter10/setting-up-a-development-environment/micro-lil-pids/webapp/routes/users.js b/Chapter10/setting-up-a-development-environment/micro-lil-pids/webapp/routes/users.js
new file mode 100644
index 0000000..8cfe88f
--- /dev/null
+++ b/Chapter10/setting-up-a-development-environment/micro-lil-pids/webapp/routes/users.js
@@ -0,0 +1,9 @@
+var express = require('express')
+var router = express.Router()
+
+/* GET users listing. */
+router.get('/', function (req, res, next) {
+ res.send('respond with a resource')
+})
+
+module.exports = router
diff --git a/Chapter10/setting-up-a-development-environment/micro-lil-pids/webapp/views/add.ejs b/Chapter10/setting-up-a-development-environment/micro-lil-pids/webapp/views/add.ejs
new file mode 100644
index 0000000..da8279c
--- /dev/null
+++ b/Chapter10/setting-up-a-development-environment/micro-lil-pids/webapp/views/add.ejs
@@ -0,0 +1,16 @@
+
+
+
+ Add
+
+
+
+ Add it up!
+
+ Submit
+ result = <%= result %>
+
+
diff --git a/Chapter10/setting-up-a-development-environment/micro-lil-pids/webapp/views/error.ejs b/Chapter10/setting-up-a-development-environment/micro-lil-pids/webapp/views/error.ejs
new file mode 100644
index 0000000..7cf94ed
--- /dev/null
+++ b/Chapter10/setting-up-a-development-environment/micro-lil-pids/webapp/views/error.ejs
@@ -0,0 +1,3 @@
+<%= message %>
+<%= error.status %>
+<%= error.stack %>
diff --git a/Chapter10/setting-up-a-development-environment/micro-lil-pids/webapp/views/index.ejs b/Chapter10/setting-up-a-development-environment/micro-lil-pids/webapp/views/index.ejs
new file mode 100644
index 0000000..7b7a1d6
--- /dev/null
+++ b/Chapter10/setting-up-a-development-environment/micro-lil-pids/webapp/views/index.ejs
@@ -0,0 +1,11 @@
+
+
+
+ <%= title %>
+
+
+
+ <%= title %>
+ Welcome to <%= title %>
+
+
diff --git a/Chapter10/setting-up-a-development-environment/micro/adderservice/package.json b/Chapter10/setting-up-a-development-environment/micro/adderservice/package.json
new file mode 100644
index 0000000..dedef88
--- /dev/null
+++ b/Chapter10/setting-up-a-development-environment/micro/adderservice/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "adderservice",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "Peter Elger",
+ "license": "ISC",
+ "dependencies": {
+ "restify": "^4.3.0"
+ }
+}
diff --git a/Chapter10/setting-up-a-development-environment/micro/adderservice/service.js b/Chapter10/setting-up-a-development-environment/micro/adderservice/service.js
new file mode 100644
index 0000000..5131b53
--- /dev/null
+++ b/Chapter10/setting-up-a-development-environment/micro/adderservice/service.js
@@ -0,0 +1,19 @@
+'use strict'
+
+const restify = require('restify')
+
+function respond (req, res, next) {
+ const result = (parseInt(req.params.first, 10) +
+ parseInt(req.params.second, 10)).toString()
+ console.log('adding numbers!')
+ res.send(result)
+ next()
+}
+
+const server = restify.createServer()
+server.get('/add/:first/:second', respond)
+
+server.listen(8080, () => {
+ console.log('%s listening at %s', server.name, server.url)
+})
+
diff --git a/Chapter10/setting-up-a-development-environment/micro/fuge/fuge.yml b/Chapter10/setting-up-a-development-environment/micro/fuge/fuge.yml
new file mode 100644
index 0000000..1868afd
--- /dev/null
+++ b/Chapter10/setting-up-a-development-environment/micro/fuge/fuge.yml
@@ -0,0 +1,19 @@
+fuge_global:
+ tail: true
+ monitor: true
+ monitor_excludes:
+ - '**/node_modules/**'
+ - '**/.git/**'
+ - '*.log'
+adderservice:
+ type: process
+ path: ../adderservice
+ run: 'node service.js'
+ ports:
+ - main=8080
+webapp:
+ type: process
+ path: ../webapp
+ run: 'npm start'
+ ports:
+ - main=3000
diff --git a/Chapter10/setting-up-a-development-environment/micro/fuge/fuge2.yml b/Chapter10/setting-up-a-development-environment/micro/fuge/fuge2.yml
new file mode 100644
index 0000000..642a1ba
--- /dev/null
+++ b/Chapter10/setting-up-a-development-environment/micro/fuge/fuge2.yml
@@ -0,0 +1,19 @@
+fuge_global:
+ tail: true
+ monitor: true
+ monitor_excludes:
+ - '**/node_modules/**'
+ - '**/.git/**'
+ - '*.log'
+adderservice:
+ type: node
+ path: ../adderservice
+ run: 'node service.js'
+ ports:
+ - main=8080
+webapp:
+ type: process
+ path: ../webapp
+ run: 'npm start'
+ ports:
+ - main=3000
diff --git a/Chapter10/setting-up-a-development-environment/micro/webapp/app.js b/Chapter10/setting-up-a-development-environment/micro/webapp/app.js
new file mode 100644
index 0000000..90f0e7a
--- /dev/null
+++ b/Chapter10/setting-up-a-development-environment/micro/webapp/app.js
@@ -0,0 +1,48 @@
+var express = require('express')
+var path = require('path')
+var favicon = require('serve-favicon')
+var logger = require('morgan')
+var cookieParser = require('cookie-parser')
+var bodyParser = require('body-parser')
+
+var index = require('./routes/index')
+var users = require('./routes/users')
+var add = require('./routes/add')
+
+var app = express()
+
+// view engine setup
+app.set('views', path.join(__dirname, 'views'))
+app.set('view engine', 'ejs')
+
+// uncomment after placing your favicon in /public
+// app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
+app.use(logger('dev'))
+app.use(bodyParser.json())
+app.use(bodyParser.urlencoded({ extended: false }))
+app.use(cookieParser())
+app.use(express.static(path.join(__dirname, 'public')))
+
+app.use('/', index)
+app.use('/users', users)
+app.use('/add', add)
+
+// catch 404 and forward to error handler
+app.use(function (req, res, next) {
+ var err = new Error('Not Found')
+ err.status = 404
+ next(err)
+})
+
+// error handler
+app.use(function (err, req, res, next) {
+ // set locals, only providing error in development
+ res.locals.message = err.message
+ res.locals.error = req.app.get('env') === 'development' ? err : {}
+
+ // render the error page
+ res.status(err.status || 500)
+ res.render('error')
+})
+
+module.exports = app
diff --git a/Chapter10/setting-up-a-development-environment/micro/webapp/bin/www b/Chapter10/setting-up-a-development-environment/micro/webapp/bin/www
new file mode 100644
index 0000000..8715eeb
--- /dev/null
+++ b/Chapter10/setting-up-a-development-environment/micro/webapp/bin/www
@@ -0,0 +1,90 @@
+#!/usr/bin/env node
+
+/**
+ * Module dependencies.
+ */
+
+var app = require('../app');
+var debug = require('debug')('webapp:server');
+var http = require('http');
+
+/**
+ * Get port from environment and store in Express.
+ */
+
+var port = normalizePort(process.env.PORT || '3000');
+app.set('port', port);
+
+/**
+ * Create HTTP server.
+ */
+
+var server = http.createServer(app);
+
+/**
+ * Listen on provided port, on all network interfaces.
+ */
+
+server.listen(port);
+server.on('error', onError);
+server.on('listening', onListening);
+
+/**
+ * Normalize a port into a number, string, or false.
+ */
+
+function normalizePort(val) {
+ var port = parseInt(val, 10);
+
+ if (isNaN(port)) {
+ // named pipe
+ return val;
+ }
+
+ if (port >= 0) {
+ // port number
+ return port;
+ }
+
+ return false;
+}
+
+/**
+ * Event listener for HTTP server "error" event.
+ */
+
+function onError(error) {
+ if (error.syscall !== 'listen') {
+ throw error;
+ }
+
+ var bind = typeof port === 'string'
+ ? 'Pipe ' + port
+ : 'Port ' + port;
+
+ // handle specific listen errors with friendly messages
+ switch (error.code) {
+ case 'EACCES':
+ console.error(bind + ' requires elevated privileges');
+ process.exit(1);
+ break;
+ case 'EADDRINUSE':
+ console.error(bind + ' is already in use');
+ process.exit(1);
+ break;
+ default:
+ throw error;
+ }
+}
+
+/**
+ * Event listener for HTTP server "listening" event.
+ */
+
+function onListening() {
+ var addr = server.address();
+ var bind = typeof addr === 'string'
+ ? 'pipe ' + addr
+ : 'port ' + addr.port;
+ debug('Listening on ' + bind);
+}
diff --git a/Chapter10/setting-up-a-development-environment/micro/webapp/package.json b/Chapter10/setting-up-a-development-environment/micro/webapp/package.json
new file mode 100644
index 0000000..1c0bcc8
--- /dev/null
+++ b/Chapter10/setting-up-a-development-environment/micro/webapp/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "webapp",
+ "version": "0.0.0",
+ "private": true,
+ "scripts": {
+ "start": "node ./bin/www"
+ },
+ "dependencies": {
+ "body-parser": "~1.16.0",
+ "cookie-parser": "~1.4.3",
+ "debug": "~2.6.0",
+ "ejs": "~2.5.5",
+ "express": "~4.14.1",
+ "morgan": "~1.7.0",
+ "restify": "^4.3.0",
+ "serve-favicon": "~2.3.2"
+ }
+}
diff --git a/Chapter10/setting-up-a-development-environment/micro/webapp/public/stylesheets/style.css b/Chapter10/setting-up-a-development-environment/micro/webapp/public/stylesheets/style.css
new file mode 100644
index 0000000..9453385
--- /dev/null
+++ b/Chapter10/setting-up-a-development-environment/micro/webapp/public/stylesheets/style.css
@@ -0,0 +1,8 @@
+body {
+ padding: 50px;
+ font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
+}
+
+a {
+ color: #00B7FF;
+}
diff --git a/Chapter10/setting-up-a-development-environment/micro/webapp/routes/add.js b/Chapter10/setting-up-a-development-environment/micro/webapp/routes/add.js
new file mode 100644
index 0000000..e1c2ebc
--- /dev/null
+++ b/Chapter10/setting-up-a-development-environment/micro/webapp/routes/add.js
@@ -0,0 +1,26 @@
+const { Router } = require('express')
+const restify = require('restify')
+const router = Router()
+
+router.get('/', function (req, res) {
+ res.render('add', { first: 0, second: 0, result: 0 })
+})
+
+router.post('/calculate', function (req, res, next) {
+ const client = restify.createStringClient({
+ url: 'http://localhost:8080'
+ })
+ const {first, second} = req.body
+ client.get(
+ `/add/${first}/${second}`,
+ (err, svcReq, svcRes, result) => {
+ if (err) {
+ next(err)
+ return
+ }
+ res.render('add', { first, second, result })
+ }
+ )
+})
+
+module.exports = router
diff --git a/Chapter10/setting-up-a-development-environment/micro/webapp/routes/index.js b/Chapter10/setting-up-a-development-environment/micro/webapp/routes/index.js
new file mode 100644
index 0000000..956680b
--- /dev/null
+++ b/Chapter10/setting-up-a-development-environment/micro/webapp/routes/index.js
@@ -0,0 +1,9 @@
+var express = require('express')
+var router = express.Router()
+
+/* GET home page. */
+router.get('/', function (req, res, next) {
+ res.render('index', { title: 'Express' })
+})
+
+module.exports = router
diff --git a/Chapter10/setting-up-a-development-environment/micro/webapp/routes/users.js b/Chapter10/setting-up-a-development-environment/micro/webapp/routes/users.js
new file mode 100644
index 0000000..8cfe88f
--- /dev/null
+++ b/Chapter10/setting-up-a-development-environment/micro/webapp/routes/users.js
@@ -0,0 +1,9 @@
+var express = require('express')
+var router = express.Router()
+
+/* GET users listing. */
+router.get('/', function (req, res, next) {
+ res.send('respond with a resource')
+})
+
+module.exports = router
diff --git a/Chapter10/setting-up-a-development-environment/micro/webapp/views/add.ejs b/Chapter10/setting-up-a-development-environment/micro/webapp/views/add.ejs
new file mode 100644
index 0000000..da8279c
--- /dev/null
+++ b/Chapter10/setting-up-a-development-environment/micro/webapp/views/add.ejs
@@ -0,0 +1,16 @@
+
+
+
+ Add
+
+
+
+ Add it up!
+
+ Submit
+ result = <%= result %>
+
+
diff --git a/Chapter10/setting-up-a-development-environment/micro/webapp/views/error.ejs b/Chapter10/setting-up-a-development-environment/micro/webapp/views/error.ejs
new file mode 100644
index 0000000..7cf94ed
--- /dev/null
+++ b/Chapter10/setting-up-a-development-environment/micro/webapp/views/error.ejs
@@ -0,0 +1,3 @@
+<%= message %>
+<%= error.status %>
+<%= error.stack %>
diff --git a/Chapter10/setting-up-a-development-environment/micro/webapp/views/index.ejs b/Chapter10/setting-up-a-development-environment/micro/webapp/views/index.ejs
new file mode 100644
index 0000000..7b7a1d6
--- /dev/null
+++ b/Chapter10/setting-up-a-development-environment/micro/webapp/views/index.ejs
@@ -0,0 +1,11 @@
+
+
+
+ <%= title %>
+
+
+
+ <%= title %>
+ Welcome to <%= title %>
+
+
diff --git a/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/adderservice/index.js b/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/adderservice/index.js
new file mode 100644
index 0000000..1f2d9cf
--- /dev/null
+++ b/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/adderservice/index.js
@@ -0,0 +1,7 @@
+'use strict'
+
+const wiring = require('./wiring')
+const service = require('./service')()
+
+
+wiring(service)
diff --git a/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/adderservice/package.json b/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/adderservice/package.json
new file mode 100644
index 0000000..e2d4f59
--- /dev/null
+++ b/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/adderservice/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "adderservice",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "Peter Elger",
+ "license": "ISC",
+ "dependencies": {
+ "bloomrun": "^3.0.4",
+ "net-object-stream": "^2.0.0",
+ "pump": "^1.0.2",
+ "through2": "^2.0.3"
+ },
+ "devDependencies": {
+ "tap": "^10.3.1"
+ }
+}
diff --git a/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/adderservice/service.js b/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/adderservice/service.js
new file mode 100644
index 0000000..9d0d175
--- /dev/null
+++ b/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/adderservice/service.js
@@ -0,0 +1,13 @@
+'use strict'
+
+module.exports = service
+
+function service () {
+ function add (args, cb) {
+ const {first, second} = args
+ const result = (parseInt(first, 10) + parseInt(second, 10))
+ cb(null, {result: result.toString()})
+ }
+
+ return { add }
+}
diff --git a/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/adderservice/test/test.js b/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/adderservice/test/test.js
new file mode 100644
index 0000000..3b775c3
--- /dev/null
+++ b/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/adderservice/test/test.js
@@ -0,0 +1,12 @@
+var test = require('tap').test
+var service = require('../service')()
+
+test('test add', function (t) {
+ t.plan(2)
+
+ service.add({first: 1, second: 2}, function (err, result) {
+ t.equal(err, null)
+ t.equal(result, 3)
+ })
+})
+
diff --git a/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/adderservice/wiring.js b/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/adderservice/wiring.js
new file mode 100644
index 0000000..6411252
--- /dev/null
+++ b/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/adderservice/wiring.js
@@ -0,0 +1,55 @@
+'use strict'
+
+const net = require('net')
+const nos = require('net-object-stream')
+const through = require('through2')
+const pump = require('pump')
+const bloomrun = require('bloomrun')
+
+const { ADDERSERVICE_SERVICE_PORT } = process.env
+
+module.exports = wiring
+
+function wiring (service) {
+ const patterns = createPatternRoutes(service)
+ const matcher = createMatcherStream(patterns)
+
+ const server = net.createServer((socket) => {
+ socket = nos(socket)
+ pump(socket, matcher, socket, failure)
+ })
+
+ server.listen(ADDERSERVICE_SERVICE_PORT, '0.0.0.0', () => {
+ console.log('server listening at', ADDERSERVICE_SERVICE_PORT)
+ })
+}
+
+function createPatternRoutes (service) {
+ const patterns = bloomrun()
+
+ patterns.add({role: 'adder', cmd: 'add'}, service.add)
+
+ return patterns
+}
+
+function createMatcherStream (patterns) {
+ return through.obj((object, enc, cb) => {
+ const match = patterns.lookup(object)
+ if (match === null) {
+ cb()
+ return
+ }
+ match(object, (err, data) => {
+ if (err) {
+ cb(null, {status: 'error', err: err})
+ return
+ }
+ cb(null, data)
+ })
+ })
+}
+
+function failure (err) {
+ if (err) console.error('Server error', err)
+ else console.error('Stream pipeline ended')
+}
\ No newline at end of file
diff --git a/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/fuge/fuge.yml b/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/fuge/fuge.yml
new file mode 100644
index 0000000..94093bd
--- /dev/null
+++ b/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/fuge/fuge.yml
@@ -0,0 +1,19 @@
+fuge_global:
+ tail: true
+ monitor: true
+ monitor_excludes:
+ - '**/node_modules/**'
+ - '**/.git/**'
+ - '*.log'
+adderservice:
+ type: process
+ path: ../adderservice
+ run: node index.js
+ ports:
+ - main=8080
+webapp:
+ type: process
+ path: ../webapp
+ run: npm start
+ ports:
+ - main=3000
diff --git a/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/webapp/app.js b/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/webapp/app.js
new file mode 100644
index 0000000..90f0e7a
--- /dev/null
+++ b/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/webapp/app.js
@@ -0,0 +1,48 @@
+var express = require('express')
+var path = require('path')
+var favicon = require('serve-favicon')
+var logger = require('morgan')
+var cookieParser = require('cookie-parser')
+var bodyParser = require('body-parser')
+
+var index = require('./routes/index')
+var users = require('./routes/users')
+var add = require('./routes/add')
+
+var app = express()
+
+// view engine setup
+app.set('views', path.join(__dirname, 'views'))
+app.set('view engine', 'ejs')
+
+// uncomment after placing your favicon in /public
+// app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
+app.use(logger('dev'))
+app.use(bodyParser.json())
+app.use(bodyParser.urlencoded({ extended: false }))
+app.use(cookieParser())
+app.use(express.static(path.join(__dirname, 'public')))
+
+app.use('/', index)
+app.use('/users', users)
+app.use('/add', add)
+
+// catch 404 and forward to error handler
+app.use(function (req, res, next) {
+ var err = new Error('Not Found')
+ err.status = 404
+ next(err)
+})
+
+// error handler
+app.use(function (err, req, res, next) {
+ // set locals, only providing error in development
+ res.locals.message = err.message
+ res.locals.error = req.app.get('env') === 'development' ? err : {}
+
+ // render the error page
+ res.status(err.status || 500)
+ res.render('error')
+})
+
+module.exports = app
diff --git a/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/webapp/bin/www b/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/webapp/bin/www
new file mode 100644
index 0000000..8715eeb
--- /dev/null
+++ b/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/webapp/bin/www
@@ -0,0 +1,90 @@
+#!/usr/bin/env node
+
+/**
+ * Module dependencies.
+ */
+
+var app = require('../app');
+var debug = require('debug')('webapp:server');
+var http = require('http');
+
+/**
+ * Get port from environment and store in Express.
+ */
+
+var port = normalizePort(process.env.PORT || '3000');
+app.set('port', port);
+
+/**
+ * Create HTTP server.
+ */
+
+var server = http.createServer(app);
+
+/**
+ * Listen on provided port, on all network interfaces.
+ */
+
+server.listen(port);
+server.on('error', onError);
+server.on('listening', onListening);
+
+/**
+ * Normalize a port into a number, string, or false.
+ */
+
+function normalizePort(val) {
+ var port = parseInt(val, 10);
+
+ if (isNaN(port)) {
+ // named pipe
+ return val;
+ }
+
+ if (port >= 0) {
+ // port number
+ return port;
+ }
+
+ return false;
+}
+
+/**
+ * Event listener for HTTP server "error" event.
+ */
+
+function onError(error) {
+ if (error.syscall !== 'listen') {
+ throw error;
+ }
+
+ var bind = typeof port === 'string'
+ ? 'Pipe ' + port
+ : 'Port ' + port;
+
+ // handle specific listen errors with friendly messages
+ switch (error.code) {
+ case 'EACCES':
+ console.error(bind + ' requires elevated privileges');
+ process.exit(1);
+ break;
+ case 'EADDRINUSE':
+ console.error(bind + ' is already in use');
+ process.exit(1);
+ break;
+ default:
+ throw error;
+ }
+}
+
+/**
+ * Event listener for HTTP server "listening" event.
+ */
+
+function onListening() {
+ var addr = server.address();
+ var bind = typeof addr === 'string'
+ ? 'pipe ' + addr
+ : 'port ' + addr.port;
+ debug('Listening on ' + bind);
+}
diff --git a/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/webapp/package.json b/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/webapp/package.json
new file mode 100644
index 0000000..ce66c6a
--- /dev/null
+++ b/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/webapp/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "webapp",
+ "version": "0.0.0",
+ "private": true,
+ "scripts": {
+ "start": "node ./bin/www"
+ },
+ "dependencies": {
+ "body-parser": "~1.16.0",
+ "cookie-parser": "~1.4.3",
+ "debug": "~2.6.0",
+ "ejs": "~2.5.5",
+ "express": "~4.14.1",
+ "morgan": "~1.7.0",
+ "mu": "^2.1.2",
+ "net-object-stream": "^2.0.0",
+ "restify": "^4.3.0",
+ "serve-favicon": "~2.3.2"
+ }
+}
diff --git a/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/webapp/public/stylesheets/style.css b/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/webapp/public/stylesheets/style.css
new file mode 100644
index 0000000..9453385
--- /dev/null
+++ b/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/webapp/public/stylesheets/style.css
@@ -0,0 +1,8 @@
+body {
+ padding: 50px;
+ font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
+}
+
+a {
+ color: #00B7FF;
+}
diff --git a/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/webapp/routes/add.js b/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/webapp/routes/add.js
new file mode 100644
index 0000000..d96af85
--- /dev/null
+++ b/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/webapp/routes/add.js
@@ -0,0 +1,38 @@
+'use strict'
+
+const { Router } = require('express')
+const restify = require('restify')
+const net = require('net')
+const nos = require('net-object-stream')
+const router = Router()
+
+const {
+ ADDERSERVICE_SERVICE_HOST,
+ ADDERSERVICE_SERVICE_PORT
+} = process.env
+
+function createClient (ns, opts) {
+ return createClient[ns] || (createClient[ns] = nos(net.connect(opts)))
+}
+
+router.get('/', function (req, res) {
+ res.render('add', { first: 0, second: 0, result: 0 })
+})
+
+router.post('/calculate', function (req, res, next) {
+ const client = createClient('calculate', {
+ host: ADDERSERVICE_SERVICE_HOST,
+ port: ADDERSERVICE_SERVICE_PORT
+ })
+
+ const role = 'adder'
+ const cmd = 'add'
+ const { first, second } = req.body
+ client.once('data', (data) => {
+ const { result } = data
+ res.render('add', { first, second, result })
+ })
+ client.write({role, cmd, first, second})
+})
+
+module.exports = router
diff --git a/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/webapp/routes/index.js b/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/webapp/routes/index.js
new file mode 100644
index 0000000..956680b
--- /dev/null
+++ b/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/webapp/routes/index.js
@@ -0,0 +1,9 @@
+var express = require('express')
+var router = express.Router()
+
+/* GET home page. */
+router.get('/', function (req, res, next) {
+ res.render('index', { title: 'Express' })
+})
+
+module.exports = router
diff --git a/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/webapp/routes/users.js b/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/webapp/routes/users.js
new file mode 100644
index 0000000..8cfe88f
--- /dev/null
+++ b/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/webapp/routes/users.js
@@ -0,0 +1,9 @@
+var express = require('express')
+var router = express.Router()
+
+/* GET users listing. */
+router.get('/', function (req, res, next) {
+ res.send('respond with a resource')
+})
+
+module.exports = router
diff --git a/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/webapp/views/add.ejs b/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/webapp/views/add.ejs
new file mode 100644
index 0000000..da8279c
--- /dev/null
+++ b/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/webapp/views/add.ejs
@@ -0,0 +1,16 @@
+
+
+
+ Add
+
+
+
+ Add it up!
+
+ Submit
+ result = <%= result %>
+
+
diff --git a/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/webapp/views/error.ejs b/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/webapp/views/error.ejs
new file mode 100644
index 0000000..7cf94ed
--- /dev/null
+++ b/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/webapp/views/error.ejs
@@ -0,0 +1,3 @@
+<%= message %>
+<%= error.status %>
+<%= error.stack %>
diff --git a/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/webapp/views/index.ejs b/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/webapp/views/index.ejs
new file mode 100644
index 0000000..7b7a1d6
--- /dev/null
+++ b/Chapter10/standardizing-service-boilerplate/micro-pattern-routing/webapp/views/index.ejs
@@ -0,0 +1,11 @@
+
+
+
+ <%= title %>
+
+
+
+ <%= title %>
+ Welcome to <%= title %>
+
+
diff --git a/Chapter10/standardizing-service-boilerplate/micro/adderservice/index.js b/Chapter10/standardizing-service-boilerplate/micro/adderservice/index.js
new file mode 100644
index 0000000..1f2d9cf
--- /dev/null
+++ b/Chapter10/standardizing-service-boilerplate/micro/adderservice/index.js
@@ -0,0 +1,7 @@
+'use strict'
+
+const wiring = require('./wiring')
+const service = require('./service')()
+
+
+wiring(service)
diff --git a/Chapter10/standardizing-service-boilerplate/micro/adderservice/package.json b/Chapter10/standardizing-service-boilerplate/micro/adderservice/package.json
new file mode 100644
index 0000000..7cef23a
--- /dev/null
+++ b/Chapter10/standardizing-service-boilerplate/micro/adderservice/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "adderservice",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "tap test"
+ },
+ "keywords": [],
+ "author": "Peter Elger",
+ "license": "ISC",
+ "dependencies": {
+ "restify": "^4.3.0"
+ },
+ "devDependencies": {
+ "tap": "^10.3.1"
+ }
+}
diff --git a/Chapter10/standardizing-service-boilerplate/micro/adderservice/service.js b/Chapter10/standardizing-service-boilerplate/micro/adderservice/service.js
new file mode 100644
index 0000000..9d0d175
--- /dev/null
+++ b/Chapter10/standardizing-service-boilerplate/micro/adderservice/service.js
@@ -0,0 +1,13 @@
+'use strict'
+
+module.exports = service
+
+function service () {
+ function add (args, cb) {
+ const {first, second} = args
+ const result = (parseInt(first, 10) + parseInt(second, 10))
+ cb(null, {result: result.toString()})
+ }
+
+ return { add }
+}
diff --git a/Chapter10/standardizing-service-boilerplate/micro/adderservice/test/index.js b/Chapter10/standardizing-service-boilerplate/micro/adderservice/test/index.js
new file mode 100644
index 0000000..32c45e5
--- /dev/null
+++ b/Chapter10/standardizing-service-boilerplate/micro/adderservice/test/index.js
@@ -0,0 +1,13 @@
+'use strict'
+
+const {test} = require('tap')
+const service = require('../service')()
+
+test('test add', (t) => {
+ t.plan(2)
+
+ service.add({first: 1, second: 2}, (err, answer) => {
+ t.error(err)
+ t.same(answer, {result: 3})
+ })
+})
diff --git a/Chapter10/standardizing-service-boilerplate/micro/adderservice/wiring.js b/Chapter10/standardizing-service-boilerplate/micro/adderservice/wiring.js
new file mode 100644
index 0000000..0ed018c
--- /dev/null
+++ b/Chapter10/standardizing-service-boilerplate/micro/adderservice/wiring.js
@@ -0,0 +1,27 @@
+'use strict'
+
+const restify = require('restify')
+const { ADDERSERVICE_SERVICE_PORT } = process.env
+
+module.exports = wiring
+
+function wiring (service) {
+ const server = restify.createServer()
+
+ server.get('/add/:first/:second', (req, res, next) => {
+ service.add(req.params, (err, result) => {
+ if (err) {
+ res.send(err)
+ next()
+ return
+ }
+ res.send(200, result)
+ next()
+ })
+ })
+
+ server.listen(ADDERSERVICE_SERVICE_PORT, '0.0.0.0', () => {
+ console.log('%s listening at %s', server.name, server.url)
+ })
+}
+
diff --git a/Chapter10/standardizing-service-boilerplate/micro/fuge/fuge.yml b/Chapter10/standardizing-service-boilerplate/micro/fuge/fuge.yml
new file mode 100644
index 0000000..94093bd
--- /dev/null
+++ b/Chapter10/standardizing-service-boilerplate/micro/fuge/fuge.yml
@@ -0,0 +1,19 @@
+fuge_global:
+ tail: true
+ monitor: true
+ monitor_excludes:
+ - '**/node_modules/**'
+ - '**/.git/**'
+ - '*.log'
+adderservice:
+ type: process
+ path: ../adderservice
+ run: node index.js
+ ports:
+ - main=8080
+webapp:
+ type: process
+ path: ../webapp
+ run: npm start
+ ports:
+ - main=3000
diff --git a/Chapter10/standardizing-service-boilerplate/micro/webapp/app.js b/Chapter10/standardizing-service-boilerplate/micro/webapp/app.js
new file mode 100644
index 0000000..90f0e7a
--- /dev/null
+++ b/Chapter10/standardizing-service-boilerplate/micro/webapp/app.js
@@ -0,0 +1,48 @@
+var express = require('express')
+var path = require('path')
+var favicon = require('serve-favicon')
+var logger = require('morgan')
+var cookieParser = require('cookie-parser')
+var bodyParser = require('body-parser')
+
+var index = require('./routes/index')
+var users = require('./routes/users')
+var add = require('./routes/add')
+
+var app = express()
+
+// view engine setup
+app.set('views', path.join(__dirname, 'views'))
+app.set('view engine', 'ejs')
+
+// uncomment after placing your favicon in /public
+// app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
+app.use(logger('dev'))
+app.use(bodyParser.json())
+app.use(bodyParser.urlencoded({ extended: false }))
+app.use(cookieParser())
+app.use(express.static(path.join(__dirname, 'public')))
+
+app.use('/', index)
+app.use('/users', users)
+app.use('/add', add)
+
+// catch 404 and forward to error handler
+app.use(function (req, res, next) {
+ var err = new Error('Not Found')
+ err.status = 404
+ next(err)
+})
+
+// error handler
+app.use(function (err, req, res, next) {
+ // set locals, only providing error in development
+ res.locals.message = err.message
+ res.locals.error = req.app.get('env') === 'development' ? err : {}
+
+ // render the error page
+ res.status(err.status || 500)
+ res.render('error')
+})
+
+module.exports = app
diff --git a/Chapter10/standardizing-service-boilerplate/micro/webapp/bin/www b/Chapter10/standardizing-service-boilerplate/micro/webapp/bin/www
new file mode 100644
index 0000000..8715eeb
--- /dev/null
+++ b/Chapter10/standardizing-service-boilerplate/micro/webapp/bin/www
@@ -0,0 +1,90 @@
+#!/usr/bin/env node
+
+/**
+ * Module dependencies.
+ */
+
+var app = require('../app');
+var debug = require('debug')('webapp:server');
+var http = require('http');
+
+/**
+ * Get port from environment and store in Express.
+ */
+
+var port = normalizePort(process.env.PORT || '3000');
+app.set('port', port);
+
+/**
+ * Create HTTP server.
+ */
+
+var server = http.createServer(app);
+
+/**
+ * Listen on provided port, on all network interfaces.
+ */
+
+server.listen(port);
+server.on('error', onError);
+server.on('listening', onListening);
+
+/**
+ * Normalize a port into a number, string, or false.
+ */
+
+function normalizePort(val) {
+ var port = parseInt(val, 10);
+
+ if (isNaN(port)) {
+ // named pipe
+ return val;
+ }
+
+ if (port >= 0) {
+ // port number
+ return port;
+ }
+
+ return false;
+}
+
+/**
+ * Event listener for HTTP server "error" event.
+ */
+
+function onError(error) {
+ if (error.syscall !== 'listen') {
+ throw error;
+ }
+
+ var bind = typeof port === 'string'
+ ? 'Pipe ' + port
+ : 'Port ' + port;
+
+ // handle specific listen errors with friendly messages
+ switch (error.code) {
+ case 'EACCES':
+ console.error(bind + ' requires elevated privileges');
+ process.exit(1);
+ break;
+ case 'EADDRINUSE':
+ console.error(bind + ' is already in use');
+ process.exit(1);
+ break;
+ default:
+ throw error;
+ }
+}
+
+/**
+ * Event listener for HTTP server "listening" event.
+ */
+
+function onListening() {
+ var addr = server.address();
+ var bind = typeof addr === 'string'
+ ? 'pipe ' + addr
+ : 'port ' + addr.port;
+ debug('Listening on ' + bind);
+}
diff --git a/Chapter10/standardizing-service-boilerplate/micro/webapp/package.json b/Chapter10/standardizing-service-boilerplate/micro/webapp/package.json
new file mode 100644
index 0000000..c0d6ed3
--- /dev/null
+++ b/Chapter10/standardizing-service-boilerplate/micro/webapp/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "webapp",
+ "version": "0.0.0",
+ "private": true,
+ "scripts": {
+ "start": "node ./bin/www"
+ },
+ "dependencies": {
+ "body-parser": "~1.16.0",
+ "cookie-parser": "~1.4.3",
+ "debug": "~2.6.0",
+ "ejs": "~2.5.5",
+ "express": "~4.14.1",
+ "morgan": "~1.7.0",
+ "mu": "^2.1.2",
+ "restify": "^4.3.0",
+ "serve-favicon": "~2.3.2"
+ }
+}
diff --git a/Chapter10/standardizing-service-boilerplate/micro/webapp/public/stylesheets/style.css b/Chapter10/standardizing-service-boilerplate/micro/webapp/public/stylesheets/style.css
new file mode 100644
index 0000000..9453385
--- /dev/null
+++ b/Chapter10/standardizing-service-boilerplate/micro/webapp/public/stylesheets/style.css
@@ -0,0 +1,8 @@
+body {
+ padding: 50px;
+ font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
+}
+
+a {
+ color: #00B7FF;
+}
diff --git a/Chapter10/standardizing-service-boilerplate/micro/webapp/routes/add.js b/Chapter10/standardizing-service-boilerplate/micro/webapp/routes/add.js
new file mode 100644
index 0000000..2074c32
--- /dev/null
+++ b/Chapter10/standardizing-service-boilerplate/micro/webapp/routes/add.js
@@ -0,0 +1,34 @@
+'use strict'
+
+const { Router } = require('express')
+const restify = require('restify')
+const router = Router()
+
+const {
+ ADDERSERVICE_SERVICE_HOST,
+ ADDERSERVICE_SERVICE_PORT
+} = process.env
+
+router.get('/', function (req, res) {
+ res.render('add', { first: 0, second: 0, result: 0 })
+})
+
+router.post('/calculate', function (req, res, next) {
+ const client = restify.createJSONClient({
+ url: `http://${ADDERSERVICE_SERVICE_HOST}:${ADDERSERVICE_SERVICE_PORT}`
+ })
+ const { first, second } = req.body
+ client.get(
+ `/add/${first}/${second}`,
+ (err, svcReq, svcRes, data) => {
+ if (err) {
+ next(err)
+ return
+ }
+ const { result } = data
+ res.render('add', { first, second, result })
+ }
+ )
+})
+
+module.exports = router
diff --git a/Chapter10/standardizing-service-boilerplate/micro/webapp/routes/index.js b/Chapter10/standardizing-service-boilerplate/micro/webapp/routes/index.js
new file mode 100644
index 0000000..956680b
--- /dev/null
+++ b/Chapter10/standardizing-service-boilerplate/micro/webapp/routes/index.js
@@ -0,0 +1,9 @@
+var express = require('express')
+var router = express.Router()
+
+/* GET home page. */
+router.get('/', function (req, res, next) {
+ res.render('index', { title: 'Express' })
+})
+
+module.exports = router
diff --git a/Chapter10/standardizing-service-boilerplate/micro/webapp/routes/users.js b/Chapter10/standardizing-service-boilerplate/micro/webapp/routes/users.js
new file mode 100644
index 0000000..8cfe88f
--- /dev/null
+++ b/Chapter10/standardizing-service-boilerplate/micro/webapp/routes/users.js
@@ -0,0 +1,9 @@
+var express = require('express')
+var router = express.Router()
+
+/* GET users listing. */
+router.get('/', function (req, res, next) {
+ res.send('respond with a resource')
+})
+
+module.exports = router
diff --git a/Chapter10/standardizing-service-boilerplate/micro/webapp/views/add.ejs b/Chapter10/standardizing-service-boilerplate/micro/webapp/views/add.ejs
new file mode 100644
index 0000000..da8279c
--- /dev/null
+++ b/Chapter10/standardizing-service-boilerplate/micro/webapp/views/add.ejs
@@ -0,0 +1,16 @@
+
+
+
+ Add
+
+
+
+ Add it up!
+
+ Submit
+ result = <%= result %>
+
+
diff --git a/Chapter10/standardizing-service-boilerplate/micro/webapp/views/error.ejs b/Chapter10/standardizing-service-boilerplate/micro/webapp/views/error.ejs
new file mode 100644
index 0000000..7cf94ed
--- /dev/null
+++ b/Chapter10/standardizing-service-boilerplate/micro/webapp/views/error.ejs
@@ -0,0 +1,3 @@
+<%= message %>
+<%= error.status %>
+<%= error.stack %>
diff --git a/Chapter10/standardizing-service-boilerplate/micro/webapp/views/index.ejs b/Chapter10/standardizing-service-boilerplate/micro/webapp/views/index.ejs
new file mode 100644
index 0000000..7b7a1d6
--- /dev/null
+++ b/Chapter10/standardizing-service-boilerplate/micro/webapp/views/index.ejs
@@ -0,0 +1,11 @@
+
+
+
+ <%= title %>
+
+
+
+ <%= title %>
+ Welcome to <%= title %>
+
+
diff --git a/Chapter10/using-containerized-infrastructure/micro/adderservice/index.js b/Chapter10/using-containerized-infrastructure/micro/adderservice/index.js
new file mode 100644
index 0000000..1f2d9cf
--- /dev/null
+++ b/Chapter10/using-containerized-infrastructure/micro/adderservice/index.js
@@ -0,0 +1,7 @@
+'use strict'
+
+const wiring = require('./wiring')
+const service = require('./service')()
+
+
+wiring(service)
diff --git a/Chapter10/using-containerized-infrastructure/micro/adderservice/package.json b/Chapter10/using-containerized-infrastructure/micro/adderservice/package.json
new file mode 100644
index 0000000..7cef23a
--- /dev/null
+++ b/Chapter10/using-containerized-infrastructure/micro/adderservice/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "adderservice",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "tap test"
+ },
+ "keywords": [],
+ "author": "Peter Elger",
+ "license": "ISC",
+ "dependencies": {
+ "restify": "^4.3.0"
+ },
+ "devDependencies": {
+ "tap": "^10.3.1"
+ }
+}
diff --git a/Chapter10/using-containerized-infrastructure/micro/adderservice/service.js b/Chapter10/using-containerized-infrastructure/micro/adderservice/service.js
new file mode 100644
index 0000000..9d0d175
--- /dev/null
+++ b/Chapter10/using-containerized-infrastructure/micro/adderservice/service.js
@@ -0,0 +1,13 @@
+'use strict'
+
+module.exports = service
+
+function service () {
+ function add (args, cb) {
+ const {first, second} = args
+ const result = (parseInt(first, 10) + parseInt(second, 10))
+ cb(null, {result: result.toString()})
+ }
+
+ return { add }
+}
diff --git a/Chapter10/using-containerized-infrastructure/micro/adderservice/test/index.js b/Chapter10/using-containerized-infrastructure/micro/adderservice/test/index.js
new file mode 100644
index 0000000..32c45e5
--- /dev/null
+++ b/Chapter10/using-containerized-infrastructure/micro/adderservice/test/index.js
@@ -0,0 +1,13 @@
+'use strict'
+
+const {test} = require('tap')
+const service = require('../service')()
+
+test('test add', (t) => {
+ t.plan(2)
+
+ service.add({first: 1, second: 2}, (err, answer) => {
+ t.error(err)
+ t.same(answer, {result: 3})
+ })
+})
diff --git a/Chapter10/using-containerized-infrastructure/micro/adderservice/wiring.js b/Chapter10/using-containerized-infrastructure/micro/adderservice/wiring.js
new file mode 100644
index 0000000..0ed018c
--- /dev/null
+++ b/Chapter10/using-containerized-infrastructure/micro/adderservice/wiring.js
@@ -0,0 +1,27 @@
+'use strict'
+
+const restify = require('restify')
+const { ADDERSERVICE_SERVICE_PORT } = process.env
+
+module.exports = wiring
+
+function wiring (service) {
+ const server = restify.createServer()
+
+ server.get('/add/:first/:second', (req, res, next) => {
+ service.add(req.params, (err, result) => {
+ if (err) {
+ res.send(err)
+ next()
+ return
+ }
+ res.send(200, result)
+ next()
+ })
+ })
+
+ server.listen(ADDERSERVICE_SERVICE_PORT, '0.0.0.0', () => {
+ console.log('%s listening at %s', server.name, server.url)
+ })
+}
+
diff --git a/Chapter10/using-containerized-infrastructure/micro/auditservice/index.js b/Chapter10/using-containerized-infrastructure/micro/auditservice/index.js
new file mode 100644
index 0000000..40a4677
--- /dev/null
+++ b/Chapter10/using-containerized-infrastructure/micro/auditservice/index.js
@@ -0,0 +1,6 @@
+'use strict'
+
+const wiring = require('./wiring')
+const service = require('./service')()
+
+wiring(service)
diff --git a/Chapter10/using-containerized-infrastructure/micro/auditservice/package.json b/Chapter10/using-containerized-infrastructure/micro/auditservice/package.json
new file mode 100644
index 0000000..0231ff1
--- /dev/null
+++ b/Chapter10/using-containerized-infrastructure/micro/auditservice/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "auditservice",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "Peter Elger",
+ "license": "ISC",
+ "dependencies": {
+ "mongo": "^0.1.0",
+ "restify": "^4.3.0"
+ }
+}
diff --git a/Chapter10/using-containerized-infrastructure/micro/auditservice/service.js b/Chapter10/using-containerized-infrastructure/micro/auditservice/service.js
new file mode 100644
index 0000000..f6592f2
--- /dev/null
+++ b/Chapter10/using-containerized-infrastructure/micro/auditservice/service.js
@@ -0,0 +1,58 @@
+'use strict'
+
+const { MongoClient } = require('mongodb')
+const {
+ MONGO_SERVICE_HOST,
+ MONGO_SERVICE_PORT
+} = process.env
+const url = `mongodb://${MONGO_SERVICE_HOST}:${MONGO_SERVICE_PORT}/audit`
+
+module.exports = service
+
+function service () {
+ function append (args, cb) {
+ MongoClient.connect(url, (err, db) => {
+ if (err) {
+ cb(err)
+ return
+ }
+
+ const audit = db.collection('audit')
+ const data = {
+ ts: Date.now(),
+ calc: args.calc,
+ result: args.calcResult
+ }
+
+ audit.insert(data, (err, result) => {
+ if (err) {
+ cb(err)
+ return
+ }
+ cb(null, {result: result.toString()})
+ db.close()
+ })
+ })
+ }
+
+ function list (args, cb) {
+ MongoClient.connect(url, (err, db) => {
+ if (err) {
+ cb(err)
+ return
+ }
+ const audit = db.collection('audit')
+ audit.find({}, {limit: 10}).toArray((err, docs) => {
+ if (err) {
+ cb(err)
+ return
+ }
+ cb(null, {list: docs})
+ db.close()
+ })
+ })
+ }
+
+ return { append, list }
+}
+
diff --git a/Chapter10/using-containerized-infrastructure/micro/auditservice/wiring.js b/Chapter10/using-containerized-infrastructure/micro/auditservice/wiring.js
new file mode 100644
index 0000000..b18dce8
--- /dev/null
+++ b/Chapter10/using-containerized-infrastructure/micro/auditservice/wiring.js
@@ -0,0 +1,39 @@
+'use strict'
+
+const restify = require('restify')
+const { AUDITSERVICE_SERVICE_PORT } = process.env
+
+module.exports = wiring
+
+function wiring (service) {
+ const server = restify.createServer()
+
+ server.use(restify.bodyParser())
+
+ server.post('/append', (req, res, next) => {
+ service.append(req.params, (err, result) => {
+ if (err) {
+ res.send(err)
+ return
+ }
+ res.send(result)
+ next()
+ })
+ })
+
+ server.get('/list', (req, res, next) => {
+ service.list(req.params, (err, result) => {
+ if (err) {
+ res.send(err)
+ return
+ }
+ res.send(200, result)
+ next()
+ })
+ })
+
+ server.listen(AUDITSERVICE_SERVICE_PORT, '0.0.0.0', () => {
+ console.log('%s listening at %s', server.name, server.url)
+ })
+}
+
diff --git a/Chapter10/using-containerized-infrastructure/micro/fuge/fuge.yml b/Chapter10/using-containerized-infrastructure/micro/fuge/fuge.yml
new file mode 100644
index 0000000..c3311d4
--- /dev/null
+++ b/Chapter10/using-containerized-infrastructure/micro/fuge/fuge.yml
@@ -0,0 +1,30 @@
+fuge_global:
+ tail: true
+ monitor: true
+ monitor_excludes:
+ - '**/node_modules/**'
+ - '**/.git/**'
+ - '*.log'
+adderservice:
+ type: process
+ path: ../adderservice
+ run: node index.js
+ ports:
+ - main=8080
+webapp:
+ type: process
+ path: ../webapp
+ run: npm start
+ ports:
+ - main=3000
+auditservice:
+ type: process
+ path: ../auditservice
+ run: 'node index.js'
+ ports:
+ - main=8081
+mongo:
+ image: mongo
+ type: container
+ ports:
+ - main=27017:27017
diff --git a/Chapter10/using-containerized-infrastructure/micro/fuge/fuge2.yml b/Chapter10/using-containerized-infrastructure/micro/fuge/fuge2.yml
new file mode 100644
index 0000000..e5c2a4b
--- /dev/null
+++ b/Chapter10/using-containerized-infrastructure/micro/fuge/fuge2.yml
@@ -0,0 +1,31 @@
+fuge_global:
+ run_containers: false
+ tail: true
+ monitor: true
+ monitor_excludes:
+ - '**/node_modules/**'
+ - '**/.git/**'
+ - '*.log'
+adderservice:
+ type: process
+ path: ../adderservice
+ run: node index.js
+ ports:
+ - main=8080
+webapp:
+ type: process
+ path: ../webapp
+ run: npm start
+ ports:
+ - main=3000
+auditservice:
+ type: process
+ path: ../auditservice
+ run: 'node index.js'
+ ports:
+ - main=8081
+mongo:
+ image: mongo
+ type: container
+ ports:
+ - main=27017:27017
diff --git a/Chapter10/using-containerized-infrastructure/micro/webapp/app.js b/Chapter10/using-containerized-infrastructure/micro/webapp/app.js
new file mode 100644
index 0000000..ec527f4
--- /dev/null
+++ b/Chapter10/using-containerized-infrastructure/micro/webapp/app.js
@@ -0,0 +1,50 @@
+var express = require('express')
+var path = require('path')
+var favicon = require('serve-favicon')
+var logger = require('morgan')
+var cookieParser = require('cookie-parser')
+var bodyParser = require('body-parser')
+
+var index = require('./routes/index')
+var users = require('./routes/users')
+var add = require('./routes/add')
+var audit = require('./routes/audit')
+
+var app = express()
+
+// view engine setup
+app.set('views', path.join(__dirname, 'views'))
+app.set('view engine', 'ejs')
+
+// uncomment after placing your favicon in /public
+// app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
+app.use(logger('dev'))
+app.use(bodyParser.json())
+app.use(bodyParser.urlencoded({ extended: false }))
+app.use(cookieParser())
+app.use(express.static(path.join(__dirname, 'public')))
+
+app.use('/', index)
+app.use('/users', users)
+app.use('/add', add)
+app.use('/audit', audit)
+
+// catch 404 and forward to error handler
+app.use(function (req, res, next) {
+ var err = new Error('Not Found')
+ err.status = 404
+ next(err)
+})
+
+// error handler
+app.use(function (err, req, res, next) {
+ // set locals, only providing error in development
+ res.locals.message = err.message
+ res.locals.error = req.app.get('env') === 'development' ? err : {}
+
+ // render the error page
+ res.status(err.status || 500)
+ res.render('error')
+})
+
+module.exports = app
diff --git a/Chapter10/using-containerized-infrastructure/micro/webapp/bin/www b/Chapter10/using-containerized-infrastructure/micro/webapp/bin/www
new file mode 100644
index 0000000..8715eeb
--- /dev/null
+++ b/Chapter10/using-containerized-infrastructure/micro/webapp/bin/www
@@ -0,0 +1,90 @@
+#!/usr/bin/env node
+
+/**
+ * Module dependencies.
+ */
+
+var app = require('../app');
+var debug = require('debug')('webapp:server');
+var http = require('http');
+
+/**
+ * Get port from environment and store in Express.
+ */
+
+var port = normalizePort(process.env.PORT || '3000');
+app.set('port', port);
+
+/**
+ * Create HTTP server.
+ */
+
+var server = http.createServer(app);
+
+/**
+ * Listen on provided port, on all network interfaces.
+ */
+
+server.listen(port);
+server.on('error', onError);
+server.on('listening', onListening);
+
+/**
+ * Normalize a port into a number, string, or false.
+ */
+
+function normalizePort(val) {
+ var port = parseInt(val, 10);
+
+ if (isNaN(port)) {
+ // named pipe
+ return val;
+ }
+
+ if (port >= 0) {
+ // port number
+ return port;
+ }
+
+ return false;
+}
+
+/**
+ * Event listener for HTTP server "error" event.
+ */
+
+function onError(error) {
+ if (error.syscall !== 'listen') {
+ throw error;
+ }
+
+ var bind = typeof port === 'string'
+ ? 'Pipe ' + port
+ : 'Port ' + port;
+
+ // handle specific listen errors with friendly messages
+ switch (error.code) {
+ case 'EACCES':
+ console.error(bind + ' requires elevated privileges');
+ process.exit(1);
+ break;
+ case 'EADDRINUSE':
+ console.error(bind + ' is already in use');
+ process.exit(1);
+ break;
+ default:
+ throw error;
+ }
+}
+
+/**
+ * Event listener for HTTP server "listening" event.
+ */
+
+function onListening() {
+ var addr = server.address();
+ var bind = typeof addr === 'string'
+ ? 'pipe ' + addr
+ : 'port ' + addr.port;
+ debug('Listening on ' + bind);
+}
diff --git a/Chapter10/using-containerized-infrastructure/micro/webapp/package.json b/Chapter10/using-containerized-infrastructure/micro/webapp/package.json
new file mode 100644
index 0000000..c0d6ed3
--- /dev/null
+++ b/Chapter10/using-containerized-infrastructure/micro/webapp/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "webapp",
+ "version": "0.0.0",
+ "private": true,
+ "scripts": {
+ "start": "node ./bin/www"
+ },
+ "dependencies": {
+ "body-parser": "~1.16.0",
+ "cookie-parser": "~1.4.3",
+ "debug": "~2.6.0",
+ "ejs": "~2.5.5",
+ "express": "~4.14.1",
+ "morgan": "~1.7.0",
+ "mu": "^2.1.2",
+ "restify": "^4.3.0",
+ "serve-favicon": "~2.3.2"
+ }
+}
diff --git a/Chapter10/using-containerized-infrastructure/micro/webapp/public/stylesheets/style.css b/Chapter10/using-containerized-infrastructure/micro/webapp/public/stylesheets/style.css
new file mode 100644
index 0000000..9453385
--- /dev/null
+++ b/Chapter10/using-containerized-infrastructure/micro/webapp/public/stylesheets/style.css
@@ -0,0 +1,8 @@
+body {
+ padding: 50px;
+ font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
+}
+
+a {
+ color: #00B7FF;
+}
diff --git a/Chapter10/using-containerized-infrastructure/micro/webapp/routes/add.js b/Chapter10/using-containerized-infrastructure/micro/webapp/routes/add.js
new file mode 100644
index 0000000..2744945
--- /dev/null
+++ b/Chapter10/using-containerized-infrastructure/micro/webapp/routes/add.js
@@ -0,0 +1,49 @@
+'use strict'
+
+const { Router } = require('express')
+const restify = require('restify')
+const router = Router()
+
+const {
+ ADDERSERVICE_SERVICE_HOST,
+ ADDERSERVICE_SERVICE_PORT,
+ AUDITSERVICE_SERVICE_HOST,
+ AUDITSERVICE_SERVICE_PORT,
+} = process.env
+
+router.get('/', function (req, res) {
+ res.render('add', { first: 0, second: 0, result: 0 })
+})
+
+router.post('/calculate', function (req, res, next) {
+ const clients = {
+ adder: restify.createJSONClient({
+ url: `http://${ADDERSERVICE_SERVICE_HOST}:${ADDERSERVICE_SERVICE_PORT}`
+ }),
+ audit: restify.createJSONClient({
+ url: `http://${AUDITSERVICE_SERVICE_HOST}:${AUDITSERVICE_SERVICE_PORT}`
+ })
+ }
+ const { first, second } = req.body
+ clients.adder.get(
+ `/add/${first}/${second}`,
+ (err, svcReq, svcRes, data) => {
+ if (err) {
+ next(err)
+ return
+ }
+
+ const { result } = data
+ clients.audit.post('/append', {
+ calc: first + '+' + second,
+ calcResult: result
+ }, (err) => {
+ if (err) console.error(err)
+ })
+
+ res.render('add', { first, second, result })
+ }
+ )
+})
+
+module.exports = router
diff --git a/Chapter10/using-containerized-infrastructure/micro/webapp/routes/audit.js b/Chapter10/using-containerized-infrastructure/micro/webapp/routes/audit.js
new file mode 100644
index 0000000..1385f8f
--- /dev/null
+++ b/Chapter10/using-containerized-infrastructure/micro/webapp/routes/audit.js
@@ -0,0 +1,25 @@
+'use strict'
+
+const { Router } = require('express')
+const restify = require('restify')
+const router = Router()
+
+const {
+ AUDITSERVICE_SERVICE_HOST,
+ AUDITSERVICE_SERVICE_PORT
+} = process.env
+
+router.get('/', (req, res, next) => {
+ const url = `http://${AUDITSERVICE_SERVICE_HOST}:${AUDITSERVICE_SERVICE_PORT}`
+ const client = restify.createJsonClient({ url })
+
+ client.get('/list', (err, svcReq, svcRes, data) => {
+ if (err) {
+ next(err)
+ return
+ }
+ res.render('audit', data)
+ })
+})
+
+module.exports = router
diff --git a/Chapter10/using-containerized-infrastructure/micro/webapp/routes/index.js b/Chapter10/using-containerized-infrastructure/micro/webapp/routes/index.js
new file mode 100644
index 0000000..956680b
--- /dev/null
+++ b/Chapter10/using-containerized-infrastructure/micro/webapp/routes/index.js
@@ -0,0 +1,9 @@
+var express = require('express')
+var router = express.Router()
+
+/* GET home page. */
+router.get('/', function (req, res, next) {
+ res.render('index', { title: 'Express' })
+})
+
+module.exports = router
diff --git a/Chapter10/using-containerized-infrastructure/micro/webapp/routes/users.js b/Chapter10/using-containerized-infrastructure/micro/webapp/routes/users.js
new file mode 100644
index 0000000..8cfe88f
--- /dev/null
+++ b/Chapter10/using-containerized-infrastructure/micro/webapp/routes/users.js
@@ -0,0 +1,9 @@
+var express = require('express')
+var router = express.Router()
+
+/* GET users listing. */
+router.get('/', function (req, res, next) {
+ res.send('respond with a resource')
+})
+
+module.exports = router
diff --git a/Chapter10/using-containerized-infrastructure/micro/webapp/views/add.ejs b/Chapter10/using-containerized-infrastructure/micro/webapp/views/add.ejs
new file mode 100644
index 0000000..da8279c
--- /dev/null
+++ b/Chapter10/using-containerized-infrastructure/micro/webapp/views/add.ejs
@@ -0,0 +1,16 @@
+
+
+
+ Add
+
+
+
+ Add it up!
+
+ Submit
+ result = <%= result %>
+
+
diff --git a/Chapter10/using-containerized-infrastructure/micro/webapp/views/audit.ejs b/Chapter10/using-containerized-infrastructure/micro/webapp/views/audit.ejs
new file mode 100644
index 0000000..dd6d71b
--- /dev/null
+++ b/Chapter10/using-containerized-infrastructure/micro/webapp/views/audit.ejs
@@ -0,0 +1,15 @@
+
+
+
+ Audit
+
+
+
+ Calculation History
+
+ <% list.forEach(function (el) { %>
+ at: <%= new Date(el.ts).toLocaleString() %>, calculated: <%= el.calc %>, result: <%= el.result %>
+ <% }) %>
+
+
+
\ No newline at end of file
diff --git a/Chapter10/using-containerized-infrastructure/micro/webapp/views/error.ejs b/Chapter10/using-containerized-infrastructure/micro/webapp/views/error.ejs
new file mode 100644
index 0000000..7cf94ed
--- /dev/null
+++ b/Chapter10/using-containerized-infrastructure/micro/webapp/views/error.ejs
@@ -0,0 +1,3 @@
+<%= message %>
+<%= error.status %>
+<%= error.stack %>
diff --git a/Chapter10/using-containerized-infrastructure/micro/webapp/views/index.ejs b/Chapter10/using-containerized-infrastructure/micro/webapp/views/index.ejs
new file mode 100644
index 0000000..7b7a1d6
--- /dev/null
+++ b/Chapter10/using-containerized-infrastructure/micro/webapp/views/index.ejs
@@ -0,0 +1,11 @@
+
+
+
+ <%= title %>
+
+
+
+ <%= title %>
+ Welcome to <%= title %>
+
+
diff --git a/Chapter11/building-a-single-container/micro/adderservice/.dockerignore b/Chapter11/building-a-single-container/micro/adderservice/.dockerignore
new file mode 100644
index 0000000..90718e6
--- /dev/null
+++ b/Chapter11/building-a-single-container/micro/adderservice/.dockerignore
@@ -0,0 +1,4 @@
+.git
+.gitignore
+node_modules
+npm-debug.log
diff --git a/Chapter11/building-a-single-container/micro/adderservice/Dockerfile b/Chapter11/building-a-single-container/micro/adderservice/Dockerfile
new file mode 100644
index 0000000..4d2e56b
--- /dev/null
+++ b/Chapter11/building-a-single-container/micro/adderservice/Dockerfile
@@ -0,0 +1,7 @@
+FROM node:slim
+RUN mkdir -p /home/node/service
+WORKDIR /home/node/service
+COPY package.json /home/node/service
+RUN npm install
+COPY . /home/node/service
+CMD [ "node", "index.js" ]
diff --git a/Chapter11/building-a-single-container/micro/adderservice/index.js b/Chapter11/building-a-single-container/micro/adderservice/index.js
new file mode 100644
index 0000000..753a4ce
--- /dev/null
+++ b/Chapter11/building-a-single-container/micro/adderservice/index.js
@@ -0,0 +1,6 @@
+'use strict'
+
+const wiring = require('./wiring')
+const service = require('./service')()
+
+wiring(service)
\ No newline at end of file
diff --git a/Chapter11/building-a-single-container/micro/adderservice/package.json b/Chapter11/building-a-single-container/micro/adderservice/package.json
new file mode 100644
index 0000000..595a5b0
--- /dev/null
+++ b/Chapter11/building-a-single-container/micro/adderservice/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "adder-service",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "Peter Elger ",
+ "license": "ISC",
+ "dependencies": {
+ "restify": "^4.3.0"
+ }
+}
diff --git a/Chapter11/building-a-single-container/micro/adderservice/service.js b/Chapter11/building-a-single-container/micro/adderservice/service.js
new file mode 100644
index 0000000..af95084
--- /dev/null
+++ b/Chapter11/building-a-single-container/micro/adderservice/service.js
@@ -0,0 +1,13 @@
+'use strict'
+
+module.exports = service
+
+function service () {
+ function add (args, cb) {
+ const {first, second} = args
+ const result = (parseInt(first, 10) + parseInt(second, 10))
+ cb(null, {result: result.toString()})
+ }
+
+ return { add }
+}
\ No newline at end of file
diff --git a/Chapter11/building-a-single-container/micro/adderservice/wiring.js b/Chapter11/building-a-single-container/micro/adderservice/wiring.js
new file mode 100644
index 0000000..0e81c6c
--- /dev/null
+++ b/Chapter11/building-a-single-container/micro/adderservice/wiring.js
@@ -0,0 +1,26 @@
+'use strict'
+
+const restify = require('restify')
+const { ADDERSERVICE_SERVICE_PORT } = process.env
+
+module.exports = wiring
+
+function wiring (service) {
+ const server = restify.createServer()
+
+ server.get('/add/:first/:second', (req, res, next) => {
+ service.add(req.params, (err, result) => {
+ if (err) {
+ res.send(err)
+ next()
+ return
+ }
+ res.send(200, result)
+ next()
+ })
+ })
+
+ server.listen(ADDERSERVICE_SERVICE_PORT, '0.0.0.0', () => {
+ console.log('%s listening at %s', server.name, server.url)
+ })
+}
\ No newline at end of file
diff --git a/Chapter11/building-a-single-container/test.sh b/Chapter11/building-a-single-container/test.sh
new file mode 100644
index 0000000..5086478
--- /dev/null
+++ b/Chapter11/building-a-single-container/test.sh
@@ -0,0 +1 @@
+curl http://localhost:8080/add/2/3
diff --git a/Chapter11/creating-a-deployment-pipeline/micro/adderservice/.dockerignore b/Chapter11/creating-a-deployment-pipeline/micro/adderservice/.dockerignore
new file mode 100644
index 0000000..90718e6
--- /dev/null
+++ b/Chapter11/creating-a-deployment-pipeline/micro/adderservice/.dockerignore
@@ -0,0 +1,4 @@
+.git
+.gitignore
+node_modules
+npm-debug.log
diff --git a/Chapter11/creating-a-deployment-pipeline/micro/adderservice/Dockerfile b/Chapter11/creating-a-deployment-pipeline/micro/adderservice/Dockerfile
new file mode 100644
index 0000000..4d2e56b
--- /dev/null
+++ b/Chapter11/creating-a-deployment-pipeline/micro/adderservice/Dockerfile
@@ -0,0 +1,7 @@
+FROM node:slim
+RUN mkdir -p /home/node/service
+WORKDIR /home/node/service
+COPY package.json /home/node/service
+RUN npm install
+COPY . /home/node/service
+CMD [ "node", "index.js" ]
diff --git a/Chapter11/creating-a-deployment-pipeline/micro/adderservice/Jenkinsfile b/Chapter11/creating-a-deployment-pipeline/micro/adderservice/Jenkinsfile
new file mode 100644
index 0000000..8ea826e
--- /dev/null
+++ b/Chapter11/creating-a-deployment-pipeline/micro/adderservice/Jenkinsfile
@@ -0,0 +1,32 @@
+pipeline {
+ agent any
+
+ stages {
+ stage('Checkout') {
+ steps {
+ checkout scm
+ }
+ }
+ stage('Build') {
+ steps {
+ sh 'source ~/.bashrc && cd adderservice && npm install'
+ }
+ }
+ stage('Test'){
+ steps {
+ sh 'source ~/.bashrc && cd adderservice && npm test'
+ }
+ }
+ stage('Container'){
+ steps {
+ sh 'source ~/.bashrc && cd adderservice && sh build.sh container'
+ }
+ }
+ stage('Deploy'){
+ steps {
+ sh 'source ~/.bashrc && cd adderservice && sh build.sh deploy'
+ }
+ }
+ }
+}
+
diff --git a/Chapter11/creating-a-deployment-pipeline/micro/adderservice/build.sh b/Chapter11/creating-a-deployment-pipeline/micro/adderservice/build.sh
new file mode 100644
index 0000000..7da8cb6
--- /dev/null
+++ b/Chapter11/creating-a-deployment-pipeline/micro/adderservice/build.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+source ~/.bashrc
+
+GITSHA=$(git rev-parse --short HEAD)
+
+case "$1" in
+ container)
+ sudo -u pelger docker build -t adderservice:$GITSHA .
+ sudo -u pelger docker tag adderservice:$GITSHA pelger/adderservice:$GITSHA
+ sudo -i -u pelger docker push pelger/adderservice:$GITSHA
+ ;;
+ deploy)
+ sed -e s/_NAME_/adderservice/ -e s/_PORT_/8080/ < ../deployment/service-template.yml > svc.yml
+ sed -e s/_NAME_/adderservice/ -e s/_PORT_/8080/ -e s/_IMAGE_/pelger\\/adderservice:$GITSHA/ < ../deployment/deployment-template.yml > dep.yml
+ sudo -i -u pelger kubectl apply -f $(pwd)/svc.yml
+ sudo -i -u pelger kubectl apply -f $(pwd)/dep.yml
+ ;;
+ *)
+ echo 'invalid build command'
+ exit 1
+ ;;
+esac
+
diff --git a/Chapter11/creating-a-deployment-pipeline/micro/adderservice/index.js b/Chapter11/creating-a-deployment-pipeline/micro/adderservice/index.js
new file mode 100644
index 0000000..1f2d9cf
--- /dev/null
+++ b/Chapter11/creating-a-deployment-pipeline/micro/adderservice/index.js
@@ -0,0 +1,7 @@
+'use strict'
+
+const wiring = require('./wiring')
+const service = require('./service')()
+
+
+wiring(service)
diff --git a/Chapter11/creating-a-deployment-pipeline/micro/adderservice/package.json b/Chapter11/creating-a-deployment-pipeline/micro/adderservice/package.json
new file mode 100644
index 0000000..7cef23a
--- /dev/null
+++ b/Chapter11/creating-a-deployment-pipeline/micro/adderservice/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "adderservice",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "tap test"
+ },
+ "keywords": [],
+ "author": "Peter Elger",
+ "license": "ISC",
+ "dependencies": {
+ "restify": "^4.3.0"
+ },
+ "devDependencies": {
+ "tap": "^10.3.1"
+ }
+}
diff --git a/Chapter11/creating-a-deployment-pipeline/micro/adderservice/service.js b/Chapter11/creating-a-deployment-pipeline/micro/adderservice/service.js
new file mode 100644
index 0000000..9d0d175
--- /dev/null
+++ b/Chapter11/creating-a-deployment-pipeline/micro/adderservice/service.js
@@ -0,0 +1,13 @@
+'use strict'
+
+module.exports = service
+
+function service () {
+ function add (args, cb) {
+ const {first, second} = args
+ const result = (parseInt(first, 10) + parseInt(second, 10))
+ cb(null, {result: result.toString()})
+ }
+
+ return { add }
+}
diff --git a/Chapter11/creating-a-deployment-pipeline/micro/adderservice/wiring.js b/Chapter11/creating-a-deployment-pipeline/micro/adderservice/wiring.js
new file mode 100644
index 0000000..0ed018c
--- /dev/null
+++ b/Chapter11/creating-a-deployment-pipeline/micro/adderservice/wiring.js
@@ -0,0 +1,27 @@
+'use strict'
+
+const restify = require('restify')
+const { ADDERSERVICE_SERVICE_PORT } = process.env
+
+module.exports = wiring
+
+function wiring (service) {
+ const server = restify.createServer()
+
+ server.get('/add/:first/:second', (req, res, next) => {
+ service.add(req.params, (err, result) => {
+ if (err) {
+ res.send(err)
+ next()
+ return
+ }
+ res.send(200, result)
+ next()
+ })
+ })
+
+ server.listen(ADDERSERVICE_SERVICE_PORT, '0.0.0.0', () => {
+ console.log('%s listening at %s', server.name, server.url)
+ })
+}
+
diff --git a/Chapter11/creating-a-deployment-pipeline/micro/deployment/deployment-template.yml b/Chapter11/creating-a-deployment-pipeline/micro/deployment/deployment-template.yml
new file mode 100644
index 0000000..42ee070
--- /dev/null
+++ b/Chapter11/creating-a-deployment-pipeline/micro/deployment/deployment-template.yml
@@ -0,0 +1,17 @@
+apiVersion: extensions/v1beta1
+kind: Deployment
+metadata:
+ name: _NAME_
+spec:
+ replicas: 1
+ template:
+ metadata:
+ labels:
+ run: _NAME_
+ spec:
+ containers:
+ - name: _NAME_
+ image: _IMAGE_
+ ports:
+ - containerPort: _PORT_
+
diff --git a/Chapter11/creating-a-deployment-pipeline/micro/deployment/namespace.yml b/Chapter11/creating-a-deployment-pipeline/micro/deployment/namespace.yml
new file mode 100644
index 0000000..1351cbb
--- /dev/null
+++ b/Chapter11/creating-a-deployment-pipeline/micro/deployment/namespace.yml
@@ -0,0 +1,6 @@
+apiVersion: v1
+kind: Namespace
+metadata:
+ name: micro
+ labels:
+ name: micro
diff --git a/Chapter11/creating-a-deployment-pipeline/micro/deployment/service-template.yml b/Chapter11/creating-a-deployment-pipeline/micro/deployment/service-template.yml
new file mode 100644
index 0000000..11010f6
--- /dev/null
+++ b/Chapter11/creating-a-deployment-pipeline/micro/deployment/service-template.yml
@@ -0,0 +1,16 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: _NAME_
+ labels:
+ run: _NAME_
+spec:
+ ports:
+ - port: _PORT_
+ name: main
+ protocol: TCP
+ targetPort: _PORT_
+ selector:
+ run: _NAME_
+ type: NodePort
+
diff --git a/Chapter11/deploying-a-container-to-kubernetes/micro/adderservice/.dockerignore b/Chapter11/deploying-a-container-to-kubernetes/micro/adderservice/.dockerignore
new file mode 100644
index 0000000..90718e6
--- /dev/null
+++ b/Chapter11/deploying-a-container-to-kubernetes/micro/adderservice/.dockerignore
@@ -0,0 +1,4 @@
+.git
+.gitignore
+node_modules
+npm-debug.log
diff --git a/Chapter11/deploying-a-container-to-kubernetes/micro/adderservice/Dockerfile b/Chapter11/deploying-a-container-to-kubernetes/micro/adderservice/Dockerfile
new file mode 100644
index 0000000..4d2e56b
--- /dev/null
+++ b/Chapter11/deploying-a-container-to-kubernetes/micro/adderservice/Dockerfile
@@ -0,0 +1,7 @@
+FROM node:slim
+RUN mkdir -p /home/node/service
+WORKDIR /home/node/service
+COPY package.json /home/node/service
+RUN npm install
+COPY . /home/node/service
+CMD [ "node", "index.js" ]
diff --git a/Chapter11/deploying-a-container-to-kubernetes/micro/adderservice/index.js b/Chapter11/deploying-a-container-to-kubernetes/micro/adderservice/index.js
new file mode 100644
index 0000000..753a4ce
--- /dev/null
+++ b/Chapter11/deploying-a-container-to-kubernetes/micro/adderservice/index.js
@@ -0,0 +1,6 @@
+'use strict'
+
+const wiring = require('./wiring')
+const service = require('./service')()
+
+wiring(service)
\ No newline at end of file
diff --git a/Chapter11/deploying-a-container-to-kubernetes/micro/adderservice/package.json b/Chapter11/deploying-a-container-to-kubernetes/micro/adderservice/package.json
new file mode 100644
index 0000000..595a5b0
--- /dev/null
+++ b/Chapter11/deploying-a-container-to-kubernetes/micro/adderservice/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "adder-service",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "Peter Elger ",
+ "license": "ISC",
+ "dependencies": {
+ "restify": "^4.3.0"
+ }
+}
diff --git a/Chapter11/deploying-a-container-to-kubernetes/micro/adderservice/service.js b/Chapter11/deploying-a-container-to-kubernetes/micro/adderservice/service.js
new file mode 100644
index 0000000..af95084
--- /dev/null
+++ b/Chapter11/deploying-a-container-to-kubernetes/micro/adderservice/service.js
@@ -0,0 +1,13 @@
+'use strict'
+
+module.exports = service
+
+function service () {
+ function add (args, cb) {
+ const {first, second} = args
+ const result = (parseInt(first, 10) + parseInt(second, 10))
+ cb(null, {result: result.toString()})
+ }
+
+ return { add }
+}
\ No newline at end of file
diff --git a/Chapter11/deploying-a-container-to-kubernetes/micro/adderservice/wiring.js b/Chapter11/deploying-a-container-to-kubernetes/micro/adderservice/wiring.js
new file mode 100644
index 0000000..0e81c6c
--- /dev/null
+++ b/Chapter11/deploying-a-container-to-kubernetes/micro/adderservice/wiring.js
@@ -0,0 +1,26 @@
+'use strict'
+
+const restify = require('restify')
+const { ADDERSERVICE_SERVICE_PORT } = process.env
+
+module.exports = wiring
+
+function wiring (service) {
+ const server = restify.createServer()
+
+ server.get('/add/:first/:second', (req, res, next) => {
+ service.add(req.params, (err, result) => {
+ if (err) {
+ res.send(err)
+ next()
+ return
+ }
+ res.send(200, result)
+ next()
+ })
+ })
+
+ server.listen(ADDERSERVICE_SERVICE_PORT, '0.0.0.0', () => {
+ console.log('%s listening at %s', server.name, server.url)
+ })
+}
\ No newline at end of file
diff --git a/Chapter11/deploying-a-container-to-kubernetes/micro/deployment/adderservice-dep.yml b/Chapter11/deploying-a-container-to-kubernetes/micro/deployment/adderservice-dep.yml
new file mode 100644
index 0000000..65b998f
--- /dev/null
+++ b/Chapter11/deploying-a-container-to-kubernetes/micro/deployment/adderservice-dep.yml
@@ -0,0 +1,16 @@
+apiVersion: extensions/v1beta1
+kind: Deployment
+metadata:
+ name: adderservice
+spec:
+ replicas: 1
+ template:
+ metadata:
+ labels:
+ run: adderservice
+ spec:
+ containers:
+ - name: adderservice
+ image: pelger/adderservice
+ ports:
+ - containerPort: 8080
diff --git a/Chapter11/deploying-a-container-to-kubernetes/micro/deployment/adderservice-svc.yml b/Chapter11/deploying-a-container-to-kubernetes/micro/deployment/adderservice-svc.yml
new file mode 100644
index 0000000..5327532
--- /dev/null
+++ b/Chapter11/deploying-a-container-to-kubernetes/micro/deployment/adderservice-svc.yml
@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: adderservice
+ labels:
+ run: adderservice
+spec:
+ ports:
+ - port: 8080
+ name: main
+ protocol: TCP
+ targetPort: 8080
+ selector:
+ run: adderservice
+ type: NodePort
diff --git a/Chapter11/deploying-a-container-to-kubernetes/micro/deployment/namespace.yml b/Chapter11/deploying-a-container-to-kubernetes/micro/deployment/namespace.yml
new file mode 100644
index 0000000..1351cbb
--- /dev/null
+++ b/Chapter11/deploying-a-container-to-kubernetes/micro/deployment/namespace.yml
@@ -0,0 +1,6 @@
+apiVersion: v1
+kind: Namespace
+metadata:
+ name: micro
+ labels:
+ name: micro
diff --git a/Chapter11/deploying-a-container-to-kubernetes/micro2/adderservice/.dockerignore b/Chapter11/deploying-a-container-to-kubernetes/micro2/adderservice/.dockerignore
new file mode 100644
index 0000000..90718e6
--- /dev/null
+++ b/Chapter11/deploying-a-container-to-kubernetes/micro2/adderservice/.dockerignore
@@ -0,0 +1,4 @@
+.git
+.gitignore
+node_modules
+npm-debug.log
diff --git a/Chapter11/deploying-a-container-to-kubernetes/micro2/adderservice/Dockerfile b/Chapter11/deploying-a-container-to-kubernetes/micro2/adderservice/Dockerfile
new file mode 100644
index 0000000..4d2e56b
--- /dev/null
+++ b/Chapter11/deploying-a-container-to-kubernetes/micro2/adderservice/Dockerfile
@@ -0,0 +1,7 @@
+FROM node:slim
+RUN mkdir -p /home/node/service
+WORKDIR /home/node/service
+COPY package.json /home/node/service
+RUN npm install
+COPY . /home/node/service
+CMD [ "node", "index.js" ]
diff --git a/Chapter11/deploying-a-container-to-kubernetes/micro2/adderservice/index.js b/Chapter11/deploying-a-container-to-kubernetes/micro2/adderservice/index.js
new file mode 100644
index 0000000..753a4ce
--- /dev/null
+++ b/Chapter11/deploying-a-container-to-kubernetes/micro2/adderservice/index.js
@@ -0,0 +1,6 @@
+'use strict'
+
+const wiring = require('./wiring')
+const service = require('./service')()
+
+wiring(service)
\ No newline at end of file
diff --git a/Chapter11/deploying-a-container-to-kubernetes/micro2/adderservice/package.json b/Chapter11/deploying-a-container-to-kubernetes/micro2/adderservice/package.json
new file mode 100644
index 0000000..595a5b0
--- /dev/null
+++ b/Chapter11/deploying-a-container-to-kubernetes/micro2/adderservice/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "adder-service",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "Peter Elger ",
+ "license": "ISC",
+ "dependencies": {
+ "restify": "^4.3.0"
+ }
+}
diff --git a/Chapter11/deploying-a-container-to-kubernetes/micro2/adderservice/service.js b/Chapter11/deploying-a-container-to-kubernetes/micro2/adderservice/service.js
new file mode 100644
index 0000000..d7510a9
--- /dev/null
+++ b/Chapter11/deploying-a-container-to-kubernetes/micro2/adderservice/service.js
@@ -0,0 +1,14 @@
+'use strict'
+
+module.exports = service
+
+function service () {
+ function add (args, cb) {
+ const {first, second} = args
+ console.log(`add called: ${first} ${second}`)
+ const result = (parseInt(first, 10) + parseInt(second, 10))
+ cb(null, {result: result.toString()})
+ }
+
+ return { add }
+}
\ No newline at end of file
diff --git a/Chapter11/deploying-a-container-to-kubernetes/micro2/adderservice/wiring.js b/Chapter11/deploying-a-container-to-kubernetes/micro2/adderservice/wiring.js
new file mode 100644
index 0000000..0e81c6c
--- /dev/null
+++ b/Chapter11/deploying-a-container-to-kubernetes/micro2/adderservice/wiring.js
@@ -0,0 +1,26 @@
+'use strict'
+
+const restify = require('restify')
+const { ADDERSERVICE_SERVICE_PORT } = process.env
+
+module.exports = wiring
+
+function wiring (service) {
+ const server = restify.createServer()
+
+ server.get('/add/:first/:second', (req, res, next) => {
+ service.add(req.params, (err, result) => {
+ if (err) {
+ res.send(err)
+ next()
+ return
+ }
+ res.send(200, result)
+ next()
+ })
+ })
+
+ server.listen(ADDERSERVICE_SERVICE_PORT, '0.0.0.0', () => {
+ console.log('%s listening at %s', server.name, server.url)
+ })
+}
\ No newline at end of file
diff --git a/Chapter11/deploying-a-container-to-kubernetes/micro2/deployment/adderservice-dep.yml b/Chapter11/deploying-a-container-to-kubernetes/micro2/deployment/adderservice-dep.yml
new file mode 100644
index 0000000..540d543
--- /dev/null
+++ b/Chapter11/deploying-a-container-to-kubernetes/micro2/deployment/adderservice-dep.yml
@@ -0,0 +1,16 @@
+apiVersion: extensions/v1beta1
+kind: Deployment
+metadata:
+ name: adderservice
+spec:
+ replicas: 1
+ template:
+ metadata:
+ labels:
+ run: adderservice
+ spec:
+ containers:
+ - name: adderservice
+ image: pelger/adderservice:2
+ ports:
+ - containerPort: 8080
diff --git a/Chapter11/deploying-a-container-to-kubernetes/micro2/deployment/adderservice-svc.yml b/Chapter11/deploying-a-container-to-kubernetes/micro2/deployment/adderservice-svc.yml
new file mode 100644
index 0000000..5327532
--- /dev/null
+++ b/Chapter11/deploying-a-container-to-kubernetes/micro2/deployment/adderservice-svc.yml
@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: adderservice
+ labels:
+ run: adderservice
+spec:
+ ports:
+ - port: 8080
+ name: main
+ protocol: TCP
+ targetPort: 8080
+ selector:
+ run: adderservice
+ type: NodePort
diff --git a/Chapter11/deploying-a-container-to-kubernetes/micro2/deployment/namespace.yml b/Chapter11/deploying-a-container-to-kubernetes/micro2/deployment/namespace.yml
new file mode 100644
index 0000000..1351cbb
--- /dev/null
+++ b/Chapter11/deploying-a-container-to-kubernetes/micro2/deployment/namespace.yml
@@ -0,0 +1,6 @@
+apiVersion: v1
+kind: Namespace
+metadata:
+ name: micro
+ labels:
+ name: micro
diff --git a/Chapter11/deploying-a-container-to-kubernetes/test.sh b/Chapter11/deploying-a-container-to-kubernetes/test.sh
new file mode 100644
index 0000000..5086478
--- /dev/null
+++ b/Chapter11/deploying-a-container-to-kubernetes/test.sh
@@ -0,0 +1 @@
+curl http://localhost:8080/add/2/3
diff --git a/Chapter11/deploying-a-full-system/micro/adderservice/.dockerignore b/Chapter11/deploying-a-full-system/micro/adderservice/.dockerignore
new file mode 100644
index 0000000..90718e6
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/adderservice/.dockerignore
@@ -0,0 +1,4 @@
+.git
+.gitignore
+node_modules
+npm-debug.log
diff --git a/Chapter11/deploying-a-full-system/micro/adderservice/Dockerfile b/Chapter11/deploying-a-full-system/micro/adderservice/Dockerfile
new file mode 100644
index 0000000..4d2e56b
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/adderservice/Dockerfile
@@ -0,0 +1,7 @@
+FROM node:slim
+RUN mkdir -p /home/node/service
+WORKDIR /home/node/service
+COPY package.json /home/node/service
+RUN npm install
+COPY . /home/node/service
+CMD [ "node", "index.js" ]
diff --git a/Chapter11/deploying-a-full-system/micro/adderservice/Jenkinsfile b/Chapter11/deploying-a-full-system/micro/adderservice/Jenkinsfile
new file mode 100644
index 0000000..8ea826e
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/adderservice/Jenkinsfile
@@ -0,0 +1,32 @@
+pipeline {
+ agent any
+
+ stages {
+ stage('Checkout') {
+ steps {
+ checkout scm
+ }
+ }
+ stage('Build') {
+ steps {
+ sh 'source ~/.bashrc && cd adderservice && npm install'
+ }
+ }
+ stage('Test'){
+ steps {
+ sh 'source ~/.bashrc && cd adderservice && npm test'
+ }
+ }
+ stage('Container'){
+ steps {
+ sh 'source ~/.bashrc && cd adderservice && sh build.sh container'
+ }
+ }
+ stage('Deploy'){
+ steps {
+ sh 'source ~/.bashrc && cd adderservice && sh build.sh deploy'
+ }
+ }
+ }
+}
+
diff --git a/Chapter11/deploying-a-full-system/micro/adderservice/build.sh b/Chapter11/deploying-a-full-system/micro/adderservice/build.sh
new file mode 100644
index 0000000..7da8cb6
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/adderservice/build.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+source ~/.bashrc
+
+GITSHA=$(git rev-parse --short HEAD)
+
+case "$1" in
+ container)
+ sudo -u pelger docker build -t adderservice:$GITSHA .
+ sudo -u pelger docker tag adderservice:$GITSHA pelger/adderservice:$GITSHA
+ sudo -i -u pelger docker push pelger/adderservice:$GITSHA
+ ;;
+ deploy)
+ sed -e s/_NAME_/adderservice/ -e s/_PORT_/8080/ < ../deployment/service-template.yml > svc.yml
+ sed -e s/_NAME_/adderservice/ -e s/_PORT_/8080/ -e s/_IMAGE_/pelger\\/adderservice:$GITSHA/ < ../deployment/deployment-template.yml > dep.yml
+ sudo -i -u pelger kubectl apply -f $(pwd)/svc.yml
+ sudo -i -u pelger kubectl apply -f $(pwd)/dep.yml
+ ;;
+ *)
+ echo 'invalid build command'
+ exit 1
+ ;;
+esac
+
diff --git a/Chapter11/deploying-a-full-system/micro/adderservice/index.js b/Chapter11/deploying-a-full-system/micro/adderservice/index.js
new file mode 100644
index 0000000..1f2d9cf
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/adderservice/index.js
@@ -0,0 +1,7 @@
+'use strict'
+
+const wiring = require('./wiring')
+const service = require('./service')()
+
+
+wiring(service)
diff --git a/Chapter11/deploying-a-full-system/micro/adderservice/package.json b/Chapter11/deploying-a-full-system/micro/adderservice/package.json
new file mode 100644
index 0000000..c612751
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/adderservice/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "adder-service",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"no test specified\" && exit 0"
+ },
+ "keywords": [],
+ "author": "Peter Elger ",
+ "license": "ISC",
+ "dependencies": {
+ "restify": "^4.3.0"
+ }
+}
diff --git a/Chapter11/deploying-a-full-system/micro/adderservice/service.js b/Chapter11/deploying-a-full-system/micro/adderservice/service.js
new file mode 100644
index 0000000..9d0d175
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/adderservice/service.js
@@ -0,0 +1,13 @@
+'use strict'
+
+module.exports = service
+
+function service () {
+ function add (args, cb) {
+ const {first, second} = args
+ const result = (parseInt(first, 10) + parseInt(second, 10))
+ cb(null, {result: result.toString()})
+ }
+
+ return { add }
+}
diff --git a/Chapter11/deploying-a-full-system/micro/adderservice/wiring.js b/Chapter11/deploying-a-full-system/micro/adderservice/wiring.js
new file mode 100644
index 0000000..0ed018c
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/adderservice/wiring.js
@@ -0,0 +1,27 @@
+'use strict'
+
+const restify = require('restify')
+const { ADDERSERVICE_SERVICE_PORT } = process.env
+
+module.exports = wiring
+
+function wiring (service) {
+ const server = restify.createServer()
+
+ server.get('/add/:first/:second', (req, res, next) => {
+ service.add(req.params, (err, result) => {
+ if (err) {
+ res.send(err)
+ next()
+ return
+ }
+ res.send(200, result)
+ next()
+ })
+ })
+
+ server.listen(ADDERSERVICE_SERVICE_PORT, '0.0.0.0', () => {
+ console.log('%s listening at %s', server.name, server.url)
+ })
+}
+
diff --git a/Chapter11/deploying-a-full-system/micro/auditservice/.dockerignore b/Chapter11/deploying-a-full-system/micro/auditservice/.dockerignore
new file mode 100644
index 0000000..90718e6
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/auditservice/.dockerignore
@@ -0,0 +1,4 @@
+.git
+.gitignore
+node_modules
+npm-debug.log
diff --git a/Chapter11/deploying-a-full-system/micro/auditservice/Dockerfile b/Chapter11/deploying-a-full-system/micro/auditservice/Dockerfile
new file mode 100644
index 0000000..4d2e56b
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/auditservice/Dockerfile
@@ -0,0 +1,7 @@
+FROM node:slim
+RUN mkdir -p /home/node/service
+WORKDIR /home/node/service
+COPY package.json /home/node/service
+RUN npm install
+COPY . /home/node/service
+CMD [ "node", "index.js" ]
diff --git a/Chapter11/deploying-a-full-system/micro/auditservice/Jenkinsfile b/Chapter11/deploying-a-full-system/micro/auditservice/Jenkinsfile
new file mode 100644
index 0000000..6a75d1a
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/auditservice/Jenkinsfile
@@ -0,0 +1,32 @@
+pipeline {
+ agent any
+
+ stages {
+ stage('Checkout') {
+ steps {
+ checkout scm
+ }
+ }
+ stage('Build') {
+ steps {
+ sh 'source ~/.bashrc && cd auditservice && npm install'
+ }
+ }
+ stage('Test'){
+ steps {
+ sh 'source ~/.bashrc && cd auditservice && npm test'
+ }
+ }
+ stage('Container'){
+ steps {
+ sh 'source ~/.bashrc && cd auditservice && sh build.sh container'
+ }
+ }
+ stage('Deploy'){
+ steps {
+ sh 'source ~/.bashrc && cd auditservice && sh build.sh deploy'
+ }
+ }
+ }
+}
+
diff --git a/Chapter11/deploying-a-full-system/micro/auditservice/build.sh b/Chapter11/deploying-a-full-system/micro/auditservice/build.sh
new file mode 100644
index 0000000..efa553c
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/auditservice/build.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+source ~/.bashrc
+
+GITSHA=$(git rev-parse --short HEAD)
+
+case "$1" in
+ container)
+ sudo -u pelger docker build -t auditservice:$GITSHA .
+ sudo -u pelger docker tag auditservice:$GITSHA pelger/auditservice:$GITSHA
+ sudo -i -u pelger docker push pelger/auditservice:$GITSHA
+ ;;
+ deploy)
+ sed -e s/_NAME_/auditservice/ -e s/_PORT_/8081/ < ../deployment/service-template.yml > svc.yml
+ sed -e s/_NAME_/auditservice/ -e s/_PORT_/8081/ -e s/_IMAGE_/pelger\\/auditservice:$GITSHA/ < ../deployment/deployment-template.yml > dep.yml
+ sudo -i -u pelger kubectl apply -f $(pwd)/svc.yml
+ sudo -i -u pelger kubectl apply -f $(pwd)/dep.yml
+ ;;
+ *)
+ echo 'invalid build command'
+ exit 1
+ ;;
+esac
+
diff --git a/Chapter11/deploying-a-full-system/micro/auditservice/index.js b/Chapter11/deploying-a-full-system/micro/auditservice/index.js
new file mode 100644
index 0000000..40a4677
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/auditservice/index.js
@@ -0,0 +1,6 @@
+'use strict'
+
+const wiring = require('./wiring')
+const service = require('./service')()
+
+wiring(service)
diff --git a/Chapter11/deploying-a-full-system/micro/auditservice/package.json b/Chapter11/deploying-a-full-system/micro/auditservice/package.json
new file mode 100644
index 0000000..bcb0138
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/auditservice/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "auditservice",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"no test specified\" && exit 0"
+ },
+ "keywords": [],
+ "author": "Peter Elger ",
+ "license": "ISC",
+ "dependencies": {
+ "concordant": "^0.2.1",
+ "mongodb": "^2.2.25",
+ "restify": "^4.3.0"
+ }
+}
diff --git a/Chapter11/deploying-a-full-system/micro/auditservice/service.js b/Chapter11/deploying-a-full-system/micro/auditservice/service.js
new file mode 100644
index 0000000..c416e4c
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/auditservice/service.js
@@ -0,0 +1,74 @@
+'use strict'
+
+const { MongoClient } = require('mongodb')
+const { dns } = require('concordant')()
+
+module.exports = service
+
+function service () {
+
+ var db
+
+ setup()
+
+ function setup () {
+ const mongo = '_main._tcp.mongo.micro.svc.cluster.local'
+
+ dns.resolve(mongo, (err, locs) => {
+ if (err) {
+ console.error(err)
+ return
+ }
+ const { host, port } = locs[0]
+ const url = `mongodb://${host}:${port}/audit`
+ MongoClient.connect(url, (err, client) => {
+ if (err) {
+ console.log('failed to connect to MongoDB retrying in 100ms')
+ setTimeout(setup, 100)
+ return
+ }
+ db = client
+ db.on('close', () => db = null)
+ })
+ })
+ }
+
+ function append (args, cb) {
+ if (!db) {
+ cb(Error('No database connection'))
+ return
+ }
+ const audit = db.collection('audit')
+ const data = {
+ ts: Date.now(),
+ calc: args.calc,
+ result: args.calcResult
+ }
+
+ audit.insert(data, (err, result) => {
+ if (err) {
+ cb(err)
+ return
+ }
+ cb(null, {result: result.toString()})
+ })
+ }
+
+ function list (args, cb) {
+ if (!db) {
+ cb(Error('No database connection'))
+ return
+ }
+ const audit = db.collection('audit')
+ audit.find({}, {limit: 10}).toArray((err, docs) => {
+ if (err) {
+ cb(err)
+ return
+ }
+ cb(null, {list: docs})
+ })
+ }
+
+ return { append, list }
+}
+
diff --git a/Chapter11/deploying-a-full-system/micro/auditservice/wiring.js b/Chapter11/deploying-a-full-system/micro/auditservice/wiring.js
new file mode 100644
index 0000000..b18dce8
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/auditservice/wiring.js
@@ -0,0 +1,39 @@
+'use strict'
+
+const restify = require('restify')
+const { AUDITSERVICE_SERVICE_PORT } = process.env
+
+module.exports = wiring
+
+function wiring (service) {
+ const server = restify.createServer()
+
+ server.use(restify.bodyParser())
+
+ server.post('/append', (req, res, next) => {
+ service.append(req.params, (err, result) => {
+ if (err) {
+ res.send(err)
+ return
+ }
+ res.send(result)
+ next()
+ })
+ })
+
+ server.get('/list', (req, res, next) => {
+ service.list(req.params, (err, result) => {
+ if (err) {
+ res.send(err)
+ return
+ }
+ res.send(200, result)
+ next()
+ })
+ })
+
+ server.listen(AUDITSERVICE_SERVICE_PORT, '0.0.0.0', () => {
+ console.log('%s listening at %s', server.name, server.url)
+ })
+}
+
diff --git a/Chapter11/deploying-a-full-system/micro/deployment/deployment-template.yml b/Chapter11/deploying-a-full-system/micro/deployment/deployment-template.yml
new file mode 100644
index 0000000..42ee070
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/deployment/deployment-template.yml
@@ -0,0 +1,17 @@
+apiVersion: extensions/v1beta1
+kind: Deployment
+metadata:
+ name: _NAME_
+spec:
+ replicas: 1
+ template:
+ metadata:
+ labels:
+ run: _NAME_
+ spec:
+ containers:
+ - name: _NAME_
+ image: _IMAGE_
+ ports:
+ - containerPort: _PORT_
+
diff --git a/Chapter11/deploying-a-full-system/micro/deployment/namespace.yml b/Chapter11/deploying-a-full-system/micro/deployment/namespace.yml
new file mode 100644
index 0000000..1351cbb
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/deployment/namespace.yml
@@ -0,0 +1,6 @@
+apiVersion: v1
+kind: Namespace
+metadata:
+ name: micro
+ labels:
+ name: micro
diff --git a/Chapter11/deploying-a-full-system/micro/deployment/service-template.yml b/Chapter11/deploying-a-full-system/micro/deployment/service-template.yml
new file mode 100644
index 0000000..11010f6
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/deployment/service-template.yml
@@ -0,0 +1,16 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: _NAME_
+ labels:
+ run: _NAME_
+spec:
+ ports:
+ - port: _PORT_
+ name: main
+ protocol: TCP
+ targetPort: _PORT_
+ selector:
+ run: _NAME_
+ type: NodePort
+
diff --git a/Chapter11/deploying-a-full-system/micro/eventservice/.dockerignore b/Chapter11/deploying-a-full-system/micro/eventservice/.dockerignore
new file mode 100644
index 0000000..90718e6
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/eventservice/.dockerignore
@@ -0,0 +1,4 @@
+.git
+.gitignore
+node_modules
+npm-debug.log
diff --git a/Chapter11/deploying-a-full-system/micro/eventservice/Dockerfile b/Chapter11/deploying-a-full-system/micro/eventservice/Dockerfile
new file mode 100644
index 0000000..4d2e56b
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/eventservice/Dockerfile
@@ -0,0 +1,7 @@
+FROM node:slim
+RUN mkdir -p /home/node/service
+WORKDIR /home/node/service
+COPY package.json /home/node/service
+RUN npm install
+COPY . /home/node/service
+CMD [ "node", "index.js" ]
diff --git a/Chapter11/deploying-a-full-system/micro/eventservice/Jenkinsfile b/Chapter11/deploying-a-full-system/micro/eventservice/Jenkinsfile
new file mode 100644
index 0000000..81614ce
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/eventservice/Jenkinsfile
@@ -0,0 +1,32 @@
+pipeline {
+ agent any
+
+ stages {
+ stage('Checkout') {
+ steps {
+ checkout scm
+ }
+ }
+ stage('Build') {
+ steps {
+ sh 'source ~/.bashrc && cd eventservice && npm install'
+ }
+ }
+ stage('Test'){
+ steps {
+ sh 'source ~/.bashrc && cd eventservice && npm test'
+ }
+ }
+ stage('Container'){
+ steps {
+ sh 'source ~/.bashrc && cd eventservice && sh build.sh container'
+ }
+ }
+ stage('Deploy'){
+ steps {
+ sh 'source ~/.bashrc && cd eventservice && sh build.sh deploy'
+ }
+ }
+ }
+}
+
diff --git a/Chapter11/deploying-a-full-system/micro/eventservice/build.sh b/Chapter11/deploying-a-full-system/micro/eventservice/build.sh
new file mode 100644
index 0000000..68099f0
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/eventservice/build.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+source ~/.bashrc
+
+GITSHA=$(git rev-parse --short HEAD)
+
+case "$1" in
+ container)
+ sudo -u pelger docker build -t eventservice:$GITSHA .
+ sudo -u pelger docker tag eventservice:$GITSHA pelger/eventservice:$GITSHA
+ sudo -i -u pelger docker push pelger/eventservice:$GITSHA
+ ;;
+ deploy)
+ sed -e s/_NAME_/eventservice/ -e s/_PORT_/8082/ < ../deployment/service-template.yml > svc.yml
+ sed -e s/_NAME_/eventservice/ -e s/_PORT_/8082/ -e s/_IMAGE_/pelger\\/eventservice:$GITSHA/ < ../deployment/deployment-template.yml > dep.yml
+ sudo -i -u pelger kubectl apply -f $(pwd)/svc.yml
+ sudo -i -u pelger kubectl apply -f $(pwd)/dep.yml
+ ;;
+ *)
+ echo 'invalid build command'
+ exit 1
+ ;;
+esac
+
diff --git a/Chapter11/deploying-a-full-system/micro/eventservice/index.js b/Chapter11/deploying-a-full-system/micro/eventservice/index.js
new file mode 100644
index 0000000..df3a380
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/eventservice/index.js
@@ -0,0 +1,5 @@
+'use strict'
+
+const wiring = require('./wiring')
+const service = require('./service')()
+wiring(service)
diff --git a/Chapter11/deploying-a-full-system/micro/eventservice/package.json b/Chapter11/deploying-a-full-system/micro/eventservice/package.json
new file mode 100644
index 0000000..baca7cb
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/eventservice/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "event-service",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"no test specified\" && exit 0"
+ },
+ "keywords": [],
+ "author": "Peter Elger ",
+ "license": "ISC",
+ "dependencies": {
+ "concordant": "^0.2.1",
+ "mongodb": "^2.2.25",
+ "redis": "^2.6.5"
+ }
+}
diff --git a/Chapter11/deploying-a-full-system/micro/eventservice/service.js b/Chapter11/deploying-a-full-system/micro/eventservice/service.js
new file mode 100644
index 0000000..4082d3e
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/eventservice/service.js
@@ -0,0 +1,80 @@
+'use strict'
+
+const { MongoClient } = require('mongodb')
+const { dns } = require('concordant')()
+
+module.exports = service
+
+function service () {
+ var db
+
+ setup()
+
+ function setup () {
+ const mongo = '_main._tcp.mongo.micro.svc.cluster.local'
+
+ dns.resolve(mongo, (err, locs) => {
+ if (err) {
+ console.error(err)
+ return
+ }
+ const { host, port } = locs[0]
+ const url = `mongodb://${host}:${port}/events`
+ MongoClient.connect(url, (err, client) => {
+ if (err) {
+ console.log('failed to connect to MongoDB retrying in 100ms')
+ setTimeout(setup, 100)
+ return
+ }
+ db = client
+ db.on('close', () => db = null)
+ })
+ })
+ }
+
+ function record (args, cb) {
+ if (!db) {
+ cb(Error('No database connection'))
+ return
+ }
+ const events = db.collection('events')
+ const data = {
+ ts: Date.now(),
+ eventType: args.type,
+ url: args.url
+ }
+ events.insert(data, (err, result) => {
+ if (err) {
+ cb(err)
+ return
+ }
+ cb(null, result)
+ })
+ }
+
+ function summary (args, cb) {
+ if (!db) {
+ cb(Error('No database connection'))
+ return
+ }
+ const summary = {}
+ const events = db.collection('events')
+ events.find({}).toArray( (err, docs) => {
+ if (err) return cb(err)
+
+ docs.forEach(function (doc) {
+ if (!(summary[doc.url])) {
+ summary[doc.url] = 1
+ } else {
+ summary[doc.url]++
+ }
+ })
+ cb(null, summary)
+ })
+ }
+
+ return {
+ record: record,
+ summary: summary
+ }
+}
diff --git a/Chapter11/deploying-a-full-system/micro/eventservice/wiring.js b/Chapter11/deploying-a-full-system/micro/eventservice/wiring.js
new file mode 100644
index 0000000..b04ab35
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/eventservice/wiring.js
@@ -0,0 +1,53 @@
+'use strict'
+
+const { dns } = require('concordant')()
+const redis = require('redis')
+const QNAME = 'eventservice'
+
+module.exports = wiring
+
+function wiring (service) {
+
+ const endpoint = '_main._tcp.redis.micro.svc.cluster.local'
+
+ dns.resolve(endpoint, (err, locs) => {
+ if (err) {
+ console.log(err)
+ return
+ }
+ const { port, host } = locs[0]
+ pullFromQueue(redis.createClient(port, host))
+ })
+
+ function pullFromQueue (client) {
+ client.brpop(QNAME, 5, function (err, data) {
+ if (err) console.error(err)
+ if (err || !data) {
+ pullFromQueue(client)
+ return
+ }
+ const msg = JSON.parse(data[1])
+ const { action, returnPath } = msg
+ const cmd = service[action]
+ if (typeof cmd !== 'function') {
+ pullFromQueue(client)
+ return
+ }
+ cmd(msg, (err, result) => {
+ if (err) {
+ console.error(err)
+ pullFromQueue(client)
+ return
+ }
+ if (!returnPath) {
+ pullFromQueue(client)
+ return
+ }
+ client.lpush(returnPath, JSON.stringify(result), (err) => {
+ if (err) console.error(err)
+ pullFromQueue(client)
+ })
+ })
+ })
+ }
+}
diff --git a/Chapter11/deploying-a-full-system/micro/fuge/fuge.yml b/Chapter11/deploying-a-full-system/micro/fuge/fuge.yml
new file mode 100644
index 0000000..839ba34
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/fuge/fuge.yml
@@ -0,0 +1,45 @@
+fuge_global:
+ run_containers: true
+ dns_enabled: true
+ dns_host: 127.0.0.1
+ dns_port: 53053
+ dns_suffix: svc.cluster.local
+ dns_namespace: micro
+ tail: true
+ monitor: true
+ monitor_excludes:
+ - '**/node_modules/**'
+ - '**/.git/**'
+ - '**/*.log'
+adderservice:
+ type: node
+ path: ../adderservice
+ run: node index.js
+ ports:
+ - main=8080
+auditservice:
+ type: process
+ path: ../auditservice
+ run: 'node index.js'
+ ports:
+ - main=8081
+eventservice:
+ type: process
+ path: ../eventservice
+ run: 'node index.js'
+webapp:
+ type: process
+ path: ../webapp
+ run: npm start
+ ports:
+ - http=3000
+mongo:
+ image: mongo
+ type: container
+ ports:
+ - main=27017:27017
+redis:
+ image: redis
+ type: container
+ ports:
+ - main=6379:6379
diff --git a/Chapter11/deploying-a-full-system/micro/infrastructure/Jenkinsfile b/Chapter11/deploying-a-full-system/micro/infrastructure/Jenkinsfile
new file mode 100644
index 0000000..d204b09
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/infrastructure/Jenkinsfile
@@ -0,0 +1,22 @@
+pipeline {
+ agent any
+
+ stages {
+ stage('Checkout') {
+ steps {
+ checkout scm
+ }
+ }
+ stage('DeployMongo'){
+ steps {
+ sh 'source ~/.bashrc && cd infrastructure && sh build.sh mongo'
+ }
+ }
+ stage('DeployRedis'){
+ steps {
+ sh 'source ~/.bashrc && cd infrastructure && sh build.sh redis'
+ }
+ }
+ }
+}
+
diff --git a/Chapter11/deploying-a-full-system/micro/infrastructure/build.sh b/Chapter11/deploying-a-full-system/micro/infrastructure/build.sh
new file mode 100644
index 0000000..dc36a20
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/infrastructure/build.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+sh source ~/.bashrc
+
+case "$1" in
+ mongo)
+ sed -e s/_NAME_/mongo/ -e s/_PORT_/27017/ < ../deployment/service-template.yml > svc.yml
+ sed -e s/_NAME_/mongo/ -e s/_PORT_/27017/ -e s/_IMAGE_/mongo/ < ../deployment/deployment-template.yml > dep.yml
+ sudo -i -u pelger kubectl apply -f $(pwd)/svc.yml
+ sudo -i -u pelger kubectl apply -f $(pwd)/dep.yml
+ ;;
+ redis)
+ sed -e s/_NAME_/redis/ -e s/_PORT_/6379/ < ../deployment/service-template.yml > svc.yml
+ sed -e s/_NAME_/redis/ -e s/_PORT_/6379/ -e s/_IMAGE_/redis/ < ../deployment/deployment-template.yml > dep.yml
+ sudo -i -u pelger kubectl apply -f $(pwd)/svc.yml
+ sudo -i -u pelger kubectl apply -f $(pwd)/dep.yml
+ ;;
+ *)
+ echo 'invalid build command'
+ exit 1
+ ;;
+esac
+
diff --git a/Chapter11/deploying-a-full-system/micro/report/.dockerignore b/Chapter11/deploying-a-full-system/micro/report/.dockerignore
new file mode 100644
index 0000000..90718e6
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/report/.dockerignore
@@ -0,0 +1,4 @@
+.git
+.gitignore
+node_modules
+npm-debug.log
diff --git a/Chapter11/deploying-a-full-system/micro/report/Dockerfile b/Chapter11/deploying-a-full-system/micro/report/Dockerfile
new file mode 100644
index 0000000..4d2e56b
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/report/Dockerfile
@@ -0,0 +1,7 @@
+FROM node:slim
+RUN mkdir -p /home/node/service
+WORKDIR /home/node/service
+COPY package.json /home/node/service
+RUN npm install
+COPY . /home/node/service
+CMD [ "node", "index.js" ]
diff --git a/Chapter11/deploying-a-full-system/micro/report/env.js b/Chapter11/deploying-a-full-system/micro/report/env.js
new file mode 100644
index 0000000..a4d26e9
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/report/env.js
@@ -0,0 +1,17 @@
+'use strict'
+
+// provides environment variable setup for concordant
+
+const env = {
+ DNS_NAMESPACE: 'micro',
+ DNS_SUFFIX: 'svc.cluster.local'
+}
+
+if (process.env.NODE_ENV !== 'production') {
+ Object.assign(env, {
+ DNS_HOST: '127.0.0.1',
+ DNS_PORT: '53053'
+ })
+}
+
+Object.assign(process.env, env)
\ No newline at end of file
diff --git a/Chapter11/deploying-a-full-system/micro/report/index.js b/Chapter11/deploying-a-full-system/micro/report/index.js
new file mode 100644
index 0000000..b65b65a
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/report/index.js
@@ -0,0 +1,43 @@
+'use strict'
+require('./env')
+const { dns } = require('concordant')()
+const redis = require('redis')
+const CliTable = require('cli-table')
+const QNAME = 'eventservice'
+const RESPONSE_QUEUE = 'summary'
+const ENDPOINT = '_main._tcp.redis.micro.svc.cluster.local'
+
+dns.resolve(ENDPOINT, report)
+
+function report (err, locs) {
+ if (err) { return console.log(err) }
+ const { port, host } = locs[0]
+ const client = redis.createClient(port, host)
+ const event = JSON.stringify({
+ action: 'summary',
+ returnPath: RESPONSE_QUEUE
+ })
+
+ client.lpush(QNAME, event, (err) => {
+ if (err) {
+ console.error(err)
+ return
+ }
+
+ client.brpop(RESPONSE_QUEUE, 5, (err, data) => {
+ if (err) {
+ console.error(err)
+ return
+ }
+ const summary = JSON.parse(data[1])
+ const cols = Object.keys(summary).map((url) => [url, summary[url]])
+ const table = new CliTable({
+ head: ['url', 'count'],
+ colWidths: [50, 10]
+ })
+ table.push(...cols)
+ console.log(table.toString())
+ client.quit()
+ })
+ })
+}
\ No newline at end of file
diff --git a/Chapter11/deploying-a-full-system/micro/report/package.json b/Chapter11/deploying-a-full-system/micro/report/package.json
new file mode 100644
index 0000000..da2bd04
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/report/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "report",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"no test specified\" && exit 0"
+ },
+ "keywords": [],
+ "author": "Peter Elger ",
+ "license": "ISC",
+ "dependencies": {
+ "cli-table": "^0.3.1",
+ "concordant": "^0.2.1",
+ "redis": "^2.6.5"
+ }
+}
diff --git a/Chapter11/deploying-a-full-system/micro/webapp/.dockerignore b/Chapter11/deploying-a-full-system/micro/webapp/.dockerignore
new file mode 100644
index 0000000..90718e6
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/webapp/.dockerignore
@@ -0,0 +1,4 @@
+.git
+.gitignore
+node_modules
+npm-debug.log
diff --git a/Chapter11/deploying-a-full-system/micro/webapp/Dockerfile b/Chapter11/deploying-a-full-system/micro/webapp/Dockerfile
new file mode 100644
index 0000000..b2ffa24
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/webapp/Dockerfile
@@ -0,0 +1,7 @@
+FROM node:slim
+RUN mkdir -p /home/node/service
+WORKDIR /home/node/service
+COPY package.json /home/node/service
+RUN npm install
+COPY . /home/node/service
+CMD [ "npm", "start" ]
diff --git a/Chapter11/deploying-a-full-system/micro/webapp/app.js b/Chapter11/deploying-a-full-system/micro/webapp/app.js
new file mode 100644
index 0000000..eef315b
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/webapp/app.js
@@ -0,0 +1,52 @@
+var express = require('express')
+var path = require('path')
+var favicon = require('serve-favicon')
+var logger = require('morgan')
+var cookieParser = require('cookie-parser')
+var bodyParser = require('body-parser')
+var eventLogger = require('./lib/event-logger')
+
+var index = require('./routes/index')
+var users = require('./routes/users')
+var add = require('./routes/add')
+var audit = require('./routes/audit')
+
+var app = express()
+
+// view engine setup
+app.set('views', path.join(__dirname, 'views'))
+app.set('view engine', 'ejs')
+
+// uncomment after placing your favicon in /public
+// app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
+app.use(eventLogger())
+app.use(logger('dev'))
+app.use(bodyParser.json())
+app.use(bodyParser.urlencoded({ extended: false }))
+app.use(cookieParser())
+app.use(express.static(path.join(__dirname, 'public')))
+
+app.use('/', index)
+app.use('/users', users)
+app.use('/add', add)
+app.use('/audit', audit)
+
+// catch 404 and forward to error handler
+app.use(function (req, res, next) {
+ var err = new Error('Not Found')
+ err.status = 404
+ next(err)
+})
+
+// error handler
+app.use(function (err, req, res, next) {
+ // set locals, only providing error in development
+ res.locals.message = err.message
+ res.locals.error = req.app.get('env') === 'development' ? err : {}
+
+ // render the error page
+ res.status(err.status || 500)
+ res.render('error')
+})
+
+module.exports = app
diff --git a/Chapter11/deploying-a-full-system/micro/webapp/bin/www b/Chapter11/deploying-a-full-system/micro/webapp/bin/www
new file mode 100644
index 0000000..8715eeb
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/webapp/bin/www
@@ -0,0 +1,90 @@
+#!/usr/bin/env node
+
+/**
+ * Module dependencies.
+ */
+
+var app = require('../app');
+var debug = require('debug')('webapp:server');
+var http = require('http');
+
+/**
+ * Get port from environment and store in Express.
+ */
+
+var port = normalizePort(process.env.PORT || '3000');
+app.set('port', port);
+
+/**
+ * Create HTTP server.
+ */
+
+var server = http.createServer(app);
+
+/**
+ * Listen on provided port, on all network interfaces.
+ */
+
+server.listen(port);
+server.on('error', onError);
+server.on('listening', onListening);
+
+/**
+ * Normalize a port into a number, string, or false.
+ */
+
+function normalizePort(val) {
+ var port = parseInt(val, 10);
+
+ if (isNaN(port)) {
+ // named pipe
+ return val;
+ }
+
+ if (port >= 0) {
+ // port number
+ return port;
+ }
+
+ return false;
+}
+
+/**
+ * Event listener for HTTP server "error" event.
+ */
+
+function onError(error) {
+ if (error.syscall !== 'listen') {
+ throw error;
+ }
+
+ var bind = typeof port === 'string'
+ ? 'Pipe ' + port
+ : 'Port ' + port;
+
+ // handle specific listen errors with friendly messages
+ switch (error.code) {
+ case 'EACCES':
+ console.error(bind + ' requires elevated privileges');
+ process.exit(1);
+ break;
+ case 'EADDRINUSE':
+ console.error(bind + ' is already in use');
+ process.exit(1);
+ break;
+ default:
+ throw error;
+ }
+}
+
+/**
+ * Event listener for HTTP server "listening" event.
+ */
+
+function onListening() {
+ var addr = server.address();
+ var bind = typeof addr === 'string'
+ ? 'pipe ' + addr
+ : 'port ' + addr.port;
+ debug('Listening on ' + bind);
+}
diff --git a/Chapter11/deploying-a-full-system/micro/webapp/lib/event-logger.js b/Chapter11/deploying-a-full-system/micro/webapp/lib/event-logger.js
new file mode 100644
index 0000000..0140d3f
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/webapp/lib/event-logger.js
@@ -0,0 +1,40 @@
+'use strict'
+
+const { dns } = require('concordant')()
+const redis = require('redis')
+
+module.exports = eventLogger
+
+function eventLogger () {
+ const QNAME = 'eventservice'
+ var client
+
+ const endpoint = '_main._tcp.redis.micro.svc.cluster.local'
+ dns.resolve(endpoint, (err, locs) => {
+ if (err) {
+ console.error(err)
+ return
+ }
+ const { port, host } = locs[0]
+ client = redis.createClient(port, host)
+ })
+
+ function middleware (req, res, next) {
+ if (!client) {
+ console.log('client not ready, waiting 100ms')
+ setTimeout(middleware, 100, req, res, next)
+ return
+ }
+ const event = {
+ action: 'record',
+ type: 'page',
+ url: `${req.protocol}://${req.get('host')}${req.originalUrl}`
+ }
+ client.lpush(QNAME, JSON.stringify(event), (err) => {
+ if (err) console.error(err)
+ next()
+ })
+ }
+
+ return middleware
+}
diff --git a/Chapter11/deploying-a-full-system/micro/webapp/package.json b/Chapter11/deploying-a-full-system/micro/webapp/package.json
new file mode 100644
index 0000000..1c45acf
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/webapp/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "webapp",
+ "version": "0.0.0",
+ "private": true,
+ "scripts": {
+ "start": "node ./bin/www"
+ },
+ "dependencies": {
+ "body-parser": "~1.16.0",
+ "concordant": "^0.2.1",
+ "cookie-parser": "~1.4.3",
+ "debug": "~2.6.0",
+ "ejs": "~2.5.5",
+ "express": "~4.14.1",
+ "morgan": "~1.7.0",
+ "mu": "^2.1.2",
+ "restify": "^4.3.0",
+ "serve-favicon": "~2.3.2"
+ }
+}
diff --git a/Chapter11/deploying-a-full-system/micro/webapp/public/stylesheets/style.css b/Chapter11/deploying-a-full-system/micro/webapp/public/stylesheets/style.css
new file mode 100644
index 0000000..9453385
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/webapp/public/stylesheets/style.css
@@ -0,0 +1,8 @@
+body {
+ padding: 50px;
+ font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
+}
+
+a {
+ color: #00B7FF;
+}
diff --git a/Chapter11/deploying-a-full-system/micro/webapp/routes/add.js b/Chapter11/deploying-a-full-system/micro/webapp/routes/add.js
new file mode 100644
index 0000000..3ab03cc
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/webapp/routes/add.js
@@ -0,0 +1,68 @@
+'use strict'
+
+const { Router } = require('express')
+const restify = require('restify')
+const { dns } = require('concordant')()
+const router = Router()
+var clients
+
+router.get('/', function (req, res) {
+ res.render('add', { first: 0, second: 0, result: 0 })
+})
+
+router.post('/calculate', resolve, respond)
+
+function resolve (req, res, next) {
+ if (clients) {
+ next()
+ return
+ }
+ const adderservice = `_main._tcp.adderservice.micro.svc.cluster.local`
+ const auditservice = `_main._tcp.auditservice.micro.svc.cluster.local`
+ dns.resolve(adderservice, (err, locs) => {
+ if (err) {
+ next(err)
+ return
+ }
+ const { host, port } = locs[0]
+ const adder = `${host}:${port}`
+ dns.resolve(auditservice, (err, locs) => {
+ if (err) {
+ next(err)
+ return
+ }
+ const { host, port } = locs[0]
+ const audit = `${host}:${port}`
+ clients = {
+ adder: restify.createJSONClient({url: `http://${adder}`}),
+ audit: restify.createJSONClient({url: `http://${audit}`})
+ }
+ next()
+ })
+ })
+}
+
+function respond (req, res, next) {
+ const { first, second } = req.body
+ clients.adder.get(
+ `/add/${first}/${second}`,
+ (err, svcReq, svcRes, data) => {
+ if (err) {
+ next(err)
+ return
+ }
+
+ const { result } = data
+ clients.audit.post('/append', {
+ calc: first + '+' + second,
+ calcResult: result
+ }, (err) => {
+ if (err) console.error(err)
+ })
+
+ res.render('add', { first, second, result })
+ }
+ )
+}
+
+module.exports = router
diff --git a/Chapter11/deploying-a-full-system/micro/webapp/routes/audit.js b/Chapter11/deploying-a-full-system/micro/webapp/routes/audit.js
new file mode 100644
index 0000000..a083eef
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/webapp/routes/audit.js
@@ -0,0 +1,37 @@
+'use strict'
+
+const { Router } = require('express')
+const restify = require('restify')
+const { dns } = require('concordant')()
+const router = Router()
+var client
+
+router.get('/', resolve, respond)
+
+function resolve (req, res, next) {
+ if (client) {
+ next()
+ return
+ }
+ const auditservice = `_main._tcp.auditservice.micro.svc.cluster.local`
+ dns.resolve(auditservice, (err, locs) => {
+ if (err) {
+ next(err)
+ return
+ }
+ const { host, port } = locs[0]
+ client = restify.createJSONClient(`http://${host}:${port}`)
+ })
+}
+
+function respond (req, res, next) {
+ client.get('/list', (err, svcReq, svcRes, data) => {
+ if (err) {
+ next(err)
+ return
+ }
+ res.render('audit', data)
+ })
+}
+
+module.exports = router
diff --git a/Chapter11/deploying-a-full-system/micro/webapp/routes/index.js b/Chapter11/deploying-a-full-system/micro/webapp/routes/index.js
new file mode 100644
index 0000000..956680b
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/webapp/routes/index.js
@@ -0,0 +1,9 @@
+var express = require('express')
+var router = express.Router()
+
+/* GET home page. */
+router.get('/', function (req, res, next) {
+ res.render('index', { title: 'Express' })
+})
+
+module.exports = router
diff --git a/Chapter11/deploying-a-full-system/micro/webapp/routes/users.js b/Chapter11/deploying-a-full-system/micro/webapp/routes/users.js
new file mode 100644
index 0000000..8cfe88f
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/webapp/routes/users.js
@@ -0,0 +1,9 @@
+var express = require('express')
+var router = express.Router()
+
+/* GET users listing. */
+router.get('/', function (req, res, next) {
+ res.send('respond with a resource')
+})
+
+module.exports = router
diff --git a/Chapter11/deploying-a-full-system/micro/webapp/views/add.ejs b/Chapter11/deploying-a-full-system/micro/webapp/views/add.ejs
new file mode 100644
index 0000000..da8279c
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/webapp/views/add.ejs
@@ -0,0 +1,16 @@
+
+
+
+ Add
+
+
+
+ Add it up!
+
+ Submit
+ result = <%= result %>
+
+
diff --git a/Chapter11/deploying-a-full-system/micro/webapp/views/audit.ejs b/Chapter11/deploying-a-full-system/micro/webapp/views/audit.ejs
new file mode 100644
index 0000000..dd6d71b
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/webapp/views/audit.ejs
@@ -0,0 +1,15 @@
+
+
+
+ Audit
+
+
+
+ Calculation History
+
+ <% list.forEach(function (el) { %>
+ at: <%= new Date(el.ts).toLocaleString() %>, calculated: <%= el.calc %>, result: <%= el.result %>
+ <% }) %>
+
+
+
\ No newline at end of file
diff --git a/Chapter11/deploying-a-full-system/micro/webapp/views/error.ejs b/Chapter11/deploying-a-full-system/micro/webapp/views/error.ejs
new file mode 100644
index 0000000..7cf94ed
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/webapp/views/error.ejs
@@ -0,0 +1,3 @@
+<%= message %>
+<%= error.status %>
+<%= error.stack %>
diff --git a/Chapter11/deploying-a-full-system/micro/webapp/views/index.ejs b/Chapter11/deploying-a-full-system/micro/webapp/views/index.ejs
new file mode 100644
index 0000000..7b7a1d6
--- /dev/null
+++ b/Chapter11/deploying-a-full-system/micro/webapp/views/index.ejs
@@ -0,0 +1,11 @@
+
+
+
+ <%= title %>
+
+
+
+ <%= title %>
+ Welcome to <%= title %>
+
+
diff --git a/Chapter11/deploying-to-the-cloud/micro/adderservice/.dockerignore b/Chapter11/deploying-to-the-cloud/micro/adderservice/.dockerignore
new file mode 100644
index 0000000..90718e6
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/adderservice/.dockerignore
@@ -0,0 +1,4 @@
+.git
+.gitignore
+node_modules
+npm-debug.log
diff --git a/Chapter11/deploying-to-the-cloud/micro/adderservice/Dockerfile b/Chapter11/deploying-to-the-cloud/micro/adderservice/Dockerfile
new file mode 100644
index 0000000..4d2e56b
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/adderservice/Dockerfile
@@ -0,0 +1,7 @@
+FROM node:slim
+RUN mkdir -p /home/node/service
+WORKDIR /home/node/service
+COPY package.json /home/node/service
+RUN npm install
+COPY . /home/node/service
+CMD [ "node", "index.js" ]
diff --git a/Chapter11/deploying-to-the-cloud/micro/adderservice/Jenkinsfile b/Chapter11/deploying-to-the-cloud/micro/adderservice/Jenkinsfile
new file mode 100644
index 0000000..8ea826e
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/adderservice/Jenkinsfile
@@ -0,0 +1,32 @@
+pipeline {
+ agent any
+
+ stages {
+ stage('Checkout') {
+ steps {
+ checkout scm
+ }
+ }
+ stage('Build') {
+ steps {
+ sh 'source ~/.bashrc && cd adderservice && npm install'
+ }
+ }
+ stage('Test'){
+ steps {
+ sh 'source ~/.bashrc && cd adderservice && npm test'
+ }
+ }
+ stage('Container'){
+ steps {
+ sh 'source ~/.bashrc && cd adderservice && sh build.sh container'
+ }
+ }
+ stage('Deploy'){
+ steps {
+ sh 'source ~/.bashrc && cd adderservice && sh build.sh deploy'
+ }
+ }
+ }
+}
+
diff --git a/Chapter11/deploying-to-the-cloud/micro/adderservice/build.sh b/Chapter11/deploying-to-the-cloud/micro/adderservice/build.sh
new file mode 100644
index 0000000..7da8cb6
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/adderservice/build.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+source ~/.bashrc
+
+GITSHA=$(git rev-parse --short HEAD)
+
+case "$1" in
+ container)
+ sudo -u pelger docker build -t adderservice:$GITSHA .
+ sudo -u pelger docker tag adderservice:$GITSHA pelger/adderservice:$GITSHA
+ sudo -i -u pelger docker push pelger/adderservice:$GITSHA
+ ;;
+ deploy)
+ sed -e s/_NAME_/adderservice/ -e s/_PORT_/8080/ < ../deployment/service-template.yml > svc.yml
+ sed -e s/_NAME_/adderservice/ -e s/_PORT_/8080/ -e s/_IMAGE_/pelger\\/adderservice:$GITSHA/ < ../deployment/deployment-template.yml > dep.yml
+ sudo -i -u pelger kubectl apply -f $(pwd)/svc.yml
+ sudo -i -u pelger kubectl apply -f $(pwd)/dep.yml
+ ;;
+ *)
+ echo 'invalid build command'
+ exit 1
+ ;;
+esac
+
diff --git a/Chapter11/deploying-to-the-cloud/micro/adderservice/index.js b/Chapter11/deploying-to-the-cloud/micro/adderservice/index.js
new file mode 100644
index 0000000..1f2d9cf
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/adderservice/index.js
@@ -0,0 +1,7 @@
+'use strict'
+
+const wiring = require('./wiring')
+const service = require('./service')()
+
+
+wiring(service)
diff --git a/Chapter11/deploying-to-the-cloud/micro/adderservice/package.json b/Chapter11/deploying-to-the-cloud/micro/adderservice/package.json
new file mode 100644
index 0000000..c612751
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/adderservice/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "adder-service",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"no test specified\" && exit 0"
+ },
+ "keywords": [],
+ "author": "Peter Elger ",
+ "license": "ISC",
+ "dependencies": {
+ "restify": "^4.3.0"
+ }
+}
diff --git a/Chapter11/deploying-to-the-cloud/micro/adderservice/service.js b/Chapter11/deploying-to-the-cloud/micro/adderservice/service.js
new file mode 100644
index 0000000..9d0d175
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/adderservice/service.js
@@ -0,0 +1,13 @@
+'use strict'
+
+module.exports = service
+
+function service () {
+ function add (args, cb) {
+ const {first, second} = args
+ const result = (parseInt(first, 10) + parseInt(second, 10))
+ cb(null, {result: result.toString()})
+ }
+
+ return { add }
+}
diff --git a/Chapter11/deploying-to-the-cloud/micro/adderservice/wiring.js b/Chapter11/deploying-to-the-cloud/micro/adderservice/wiring.js
new file mode 100644
index 0000000..0ed018c
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/adderservice/wiring.js
@@ -0,0 +1,27 @@
+'use strict'
+
+const restify = require('restify')
+const { ADDERSERVICE_SERVICE_PORT } = process.env
+
+module.exports = wiring
+
+function wiring (service) {
+ const server = restify.createServer()
+
+ server.get('/add/:first/:second', (req, res, next) => {
+ service.add(req.params, (err, result) => {
+ if (err) {
+ res.send(err)
+ next()
+ return
+ }
+ res.send(200, result)
+ next()
+ })
+ })
+
+ server.listen(ADDERSERVICE_SERVICE_PORT, '0.0.0.0', () => {
+ console.log('%s listening at %s', server.name, server.url)
+ })
+}
+
diff --git a/Chapter11/deploying-to-the-cloud/micro/auditservice/.dockerignore b/Chapter11/deploying-to-the-cloud/micro/auditservice/.dockerignore
new file mode 100644
index 0000000..90718e6
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/auditservice/.dockerignore
@@ -0,0 +1,4 @@
+.git
+.gitignore
+node_modules
+npm-debug.log
diff --git a/Chapter11/deploying-to-the-cloud/micro/auditservice/Dockerfile b/Chapter11/deploying-to-the-cloud/micro/auditservice/Dockerfile
new file mode 100644
index 0000000..4d2e56b
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/auditservice/Dockerfile
@@ -0,0 +1,7 @@
+FROM node:slim
+RUN mkdir -p /home/node/service
+WORKDIR /home/node/service
+COPY package.json /home/node/service
+RUN npm install
+COPY . /home/node/service
+CMD [ "node", "index.js" ]
diff --git a/Chapter11/deploying-to-the-cloud/micro/auditservice/Jenkinsfile b/Chapter11/deploying-to-the-cloud/micro/auditservice/Jenkinsfile
new file mode 100644
index 0000000..6a75d1a
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/auditservice/Jenkinsfile
@@ -0,0 +1,32 @@
+pipeline {
+ agent any
+
+ stages {
+ stage('Checkout') {
+ steps {
+ checkout scm
+ }
+ }
+ stage('Build') {
+ steps {
+ sh 'source ~/.bashrc && cd auditservice && npm install'
+ }
+ }
+ stage('Test'){
+ steps {
+ sh 'source ~/.bashrc && cd auditservice && npm test'
+ }
+ }
+ stage('Container'){
+ steps {
+ sh 'source ~/.bashrc && cd auditservice && sh build.sh container'
+ }
+ }
+ stage('Deploy'){
+ steps {
+ sh 'source ~/.bashrc && cd auditservice && sh build.sh deploy'
+ }
+ }
+ }
+}
+
diff --git a/Chapter11/deploying-to-the-cloud/micro/auditservice/build.sh b/Chapter11/deploying-to-the-cloud/micro/auditservice/build.sh
new file mode 100644
index 0000000..efa553c
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/auditservice/build.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+source ~/.bashrc
+
+GITSHA=$(git rev-parse --short HEAD)
+
+case "$1" in
+ container)
+ sudo -u pelger docker build -t auditservice:$GITSHA .
+ sudo -u pelger docker tag auditservice:$GITSHA pelger/auditservice:$GITSHA
+ sudo -i -u pelger docker push pelger/auditservice:$GITSHA
+ ;;
+ deploy)
+ sed -e s/_NAME_/auditservice/ -e s/_PORT_/8081/ < ../deployment/service-template.yml > svc.yml
+ sed -e s/_NAME_/auditservice/ -e s/_PORT_/8081/ -e s/_IMAGE_/pelger\\/auditservice:$GITSHA/ < ../deployment/deployment-template.yml > dep.yml
+ sudo -i -u pelger kubectl apply -f $(pwd)/svc.yml
+ sudo -i -u pelger kubectl apply -f $(pwd)/dep.yml
+ ;;
+ *)
+ echo 'invalid build command'
+ exit 1
+ ;;
+esac
+
diff --git a/Chapter11/deploying-to-the-cloud/micro/auditservice/index.js b/Chapter11/deploying-to-the-cloud/micro/auditservice/index.js
new file mode 100644
index 0000000..40a4677
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/auditservice/index.js
@@ -0,0 +1,6 @@
+'use strict'
+
+const wiring = require('./wiring')
+const service = require('./service')()
+
+wiring(service)
diff --git a/Chapter11/deploying-to-the-cloud/micro/auditservice/package.json b/Chapter11/deploying-to-the-cloud/micro/auditservice/package.json
new file mode 100644
index 0000000..bcb0138
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/auditservice/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "auditservice",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"no test specified\" && exit 0"
+ },
+ "keywords": [],
+ "author": "Peter Elger ",
+ "license": "ISC",
+ "dependencies": {
+ "concordant": "^0.2.1",
+ "mongodb": "^2.2.25",
+ "restify": "^4.3.0"
+ }
+}
diff --git a/Chapter11/deploying-to-the-cloud/micro/auditservice/service.js b/Chapter11/deploying-to-the-cloud/micro/auditservice/service.js
new file mode 100644
index 0000000..c416e4c
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/auditservice/service.js
@@ -0,0 +1,74 @@
+'use strict'
+
+const { MongoClient } = require('mongodb')
+const { dns } = require('concordant')()
+
+module.exports = service
+
+function service () {
+
+ var db
+
+ setup()
+
+ function setup () {
+ const mongo = '_main._tcp.mongo.micro.svc.cluster.local'
+
+ dns.resolve(mongo, (err, locs) => {
+ if (err) {
+ console.error(err)
+ return
+ }
+ const { host, port } = locs[0]
+ const url = `mongodb://${host}:${port}/audit`
+ MongoClient.connect(url, (err, client) => {
+ if (err) {
+ console.log('failed to connect to MongoDB retrying in 100ms')
+ setTimeout(setup, 100)
+ return
+ }
+ db = client
+ db.on('close', () => db = null)
+ })
+ })
+ }
+
+ function append (args, cb) {
+ if (!db) {
+ cb(Error('No database connection'))
+ return
+ }
+ const audit = db.collection('audit')
+ const data = {
+ ts: Date.now(),
+ calc: args.calc,
+ result: args.calcResult
+ }
+
+ audit.insert(data, (err, result) => {
+ if (err) {
+ cb(err)
+ return
+ }
+ cb(null, {result: result.toString()})
+ })
+ }
+
+ function list (args, cb) {
+ if (!db) {
+ cb(Error('No database connection'))
+ return
+ }
+ const audit = db.collection('audit')
+ audit.find({}, {limit: 10}).toArray((err, docs) => {
+ if (err) {
+ cb(err)
+ return
+ }
+ cb(null, {list: docs})
+ })
+ }
+
+ return { append, list }
+}
+
diff --git a/Chapter11/deploying-to-the-cloud/micro/auditservice/wiring.js b/Chapter11/deploying-to-the-cloud/micro/auditservice/wiring.js
new file mode 100644
index 0000000..b18dce8
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/auditservice/wiring.js
@@ -0,0 +1,39 @@
+'use strict'
+
+const restify = require('restify')
+const { AUDITSERVICE_SERVICE_PORT } = process.env
+
+module.exports = wiring
+
+function wiring (service) {
+ const server = restify.createServer()
+
+ server.use(restify.bodyParser())
+
+ server.post('/append', (req, res, next) => {
+ service.append(req.params, (err, result) => {
+ if (err) {
+ res.send(err)
+ return
+ }
+ res.send(result)
+ next()
+ })
+ })
+
+ server.get('/list', (req, res, next) => {
+ service.list(req.params, (err, result) => {
+ if (err) {
+ res.send(err)
+ return
+ }
+ res.send(200, result)
+ next()
+ })
+ })
+
+ server.listen(AUDITSERVICE_SERVICE_PORT, '0.0.0.0', () => {
+ console.log('%s listening at %s', server.name, server.url)
+ })
+}
+
diff --git a/Chapter11/deploying-to-the-cloud/micro/deployment/deployment-template.yml b/Chapter11/deploying-to-the-cloud/micro/deployment/deployment-template.yml
new file mode 100644
index 0000000..42ee070
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/deployment/deployment-template.yml
@@ -0,0 +1,17 @@
+apiVersion: extensions/v1beta1
+kind: Deployment
+metadata:
+ name: _NAME_
+spec:
+ replicas: 1
+ template:
+ metadata:
+ labels:
+ run: _NAME_
+ spec:
+ containers:
+ - name: _NAME_
+ image: _IMAGE_
+ ports:
+ - containerPort: _PORT_
+
diff --git a/Chapter11/deploying-to-the-cloud/micro/deployment/namespace.yml b/Chapter11/deploying-to-the-cloud/micro/deployment/namespace.yml
new file mode 100644
index 0000000..1351cbb
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/deployment/namespace.yml
@@ -0,0 +1,6 @@
+apiVersion: v1
+kind: Namespace
+metadata:
+ name: micro
+ labels:
+ name: micro
diff --git a/Chapter11/deploying-to-the-cloud/micro/deployment/service-template-lb.yml b/Chapter11/deploying-to-the-cloud/micro/deployment/service-template-lb.yml
new file mode 100644
index 0000000..0f6a1b6
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/deployment/service-template-lb.yml
@@ -0,0 +1,16 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: _NAME_
+ labels:
+ run: _NAME_
+spec:
+ ports:
+ - port: _PORT_
+ name: main
+ protocol: TCP
+ targetPort: _PORT_
+ selector:
+ run: _NAME_
+ type: LoadBalancer
+
diff --git a/Chapter11/deploying-to-the-cloud/micro/deployment/service-template.yml b/Chapter11/deploying-to-the-cloud/micro/deployment/service-template.yml
new file mode 100644
index 0000000..11010f6
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/deployment/service-template.yml
@@ -0,0 +1,16 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: _NAME_
+ labels:
+ run: _NAME_
+spec:
+ ports:
+ - port: _PORT_
+ name: main
+ protocol: TCP
+ targetPort: _PORT_
+ selector:
+ run: _NAME_
+ type: NodePort
+
diff --git a/Chapter11/deploying-to-the-cloud/micro/eventservice/.dockerignore b/Chapter11/deploying-to-the-cloud/micro/eventservice/.dockerignore
new file mode 100644
index 0000000..90718e6
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/eventservice/.dockerignore
@@ -0,0 +1,4 @@
+.git
+.gitignore
+node_modules
+npm-debug.log
diff --git a/Chapter11/deploying-to-the-cloud/micro/eventservice/Dockerfile b/Chapter11/deploying-to-the-cloud/micro/eventservice/Dockerfile
new file mode 100644
index 0000000..4d2e56b
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/eventservice/Dockerfile
@@ -0,0 +1,7 @@
+FROM node:slim
+RUN mkdir -p /home/node/service
+WORKDIR /home/node/service
+COPY package.json /home/node/service
+RUN npm install
+COPY . /home/node/service
+CMD [ "node", "index.js" ]
diff --git a/Chapter11/deploying-to-the-cloud/micro/eventservice/Jenkinsfile b/Chapter11/deploying-to-the-cloud/micro/eventservice/Jenkinsfile
new file mode 100644
index 0000000..81614ce
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/eventservice/Jenkinsfile
@@ -0,0 +1,32 @@
+pipeline {
+ agent any
+
+ stages {
+ stage('Checkout') {
+ steps {
+ checkout scm
+ }
+ }
+ stage('Build') {
+ steps {
+ sh 'source ~/.bashrc && cd eventservice && npm install'
+ }
+ }
+ stage('Test'){
+ steps {
+ sh 'source ~/.bashrc && cd eventservice && npm test'
+ }
+ }
+ stage('Container'){
+ steps {
+ sh 'source ~/.bashrc && cd eventservice && sh build.sh container'
+ }
+ }
+ stage('Deploy'){
+ steps {
+ sh 'source ~/.bashrc && cd eventservice && sh build.sh deploy'
+ }
+ }
+ }
+}
+
diff --git a/Chapter11/deploying-to-the-cloud/micro/eventservice/build.sh b/Chapter11/deploying-to-the-cloud/micro/eventservice/build.sh
new file mode 100644
index 0000000..68099f0
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/eventservice/build.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+source ~/.bashrc
+
+GITSHA=$(git rev-parse --short HEAD)
+
+case "$1" in
+ container)
+ sudo -u pelger docker build -t eventservice:$GITSHA .
+ sudo -u pelger docker tag eventservice:$GITSHA pelger/eventservice:$GITSHA
+ sudo -i -u pelger docker push pelger/eventservice:$GITSHA
+ ;;
+ deploy)
+ sed -e s/_NAME_/eventservice/ -e s/_PORT_/8082/ < ../deployment/service-template.yml > svc.yml
+ sed -e s/_NAME_/eventservice/ -e s/_PORT_/8082/ -e s/_IMAGE_/pelger\\/eventservice:$GITSHA/ < ../deployment/deployment-template.yml > dep.yml
+ sudo -i -u pelger kubectl apply -f $(pwd)/svc.yml
+ sudo -i -u pelger kubectl apply -f $(pwd)/dep.yml
+ ;;
+ *)
+ echo 'invalid build command'
+ exit 1
+ ;;
+esac
+
diff --git a/Chapter11/deploying-to-the-cloud/micro/eventservice/index.js b/Chapter11/deploying-to-the-cloud/micro/eventservice/index.js
new file mode 100644
index 0000000..df3a380
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/eventservice/index.js
@@ -0,0 +1,5 @@
+'use strict'
+
+const wiring = require('./wiring')
+const service = require('./service')()
+wiring(service)
diff --git a/Chapter11/deploying-to-the-cloud/micro/eventservice/package.json b/Chapter11/deploying-to-the-cloud/micro/eventservice/package.json
new file mode 100644
index 0000000..baca7cb
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/eventservice/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "event-service",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"no test specified\" && exit 0"
+ },
+ "keywords": [],
+ "author": "Peter Elger ",
+ "license": "ISC",
+ "dependencies": {
+ "concordant": "^0.2.1",
+ "mongodb": "^2.2.25",
+ "redis": "^2.6.5"
+ }
+}
diff --git a/Chapter11/deploying-to-the-cloud/micro/eventservice/service.js b/Chapter11/deploying-to-the-cloud/micro/eventservice/service.js
new file mode 100644
index 0000000..4082d3e
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/eventservice/service.js
@@ -0,0 +1,80 @@
+'use strict'
+
+const { MongoClient } = require('mongodb')
+const { dns } = require('concordant')()
+
+module.exports = service
+
+function service () {
+ var db
+
+ setup()
+
+ function setup () {
+ const mongo = '_main._tcp.mongo.micro.svc.cluster.local'
+
+ dns.resolve(mongo, (err, locs) => {
+ if (err) {
+ console.error(err)
+ return
+ }
+ const { host, port } = locs[0]
+ const url = `mongodb://${host}:${port}/events`
+ MongoClient.connect(url, (err, client) => {
+ if (err) {
+ console.log('failed to connect to MongoDB retrying in 100ms')
+ setTimeout(setup, 100)
+ return
+ }
+ db = client
+ db.on('close', () => db = null)
+ })
+ })
+ }
+
+ function record (args, cb) {
+ if (!db) {
+ cb(Error('No database connection'))
+ return
+ }
+ const events = db.collection('events')
+ const data = {
+ ts: Date.now(),
+ eventType: args.type,
+ url: args.url
+ }
+ events.insert(data, (err, result) => {
+ if (err) {
+ cb(err)
+ return
+ }
+ cb(null, result)
+ })
+ }
+
+ function summary (args, cb) {
+ if (!db) {
+ cb(Error('No database connection'))
+ return
+ }
+ const summary = {}
+ const events = db.collection('events')
+ events.find({}).toArray( (err, docs) => {
+ if (err) return cb(err)
+
+ docs.forEach(function (doc) {
+ if (!(summary[doc.url])) {
+ summary[doc.url] = 1
+ } else {
+ summary[doc.url]++
+ }
+ })
+ cb(null, summary)
+ })
+ }
+
+ return {
+ record: record,
+ summary: summary
+ }
+}
diff --git a/Chapter11/deploying-to-the-cloud/micro/eventservice/wiring.js b/Chapter11/deploying-to-the-cloud/micro/eventservice/wiring.js
new file mode 100644
index 0000000..b04ab35
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/eventservice/wiring.js
@@ -0,0 +1,53 @@
+'use strict'
+
+const { dns } = require('concordant')()
+const redis = require('redis')
+const QNAME = 'eventservice'
+
+module.exports = wiring
+
+function wiring (service) {
+
+ const endpoint = '_main._tcp.redis.micro.svc.cluster.local'
+
+ dns.resolve(endpoint, (err, locs) => {
+ if (err) {
+ console.log(err)
+ return
+ }
+ const { port, host } = locs[0]
+ pullFromQueue(redis.createClient(port, host))
+ })
+
+ function pullFromQueue (client) {
+ client.brpop(QNAME, 5, function (err, data) {
+ if (err) console.error(err)
+ if (err || !data) {
+ pullFromQueue(client)
+ return
+ }
+ const msg = JSON.parse(data[1])
+ const { action, returnPath } = msg
+ const cmd = service[action]
+ if (typeof cmd !== 'function') {
+ pullFromQueue(client)
+ return
+ }
+ cmd(msg, (err, result) => {
+ if (err) {
+ console.error(err)
+ pullFromQueue(client)
+ return
+ }
+ if (!returnPath) {
+ pullFromQueue(client)
+ return
+ }
+ client.lpush(returnPath, JSON.stringify(result), (err) => {
+ if (err) console.error(err)
+ pullFromQueue(client)
+ })
+ })
+ })
+ }
+}
diff --git a/Chapter11/deploying-to-the-cloud/micro/fuge/fuge.yml b/Chapter11/deploying-to-the-cloud/micro/fuge/fuge.yml
new file mode 100644
index 0000000..839ba34
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/fuge/fuge.yml
@@ -0,0 +1,45 @@
+fuge_global:
+ run_containers: true
+ dns_enabled: true
+ dns_host: 127.0.0.1
+ dns_port: 53053
+ dns_suffix: svc.cluster.local
+ dns_namespace: micro
+ tail: true
+ monitor: true
+ monitor_excludes:
+ - '**/node_modules/**'
+ - '**/.git/**'
+ - '**/*.log'
+adderservice:
+ type: node
+ path: ../adderservice
+ run: node index.js
+ ports:
+ - main=8080
+auditservice:
+ type: process
+ path: ../auditservice
+ run: 'node index.js'
+ ports:
+ - main=8081
+eventservice:
+ type: process
+ path: ../eventservice
+ run: 'node index.js'
+webapp:
+ type: process
+ path: ../webapp
+ run: npm start
+ ports:
+ - http=3000
+mongo:
+ image: mongo
+ type: container
+ ports:
+ - main=27017:27017
+redis:
+ image: redis
+ type: container
+ ports:
+ - main=6379:6379
diff --git a/Chapter11/deploying-to-the-cloud/micro/infrastructure/Jenkinsfile b/Chapter11/deploying-to-the-cloud/micro/infrastructure/Jenkinsfile
new file mode 100644
index 0000000..d204b09
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/infrastructure/Jenkinsfile
@@ -0,0 +1,22 @@
+pipeline {
+ agent any
+
+ stages {
+ stage('Checkout') {
+ steps {
+ checkout scm
+ }
+ }
+ stage('DeployMongo'){
+ steps {
+ sh 'source ~/.bashrc && cd infrastructure && sh build.sh mongo'
+ }
+ }
+ stage('DeployRedis'){
+ steps {
+ sh 'source ~/.bashrc && cd infrastructure && sh build.sh redis'
+ }
+ }
+ }
+}
+
diff --git a/Chapter11/deploying-to-the-cloud/micro/infrastructure/build.sh b/Chapter11/deploying-to-the-cloud/micro/infrastructure/build.sh
new file mode 100644
index 0000000..dc36a20
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/infrastructure/build.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+sh source ~/.bashrc
+
+case "$1" in
+ mongo)
+ sed -e s/_NAME_/mongo/ -e s/_PORT_/27017/ < ../deployment/service-template.yml > svc.yml
+ sed -e s/_NAME_/mongo/ -e s/_PORT_/27017/ -e s/_IMAGE_/mongo/ < ../deployment/deployment-template.yml > dep.yml
+ sudo -i -u pelger kubectl apply -f $(pwd)/svc.yml
+ sudo -i -u pelger kubectl apply -f $(pwd)/dep.yml
+ ;;
+ redis)
+ sed -e s/_NAME_/redis/ -e s/_PORT_/6379/ < ../deployment/service-template.yml > svc.yml
+ sed -e s/_NAME_/redis/ -e s/_PORT_/6379/ -e s/_IMAGE_/redis/ < ../deployment/deployment-template.yml > dep.yml
+ sudo -i -u pelger kubectl apply -f $(pwd)/svc.yml
+ sudo -i -u pelger kubectl apply -f $(pwd)/dep.yml
+ ;;
+ *)
+ echo 'invalid build command'
+ exit 1
+ ;;
+esac
+
diff --git a/Chapter11/deploying-to-the-cloud/micro/report/.dockerignore b/Chapter11/deploying-to-the-cloud/micro/report/.dockerignore
new file mode 100644
index 0000000..90718e6
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/report/.dockerignore
@@ -0,0 +1,4 @@
+.git
+.gitignore
+node_modules
+npm-debug.log
diff --git a/Chapter11/deploying-to-the-cloud/micro/report/Dockerfile b/Chapter11/deploying-to-the-cloud/micro/report/Dockerfile
new file mode 100644
index 0000000..4d2e56b
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/report/Dockerfile
@@ -0,0 +1,7 @@
+FROM node:slim
+RUN mkdir -p /home/node/service
+WORKDIR /home/node/service
+COPY package.json /home/node/service
+RUN npm install
+COPY . /home/node/service
+CMD [ "node", "index.js" ]
diff --git a/Chapter11/deploying-to-the-cloud/micro/report/env.js b/Chapter11/deploying-to-the-cloud/micro/report/env.js
new file mode 100644
index 0000000..a4d26e9
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/report/env.js
@@ -0,0 +1,17 @@
+'use strict'
+
+// provides environment variable setup for concordant
+
+const env = {
+ DNS_NAMESPACE: 'micro',
+ DNS_SUFFIX: 'svc.cluster.local'
+}
+
+if (process.env.NODE_ENV !== 'production') {
+ Object.assign(env, {
+ DNS_HOST: '127.0.0.1',
+ DNS_PORT: '53053'
+ })
+}
+
+Object.assign(process.env, env)
\ No newline at end of file
diff --git a/Chapter11/deploying-to-the-cloud/micro/report/index.js b/Chapter11/deploying-to-the-cloud/micro/report/index.js
new file mode 100644
index 0000000..b65b65a
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/report/index.js
@@ -0,0 +1,43 @@
+'use strict'
+require('./env')
+const { dns } = require('concordant')()
+const redis = require('redis')
+const CliTable = require('cli-table')
+const QNAME = 'eventservice'
+const RESPONSE_QUEUE = 'summary'
+const ENDPOINT = '_main._tcp.redis.micro.svc.cluster.local'
+
+dns.resolve(ENDPOINT, report)
+
+function report (err, locs) {
+ if (err) { return console.log(err) }
+ const { port, host } = locs[0]
+ const client = redis.createClient(port, host)
+ const event = JSON.stringify({
+ action: 'summary',
+ returnPath: RESPONSE_QUEUE
+ })
+
+ client.lpush(QNAME, event, (err) => {
+ if (err) {
+ console.error(err)
+ return
+ }
+
+ client.brpop(RESPONSE_QUEUE, 5, (err, data) => {
+ if (err) {
+ console.error(err)
+ return
+ }
+ const summary = JSON.parse(data[1])
+ const cols = Object.keys(summary).map((url) => [url, summary[url]])
+ const table = new CliTable({
+ head: ['url', 'count'],
+ colWidths: [50, 10]
+ })
+ table.push(...cols)
+ console.log(table.toString())
+ client.quit()
+ })
+ })
+}
\ No newline at end of file
diff --git a/Chapter11/deploying-to-the-cloud/micro/report/package.json b/Chapter11/deploying-to-the-cloud/micro/report/package.json
new file mode 100644
index 0000000..da2bd04
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/report/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "report",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"no test specified\" && exit 0"
+ },
+ "keywords": [],
+ "author": "Peter Elger ",
+ "license": "ISC",
+ "dependencies": {
+ "cli-table": "^0.3.1",
+ "concordant": "^0.2.1",
+ "redis": "^2.6.5"
+ }
+}
diff --git a/Chapter11/deploying-to-the-cloud/micro/webapp/.dockerignore b/Chapter11/deploying-to-the-cloud/micro/webapp/.dockerignore
new file mode 100644
index 0000000..90718e6
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/webapp/.dockerignore
@@ -0,0 +1,4 @@
+.git
+.gitignore
+node_modules
+npm-debug.log
diff --git a/Chapter11/deploying-to-the-cloud/micro/webapp/Dockerfile b/Chapter11/deploying-to-the-cloud/micro/webapp/Dockerfile
new file mode 100644
index 0000000..b2ffa24
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/webapp/Dockerfile
@@ -0,0 +1,7 @@
+FROM node:slim
+RUN mkdir -p /home/node/service
+WORKDIR /home/node/service
+COPY package.json /home/node/service
+RUN npm install
+COPY . /home/node/service
+CMD [ "npm", "start" ]
diff --git a/Chapter11/deploying-to-the-cloud/micro/webapp/app.js b/Chapter11/deploying-to-the-cloud/micro/webapp/app.js
new file mode 100644
index 0000000..eef315b
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/webapp/app.js
@@ -0,0 +1,52 @@
+var express = require('express')
+var path = require('path')
+var favicon = require('serve-favicon')
+var logger = require('morgan')
+var cookieParser = require('cookie-parser')
+var bodyParser = require('body-parser')
+var eventLogger = require('./lib/event-logger')
+
+var index = require('./routes/index')
+var users = require('./routes/users')
+var add = require('./routes/add')
+var audit = require('./routes/audit')
+
+var app = express()
+
+// view engine setup
+app.set('views', path.join(__dirname, 'views'))
+app.set('view engine', 'ejs')
+
+// uncomment after placing your favicon in /public
+// app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
+app.use(eventLogger())
+app.use(logger('dev'))
+app.use(bodyParser.json())
+app.use(bodyParser.urlencoded({ extended: false }))
+app.use(cookieParser())
+app.use(express.static(path.join(__dirname, 'public')))
+
+app.use('/', index)
+app.use('/users', users)
+app.use('/add', add)
+app.use('/audit', audit)
+
+// catch 404 and forward to error handler
+app.use(function (req, res, next) {
+ var err = new Error('Not Found')
+ err.status = 404
+ next(err)
+})
+
+// error handler
+app.use(function (err, req, res, next) {
+ // set locals, only providing error in development
+ res.locals.message = err.message
+ res.locals.error = req.app.get('env') === 'development' ? err : {}
+
+ // render the error page
+ res.status(err.status || 500)
+ res.render('error')
+})
+
+module.exports = app
diff --git a/Chapter11/deploying-to-the-cloud/micro/webapp/bin/www b/Chapter11/deploying-to-the-cloud/micro/webapp/bin/www
new file mode 100644
index 0000000..8715eeb
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/webapp/bin/www
@@ -0,0 +1,90 @@
+#!/usr/bin/env node
+
+/**
+ * Module dependencies.
+ */
+
+var app = require('../app');
+var debug = require('debug')('webapp:server');
+var http = require('http');
+
+/**
+ * Get port from environment and store in Express.
+ */
+
+var port = normalizePort(process.env.PORT || '3000');
+app.set('port', port);
+
+/**
+ * Create HTTP server.
+ */
+
+var server = http.createServer(app);
+
+/**
+ * Listen on provided port, on all network interfaces.
+ */
+
+server.listen(port);
+server.on('error', onError);
+server.on('listening', onListening);
+
+/**
+ * Normalize a port into a number, string, or false.
+ */
+
+function normalizePort(val) {
+ var port = parseInt(val, 10);
+
+ if (isNaN(port)) {
+ // named pipe
+ return val;
+ }
+
+ if (port >= 0) {
+ // port number
+ return port;
+ }
+
+ return false;
+}
+
+/**
+ * Event listener for HTTP server "error" event.
+ */
+
+function onError(error) {
+ if (error.syscall !== 'listen') {
+ throw error;
+ }
+
+ var bind = typeof port === 'string'
+ ? 'Pipe ' + port
+ : 'Port ' + port;
+
+ // handle specific listen errors with friendly messages
+ switch (error.code) {
+ case 'EACCES':
+ console.error(bind + ' requires elevated privileges');
+ process.exit(1);
+ break;
+ case 'EADDRINUSE':
+ console.error(bind + ' is already in use');
+ process.exit(1);
+ break;
+ default:
+ throw error;
+ }
+}
+
+/**
+ * Event listener for HTTP server "listening" event.
+ */
+
+function onListening() {
+ var addr = server.address();
+ var bind = typeof addr === 'string'
+ ? 'pipe ' + addr
+ : 'port ' + addr.port;
+ debug('Listening on ' + bind);
+}
diff --git a/Chapter11/deploying-to-the-cloud/micro/webapp/lib/event-logger.js b/Chapter11/deploying-to-the-cloud/micro/webapp/lib/event-logger.js
new file mode 100644
index 0000000..0140d3f
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/webapp/lib/event-logger.js
@@ -0,0 +1,40 @@
+'use strict'
+
+const { dns } = require('concordant')()
+const redis = require('redis')
+
+module.exports = eventLogger
+
+function eventLogger () {
+ const QNAME = 'eventservice'
+ var client
+
+ const endpoint = '_main._tcp.redis.micro.svc.cluster.local'
+ dns.resolve(endpoint, (err, locs) => {
+ if (err) {
+ console.error(err)
+ return
+ }
+ const { port, host } = locs[0]
+ client = redis.createClient(port, host)
+ })
+
+ function middleware (req, res, next) {
+ if (!client) {
+ console.log('client not ready, waiting 100ms')
+ setTimeout(middleware, 100, req, res, next)
+ return
+ }
+ const event = {
+ action: 'record',
+ type: 'page',
+ url: `${req.protocol}://${req.get('host')}${req.originalUrl}`
+ }
+ client.lpush(QNAME, JSON.stringify(event), (err) => {
+ if (err) console.error(err)
+ next()
+ })
+ }
+
+ return middleware
+}
diff --git a/Chapter11/deploying-to-the-cloud/micro/webapp/package.json b/Chapter11/deploying-to-the-cloud/micro/webapp/package.json
new file mode 100644
index 0000000..1c45acf
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/webapp/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "webapp",
+ "version": "0.0.0",
+ "private": true,
+ "scripts": {
+ "start": "node ./bin/www"
+ },
+ "dependencies": {
+ "body-parser": "~1.16.0",
+ "concordant": "^0.2.1",
+ "cookie-parser": "~1.4.3",
+ "debug": "~2.6.0",
+ "ejs": "~2.5.5",
+ "express": "~4.14.1",
+ "morgan": "~1.7.0",
+ "mu": "^2.1.2",
+ "restify": "^4.3.0",
+ "serve-favicon": "~2.3.2"
+ }
+}
diff --git a/Chapter11/deploying-to-the-cloud/micro/webapp/public/stylesheets/style.css b/Chapter11/deploying-to-the-cloud/micro/webapp/public/stylesheets/style.css
new file mode 100644
index 0000000..9453385
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/webapp/public/stylesheets/style.css
@@ -0,0 +1,8 @@
+body {
+ padding: 50px;
+ font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
+}
+
+a {
+ color: #00B7FF;
+}
diff --git a/Chapter11/deploying-to-the-cloud/micro/webapp/routes/add.js b/Chapter11/deploying-to-the-cloud/micro/webapp/routes/add.js
new file mode 100644
index 0000000..3ab03cc
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/webapp/routes/add.js
@@ -0,0 +1,68 @@
+'use strict'
+
+const { Router } = require('express')
+const restify = require('restify')
+const { dns } = require('concordant')()
+const router = Router()
+var clients
+
+router.get('/', function (req, res) {
+ res.render('add', { first: 0, second: 0, result: 0 })
+})
+
+router.post('/calculate', resolve, respond)
+
+function resolve (req, res, next) {
+ if (clients) {
+ next()
+ return
+ }
+ const adderservice = `_main._tcp.adderservice.micro.svc.cluster.local`
+ const auditservice = `_main._tcp.auditservice.micro.svc.cluster.local`
+ dns.resolve(adderservice, (err, locs) => {
+ if (err) {
+ next(err)
+ return
+ }
+ const { host, port } = locs[0]
+ const adder = `${host}:${port}`
+ dns.resolve(auditservice, (err, locs) => {
+ if (err) {
+ next(err)
+ return
+ }
+ const { host, port } = locs[0]
+ const audit = `${host}:${port}`
+ clients = {
+ adder: restify.createJSONClient({url: `http://${adder}`}),
+ audit: restify.createJSONClient({url: `http://${audit}`})
+ }
+ next()
+ })
+ })
+}
+
+function respond (req, res, next) {
+ const { first, second } = req.body
+ clients.adder.get(
+ `/add/${first}/${second}`,
+ (err, svcReq, svcRes, data) => {
+ if (err) {
+ next(err)
+ return
+ }
+
+ const { result } = data
+ clients.audit.post('/append', {
+ calc: first + '+' + second,
+ calcResult: result
+ }, (err) => {
+ if (err) console.error(err)
+ })
+
+ res.render('add', { first, second, result })
+ }
+ )
+}
+
+module.exports = router
diff --git a/Chapter11/deploying-to-the-cloud/micro/webapp/routes/audit.js b/Chapter11/deploying-to-the-cloud/micro/webapp/routes/audit.js
new file mode 100644
index 0000000..a083eef
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/webapp/routes/audit.js
@@ -0,0 +1,37 @@
+'use strict'
+
+const { Router } = require('express')
+const restify = require('restify')
+const { dns } = require('concordant')()
+const router = Router()
+var client
+
+router.get('/', resolve, respond)
+
+function resolve (req, res, next) {
+ if (client) {
+ next()
+ return
+ }
+ const auditservice = `_main._tcp.auditservice.micro.svc.cluster.local`
+ dns.resolve(auditservice, (err, locs) => {
+ if (err) {
+ next(err)
+ return
+ }
+ const { host, port } = locs[0]
+ client = restify.createJSONClient(`http://${host}:${port}`)
+ })
+}
+
+function respond (req, res, next) {
+ client.get('/list', (err, svcReq, svcRes, data) => {
+ if (err) {
+ next(err)
+ return
+ }
+ res.render('audit', data)
+ })
+}
+
+module.exports = router
diff --git a/Chapter11/deploying-to-the-cloud/micro/webapp/routes/index.js b/Chapter11/deploying-to-the-cloud/micro/webapp/routes/index.js
new file mode 100644
index 0000000..956680b
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/webapp/routes/index.js
@@ -0,0 +1,9 @@
+var express = require('express')
+var router = express.Router()
+
+/* GET home page. */
+router.get('/', function (req, res, next) {
+ res.render('index', { title: 'Express' })
+})
+
+module.exports = router
diff --git a/Chapter11/deploying-to-the-cloud/micro/webapp/routes/users.js b/Chapter11/deploying-to-the-cloud/micro/webapp/routes/users.js
new file mode 100644
index 0000000..8cfe88f
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/webapp/routes/users.js
@@ -0,0 +1,9 @@
+var express = require('express')
+var router = express.Router()
+
+/* GET users listing. */
+router.get('/', function (req, res, next) {
+ res.send('respond with a resource')
+})
+
+module.exports = router
diff --git a/Chapter11/deploying-to-the-cloud/micro/webapp/views/add.ejs b/Chapter11/deploying-to-the-cloud/micro/webapp/views/add.ejs
new file mode 100644
index 0000000..da8279c
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/webapp/views/add.ejs
@@ -0,0 +1,16 @@
+
+
+
+ Add
+
+
+
+ Add it up!
+
+ Submit
+ result = <%= result %>
+
+
diff --git a/Chapter11/deploying-to-the-cloud/micro/webapp/views/audit.ejs b/Chapter11/deploying-to-the-cloud/micro/webapp/views/audit.ejs
new file mode 100644
index 0000000..dd6d71b
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/webapp/views/audit.ejs
@@ -0,0 +1,15 @@
+
+
+
+ Audit
+
+
+
+ Calculation History
+
+ <% list.forEach(function (el) { %>
+ at: <%= new Date(el.ts).toLocaleString() %>, calculated: <%= el.calc %>, result: <%= el.result %>
+ <% }) %>
+
+
+
\ No newline at end of file
diff --git a/Chapter11/deploying-to-the-cloud/micro/webapp/views/error.ejs b/Chapter11/deploying-to-the-cloud/micro/webapp/views/error.ejs
new file mode 100644
index 0000000..7cf94ed
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/webapp/views/error.ejs
@@ -0,0 +1,3 @@
+<%= message %>
+<%= error.status %>
+<%= error.stack %>
diff --git a/Chapter11/deploying-to-the-cloud/micro/webapp/views/index.ejs b/Chapter11/deploying-to-the-cloud/micro/webapp/views/index.ejs
new file mode 100644
index 0000000..7b7a1d6
--- /dev/null
+++ b/Chapter11/deploying-to-the-cloud/micro/webapp/views/index.ejs
@@ -0,0 +1,11 @@
+
+
+
+ <%= title %>
+
+
+
+ <%= title %>
+ Welcome to <%= title %>
+
+
diff --git a/Chapter11/running-a-docker-registry/micro/adderservice/.dockerignore b/Chapter11/running-a-docker-registry/micro/adderservice/.dockerignore
new file mode 100644
index 0000000..90718e6
--- /dev/null
+++ b/Chapter11/running-a-docker-registry/micro/adderservice/.dockerignore
@@ -0,0 +1,4 @@
+.git
+.gitignore
+node_modules
+npm-debug.log
diff --git a/Chapter11/running-a-docker-registry/micro/adderservice/Dockerfile b/Chapter11/running-a-docker-registry/micro/adderservice/Dockerfile
new file mode 100644
index 0000000..4d2e56b
--- /dev/null
+++ b/Chapter11/running-a-docker-registry/micro/adderservice/Dockerfile
@@ -0,0 +1,7 @@
+FROM node:slim
+RUN mkdir -p /home/node/service
+WORKDIR /home/node/service
+COPY package.json /home/node/service
+RUN npm install
+COPY . /home/node/service
+CMD [ "node", "index.js" ]
diff --git a/Chapter11/running-a-docker-registry/micro/adderservice/index.js b/Chapter11/running-a-docker-registry/micro/adderservice/index.js
new file mode 100644
index 0000000..753a4ce
--- /dev/null
+++ b/Chapter11/running-a-docker-registry/micro/adderservice/index.js
@@ -0,0 +1,6 @@
+'use strict'
+
+const wiring = require('./wiring')
+const service = require('./service')()
+
+wiring(service)
\ No newline at end of file
diff --git a/Chapter11/running-a-docker-registry/micro/adderservice/package.json b/Chapter11/running-a-docker-registry/micro/adderservice/package.json
new file mode 100644
index 0000000..595a5b0
--- /dev/null
+++ b/Chapter11/running-a-docker-registry/micro/adderservice/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "adder-service",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "Peter Elger ",
+ "license": "ISC",
+ "dependencies": {
+ "restify": "^4.3.0"
+ }
+}
diff --git a/Chapter11/running-a-docker-registry/micro/adderservice/service.js b/Chapter11/running-a-docker-registry/micro/adderservice/service.js
new file mode 100644
index 0000000..af95084
--- /dev/null
+++ b/Chapter11/running-a-docker-registry/micro/adderservice/service.js
@@ -0,0 +1,13 @@
+'use strict'
+
+module.exports = service
+
+function service () {
+ function add (args, cb) {
+ const {first, second} = args
+ const result = (parseInt(first, 10) + parseInt(second, 10))
+ cb(null, {result: result.toString()})
+ }
+
+ return { add }
+}
\ No newline at end of file
diff --git a/Chapter11/running-a-docker-registry/micro/adderservice/wiring.js b/Chapter11/running-a-docker-registry/micro/adderservice/wiring.js
new file mode 100644
index 0000000..0e81c6c
--- /dev/null
+++ b/Chapter11/running-a-docker-registry/micro/adderservice/wiring.js
@@ -0,0 +1,26 @@
+'use strict'
+
+const restify = require('restify')
+const { ADDERSERVICE_SERVICE_PORT } = process.env
+
+module.exports = wiring
+
+function wiring (service) {
+ const server = restify.createServer()
+
+ server.get('/add/:first/:second', (req, res, next) => {
+ service.add(req.params, (err, result) => {
+ if (err) {
+ res.send(err)
+ next()
+ return
+ }
+ res.send(200, result)
+ next()
+ })
+ })
+
+ server.listen(ADDERSERVICE_SERVICE_PORT, '0.0.0.0', () => {
+ console.log('%s listening at %s', server.name, server.url)
+ })
+}
\ No newline at end of file
diff --git a/Chapter11/running-a-docker-registry/micro/certs/localhost.crt b/Chapter11/running-a-docker-registry/micro/certs/localhost.crt
new file mode 100644
index 0000000..ba20df3
--- /dev/null
+++ b/Chapter11/running-a-docker-registry/micro/certs/localhost.crt
@@ -0,0 +1,34 @@
+-----BEGIN CERTIFICATE-----
+MIIF9DCCA9ygAwIBAgIJANNfvhUTrvvCMA0GCSqGSIb3DQEBCwUAMFkxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xNzA0MjQxNTEx
+MDJaFw0xODA0MjQxNTExMDJaMFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21l
+LVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNV
+BAMTCWxvY2FsaG9zdDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMAG
++IOL0sVf1S/4YZ9bzMgIb+n9Z4f5EZQ+6Cs43ohmFpnNcHPw5GsW8I4rKNBmA+Lh
+Bgix9HwjWi7ZagbCYErw3avFRA9xSd8MUelLuIzSfhKZVSsMeDNiE1z9PtdkTASz
+i2bSgxx4iH1uGH0WDwC84vzMiIX3gNGsbThGVcjG7IFuS4QdeUA2n8zLcPG6lRLB
+gPXD6v21bVKBo7FS7UthLWsa1dhvUKk/dpMaVMXINCS90TkyXxIjTkfy8epwD+Vr
+98CLboooqt1ETwSdNii0QtP+6fkZ5dhjMfOLmuJ70bEGeK95CjW2ajRDwooNbp0j
+dzdPfYGj2tuYQfKf+EJLanTE1W3ohUCAwYpmD8xZ+tsNKxcvCFy8iypBMRiyKKLr
+swVaaO3qaM/suGSeTtKGBj2HKPU2IlTvjHfa/sogJyyHdhye0Wy1xQijsDzgL9x3
++PfoprlQ6mhGCTi4q+oJnmqVq6A6xdvo6L/pYbh/6C5GcdUoILAjL3q4bFxt+PEP
+AX+ZYpiZy9q3qRizZuysVMBcNbyNOH9uGXpX13QBjL39rQmIel3ieAmwB4LWsM+x
+6+JmPtA/UJ4WyImbUQMW9PM0KQdEkv569VBg6jo0hwjuMaliPCbZZIqBqXpTzAyr
+ctMSu6HMmYmicmsThqlHk/C027YqEnRZJao4jz9pAgMBAAGjgb4wgbswHQYDVR0O
+BBYEFGHzPwViRuKw8MgGm+HhTnox0k/aMIGLBgNVHSMEgYMwgYCAFGHzPwViRuKw
+8MgGm+HhTnox0k/aoV2kWzBZMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1T
+dGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRIwEAYDVQQD
+Ewlsb2NhbGhvc3SCCQDTX74VE677wjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEB
+CwUAA4ICAQBPdvbD/IQGPiW4GOxux+tmF83ZDNI/mA4WaNcEsn/edm89TYowktkZ
+6f7uMak4CBp0M+1wVGmi0iuM+GrQNysnVbo918+W3OPV1ukons1U6DyzT/0ObsiG
+3Iicos64xK5u95AdTvH1Hk0nyGN6Q+O9Pp/clRNA3ulESCjFMOr6pTSjxBZbb1CP
+87waZrIB/NVOxFQKPkqJd8e8Q4tiG5e5/ZmhuoYlcU82tTH7dqYFhwQMCvLkZjbv
+c9vBcImfS0Qz7nTgh3ZzZIsAyo1BOj24SxRwM/8sCf051VWIJnd0ds4LploeL6dK
+QntMK5hVHFLwdfx5glbOSFr1bLGuFQufZSI36bIpkYPd7clmVSwt/tD3C+YEhPBq
+7s/DZj8fyvNFxg2tBH6Lw0Z8QM+UVC8eQNUbaVuVDmw3M5/4szDt98hrniUBW4zF
+Ep7J3ERSSB7KRwlj623KaF+9EhWKFQTazcVxtSF7a2UmTMuXpIlWgATbYPsXP5fE
+wivMvYjXiJrdUuJoAXaGMU+Eo7yT5oEZgRr4LeoOjaCDeWCpoMPxsZ3JnNWqodCN
+ZQxGEewNrt/fHdEaEIO79R24t1so4be3vi/1fUU/l4ewdw0ImQvC6IZAFrCUlOsL
+rvwPu37M6Mgj4VZ6tcfNWgPUGuhpK9TFIF4R2l0heeHsOs/tpxb3bw==
+-----END CERTIFICATE-----
diff --git a/Chapter11/running-a-docker-registry/micro/certs/localhost.key b/Chapter11/running-a-docker-registry/micro/certs/localhost.key
new file mode 100644
index 0000000..1415238
--- /dev/null
+++ b/Chapter11/running-a-docker-registry/micro/certs/localhost.key
@@ -0,0 +1,51 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIJKAIBAAKCAgEAwAb4g4vSxV/VL/hhn1vMyAhv6f1nh/kRlD7oKzjeiGYWmc1w
+c/Dkaxbwjiso0GYD4uEGCLH0fCNaLtlqBsJgSvDdq8VED3FJ3wxR6Uu4jNJ+EplV
+Kwx4M2ITXP0+12RMBLOLZtKDHHiIfW4YfRYPALzi/MyIhfeA0axtOEZVyMbsgW5L
+hB15QDafzMtw8bqVEsGA9cPq/bVtUoGjsVLtS2EtaxrV2G9QqT92kxpUxcg0JL3R
+OTJfEiNOR/Lx6nAP5Wv3wItuiiiq3URPBJ02KLRC0/7p+Rnl2GMx84ua4nvRsQZ4
+r3kKNbZqNEPCig1unSN3N099gaPa25hB8p/4QktqdMTVbeiFQIDBimYPzFn62w0r
+Fy8IXLyLKkExGLIoouuzBVpo7epoz+y4ZJ5O0oYGPYco9TYiVO+Md9r+yiAnLId2
+HJ7RbLXFCKOwPOAv3Hf49+imuVDqaEYJOLir6gmeapWroDrF2+jov+lhuH/oLkZx
+1SggsCMverhsXG348Q8Bf5limJnL2repGLNm7KxUwFw1vI04f24ZelfXdAGMvf2t
+CYh6XeJ4CbAHgtawz7Hr4mY+0D9QnhbIiZtRAxb08zQpB0SS/nr1UGDqOjSHCO4x
+qWI8JtlkioGpelPMDKty0xK7ocyZiaJyaxOGqUeT8LTbtioSdFklqjiPP2kCAwEA
+AQKCAgBEbzeJyf7nkGutmNtRq6mjcD5SKZkIAF+fcXvUS4QjIB7V2T1GVIdTEu3P
+/Nmiy3h/FqrL1n/G7eJp59m9ZvBiRMz7NmY3CqzE7OMUPYQby7hacILFwL/lsAIu
+laIbqQbedg4sKsHHF72s1dusWHwoSyip50qQU9B46PZTo91WnG4VAnWvM8HOfKke
+lzI9M72E3alT7OqGWG4JhWINA/zWbHB/RyUG6UTZzGA5tJyZj+vlXDCALc9r62Eu
+iwpj/mPr4zp8SDSw0CV0ja+LeWufvf5DBwnjWLNtFozqusoMBQyKBkBkaMKWudfH
+oI5oHLlbDxShhZa46OhKckyuZwxWFMO7Hv+3TTLik0Sg3bXTs9iCiXhWI+21le1E
+LLpZRM36q1UBrXCuzPfenbMkI6OFp+Nz5HVDHf1gRPoYRgVhyrIawc3Bs1lUWFx/
+BM/CdDtabMS+kv3eydXtQ3AkkRxlLSAkqYAXG3kd3Rnyzmxb8+cbTBnkBY1hI4fr
+43g30Bg0pinUvSAKoGFTkoA99FKvekjyOQBo62/GiCAYqO5DG7/lHrF6LHWuJ6/H
+CgLVTjqUpd4bpcLSokmT9cF43f/vdrRotM2xvd/MsGisiG/0dO1788OR4biUIav3
+1RuSF8YXBVGUfQOvsgcK3Gwvrc0CtWa6Qv7Cu6TwQ6AE1sei8QKCAQEA+uuIl/UB
+AhNXpqQsk72fgUMxAJXjiuZc/Ed66kewxLqAQRaT1OtrjqcyY1XyFL4TCVAzTk5v
+H1N7aer8urVwFtN7r3xQmWrlJI+ANhx0L6b/smxR0/ndk6sI9IRSBFOgJrRFKpR3
+Cwdn7hi5W6WeSSMTQwIgI+e+VdUz2ByHhImQkcIFk9hXqs9dBm9ud8yr0qa6ebet
+0jLc8RCekQCoo5wV+UfSy9ev/G/nDCV7FGsnhQe7b21m7OIQ8RjhNNy7A/dlrhnY
+pD6IxnMQY3tbwqwJjip/eZJyLNdngbLVw0nkxNyJg0s0Ypx0eR3Iy0I3BzKrCTip
+BDCG3TMAtFzmewKCAQEAw+o1O1uONf8LBqsFq9dPrvn5CGLfxC8MNNsYlcFPvD8Y
+9naYg9earFrDk0YaxtygUjcHX43vO6y3CRckZzThB0+vZdizablle4qYapv9m7uw
++JeHb/OlPtcJG57bNC5OQQgHD5TldCMDt48uwPPjfq7XjCuHmeMzpQFejuTcwaAU
+Fqv04ar2nWUOrvKL4x6/2mujkS2sCpbz/m5CUIStAWXtWT9HrtOUXevqEBxv1K/9
+I29LLhO94hp6vYsFIltoDPw+dhL39WmboIuUBmpjGrfIKbYq2/ccxtspNrrkttNH
+YvaifVR91h6c59bkBnOHOdTj4aPz/9/Hxd1C+wGeawKCAQBXz4ua2jQDHO/Espmj
+Dm4+l1wTv2DFID8UTpOWX4ZeIoJ1xMfxcH3Qi3SXzOEOH0KcNMPvuIUs6lM7SvAt
+CwfyBQq47AFs3zrXo5yT0ztZ3dCICV5Nl5jSb72PjKsDNpzKTrYR46kRZAMcEOz1
+RK1zHOJFxf2ncxdqBFXDYKCQYnAEgmjfR4vOjAkbEm4PYMIU0yJZLE2ZTRGDD3Te
+e+OIdjw+Y8NCcPX2mta5qng7OhC3fnA624d0iNyy8ykFDeYoyjB8UDE/sV6+TFBE
+8Eu5gelKJlc8HWL1jGB9xC8Iy9hSiHdbSjtH80bTh/fYIhEN5M50SK7ld3ILASlS
+9Vv3AoIBAG4AzY7co4cSA2DlGjQYmzdGSFw7GCWRvSrYcn9zoY+zZhLNGGm+36wy
+8ml5DYPnUWWYXF181n0NR2ClS91fRZLTXUq0WFjermqlsVr653VP99g9TODBT2Fv
+YD/P/IjaDzpYhY5sLkH9fxLMJJZW2r/A8GpV7reraM4XbL6TJpjZhy4Ls14anopV
+ud7ldUI7e8HqelcD6/uuMqYDxtxrArEsSA66h0dUqZPq6OvO68PiZSJGVVIz54RT
+FePjcSiPZmcUIwYtNGjpuWZ1uNG5Xpgb5Rn2nS4RHGlTmVqPqeg1zXl1vlrc3CMj
+4ToT4Mj2iVEhhiql4lUjk4o36GoDyK0CggEBAI1Nw1tna7iU1jLcOaCcaXALa8qI
+AqjmtmUeQAD9zvjZaWfU9wS8EK2UNB02fG16Fxd1MMNzU9+rzZ2dmUyEFjGbRG8L
+nexR1lAuGdALP2lvU9GhBCSkZ9BpBIfaH3n/j0fWG6LKdAjnoyYCtazTqCSESQFH
+jymtnlX40y27FoBV5hKVM7pgjxpBusRTm4r08Ml7slzqQXxuaQc/x9y1db3lXtTq
+NSXP+wYTPFIwqpC9GWokZLGhF/Il//jKKOymUMKIQX0gYJ9Zt1sPUtOZs8saEZZA
+uVFa27HCSCQVNQImCQ0vQZmXd7g7xeTvFFo4hpfKGyMclHT13hCwm18rmNw=
+-----END RSA PRIVATE KEY-----
diff --git a/Chapter11/storing-images-on-dockerhub/micro/adderservice/.dockerignore b/Chapter11/storing-images-on-dockerhub/micro/adderservice/.dockerignore
new file mode 100644
index 0000000..90718e6
--- /dev/null
+++ b/Chapter11/storing-images-on-dockerhub/micro/adderservice/.dockerignore
@@ -0,0 +1,4 @@
+.git
+.gitignore
+node_modules
+npm-debug.log
diff --git a/Chapter11/storing-images-on-dockerhub/micro/adderservice/Dockerfile b/Chapter11/storing-images-on-dockerhub/micro/adderservice/Dockerfile
new file mode 100644
index 0000000..4d2e56b
--- /dev/null
+++ b/Chapter11/storing-images-on-dockerhub/micro/adderservice/Dockerfile
@@ -0,0 +1,7 @@
+FROM node:slim
+RUN mkdir -p /home/node/service
+WORKDIR /home/node/service
+COPY package.json /home/node/service
+RUN npm install
+COPY . /home/node/service
+CMD [ "node", "index.js" ]
diff --git a/Chapter11/storing-images-on-dockerhub/micro/adderservice/index.js b/Chapter11/storing-images-on-dockerhub/micro/adderservice/index.js
new file mode 100644
index 0000000..753a4ce
--- /dev/null
+++ b/Chapter11/storing-images-on-dockerhub/micro/adderservice/index.js
@@ -0,0 +1,6 @@
+'use strict'
+
+const wiring = require('./wiring')
+const service = require('./service')()
+
+wiring(service)
\ No newline at end of file
diff --git a/Chapter11/storing-images-on-dockerhub/micro/adderservice/package.json b/Chapter11/storing-images-on-dockerhub/micro/adderservice/package.json
new file mode 100644
index 0000000..595a5b0
--- /dev/null
+++ b/Chapter11/storing-images-on-dockerhub/micro/adderservice/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "adder-service",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "Peter Elger ",
+ "license": "ISC",
+ "dependencies": {
+ "restify": "^4.3.0"
+ }
+}
diff --git a/Chapter11/storing-images-on-dockerhub/micro/adderservice/service.js b/Chapter11/storing-images-on-dockerhub/micro/adderservice/service.js
new file mode 100644
index 0000000..af95084
--- /dev/null
+++ b/Chapter11/storing-images-on-dockerhub/micro/adderservice/service.js
@@ -0,0 +1,13 @@
+'use strict'
+
+module.exports = service
+
+function service () {
+ function add (args, cb) {
+ const {first, second} = args
+ const result = (parseInt(first, 10) + parseInt(second, 10))
+ cb(null, {result: result.toString()})
+ }
+
+ return { add }
+}
\ No newline at end of file
diff --git a/Chapter11/storing-images-on-dockerhub/micro/adderservice/wiring.js b/Chapter11/storing-images-on-dockerhub/micro/adderservice/wiring.js
new file mode 100644
index 0000000..0e81c6c
--- /dev/null
+++ b/Chapter11/storing-images-on-dockerhub/micro/adderservice/wiring.js
@@ -0,0 +1,26 @@
+'use strict'
+
+const restify = require('restify')
+const { ADDERSERVICE_SERVICE_PORT } = process.env
+
+module.exports = wiring
+
+function wiring (service) {
+ const server = restify.createServer()
+
+ server.get('/add/:first/:second', (req, res, next) => {
+ service.add(req.params, (err, result) => {
+ if (err) {
+ res.send(err)
+ next()
+ return
+ }
+ res.send(200, result)
+ next()
+ })
+ })
+
+ server.listen(ADDERSERVICE_SERVICE_PORT, '0.0.0.0', () => {
+ console.log('%s listening at %s', server.name, server.url)
+ })
+}
\ No newline at end of file
diff --git a/Chapter11/storing-images-on-dockerhub/test.sh b/Chapter11/storing-images-on-dockerhub/test.sh
new file mode 100644
index 0000000..5086478
--- /dev/null
+++ b/Chapter11/storing-images-on-dockerhub/test.sh
@@ -0,0 +1 @@
+curl http://localhost:8080/add/2/3