diff --git a/pipy/README.md b/pipy/README.md new file mode 100644 index 0000000000000000000000000000000000000000..5e9f4320c67fa2fc7f446a8e2da86b6b9edcbc37 --- /dev/null +++ b/pipy/README.md @@ -0,0 +1,15 @@ +## 测试 + ++ 简单示例 + + ```shell + pipy tutorial/01-hello/hello.js + ``` + ++ 指定web界面端口 + + ```shell + pipy tutorial/01-hello/hello.js --admin-port=6060 + ``` + + diff --git a/pipy/pipy-0.30.0-1.src.rpm b/pipy/pipy-0.30.0-1.src.rpm new file mode 100644 index 0000000000000000000000000000000000000000..26424b6ae5af65b50638da020d874e6ef90b5875 Binary files /dev/null and b/pipy/pipy-0.30.0-1.src.rpm differ diff --git a/pipy/pipy-0.30.0-1.x86_64.rpm b/pipy/pipy-0.30.0-1.x86_64.rpm new file mode 100644 index 0000000000000000000000000000000000000000..416dc6a7cacfb1f7606940201f14d43fd3547551 Binary files /dev/null and b/pipy/pipy-0.30.0-1.x86_64.rpm differ diff --git a/pipy/pipy.spec b/pipy/pipy.spec new file mode 100644 index 0000000000000000000000000000000000000000..376695c15ba71960917a2ae897893a7714d0f235 --- /dev/null +++ b/pipy/pipy.spec @@ -0,0 +1,68 @@ +Name: pipy +Version: 0.30.0 +Release: 1%{?dist} +Summary: Pipy is a programmable network proxy for the cloud, edge and IoT. +License: NEU License +Source0: https://github.com/flomesh-io/pipy/archive/refs/tags/0.30.0-23.tar.gz +BuildRequires: cmake3 +BuildRequires: clang +BuildRequires: nodejs >= 12 +BuildRequires: zlib +BuildRequires: libstdc++-static +BuildRequires: chrpath +#AutoReqProv: no +%define revision %{release} +%define prefix /usr/local +%define PIPY_GUI ON +%global PIPY_STATIC ON +%global BUILD_TYPE Release +%global debug_package %{nil} + +%description +Pipy is a tiny, high performance, highly stable, programmable proxy. + +%prep +%setup -q -n %{name}-0.30.0-23 + +%build +if [ %{PIPY_GUI} == "ON" ] ; then + npm install + npm run build +fi +%{__mkdir} build +cd build +CXX=clang++ CC=clang cmake3 -DPIPY_GUI=%{PIPY_GUI} -DPIPY_STATIC=ON -DPIPY_TUTORIAL=%{PIPY_GUI} -DCMAKE_BUILD_TYPE=Release .. +make %{?_smp_mflags} +cd .. + +%preun + +systemctl --no-reload disable pipy.service > /dev/null 2>&1 || true +systemctl stop pipy.service > /dev/null 2>&1 || true + + + +%pre +getent group pipy >/dev/null || groupadd -r pipy +getent passwd pipy >/dev/null || useradd -r -g pipy -G pipy -d /etc/pipy -s /sbin/nologin -c "pipy" pipy + + +%install +mkdir -p %{buildroot}%{prefix}/bin +mkdir -p %{buildroot}/etc/pipy +cp bin/pipy %{buildroot}%{prefix}/bin +# chrpath --delete %{buildroot}%{prefix}/bin/pipy + +%post + +%postun +if [ $1 -eq 0 ] ; then + userdel pipy 2> /dev/null || true +fi + + +%files +%attr(755, pipy, pipy) %{prefix}/bin/pipy + +%changelog + diff --git a/pipy/tutorial/01-hello/hello.js b/pipy/tutorial/01-hello/hello.js new file mode 100644 index 0000000000000000000000000000000000000000..92a299c5671c2a788f86fafadb4cdd202981f9b9 --- /dev/null +++ b/pipy/tutorial/01-hello/hello.js @@ -0,0 +1,6 @@ +pipy() + +.listen(8080) + .serveHTTP( + new Message('Hi, there!\n') + ) diff --git a/pipy/tutorial/02-echo/hello.js b/pipy/tutorial/02-echo/hello.js new file mode 100644 index 0000000000000000000000000000000000000000..c158f6d079f4e5b5031412b0a46bbfd4c2dc1f8d --- /dev/null +++ b/pipy/tutorial/02-echo/hello.js @@ -0,0 +1,18 @@ +pipy() + +.listen(8080) + .serveHTTP( + new Message('Hi, there!\n') + ) + +.listen(8081) + .serveHTTP( + msg => new Message(msg.body) + ) + +.listen(8082) + .serveHTTP( + msg => new Message( + `You are requesting ${msg.head.path} from ${__inbound.remoteAddress}\n` + ) + ) diff --git a/pipy/tutorial/03-proxy/proxy.js b/pipy/tutorial/03-proxy/proxy.js new file mode 100644 index 0000000000000000000000000000000000000000..f2e6593b35514353405cbf7ba94969592cf329aa --- /dev/null +++ b/pipy/tutorial/03-proxy/proxy.js @@ -0,0 +1,10 @@ +pipy() + +.listen(8000) + .demuxHTTP('forward') + +.pipeline('forward') + .muxHTTP('connection', '') + +.pipeline('connection') + .connect('localhost:8080') diff --git a/pipy/tutorial/04-routing/proxy.js b/pipy/tutorial/04-routing/proxy.js new file mode 100644 index 0000000000000000000000000000000000000000..6467344145c8fa54d2a17ef33ee166c5f36cf749 --- /dev/null +++ b/pipy/tutorial/04-routing/proxy.js @@ -0,0 +1,42 @@ +pipy({ + _router: new algo.URLRouter({ + '/hi/*': 'localhost:8080', + '/echo': 'localhost:8081', + '/ip/*': 'localhost:8082', + }), + + _target: '', +}) + +.listen(8000) + .demuxHTTP('request') + +.pipeline('request') + .handleMessageStart( + msg => ( + _target = _router.find( + msg.head.headers.host, + msg.head.path, + ) + ) + ) + .link( + 'forward', () => Boolean(_target), + '404' + ) + +.pipeline('forward') + .muxHTTP( + 'connection', + () => _target + ) + +.pipeline('connection') + .connect( + () => _target + ) + +.pipeline('404') + .replaceMessage( + new Message({ status: 404 }, 'No route') + ) diff --git a/pipy/tutorial/05-plugins/plugins/default.js b/pipy/tutorial/05-plugins/plugins/default.js new file mode 100644 index 0000000000000000000000000000000000000000..ad2165691db61e45de67ba52a7558d5c74fc4f0c --- /dev/null +++ b/pipy/tutorial/05-plugins/plugins/default.js @@ -0,0 +1,6 @@ +pipy() + +.pipeline('request') + .replaceMessage( + new Message({ status: 404 }, 'No handler') + ) diff --git a/pipy/tutorial/05-plugins/plugins/router.js b/pipy/tutorial/05-plugins/plugins/router.js new file mode 100644 index 0000000000000000000000000000000000000000..7b0fc119ff3ffd274bff88563d9879fd31cc16e3 --- /dev/null +++ b/pipy/tutorial/05-plugins/plugins/router.js @@ -0,0 +1,39 @@ +pipy({ + _router: new algo.URLRouter({ + '/hi/*': 'localhost:8080', + '/echo': 'localhost:8081', + '/ip/*': 'localhost:8082', + }), + + _target: '', +}) + +.import({ + __turnDown: 'proxy', +}) + +.pipeline('request') + .handleMessageStart( + msg => ( + _target = _router.find( + msg.head.headers.host, + msg.head.path, + ), + _target && (__turnDown = true) + ) + ) + .link( + 'forward', () => Boolean(_target), + '' + ) + +.pipeline('forward') + .muxHTTP( + 'connection', + () => _target + ) + +.pipeline('connection') + .connect( + () => _target + ) diff --git a/pipy/tutorial/05-plugins/proxy.js b/pipy/tutorial/05-plugins/proxy.js new file mode 100644 index 0000000000000000000000000000000000000000..327875bf3cebafaa850388e4b988c384f6504f99 --- /dev/null +++ b/pipy/tutorial/05-plugins/proxy.js @@ -0,0 +1,19 @@ +pipy() + +.export('proxy', { + __turnDown: false, +}) + +.listen(8000) + .demuxHTTP('request') + +.pipeline('request') + .use( + [ + 'plugins/router.js', + 'plugins/default.js', + ], + 'request', + 'response', + () => __turnDown + ) diff --git a/pipy/tutorial/06-configuration/config/proxy.json b/pipy/tutorial/06-configuration/config/proxy.json new file mode 100644 index 0000000000000000000000000000000000000000..3126d1b6cd213283de86773f79e950eed0315022 --- /dev/null +++ b/pipy/tutorial/06-configuration/config/proxy.json @@ -0,0 +1,7 @@ +{ + "listen": 8000, + "plugins": [ + "plugins/router.js", + "plugins/default.js" + ] +} diff --git a/pipy/tutorial/06-configuration/config/router.json b/pipy/tutorial/06-configuration/config/router.json new file mode 100644 index 0000000000000000000000000000000000000000..6b7f1fef95a779bc2fa188b1e7317e7bd6bf7993 --- /dev/null +++ b/pipy/tutorial/06-configuration/config/router.json @@ -0,0 +1,7 @@ +{ + "routes": { + "/hi/*": "localhost:8080", + "/echo": "localhost:8081", + "/ip/*": "localhost:8082" + } +} diff --git a/pipy/tutorial/06-configuration/plugins/default.js b/pipy/tutorial/06-configuration/plugins/default.js new file mode 100644 index 0000000000000000000000000000000000000000..ad2165691db61e45de67ba52a7558d5c74fc4f0c --- /dev/null +++ b/pipy/tutorial/06-configuration/plugins/default.js @@ -0,0 +1,6 @@ +pipy() + +.pipeline('request') + .replaceMessage( + new Message({ status: 404 }, 'No handler') + ) diff --git a/pipy/tutorial/06-configuration/plugins/router.js b/pipy/tutorial/06-configuration/plugins/router.js new file mode 100644 index 0000000000000000000000000000000000000000..11aea147747e416eb64f5cb9e22773509f0eef8f --- /dev/null +++ b/pipy/tutorial/06-configuration/plugins/router.js @@ -0,0 +1,38 @@ +(config => + +pipy({ + _router: new algo.URLRouter(config.routes), + _target: '', +}) + +.import({ + __turnDown: 'proxy', +}) + +.pipeline('request') + .handleMessageStart( + msg => ( + _target = _router.find( + msg.head.headers.host, + msg.head.path, + ), + _target && (__turnDown = true) + ) + ) + .link( + 'forward', () => Boolean(_target), + '' + ) + +.pipeline('forward') + .muxHTTP( + 'connection', + () => _target + ) + +.pipeline('connection') + .connect( + () => _target + ) + +)(JSON.decode(pipy.load('config/router.json'))) diff --git a/pipy/tutorial/06-configuration/proxy.js b/pipy/tutorial/06-configuration/proxy.js new file mode 100644 index 0000000000000000000000000000000000000000..49fb530422dc4cf921ba49310f3a2b2275df987e --- /dev/null +++ b/pipy/tutorial/06-configuration/proxy.js @@ -0,0 +1,20 @@ +(config => + +pipy() + +.export('proxy', { + __turnDown: false, +}) + +.listen(config.listen) + .demuxHTTP('request') + +.pipeline('request') + .use( + config.plugins, + 'request', + 'response', + () => __turnDown + ) + +)(JSON.decode(pipy.load('config/proxy.json'))) diff --git a/pipy/tutorial/07-load-balancing/config/balancer.json b/pipy/tutorial/07-load-balancing/config/balancer.json new file mode 100644 index 0000000000000000000000000000000000000000..047a6413ce88727d8654331c2325b02288971c22 --- /dev/null +++ b/pipy/tutorial/07-load-balancing/config/balancer.json @@ -0,0 +1,7 @@ +{ + "services": { + "service-hi" : ["127.0.0.1:8080", "127.0.0.1:8082"], + "service-echo" : ["127.0.0.1:8081"], + "service-tell-ip" : ["127.0.0.1:8082"] + } +} diff --git a/pipy/tutorial/07-load-balancing/config/proxy.json b/pipy/tutorial/07-load-balancing/config/proxy.json new file mode 100644 index 0000000000000000000000000000000000000000..208a199a284343980a57c39fda4c40058dd0ea54 --- /dev/null +++ b/pipy/tutorial/07-load-balancing/config/proxy.json @@ -0,0 +1,8 @@ +{ + "listen": 8000, + "plugins": [ + "plugins/router.js", + "plugins/balancer.js", + "plugins/default.js" + ] +} diff --git a/pipy/tutorial/07-load-balancing/config/router.json b/pipy/tutorial/07-load-balancing/config/router.json new file mode 100644 index 0000000000000000000000000000000000000000..c27d43ed765f7a61915d7e82d512917794c53778 --- /dev/null +++ b/pipy/tutorial/07-load-balancing/config/router.json @@ -0,0 +1,7 @@ +{ + "routes": { + "/hi/*": "service-hi", + "/echo": "service-echo", + "/ip/*": "service-tell-ip" + } +} diff --git a/pipy/tutorial/07-load-balancing/plugins/balancer.js b/pipy/tutorial/07-load-balancing/plugins/balancer.js new file mode 100644 index 0000000000000000000000000000000000000000..7627f07a9acccc2f32d0134fd924f7a8e0fbe719 --- /dev/null +++ b/pipy/tutorial/07-load-balancing/plugins/balancer.js @@ -0,0 +1,45 @@ +(config => + +pipy({ + _services: ( + Object.fromEntries( + Object.entries(config.services).map( + ([k, v]) => [ + k, new algo.RoundRobinLoadBalancer(v) + ] + ) + ) + ), + + _target: '', +}) + +.import({ + __turnDown: 'proxy', + __serviceID: 'router', +}) + +.pipeline('request') + .handleMessageStart( + () => ( + _target = _services[__serviceID]?.select?.(), + _target && (__turnDown = true) + ) + ) + .link( + 'forward', () => Boolean(_target), + '' + ) + +.pipeline('forward') + .muxHTTP( + 'connection', + () => _target + ) + +.pipeline('connection') + .connect( + () => _target + ) + +)(JSON.decode(pipy.load('config/balancer.json'))) diff --git a/pipy/tutorial/07-load-balancing/plugins/default.js b/pipy/tutorial/07-load-balancing/plugins/default.js new file mode 100644 index 0000000000000000000000000000000000000000..ad2165691db61e45de67ba52a7558d5c74fc4f0c --- /dev/null +++ b/pipy/tutorial/07-load-balancing/plugins/default.js @@ -0,0 +1,6 @@ +pipy() + +.pipeline('request') + .replaceMessage( + new Message({ status: 404 }, 'No handler') + ) diff --git a/pipy/tutorial/07-load-balancing/plugins/router.js b/pipy/tutorial/07-load-balancing/plugins/router.js new file mode 100644 index 0000000000000000000000000000000000000000..e3ec4920855d02f84f816302ee58c13d9ae345b7 --- /dev/null +++ b/pipy/tutorial/07-load-balancing/plugins/router.js @@ -0,0 +1,21 @@ +(config => + +pipy({ + _router: new algo.URLRouter(config.routes), +}) + +.export('router', { + __serviceID: '', +}) + +.pipeline('request') + .handleMessageStart( + msg => ( + __serviceID = _router.find( + msg.head.headers.host, + msg.head.path, + ) + ) + ) + +)(JSON.decode(pipy.load('config/router.json'))) diff --git a/pipy/tutorial/07-load-balancing/proxy.js b/pipy/tutorial/07-load-balancing/proxy.js new file mode 100644 index 0000000000000000000000000000000000000000..49fb530422dc4cf921ba49310f3a2b2275df987e --- /dev/null +++ b/pipy/tutorial/07-load-balancing/proxy.js @@ -0,0 +1,20 @@ +(config => + +pipy() + +.export('proxy', { + __turnDown: false, +}) + +.listen(config.listen) + .demuxHTTP('request') + +.pipeline('request') + .use( + config.plugins, + 'request', + 'response', + () => __turnDown + ) + +)(JSON.decode(pipy.load('config/proxy.json'))) diff --git a/pipy/tutorial/08-load-balancing-improved/config/balancer.json b/pipy/tutorial/08-load-balancing-improved/config/balancer.json new file mode 100644 index 0000000000000000000000000000000000000000..047a6413ce88727d8654331c2325b02288971c22 --- /dev/null +++ b/pipy/tutorial/08-load-balancing-improved/config/balancer.json @@ -0,0 +1,7 @@ +{ + "services": { + "service-hi" : ["127.0.0.1:8080", "127.0.0.1:8082"], + "service-echo" : ["127.0.0.1:8081"], + "service-tell-ip" : ["127.0.0.1:8082"] + } +} diff --git a/pipy/tutorial/08-load-balancing-improved/config/proxy.json b/pipy/tutorial/08-load-balancing-improved/config/proxy.json new file mode 100644 index 0000000000000000000000000000000000000000..208a199a284343980a57c39fda4c40058dd0ea54 --- /dev/null +++ b/pipy/tutorial/08-load-balancing-improved/config/proxy.json @@ -0,0 +1,8 @@ +{ + "listen": 8000, + "plugins": [ + "plugins/router.js", + "plugins/balancer.js", + "plugins/default.js" + ] +} diff --git a/pipy/tutorial/08-load-balancing-improved/config/router.json b/pipy/tutorial/08-load-balancing-improved/config/router.json new file mode 100644 index 0000000000000000000000000000000000000000..c27d43ed765f7a61915d7e82d512917794c53778 --- /dev/null +++ b/pipy/tutorial/08-load-balancing-improved/config/router.json @@ -0,0 +1,7 @@ +{ + "routes": { + "/hi/*": "service-hi", + "/echo": "service-echo", + "/ip/*": "service-tell-ip" + } +} diff --git a/pipy/tutorial/08-load-balancing-improved/plugins/balancer.js b/pipy/tutorial/08-load-balancing-improved/plugins/balancer.js new file mode 100644 index 0000000000000000000000000000000000000000..5c8bbefe609a4cadb8c3293b52c5393f9660f474 --- /dev/null +++ b/pipy/tutorial/08-load-balancing-improved/plugins/balancer.js @@ -0,0 +1,64 @@ +(config => + +pipy({ + _services: ( + Object.fromEntries( + Object.entries(config.services).map( + ([k, v]) => [ + k, new algo.RoundRobinLoadBalancer(v) + ] + ) + ) + ), + + _balancer: null, + _balancerCache: null, + _target: '', +}) + +.import({ + __turnDown: 'proxy', + __serviceID: 'router', +}) + +.pipeline('session') + .handleStreamStart( + () => ( + _balancerCache = new algo.Cache( + // k is a balancer, v is a target + (k ) => k.select(), + (k,v) => k.deselect(v), + ) + ) + ) + .handleStreamEnd( + () => ( + _balancerCache.clear() + ) + ) + +.pipeline('request') + .handleMessageStart( + () => ( + _balancer = _services[__serviceID], + _balancer && (_target = _balancerCache.get(_balancer)), + _target && (__turnDown = true) + ) + ) + .link( + 'forward', () => Boolean(_target), + '' + ) + +.pipeline('forward') + .muxHTTP( + 'connection', + () => _target + ) + +.pipeline('connection') + .connect( + () => _target + ) + +)(JSON.decode(pipy.load('config/balancer.json'))) diff --git a/pipy/tutorial/08-load-balancing-improved/plugins/default.js b/pipy/tutorial/08-load-balancing-improved/plugins/default.js new file mode 100644 index 0000000000000000000000000000000000000000..ad2165691db61e45de67ba52a7558d5c74fc4f0c --- /dev/null +++ b/pipy/tutorial/08-load-balancing-improved/plugins/default.js @@ -0,0 +1,6 @@ +pipy() + +.pipeline('request') + .replaceMessage( + new Message({ status: 404 }, 'No handler') + ) diff --git a/pipy/tutorial/08-load-balancing-improved/plugins/router.js b/pipy/tutorial/08-load-balancing-improved/plugins/router.js new file mode 100644 index 0000000000000000000000000000000000000000..e3ec4920855d02f84f816302ee58c13d9ae345b7 --- /dev/null +++ b/pipy/tutorial/08-load-balancing-improved/plugins/router.js @@ -0,0 +1,21 @@ +(config => + +pipy({ + _router: new algo.URLRouter(config.routes), +}) + +.export('router', { + __serviceID: '', +}) + +.pipeline('request') + .handleMessageStart( + msg => ( + __serviceID = _router.find( + msg.head.headers.host, + msg.head.path, + ) + ) + ) + +)(JSON.decode(pipy.load('config/router.json'))) diff --git a/pipy/tutorial/08-load-balancing-improved/proxy.js b/pipy/tutorial/08-load-balancing-improved/proxy.js new file mode 100644 index 0000000000000000000000000000000000000000..e16f3176d4957aa4b8d158aaf5775aa452a6fd36 --- /dev/null +++ b/pipy/tutorial/08-load-balancing-improved/proxy.js @@ -0,0 +1,21 @@ +(config => + +pipy() + +.export('proxy', { + __turnDown: false, +}) + +.listen(config.listen) + .use(config.plugins, 'session') + .demuxHTTP('request') + +.pipeline('request') + .use( + config.plugins, + 'request', + 'response', + () => __turnDown + ) + +)(JSON.decode(pipy.load('config/proxy.json'))) diff --git a/pipy/tutorial/09-connection-pool/config/balancer.json b/pipy/tutorial/09-connection-pool/config/balancer.json new file mode 100644 index 0000000000000000000000000000000000000000..047a6413ce88727d8654331c2325b02288971c22 --- /dev/null +++ b/pipy/tutorial/09-connection-pool/config/balancer.json @@ -0,0 +1,7 @@ +{ + "services": { + "service-hi" : ["127.0.0.1:8080", "127.0.0.1:8082"], + "service-echo" : ["127.0.0.1:8081"], + "service-tell-ip" : ["127.0.0.1:8082"] + } +} diff --git a/pipy/tutorial/09-connection-pool/config/proxy.json b/pipy/tutorial/09-connection-pool/config/proxy.json new file mode 100644 index 0000000000000000000000000000000000000000..208a199a284343980a57c39fda4c40058dd0ea54 --- /dev/null +++ b/pipy/tutorial/09-connection-pool/config/proxy.json @@ -0,0 +1,8 @@ +{ + "listen": 8000, + "plugins": [ + "plugins/router.js", + "plugins/balancer.js", + "plugins/default.js" + ] +} diff --git a/pipy/tutorial/09-connection-pool/config/router.json b/pipy/tutorial/09-connection-pool/config/router.json new file mode 100644 index 0000000000000000000000000000000000000000..c27d43ed765f7a61915d7e82d512917794c53778 --- /dev/null +++ b/pipy/tutorial/09-connection-pool/config/router.json @@ -0,0 +1,7 @@ +{ + "routes": { + "/hi/*": "service-hi", + "/echo": "service-echo", + "/ip/*": "service-tell-ip" + } +} diff --git a/pipy/tutorial/09-connection-pool/plugins/balancer.js b/pipy/tutorial/09-connection-pool/plugins/balancer.js new file mode 100644 index 0000000000000000000000000000000000000000..a82b0cb05806f55447712c775db4c54da5e7d5e5 --- /dev/null +++ b/pipy/tutorial/09-connection-pool/plugins/balancer.js @@ -0,0 +1,79 @@ +(config => + +pipy({ + _services: ( + Object.fromEntries( + Object.entries(config.services).map( + ([k, v]) => [ + k, new algo.RoundRobinLoadBalancer(v) + ] + ) + ) + ), + + _balancer: null, + _balancerCache: null, + _target: '', + _targetCache: null, + + _g: { + connectionID: 0, + }, + + _connectionPool: new algo.ResourcePool( + () => ++_g.connectionID + ), +}) + +.import({ + __turnDown: 'proxy', + __serviceID: 'router', +}) + +.pipeline('session') + .handleStreamStart( + () => ( + _balancerCache = new algo.Cache( + // k is a balancer, v is a target + (k ) => k.select(), + (k,v) => k.deselect(v), + ), + _targetCache = new algo.Cache( + // k is a target, v is a connection ID + (k ) => _connectionPool.allocate(k), + (k,v) => _connectionPool.free(v), + ) + ) + ) + .handleStreamEnd( + () => ( + _targetCache.clear(), + _balancerCache.clear() + ) + ) + +.pipeline('request') + .handleMessageStart( + () => ( + _balancer = _services[__serviceID], + _balancer && (_target = _balancerCache.get(_balancer)), + _target && (__turnDown = true) + ) + ) + .link( + 'forward', () => Boolean(_target), + '' + ) + +.pipeline('forward') + .muxHTTP( + 'connection', + () => _targetCache.get(_target) + ) + +.pipeline('connection') + .connect( + () => _target + ) + +)(JSON.decode(pipy.load('config/balancer.json'))) diff --git a/pipy/tutorial/09-connection-pool/plugins/default.js b/pipy/tutorial/09-connection-pool/plugins/default.js new file mode 100644 index 0000000000000000000000000000000000000000..ad2165691db61e45de67ba52a7558d5c74fc4f0c --- /dev/null +++ b/pipy/tutorial/09-connection-pool/plugins/default.js @@ -0,0 +1,6 @@ +pipy() + +.pipeline('request') + .replaceMessage( + new Message({ status: 404 }, 'No handler') + ) diff --git a/pipy/tutorial/09-connection-pool/plugins/router.js b/pipy/tutorial/09-connection-pool/plugins/router.js new file mode 100644 index 0000000000000000000000000000000000000000..e3ec4920855d02f84f816302ee58c13d9ae345b7 --- /dev/null +++ b/pipy/tutorial/09-connection-pool/plugins/router.js @@ -0,0 +1,21 @@ +(config => + +pipy({ + _router: new algo.URLRouter(config.routes), +}) + +.export('router', { + __serviceID: '', +}) + +.pipeline('request') + .handleMessageStart( + msg => ( + __serviceID = _router.find( + msg.head.headers.host, + msg.head.path, + ) + ) + ) + +)(JSON.decode(pipy.load('config/router.json'))) diff --git a/pipy/tutorial/09-connection-pool/proxy.js b/pipy/tutorial/09-connection-pool/proxy.js new file mode 100644 index 0000000000000000000000000000000000000000..e16f3176d4957aa4b8d158aaf5775aa452a6fd36 --- /dev/null +++ b/pipy/tutorial/09-connection-pool/proxy.js @@ -0,0 +1,21 @@ +(config => + +pipy() + +.export('proxy', { + __turnDown: false, +}) + +.listen(config.listen) + .use(config.plugins, 'session') + .demuxHTTP('request') + +.pipeline('request') + .use( + config.plugins, + 'request', + 'response', + () => __turnDown + ) + +)(JSON.decode(pipy.load('config/proxy.json'))) diff --git a/pipy/tutorial/10-path-rewriting/config/balancer.json b/pipy/tutorial/10-path-rewriting/config/balancer.json new file mode 100644 index 0000000000000000000000000000000000000000..047a6413ce88727d8654331c2325b02288971c22 --- /dev/null +++ b/pipy/tutorial/10-path-rewriting/config/balancer.json @@ -0,0 +1,7 @@ +{ + "services": { + "service-hi" : ["127.0.0.1:8080", "127.0.0.1:8082"], + "service-echo" : ["127.0.0.1:8081"], + "service-tell-ip" : ["127.0.0.1:8082"] + } +} diff --git a/pipy/tutorial/10-path-rewriting/config/proxy.json b/pipy/tutorial/10-path-rewriting/config/proxy.json new file mode 100644 index 0000000000000000000000000000000000000000..208a199a284343980a57c39fda4c40058dd0ea54 --- /dev/null +++ b/pipy/tutorial/10-path-rewriting/config/proxy.json @@ -0,0 +1,8 @@ +{ + "listen": 8000, + "plugins": [ + "plugins/router.js", + "plugins/balancer.js", + "plugins/default.js" + ] +} diff --git a/pipy/tutorial/10-path-rewriting/config/router.json b/pipy/tutorial/10-path-rewriting/config/router.json new file mode 100644 index 0000000000000000000000000000000000000000..a362f29e055b6761310f1f8d620e32c7ca41b926 --- /dev/null +++ b/pipy/tutorial/10-path-rewriting/config/router.json @@ -0,0 +1,7 @@ +{ + "routes": { + "/hi/*": { "service": "service-hi" }, + "/echo": { "service": "service-echo" }, + "/ip/*": { "service": "service-tell-ip", "rewrite": ["^/ip/?", "/"] } + } +} diff --git a/pipy/tutorial/10-path-rewriting/plugins/balancer.js b/pipy/tutorial/10-path-rewriting/plugins/balancer.js new file mode 100644 index 0000000000000000000000000000000000000000..a82b0cb05806f55447712c775db4c54da5e7d5e5 --- /dev/null +++ b/pipy/tutorial/10-path-rewriting/plugins/balancer.js @@ -0,0 +1,79 @@ +(config => + +pipy({ + _services: ( + Object.fromEntries( + Object.entries(config.services).map( + ([k, v]) => [ + k, new algo.RoundRobinLoadBalancer(v) + ] + ) + ) + ), + + _balancer: null, + _balancerCache: null, + _target: '', + _targetCache: null, + + _g: { + connectionID: 0, + }, + + _connectionPool: new algo.ResourcePool( + () => ++_g.connectionID + ), +}) + +.import({ + __turnDown: 'proxy', + __serviceID: 'router', +}) + +.pipeline('session') + .handleStreamStart( + () => ( + _balancerCache = new algo.Cache( + // k is a balancer, v is a target + (k ) => k.select(), + (k,v) => k.deselect(v), + ), + _targetCache = new algo.Cache( + // k is a target, v is a connection ID + (k ) => _connectionPool.allocate(k), + (k,v) => _connectionPool.free(v), + ) + ) + ) + .handleStreamEnd( + () => ( + _targetCache.clear(), + _balancerCache.clear() + ) + ) + +.pipeline('request') + .handleMessageStart( + () => ( + _balancer = _services[__serviceID], + _balancer && (_target = _balancerCache.get(_balancer)), + _target && (__turnDown = true) + ) + ) + .link( + 'forward', () => Boolean(_target), + '' + ) + +.pipeline('forward') + .muxHTTP( + 'connection', + () => _targetCache.get(_target) + ) + +.pipeline('connection') + .connect( + () => _target + ) + +)(JSON.decode(pipy.load('config/balancer.json'))) diff --git a/pipy/tutorial/10-path-rewriting/plugins/default.js b/pipy/tutorial/10-path-rewriting/plugins/default.js new file mode 100644 index 0000000000000000000000000000000000000000..ad2165691db61e45de67ba52a7558d5c74fc4f0c --- /dev/null +++ b/pipy/tutorial/10-path-rewriting/plugins/default.js @@ -0,0 +1,6 @@ +pipy() + +.pipeline('request') + .replaceMessage( + new Message({ status: 404 }, 'No handler') + ) diff --git a/pipy/tutorial/10-path-rewriting/plugins/router.js b/pipy/tutorial/10-path-rewriting/plugins/router.js new file mode 100644 index 0000000000000000000000000000000000000000..d27b3f19967df53b70b00f4c6ed884cbd3af82a5 --- /dev/null +++ b/pipy/tutorial/10-path-rewriting/plugins/router.js @@ -0,0 +1,47 @@ +(config => + +pipy({ + _router: new algo.URLRouter( + Object.fromEntries( + Object.entries(config.routes).map( + ([k, v]) => [ + k, + { + ...v, + rewrite: v.rewrite ? [ + new RegExp(v.rewrite[0]), + v.rewrite[1], + ] : undefined, + } + ] + ) + ) + ), + + _route: null, +}) + +.export('router', { + __serviceID: '', +}) + +.pipeline('request') + .handleMessageStart( + msg => ( + _route = _router.find( + msg.head.headers.host, + msg.head.path, + ), + _route && ( + __serviceID = _route.service, + _route.rewrite && ( + msg.head.path = msg.head.path.replace( + _route.rewrite[0], + _route.rewrite[1], + ) + ) + ) + ) + ) + +)(JSON.decode(pipy.load('config/router.json'))) diff --git a/pipy/tutorial/10-path-rewriting/proxy.js b/pipy/tutorial/10-path-rewriting/proxy.js new file mode 100644 index 0000000000000000000000000000000000000000..e16f3176d4957aa4b8d158aaf5775aa452a6fd36 --- /dev/null +++ b/pipy/tutorial/10-path-rewriting/proxy.js @@ -0,0 +1,21 @@ +(config => + +pipy() + +.export('proxy', { + __turnDown: false, +}) + +.listen(config.listen) + .use(config.plugins, 'session') + .demuxHTTP('request') + +.pipeline('request') + .use( + config.plugins, + 'request', + 'response', + () => __turnDown + ) + +)(JSON.decode(pipy.load('config/proxy.json'))) diff --git a/pipy/tutorial/11-logging/config/balancer.json b/pipy/tutorial/11-logging/config/balancer.json new file mode 100644 index 0000000000000000000000000000000000000000..047a6413ce88727d8654331c2325b02288971c22 --- /dev/null +++ b/pipy/tutorial/11-logging/config/balancer.json @@ -0,0 +1,7 @@ +{ + "services": { + "service-hi" : ["127.0.0.1:8080", "127.0.0.1:8082"], + "service-echo" : ["127.0.0.1:8081"], + "service-tell-ip" : ["127.0.0.1:8082"] + } +} diff --git a/pipy/tutorial/11-logging/config/logger.json b/pipy/tutorial/11-logging/config/logger.json new file mode 100644 index 0000000000000000000000000000000000000000..29bb91c836a2561012830f7b0fdf8407a40e8f17 --- /dev/null +++ b/pipy/tutorial/11-logging/config/logger.json @@ -0,0 +1,3 @@ +{ + "logURL": "http://127.0.0.1:8123/log" +} diff --git a/pipy/tutorial/11-logging/config/proxy.json b/pipy/tutorial/11-logging/config/proxy.json new file mode 100644 index 0000000000000000000000000000000000000000..8e7fe69681ba9a35a5cac865bf9d3b04220f9670 --- /dev/null +++ b/pipy/tutorial/11-logging/config/proxy.json @@ -0,0 +1,9 @@ +{ + "listen": 8000, + "plugins": [ + "plugins/router.js", + "plugins/logger.js", + "plugins/balancer.js", + "plugins/default.js" + ] +} diff --git a/pipy/tutorial/11-logging/config/router.json b/pipy/tutorial/11-logging/config/router.json new file mode 100644 index 0000000000000000000000000000000000000000..a362f29e055b6761310f1f8d620e32c7ca41b926 --- /dev/null +++ b/pipy/tutorial/11-logging/config/router.json @@ -0,0 +1,7 @@ +{ + "routes": { + "/hi/*": { "service": "service-hi" }, + "/echo": { "service": "service-echo" }, + "/ip/*": { "service": "service-tell-ip", "rewrite": ["^/ip/?", "/"] } + } +} diff --git a/pipy/tutorial/11-logging/plugins/balancer.js b/pipy/tutorial/11-logging/plugins/balancer.js new file mode 100644 index 0000000000000000000000000000000000000000..a82b0cb05806f55447712c775db4c54da5e7d5e5 --- /dev/null +++ b/pipy/tutorial/11-logging/plugins/balancer.js @@ -0,0 +1,79 @@ +(config => + +pipy({ + _services: ( + Object.fromEntries( + Object.entries(config.services).map( + ([k, v]) => [ + k, new algo.RoundRobinLoadBalancer(v) + ] + ) + ) + ), + + _balancer: null, + _balancerCache: null, + _target: '', + _targetCache: null, + + _g: { + connectionID: 0, + }, + + _connectionPool: new algo.ResourcePool( + () => ++_g.connectionID + ), +}) + +.import({ + __turnDown: 'proxy', + __serviceID: 'router', +}) + +.pipeline('session') + .handleStreamStart( + () => ( + _balancerCache = new algo.Cache( + // k is a balancer, v is a target + (k ) => k.select(), + (k,v) => k.deselect(v), + ), + _targetCache = new algo.Cache( + // k is a target, v is a connection ID + (k ) => _connectionPool.allocate(k), + (k,v) => _connectionPool.free(v), + ) + ) + ) + .handleStreamEnd( + () => ( + _targetCache.clear(), + _balancerCache.clear() + ) + ) + +.pipeline('request') + .handleMessageStart( + () => ( + _balancer = _services[__serviceID], + _balancer && (_target = _balancerCache.get(_balancer)), + _target && (__turnDown = true) + ) + ) + .link( + 'forward', () => Boolean(_target), + '' + ) + +.pipeline('forward') + .muxHTTP( + 'connection', + () => _targetCache.get(_target) + ) + +.pipeline('connection') + .connect( + () => _target + ) + +)(JSON.decode(pipy.load('config/balancer.json'))) diff --git a/pipy/tutorial/11-logging/plugins/default.js b/pipy/tutorial/11-logging/plugins/default.js new file mode 100644 index 0000000000000000000000000000000000000000..ad2165691db61e45de67ba52a7558d5c74fc4f0c --- /dev/null +++ b/pipy/tutorial/11-logging/plugins/default.js @@ -0,0 +1,6 @@ +pipy() + +.pipeline('request') + .replaceMessage( + new Message({ status: 404 }, 'No handler') + ) diff --git a/pipy/tutorial/11-logging/plugins/logger.js b/pipy/tutorial/11-logging/plugins/logger.js new file mode 100644 index 0000000000000000000000000000000000000000..adb80b6fb9c2cc350b2c49478eb303af6a5b729f --- /dev/null +++ b/pipy/tutorial/11-logging/plugins/logger.js @@ -0,0 +1,87 @@ +(config => + +pipy({ + _logURL: new URL(config.logURL), + _request: null, + _requestTime: 0, + _responseTime: 0, +}) + +.export('logger', { + __logInfo: {}, +}) + +.pipeline('request') + .fork('log-request') + +.pipeline('response') + .fork('log-response') + +.pipeline('log-request') + .handleMessageStart( + () => _requestTime = Date.now() + ) + .decompressHTTP() + .handleMessage( + '256k', + msg => _request = msg + ) + +.pipeline('log-response') + .handleMessageStart( + () => _responseTime = Date.now() + ) + .decompressHTTP() + .replaceMessage( + '256k', + msg => ( + new Message( + JSON.encode({ + req: { + ..._request.head, + body: _request.body.toString(), + }, + res: { + ...msg.head, + body: msg.body.toString(), + }, + reqTime: _requestTime, + resTime: _responseTime, + endTime: Date.now(), + remoteAddr: __inbound.remoteAddress, + remotePort: __inbound.remotePort, + localAddr: __inbound.localAddress, + localPort: __inbound.localPort, + ...__logInfo, + }).push('\n') + ) + ) + ) + .merge('log-send', '') + +.pipeline('log-send') + .pack( + 1000, + { + timeout: 5, + } + ) + .replaceMessageStart( + () => new MessageStart({ + method: 'POST', + path: _logURL.path, + headers: { + 'Host': _logURL.host, + 'Content-Type': 'application/json', + } + }) + ) + .encodeHTTPRequest() + .connect( + () => _logURL.host, + { + bufferLimit: '8m', + } + ) + +)(JSON.decode(pipy.load('config/logger.json'))) diff --git a/pipy/tutorial/11-logging/plugins/router.js b/pipy/tutorial/11-logging/plugins/router.js new file mode 100644 index 0000000000000000000000000000000000000000..d27b3f19967df53b70b00f4c6ed884cbd3af82a5 --- /dev/null +++ b/pipy/tutorial/11-logging/plugins/router.js @@ -0,0 +1,47 @@ +(config => + +pipy({ + _router: new algo.URLRouter( + Object.fromEntries( + Object.entries(config.routes).map( + ([k, v]) => [ + k, + { + ...v, + rewrite: v.rewrite ? [ + new RegExp(v.rewrite[0]), + v.rewrite[1], + ] : undefined, + } + ] + ) + ) + ), + + _route: null, +}) + +.export('router', { + __serviceID: '', +}) + +.pipeline('request') + .handleMessageStart( + msg => ( + _route = _router.find( + msg.head.headers.host, + msg.head.path, + ), + _route && ( + __serviceID = _route.service, + _route.rewrite && ( + msg.head.path = msg.head.path.replace( + _route.rewrite[0], + _route.rewrite[1], + ) + ) + ) + ) + ) + +)(JSON.decode(pipy.load('config/router.json'))) diff --git a/pipy/tutorial/11-logging/proxy.js b/pipy/tutorial/11-logging/proxy.js new file mode 100644 index 0000000000000000000000000000000000000000..e16f3176d4957aa4b8d158aaf5775aa452a6fd36 --- /dev/null +++ b/pipy/tutorial/11-logging/proxy.js @@ -0,0 +1,21 @@ +(config => + +pipy() + +.export('proxy', { + __turnDown: false, +}) + +.listen(config.listen) + .use(config.plugins, 'session') + .demuxHTTP('request') + +.pipeline('request') + .use( + config.plugins, + 'request', + 'response', + () => __turnDown + ) + +)(JSON.decode(pipy.load('config/proxy.json'))) diff --git a/pipy/tutorial/12-jwt/config/balancer.json b/pipy/tutorial/12-jwt/config/balancer.json new file mode 100644 index 0000000000000000000000000000000000000000..047a6413ce88727d8654331c2325b02288971c22 --- /dev/null +++ b/pipy/tutorial/12-jwt/config/balancer.json @@ -0,0 +1,7 @@ +{ + "services": { + "service-hi" : ["127.0.0.1:8080", "127.0.0.1:8082"], + "service-echo" : ["127.0.0.1:8081"], + "service-tell-ip" : ["127.0.0.1:8082"] + } +} diff --git a/pipy/tutorial/12-jwt/config/jwt.json b/pipy/tutorial/12-jwt/config/jwt.json new file mode 100644 index 0000000000000000000000000000000000000000..4d6ee19157a9d67742904c468e3d0e4391bf5883 --- /dev/null +++ b/pipy/tutorial/12-jwt/config/jwt.json @@ -0,0 +1,18 @@ +{ + "keys": { + "key-1": { + "pem": "-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWw\nkWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mr\nm/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEi\nNQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV\n3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2\nQU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQABAoIBACiARq2wkltjtcjs\nkFvZ7w1JAORHbEufEO1Eu27zOIlqbgyAcAl7q+/1bip4Z/x1IVES84/yTaM8p0go\namMhvgry/mS8vNi1BN2SAZEnb/7xSxbflb70bX9RHLJqKnp5GZe2jexw+wyXlwaM\n+bclUCrh9e1ltH7IvUrRrQnFJfh+is1fRon9Co9Li0GwoN0x0byrrngU8Ak3Y6D9\nD8GjQA4Elm94ST3izJv8iCOLSDBmzsPsXfcCUZfmTfZ5DbUDMbMxRnSo3nQeoKGC\n0Lj9FkWcfmLcpGlSXTO+Ww1L7EGq+PT3NtRae1FZPwjddQ1/4V905kyQFLamAA5Y\nlSpE2wkCgYEAy1OPLQcZt4NQnQzPz2SBJqQN2P5u3vXl+zNVKP8w4eBv0vWuJJF+\nhkGNnSxXQrTkvDOIUddSKOzHHgSg4nY6K02ecyT0PPm/UZvtRpWrnBjcEVtHEJNp\nbU9pLD5iZ0J9sbzPU/LxPmuAP2Bs8JmTn6aFRspFrP7W0s1Nmk2jsm0CgYEAyH0X\n+jpoqxj4efZfkUrg5GbSEhf+dZglf0tTOA5bVg8IYwtmNk/pniLG/zI7c+GlTc9B\nBwfMr59EzBq/eFMI7+LgXaVUsM/sS4Ry+yeK6SJx/otIMWtDfqxsLD8CPMCRvecC\n2Pip4uSgrl0MOebl9XKp57GoaUWRWRHqwV4Y6h8CgYAZhI4mh4qZtnhKjY4TKDjx\nQYufXSdLAi9v3FxmvchDwOgn4L+PRVdMwDNms2bsL0m5uPn104EzM6w1vzz1zwKz\n5pTpPI0OjgWN13Tq8+PKvm/4Ga2MjgOgPWQkslulO/oMcXbPwWC3hcRdr9tcQtn9\nImf9n2spL/6EDFId+Hp/7QKBgAqlWdiXsWckdE1Fn91/NGHsc8syKvjjk1onDcw0\nNvVi5vcba9oGdElJX3e9mxqUKMrw7msJJv1MX8LWyMQC5L6YNYHDfbPF1q5L4i8j\n8mRex97UVokJQRRA452V2vCO6S5ETgpnad36de3MUxHgCOX3qL382Qx9/THVmbma\n3YfRAoGAUxL/Eu5yvMK8SAt/dJK6FedngcM3JEFNplmtLYVLWhkIlNRGDwkg3I5K\ny18Ae9n7dHVueyslrb6weq7dTkYDi3iOYRW8HRkIQh06wEdbxt0shTzAJvvCQfrB\njg/3747WSsf/zBTcHihTRBdAv6OmdhV4/dD5YBfLAkLrd+mX7iE=\n-----END RSA PRIVATE KEY-----\n" + }, + "key-2": { + "pem": "-----BEGIN EC PRIVATE KEY-----\nMIHcAgEBBEIBiyAa7aRHFDCh2qga9sTUGINE5jHAFnmM8xWeT/uni5I4tNqhV5Xx\n0pDrmCV9mbroFtfEa0XVfKuMAxxfZ6LM/yKgBwYFK4EEACOhgYkDgYYABAGBzgdn\nP798FsLuWYTDDQA7c0r3BVk8NnRUSexpQUsRilPNv3SchO0lRw9Ru86x1khnVDx+\nduq4BiDFcvlSAcyjLACJvjvoyTLJiA+TQFdmrearjMiZNE25pT2yWP1NUndJxPcv\nVtfBW48kPOmvkY4WlqP5bAwCXwbsKrCgk6xbsp12ew==\n-----END EC PRIVATE KEY-----" + } + }, + "services": { + "service-hi": { + "keys": [ + "key-1", + "key-2" + ] + } + } +} diff --git a/pipy/tutorial/12-jwt/config/proxy.json b/pipy/tutorial/12-jwt/config/proxy.json new file mode 100644 index 0000000000000000000000000000000000000000..e290c8221f08dddb145dcbf9d6488ab5cd940f51 --- /dev/null +++ b/pipy/tutorial/12-jwt/config/proxy.json @@ -0,0 +1,9 @@ +{ + "listen": 8000, + "plugins": [ + "plugins/router.js", + "plugins/jwt.js", + "plugins/balancer.js", + "plugins/default.js" + ] +} diff --git a/pipy/tutorial/12-jwt/config/router.json b/pipy/tutorial/12-jwt/config/router.json new file mode 100644 index 0000000000000000000000000000000000000000..a362f29e055b6761310f1f8d620e32c7ca41b926 --- /dev/null +++ b/pipy/tutorial/12-jwt/config/router.json @@ -0,0 +1,7 @@ +{ + "routes": { + "/hi/*": { "service": "service-hi" }, + "/echo": { "service": "service-echo" }, + "/ip/*": { "service": "service-tell-ip", "rewrite": ["^/ip/?", "/"] } + } +} diff --git a/pipy/tutorial/12-jwt/plugins/balancer.js b/pipy/tutorial/12-jwt/plugins/balancer.js new file mode 100644 index 0000000000000000000000000000000000000000..a82b0cb05806f55447712c775db4c54da5e7d5e5 --- /dev/null +++ b/pipy/tutorial/12-jwt/plugins/balancer.js @@ -0,0 +1,79 @@ +(config => + +pipy({ + _services: ( + Object.fromEntries( + Object.entries(config.services).map( + ([k, v]) => [ + k, new algo.RoundRobinLoadBalancer(v) + ] + ) + ) + ), + + _balancer: null, + _balancerCache: null, + _target: '', + _targetCache: null, + + _g: { + connectionID: 0, + }, + + _connectionPool: new algo.ResourcePool( + () => ++_g.connectionID + ), +}) + +.import({ + __turnDown: 'proxy', + __serviceID: 'router', +}) + +.pipeline('session') + .handleStreamStart( + () => ( + _balancerCache = new algo.Cache( + // k is a balancer, v is a target + (k ) => k.select(), + (k,v) => k.deselect(v), + ), + _targetCache = new algo.Cache( + // k is a target, v is a connection ID + (k ) => _connectionPool.allocate(k), + (k,v) => _connectionPool.free(v), + ) + ) + ) + .handleStreamEnd( + () => ( + _targetCache.clear(), + _balancerCache.clear() + ) + ) + +.pipeline('request') + .handleMessageStart( + () => ( + _balancer = _services[__serviceID], + _balancer && (_target = _balancerCache.get(_balancer)), + _target && (__turnDown = true) + ) + ) + .link( + 'forward', () => Boolean(_target), + '' + ) + +.pipeline('forward') + .muxHTTP( + 'connection', + () => _targetCache.get(_target) + ) + +.pipeline('connection') + .connect( + () => _target + ) + +)(JSON.decode(pipy.load('config/balancer.json'))) diff --git a/pipy/tutorial/12-jwt/plugins/default.js b/pipy/tutorial/12-jwt/plugins/default.js new file mode 100644 index 0000000000000000000000000000000000000000..ad2165691db61e45de67ba52a7558d5c74fc4f0c --- /dev/null +++ b/pipy/tutorial/12-jwt/plugins/default.js @@ -0,0 +1,6 @@ +pipy() + +.pipeline('request') + .replaceMessage( + new Message({ status: 404 }, 'No handler') + ) diff --git a/pipy/tutorial/12-jwt/plugins/jwt.js b/pipy/tutorial/12-jwt/plugins/jwt.js new file mode 100644 index 0000000000000000000000000000000000000000..b0928ae434ca2a3ee82c8787d8f2eabacdd5b62e --- /dev/null +++ b/pipy/tutorial/12-jwt/plugins/jwt.js @@ -0,0 +1,77 @@ +(config => + +pipy({ + _keys: ( + Object.fromEntries( + Object.entries(config.keys).map( + ([k, v]) => [ + k, + new crypto.PrivateKey(v.pem) + ] + ) + ) + ), + + _services: ( + Object.fromEntries( + Object.entries(config.services).map( + ([k, v]) => [ + k, + { + keys: v.keys ? Object.fromEntries(v.keys.map(k => [k, true])) : undefined, + } + ] + ) + ) + ), +}) + +.import({ + __turnDown: 'proxy', + __serviceID: 'router', +}) + +.pipeline('request') + .replaceMessage( + msg => ( + (( + service, + header, + jwt, + kid, + key, + ) => ( + service = _services[__serviceID], + service?.keys ? ( + header = msg.head.headers.authorization || '', + header.startsWith('Bearer ') && (header = header.substring(7)), + jwt = new crypto.JWT(header), + jwt.isValid ? ( + kid = jwt.header?.kid, + key = _keys[kid], + key ? ( + service.keys[kid] ? ( + jwt.verify(key) ? ( + msg + ) : ( + __turnDown = true, + new Message({ status: 401 }, 'Invalid signature') + ) + ) : ( + __turnDown = true, + new Message({ status: 403 }, 'Access denied') + ) + ) : ( + __turnDown = true, + new Message({ status: 401 }, 'Invalid key') + ) + ) : ( + __turnDown = true, + new Message({ status: 401 }, 'Invalid token') + ) + ) : msg + ))() + ) + ) + +)(JSON.decode(pipy.load('config/jwt.json'))) diff --git a/pipy/tutorial/12-jwt/plugins/router.js b/pipy/tutorial/12-jwt/plugins/router.js new file mode 100644 index 0000000000000000000000000000000000000000..d27b3f19967df53b70b00f4c6ed884cbd3af82a5 --- /dev/null +++ b/pipy/tutorial/12-jwt/plugins/router.js @@ -0,0 +1,47 @@ +(config => + +pipy({ + _router: new algo.URLRouter( + Object.fromEntries( + Object.entries(config.routes).map( + ([k, v]) => [ + k, + { + ...v, + rewrite: v.rewrite ? [ + new RegExp(v.rewrite[0]), + v.rewrite[1], + ] : undefined, + } + ] + ) + ) + ), + + _route: null, +}) + +.export('router', { + __serviceID: '', +}) + +.pipeline('request') + .handleMessageStart( + msg => ( + _route = _router.find( + msg.head.headers.host, + msg.head.path, + ), + _route && ( + __serviceID = _route.service, + _route.rewrite && ( + msg.head.path = msg.head.path.replace( + _route.rewrite[0], + _route.rewrite[1], + ) + ) + ) + ) + ) + +)(JSON.decode(pipy.load('config/router.json'))) diff --git a/pipy/tutorial/12-jwt/proxy.js b/pipy/tutorial/12-jwt/proxy.js new file mode 100644 index 0000000000000000000000000000000000000000..e16f3176d4957aa4b8d158aaf5775aa452a6fd36 --- /dev/null +++ b/pipy/tutorial/12-jwt/proxy.js @@ -0,0 +1,21 @@ +(config => + +pipy() + +.export('proxy', { + __turnDown: false, +}) + +.listen(config.listen) + .use(config.plugins, 'session') + .demuxHTTP('request') + +.pipeline('request') + .use( + config.plugins, + 'request', + 'response', + () => __turnDown + ) + +)(JSON.decode(pipy.load('config/proxy.json'))) diff --git a/pipy/tutorial/12-jwt/secret/sample-key-ecdsa.pem b/pipy/tutorial/12-jwt/secret/sample-key-ecdsa.pem new file mode 100644 index 0000000000000000000000000000000000000000..05ee0cfa26c8807ba03d5ac31b4e6b237b1071ce --- /dev/null +++ b/pipy/tutorial/12-jwt/secret/sample-key-ecdsa.pem @@ -0,0 +1,7 @@ +-----BEGIN EC PRIVATE KEY----- +MIHcAgEBBEIBiyAa7aRHFDCh2qga9sTUGINE5jHAFnmM8xWeT/uni5I4tNqhV5Xx +0pDrmCV9mbroFtfEa0XVfKuMAxxfZ6LM/yKgBwYFK4EEACOhgYkDgYYABAGBzgdn +P798FsLuWYTDDQA7c0r3BVk8NnRUSexpQUsRilPNv3SchO0lRw9Ru86x1khnVDx+ +duq4BiDFcvlSAcyjLACJvjvoyTLJiA+TQFdmrearjMiZNE25pT2yWP1NUndJxPcv +VtfBW48kPOmvkY4WlqP5bAwCXwbsKrCgk6xbsp12ew== +-----END EC PRIVATE KEY----- diff --git a/pipy/tutorial/12-jwt/secret/sample-key-rsa.pem b/pipy/tutorial/12-jwt/secret/sample-key-rsa.pem new file mode 100644 index 0000000000000000000000000000000000000000..61056a54985217c234e5a718818b9a17b97958b0 --- /dev/null +++ b/pipy/tutorial/12-jwt/secret/sample-key-rsa.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAnzyis1ZjfNB0bBgKFMSvvkTtwlvBsaJq7S5wA+kzeVOVpVWw +kWdVha4s38XM/pa/yr47av7+z3VTmvDRyAHcaT92whREFpLv9cj5lTeJSibyr/Mr +m/YtjCZVWgaOYIhwrXwKLqPr/11inWsAkfIytvHWTxZYEcXLgAXFuUuaS3uF9gEi +NQwzGTU1v0FqkqTBr4B8nW3HCN47XUu0t8Y0e+lf4s4OxQawWD79J9/5d3Ry0vbV +3Am1FtGJiJvOwRsIfVChDpYStTcHTCMqtvWbV6L11BWkpzGXSW4Hv43qa+GSYOD2 +QU68Mb59oSk2OB+BtOLpJofmbGEGgvmwyCI9MwIDAQABAoIBACiARq2wkltjtcjs +kFvZ7w1JAORHbEufEO1Eu27zOIlqbgyAcAl7q+/1bip4Z/x1IVES84/yTaM8p0go +amMhvgry/mS8vNi1BN2SAZEnb/7xSxbflb70bX9RHLJqKnp5GZe2jexw+wyXlwaM ++bclUCrh9e1ltH7IvUrRrQnFJfh+is1fRon9Co9Li0GwoN0x0byrrngU8Ak3Y6D9 +D8GjQA4Elm94ST3izJv8iCOLSDBmzsPsXfcCUZfmTfZ5DbUDMbMxRnSo3nQeoKGC +0Lj9FkWcfmLcpGlSXTO+Ww1L7EGq+PT3NtRae1FZPwjddQ1/4V905kyQFLamAA5Y +lSpE2wkCgYEAy1OPLQcZt4NQnQzPz2SBJqQN2P5u3vXl+zNVKP8w4eBv0vWuJJF+ +hkGNnSxXQrTkvDOIUddSKOzHHgSg4nY6K02ecyT0PPm/UZvtRpWrnBjcEVtHEJNp +bU9pLD5iZ0J9sbzPU/LxPmuAP2Bs8JmTn6aFRspFrP7W0s1Nmk2jsm0CgYEAyH0X ++jpoqxj4efZfkUrg5GbSEhf+dZglf0tTOA5bVg8IYwtmNk/pniLG/zI7c+GlTc9B +BwfMr59EzBq/eFMI7+LgXaVUsM/sS4Ry+yeK6SJx/otIMWtDfqxsLD8CPMCRvecC +2Pip4uSgrl0MOebl9XKp57GoaUWRWRHqwV4Y6h8CgYAZhI4mh4qZtnhKjY4TKDjx +QYufXSdLAi9v3FxmvchDwOgn4L+PRVdMwDNms2bsL0m5uPn104EzM6w1vzz1zwKz +5pTpPI0OjgWN13Tq8+PKvm/4Ga2MjgOgPWQkslulO/oMcXbPwWC3hcRdr9tcQtn9 +Imf9n2spL/6EDFId+Hp/7QKBgAqlWdiXsWckdE1Fn91/NGHsc8syKvjjk1onDcw0 +NvVi5vcba9oGdElJX3e9mxqUKMrw7msJJv1MX8LWyMQC5L6YNYHDfbPF1q5L4i8j +8mRex97UVokJQRRA452V2vCO6S5ETgpnad36de3MUxHgCOX3qL382Qx9/THVmbma +3YfRAoGAUxL/Eu5yvMK8SAt/dJK6FedngcM3JEFNplmtLYVLWhkIlNRGDwkg3I5K +y18Ae9n7dHVueyslrb6weq7dTkYDi3iOYRW8HRkIQh06wEdbxt0shTzAJvvCQfrB +jg/3747WSsf/zBTcHihTRBdAv6OmdhV4/dD5YBfLAkLrd+mX7iE= +-----END RSA PRIVATE KEY----- diff --git a/pipy/tutorial/12-jwt/secret/sample-token-ecdsa.txt b/pipy/tutorial/12-jwt/secret/sample-token-ecdsa.txt new file mode 100644 index 0000000000000000000000000000000000000000..c3f9db6d9d73d3534f225735f2e5c0aed2b110d2 --- /dev/null +++ b/pipy/tutorial/12-jwt/secret/sample-token-ecdsa.txt @@ -0,0 +1 @@ +eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCIsImtpZCI6ImtleS0yIn0.e30.AU3fe-n_g9xMIrd3bg9d1Tntb3EPj1n8Kkc4BvWQul0QYBK0X-_mshbacmOIpBSBKXxFk-UzcoxHLKwv99UmtdBEAZ8q9JczPnTAw2jK-09MMcp12XW7MmqtMkdMaAMSEUVzTfsOgGsa8_d85jP9JHf3-3-IWUbxqkPmrmnAmiJGnv3c \ No newline at end of file diff --git a/pipy/tutorial/12-jwt/secret/sample-token-rsa.txt b/pipy/tutorial/12-jwt/secret/sample-token-rsa.txt new file mode 100644 index 0000000000000000000000000000000000000000..74921397834129ae636c956946c6942c8073c0aa --- /dev/null +++ b/pipy/tutorial/12-jwt/secret/sample-token-rsa.txt @@ -0,0 +1 @@ +eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtleS0xIn0.e30.mavj3QQXhRE7-8vx4OYogfW_PrmFxOzQ9a-Fl6D-_JRhwqM3B4qkTSh7S_H-XUCvNP2TZApjlhaCiawv4Gcp8wvGlq-2gy2EHjBb2Pz_7cZ0oCqFRcd2OI19_nRcOeE6OVFNFksGgVFLGLIbZUf0Deg1v3ST1VaERK1oxuuxWTyRXFibHA6FsjNUJ30ZfBa-ewfK8SV49gOcWrT_yiL3mMfVL9_JqPHWz17jBiE0nHsUWamI7mc7hdAfMoTeD0BO-rTHVxoplgEIw6kKvqFB5MQVTL1-Uyp9bRAj2WPlObFDRrL1t5aQ-Z9DIojo9K81pPKCSIX4QEYMWjpLovoYvg \ No newline at end of file diff --git a/pipy/tutorial/13-ban/config/balancer.json b/pipy/tutorial/13-ban/config/balancer.json new file mode 100644 index 0000000000000000000000000000000000000000..047a6413ce88727d8654331c2325b02288971c22 --- /dev/null +++ b/pipy/tutorial/13-ban/config/balancer.json @@ -0,0 +1,7 @@ +{ + "services": { + "service-hi" : ["127.0.0.1:8080", "127.0.0.1:8082"], + "service-echo" : ["127.0.0.1:8081"], + "service-tell-ip" : ["127.0.0.1:8082"] + } +} diff --git a/pipy/tutorial/13-ban/config/ban.json b/pipy/tutorial/13-ban/config/ban.json new file mode 100644 index 0000000000000000000000000000000000000000..7b6f4e538e6f9b6135ffb499e5874928979e445a --- /dev/null +++ b/pipy/tutorial/13-ban/config/ban.json @@ -0,0 +1,12 @@ +{ + "services": { + "service-hi": { + "white": [], + "black": [ + "127.0.0.1", + "::1", + "::ffff:127.0.0.1" + ] + } + } +} diff --git a/pipy/tutorial/13-ban/config/proxy.json b/pipy/tutorial/13-ban/config/proxy.json new file mode 100644 index 0000000000000000000000000000000000000000..09e267e090d7405eb83dd9c33840663a976e11e1 --- /dev/null +++ b/pipy/tutorial/13-ban/config/proxy.json @@ -0,0 +1,9 @@ +{ + "listen": 8000, + "plugins": [ + "plugins/router.js", + "plugins/ban.js", + "plugins/balancer.js", + "plugins/default.js" + ] +} diff --git a/pipy/tutorial/13-ban/config/router.json b/pipy/tutorial/13-ban/config/router.json new file mode 100644 index 0000000000000000000000000000000000000000..a362f29e055b6761310f1f8d620e32c7ca41b926 --- /dev/null +++ b/pipy/tutorial/13-ban/config/router.json @@ -0,0 +1,7 @@ +{ + "routes": { + "/hi/*": { "service": "service-hi" }, + "/echo": { "service": "service-echo" }, + "/ip/*": { "service": "service-tell-ip", "rewrite": ["^/ip/?", "/"] } + } +} diff --git a/pipy/tutorial/13-ban/plugins/balancer.js b/pipy/tutorial/13-ban/plugins/balancer.js new file mode 100644 index 0000000000000000000000000000000000000000..a82b0cb05806f55447712c775db4c54da5e7d5e5 --- /dev/null +++ b/pipy/tutorial/13-ban/plugins/balancer.js @@ -0,0 +1,79 @@ +(config => + +pipy({ + _services: ( + Object.fromEntries( + Object.entries(config.services).map( + ([k, v]) => [ + k, new algo.RoundRobinLoadBalancer(v) + ] + ) + ) + ), + + _balancer: null, + _balancerCache: null, + _target: '', + _targetCache: null, + + _g: { + connectionID: 0, + }, + + _connectionPool: new algo.ResourcePool( + () => ++_g.connectionID + ), +}) + +.import({ + __turnDown: 'proxy', + __serviceID: 'router', +}) + +.pipeline('session') + .handleStreamStart( + () => ( + _balancerCache = new algo.Cache( + // k is a balancer, v is a target + (k ) => k.select(), + (k,v) => k.deselect(v), + ), + _targetCache = new algo.Cache( + // k is a target, v is a connection ID + (k ) => _connectionPool.allocate(k), + (k,v) => _connectionPool.free(v), + ) + ) + ) + .handleStreamEnd( + () => ( + _targetCache.clear(), + _balancerCache.clear() + ) + ) + +.pipeline('request') + .handleMessageStart( + () => ( + _balancer = _services[__serviceID], + _balancer && (_target = _balancerCache.get(_balancer)), + _target && (__turnDown = true) + ) + ) + .link( + 'forward', () => Boolean(_target), + '' + ) + +.pipeline('forward') + .muxHTTP( + 'connection', + () => _targetCache.get(_target) + ) + +.pipeline('connection') + .connect( + () => _target + ) + +)(JSON.decode(pipy.load('config/balancer.json'))) diff --git a/pipy/tutorial/13-ban/plugins/ban.js b/pipy/tutorial/13-ban/plugins/ban.js new file mode 100644 index 0000000000000000000000000000000000000000..a36e33e1ba3b324d9aff3a2b90075705d07aaf99 --- /dev/null +++ b/pipy/tutorial/13-ban/plugins/ban.js @@ -0,0 +1,69 @@ +(config => + +pipy({ + _services: ( + Object.fromEntries( + Object.entries(config.services).map( + ([k, v]) => [ + k, + { + white: ( + v.white?.length > 0 ? ( + Object.fromEntries( + v.white.map( + ip => [ip, true] + ) + ) + ) : null + ), + black: ( + v.black?.length > 0 ? ( + Object.fromEntries( + v.black.map( + ip => [ip, true] + ) + ) + ) : null + ), + } + ] + ) + ) + ), + + _service: null, +}) + +.import({ + __turnDown: 'proxy', + __serviceID: 'router', +}) + +.pipeline('request') + .handleStreamStart( + () => ( + _service = _services[__serviceID], + __turnDown = Boolean( + _service && ( + _service.white ? ( + !_service.white[__inbound.remoteAddress] + ) : ( + _service.black?.[__inbound.remoteAddress] + ) + ) + ) + ) + ) + .link( + 'deny', () => __turnDown, + 'bypass' + ) + +.pipeline('deny') + .replaceMessage( + new Message({ status: 403 }, 'Access denied') + ) + +.pipeline('bypass') + +)(JSON.decode(pipy.load('config/ban.json'))) diff --git a/pipy/tutorial/13-ban/plugins/default.js b/pipy/tutorial/13-ban/plugins/default.js new file mode 100644 index 0000000000000000000000000000000000000000..ad2165691db61e45de67ba52a7558d5c74fc4f0c --- /dev/null +++ b/pipy/tutorial/13-ban/plugins/default.js @@ -0,0 +1,6 @@ +pipy() + +.pipeline('request') + .replaceMessage( + new Message({ status: 404 }, 'No handler') + ) diff --git a/pipy/tutorial/13-ban/plugins/router.js b/pipy/tutorial/13-ban/plugins/router.js new file mode 100644 index 0000000000000000000000000000000000000000..d27b3f19967df53b70b00f4c6ed884cbd3af82a5 --- /dev/null +++ b/pipy/tutorial/13-ban/plugins/router.js @@ -0,0 +1,47 @@ +(config => + +pipy({ + _router: new algo.URLRouter( + Object.fromEntries( + Object.entries(config.routes).map( + ([k, v]) => [ + k, + { + ...v, + rewrite: v.rewrite ? [ + new RegExp(v.rewrite[0]), + v.rewrite[1], + ] : undefined, + } + ] + ) + ) + ), + + _route: null, +}) + +.export('router', { + __serviceID: '', +}) + +.pipeline('request') + .handleMessageStart( + msg => ( + _route = _router.find( + msg.head.headers.host, + msg.head.path, + ), + _route && ( + __serviceID = _route.service, + _route.rewrite && ( + msg.head.path = msg.head.path.replace( + _route.rewrite[0], + _route.rewrite[1], + ) + ) + ) + ) + ) + +)(JSON.decode(pipy.load('config/router.json'))) diff --git a/pipy/tutorial/13-ban/proxy.js b/pipy/tutorial/13-ban/proxy.js new file mode 100644 index 0000000000000000000000000000000000000000..e16f3176d4957aa4b8d158aaf5775aa452a6fd36 --- /dev/null +++ b/pipy/tutorial/13-ban/proxy.js @@ -0,0 +1,21 @@ +(config => + +pipy() + +.export('proxy', { + __turnDown: false, +}) + +.listen(config.listen) + .use(config.plugins, 'session') + .demuxHTTP('request') + +.pipeline('request') + .use( + config.plugins, + 'request', + 'response', + () => __turnDown + ) + +)(JSON.decode(pipy.load('config/proxy.json'))) diff --git a/pipy/tutorial/14-throttle/config/balancer.json b/pipy/tutorial/14-throttle/config/balancer.json new file mode 100644 index 0000000000000000000000000000000000000000..047a6413ce88727d8654331c2325b02288971c22 --- /dev/null +++ b/pipy/tutorial/14-throttle/config/balancer.json @@ -0,0 +1,7 @@ +{ + "services": { + "service-hi" : ["127.0.0.1:8080", "127.0.0.1:8082"], + "service-echo" : ["127.0.0.1:8081"], + "service-tell-ip" : ["127.0.0.1:8082"] + } +} diff --git a/pipy/tutorial/14-throttle/config/proxy.json b/pipy/tutorial/14-throttle/config/proxy.json new file mode 100644 index 0000000000000000000000000000000000000000..31b43066a9f030520d19dadf5ad2d67b23e60a6e --- /dev/null +++ b/pipy/tutorial/14-throttle/config/proxy.json @@ -0,0 +1,9 @@ +{ + "listen": 8000, + "plugins": [ + "plugins/router.js", + "plugins/throttle.js", + "plugins/balancer.js", + "plugins/default.js" + ] +} diff --git a/pipy/tutorial/14-throttle/config/router.json b/pipy/tutorial/14-throttle/config/router.json new file mode 100644 index 0000000000000000000000000000000000000000..a362f29e055b6761310f1f8d620e32c7ca41b926 --- /dev/null +++ b/pipy/tutorial/14-throttle/config/router.json @@ -0,0 +1,7 @@ +{ + "routes": { + "/hi/*": { "service": "service-hi" }, + "/echo": { "service": "service-echo" }, + "/ip/*": { "service": "service-tell-ip", "rewrite": ["^/ip/?", "/"] } + } +} diff --git a/pipy/tutorial/14-throttle/config/throttle.json b/pipy/tutorial/14-throttle/config/throttle.json new file mode 100644 index 0000000000000000000000000000000000000000..10951ec774f74efc3c3fe3960da863fb7c89fc75 --- /dev/null +++ b/pipy/tutorial/14-throttle/config/throttle.json @@ -0,0 +1,7 @@ +{ + "services": { + "service-hi": { + "rateLimit": 1000 + } + } +} diff --git a/pipy/tutorial/14-throttle/plugins/balancer.js b/pipy/tutorial/14-throttle/plugins/balancer.js new file mode 100644 index 0000000000000000000000000000000000000000..a82b0cb05806f55447712c775db4c54da5e7d5e5 --- /dev/null +++ b/pipy/tutorial/14-throttle/plugins/balancer.js @@ -0,0 +1,79 @@ +(config => + +pipy({ + _services: ( + Object.fromEntries( + Object.entries(config.services).map( + ([k, v]) => [ + k, new algo.RoundRobinLoadBalancer(v) + ] + ) + ) + ), + + _balancer: null, + _balancerCache: null, + _target: '', + _targetCache: null, + + _g: { + connectionID: 0, + }, + + _connectionPool: new algo.ResourcePool( + () => ++_g.connectionID + ), +}) + +.import({ + __turnDown: 'proxy', + __serviceID: 'router', +}) + +.pipeline('session') + .handleStreamStart( + () => ( + _balancerCache = new algo.Cache( + // k is a balancer, v is a target + (k ) => k.select(), + (k,v) => k.deselect(v), + ), + _targetCache = new algo.Cache( + // k is a target, v is a connection ID + (k ) => _connectionPool.allocate(k), + (k,v) => _connectionPool.free(v), + ) + ) + ) + .handleStreamEnd( + () => ( + _targetCache.clear(), + _balancerCache.clear() + ) + ) + +.pipeline('request') + .handleMessageStart( + () => ( + _balancer = _services[__serviceID], + _balancer && (_target = _balancerCache.get(_balancer)), + _target && (__turnDown = true) + ) + ) + .link( + 'forward', () => Boolean(_target), + '' + ) + +.pipeline('forward') + .muxHTTP( + 'connection', + () => _targetCache.get(_target) + ) + +.pipeline('connection') + .connect( + () => _target + ) + +)(JSON.decode(pipy.load('config/balancer.json'))) diff --git a/pipy/tutorial/14-throttle/plugins/default.js b/pipy/tutorial/14-throttle/plugins/default.js new file mode 100644 index 0000000000000000000000000000000000000000..ad2165691db61e45de67ba52a7558d5c74fc4f0c --- /dev/null +++ b/pipy/tutorial/14-throttle/plugins/default.js @@ -0,0 +1,6 @@ +pipy() + +.pipeline('request') + .replaceMessage( + new Message({ status: 404 }, 'No handler') + ) diff --git a/pipy/tutorial/14-throttle/plugins/router.js b/pipy/tutorial/14-throttle/plugins/router.js new file mode 100644 index 0000000000000000000000000000000000000000..d27b3f19967df53b70b00f4c6ed884cbd3af82a5 --- /dev/null +++ b/pipy/tutorial/14-throttle/plugins/router.js @@ -0,0 +1,47 @@ +(config => + +pipy({ + _router: new algo.URLRouter( + Object.fromEntries( + Object.entries(config.routes).map( + ([k, v]) => [ + k, + { + ...v, + rewrite: v.rewrite ? [ + new RegExp(v.rewrite[0]), + v.rewrite[1], + ] : undefined, + } + ] + ) + ) + ), + + _route: null, +}) + +.export('router', { + __serviceID: '', +}) + +.pipeline('request') + .handleMessageStart( + msg => ( + _route = _router.find( + msg.head.headers.host, + msg.head.path, + ), + _route && ( + __serviceID = _route.service, + _route.rewrite && ( + msg.head.path = msg.head.path.replace( + _route.rewrite[0], + _route.rewrite[1], + ) + ) + ) + ) + ) + +)(JSON.decode(pipy.load('config/router.json'))) diff --git a/pipy/tutorial/14-throttle/plugins/throttle.js b/pipy/tutorial/14-throttle/plugins/throttle.js new file mode 100644 index 0000000000000000000000000000000000000000..954b19c42c50fba61320dec41ce24ebd644884c8 --- /dev/null +++ b/pipy/tutorial/14-throttle/plugins/throttle.js @@ -0,0 +1,29 @@ +(config => + +pipy({ + _services: config.services, + _rateLimit: undefined, +}) + +.import({ + __serviceID: 'router', +}) + +.pipeline('request') + .handleStreamStart( + () => _rateLimit = _services[__serviceID]?.rateLimit + ) + .link( + 'throttle', () => Boolean(_rateLimit), + 'bypass' + ) + +.pipeline('throttle') + .throttleMessageRate( + () => _rateLimit, + () => __serviceID, + ) + +.pipeline('bypass') + +)(JSON.decode(pipy.load('config/throttle.json'))) diff --git a/pipy/tutorial/14-throttle/proxy.js b/pipy/tutorial/14-throttle/proxy.js new file mode 100644 index 0000000000000000000000000000000000000000..e16f3176d4957aa4b8d158aaf5775aa452a6fd36 --- /dev/null +++ b/pipy/tutorial/14-throttle/proxy.js @@ -0,0 +1,21 @@ +(config => + +pipy() + +.export('proxy', { + __turnDown: false, +}) + +.listen(config.listen) + .use(config.plugins, 'session') + .demuxHTTP('request') + +.pipeline('request') + .use( + config.plugins, + 'request', + 'response', + () => __turnDown + ) + +)(JSON.decode(pipy.load('config/proxy.json'))) diff --git a/pipy/tutorial/15-cache/config/balancer.json b/pipy/tutorial/15-cache/config/balancer.json new file mode 100644 index 0000000000000000000000000000000000000000..047a6413ce88727d8654331c2325b02288971c22 --- /dev/null +++ b/pipy/tutorial/15-cache/config/balancer.json @@ -0,0 +1,7 @@ +{ + "services": { + "service-hi" : ["127.0.0.1:8080", "127.0.0.1:8082"], + "service-echo" : ["127.0.0.1:8081"], + "service-tell-ip" : ["127.0.0.1:8082"] + } +} diff --git a/pipy/tutorial/15-cache/config/cache.json b/pipy/tutorial/15-cache/config/cache.json new file mode 100644 index 0000000000000000000000000000000000000000..006579d060efeb395099771dc1c54ae35010be6f --- /dev/null +++ b/pipy/tutorial/15-cache/config/cache.json @@ -0,0 +1,9 @@ +{ + "services": { + "service-hi": [ + ".js", + ".json" + ] + }, + "timeout": 10000 +} diff --git a/pipy/tutorial/15-cache/config/proxy.json b/pipy/tutorial/15-cache/config/proxy.json new file mode 100644 index 0000000000000000000000000000000000000000..5f767f04af9acfbb1e7cecbabed4e98c23185e30 --- /dev/null +++ b/pipy/tutorial/15-cache/config/proxy.json @@ -0,0 +1,9 @@ +{ + "listen": 8000, + "plugins": [ + "plugins/router.js", + "plugins/cache.js", + "plugins/balancer.js", + "plugins/default.js" + ] +} diff --git a/pipy/tutorial/15-cache/config/router.json b/pipy/tutorial/15-cache/config/router.json new file mode 100644 index 0000000000000000000000000000000000000000..a362f29e055b6761310f1f8d620e32c7ca41b926 --- /dev/null +++ b/pipy/tutorial/15-cache/config/router.json @@ -0,0 +1,7 @@ +{ + "routes": { + "/hi/*": { "service": "service-hi" }, + "/echo": { "service": "service-echo" }, + "/ip/*": { "service": "service-tell-ip", "rewrite": ["^/ip/?", "/"] } + } +} diff --git a/pipy/tutorial/15-cache/plugins/balancer.js b/pipy/tutorial/15-cache/plugins/balancer.js new file mode 100644 index 0000000000000000000000000000000000000000..a82b0cb05806f55447712c775db4c54da5e7d5e5 --- /dev/null +++ b/pipy/tutorial/15-cache/plugins/balancer.js @@ -0,0 +1,79 @@ +(config => + +pipy({ + _services: ( + Object.fromEntries( + Object.entries(config.services).map( + ([k, v]) => [ + k, new algo.RoundRobinLoadBalancer(v) + ] + ) + ) + ), + + _balancer: null, + _balancerCache: null, + _target: '', + _targetCache: null, + + _g: { + connectionID: 0, + }, + + _connectionPool: new algo.ResourcePool( + () => ++_g.connectionID + ), +}) + +.import({ + __turnDown: 'proxy', + __serviceID: 'router', +}) + +.pipeline('session') + .handleStreamStart( + () => ( + _balancerCache = new algo.Cache( + // k is a balancer, v is a target + (k ) => k.select(), + (k,v) => k.deselect(v), + ), + _targetCache = new algo.Cache( + // k is a target, v is a connection ID + (k ) => _connectionPool.allocate(k), + (k,v) => _connectionPool.free(v), + ) + ) + ) + .handleStreamEnd( + () => ( + _targetCache.clear(), + _balancerCache.clear() + ) + ) + +.pipeline('request') + .handleMessageStart( + () => ( + _balancer = _services[__serviceID], + _balancer && (_target = _balancerCache.get(_balancer)), + _target && (__turnDown = true) + ) + ) + .link( + 'forward', () => Boolean(_target), + '' + ) + +.pipeline('forward') + .muxHTTP( + 'connection', + () => _targetCache.get(_target) + ) + +.pipeline('connection') + .connect( + () => _target + ) + +)(JSON.decode(pipy.load('config/balancer.json'))) diff --git a/pipy/tutorial/15-cache/plugins/cache.js b/pipy/tutorial/15-cache/plugins/cache.js new file mode 100644 index 0000000000000000000000000000000000000000..5dd0d79797af704ebb9404a38e51188ae472568a --- /dev/null +++ b/pipy/tutorial/15-cache/plugins/cache.js @@ -0,0 +1,61 @@ +(config => + +pipy({ + _cache: {}, + _cachedKey: '', + _cachedResponse: null, + _useCache: false, +}) + +.import({ + __turnDown: 'proxy', + __serviceID: 'router', +}) + +.pipeline('request') + .handleMessageStart( + msg => ( + (( + host, path + ) => ( + host = msg.head.headers.host, + path = msg.head.path, + _useCache = config.services[__serviceID]?.some?.( + ext => path.endsWith(ext) + ), + _useCache && ( + _cachedKey = host + path, + _cachedResponse = _cache[_cachedKey], + _cachedResponse?.time < Date.now() - config.timeout && ( + _cachedResponse = _cache[_cachedKey] = null + ), + _cachedResponse && (__turnDown = true) + ) + ))() + ) + ) + .link( + 'cache', () => Boolean(_cachedResponse), + 'bypass' + ) + +.pipeline('cache') + .replaceMessage( + () => _cachedResponse.message + ) + +.pipeline('response') + .handleMessage( + msg => ( + _useCache && (msg.head.status || 200) < 400 && ( + _cache[_cachedKey] = { + time: Date.now(), + message: msg, + } + ) + ) + ) + +.pipeline('bypass') + +)(JSON.decode(pipy.load('config/cache.json'))) diff --git a/pipy/tutorial/15-cache/plugins/default.js b/pipy/tutorial/15-cache/plugins/default.js new file mode 100644 index 0000000000000000000000000000000000000000..ad2165691db61e45de67ba52a7558d5c74fc4f0c --- /dev/null +++ b/pipy/tutorial/15-cache/plugins/default.js @@ -0,0 +1,6 @@ +pipy() + +.pipeline('request') + .replaceMessage( + new Message({ status: 404 }, 'No handler') + ) diff --git a/pipy/tutorial/15-cache/plugins/router.js b/pipy/tutorial/15-cache/plugins/router.js new file mode 100644 index 0000000000000000000000000000000000000000..d27b3f19967df53b70b00f4c6ed884cbd3af82a5 --- /dev/null +++ b/pipy/tutorial/15-cache/plugins/router.js @@ -0,0 +1,47 @@ +(config => + +pipy({ + _router: new algo.URLRouter( + Object.fromEntries( + Object.entries(config.routes).map( + ([k, v]) => [ + k, + { + ...v, + rewrite: v.rewrite ? [ + new RegExp(v.rewrite[0]), + v.rewrite[1], + ] : undefined, + } + ] + ) + ) + ), + + _route: null, +}) + +.export('router', { + __serviceID: '', +}) + +.pipeline('request') + .handleMessageStart( + msg => ( + _route = _router.find( + msg.head.headers.host, + msg.head.path, + ), + _route && ( + __serviceID = _route.service, + _route.rewrite && ( + msg.head.path = msg.head.path.replace( + _route.rewrite[0], + _route.rewrite[1], + ) + ) + ) + ) + ) + +)(JSON.decode(pipy.load('config/router.json'))) diff --git a/pipy/tutorial/15-cache/proxy.js b/pipy/tutorial/15-cache/proxy.js new file mode 100644 index 0000000000000000000000000000000000000000..e16f3176d4957aa4b8d158aaf5775aa452a6fd36 --- /dev/null +++ b/pipy/tutorial/15-cache/proxy.js @@ -0,0 +1,21 @@ +(config => + +pipy() + +.export('proxy', { + __turnDown: false, +}) + +.listen(config.listen) + .use(config.plugins, 'session') + .demuxHTTP('request') + +.pipeline('request') + .use( + config.plugins, + 'request', + 'response', + () => __turnDown + ) + +)(JSON.decode(pipy.load('config/proxy.json'))) diff --git a/pipy/tutorial/16-serve-static/config/balancer.json b/pipy/tutorial/16-serve-static/config/balancer.json new file mode 100644 index 0000000000000000000000000000000000000000..047a6413ce88727d8654331c2325b02288971c22 --- /dev/null +++ b/pipy/tutorial/16-serve-static/config/balancer.json @@ -0,0 +1,7 @@ +{ + "services": { + "service-hi" : ["127.0.0.1:8080", "127.0.0.1:8082"], + "service-echo" : ["127.0.0.1:8081"], + "service-tell-ip" : ["127.0.0.1:8082"] + } +} diff --git a/pipy/tutorial/16-serve-static/config/proxy.json b/pipy/tutorial/16-serve-static/config/proxy.json new file mode 100644 index 0000000000000000000000000000000000000000..d1ff2cd0ee396a340ec41f3aeded48430fcfe11e --- /dev/null +++ b/pipy/tutorial/16-serve-static/config/proxy.json @@ -0,0 +1,9 @@ +{ + "listen": 8000, + "plugins": [ + "plugins/router.js", + "plugins/serve-static.js", + "plugins/balancer.js", + "plugins/default.js" + ] +} diff --git a/pipy/tutorial/16-serve-static/config/router.json b/pipy/tutorial/16-serve-static/config/router.json new file mode 100644 index 0000000000000000000000000000000000000000..a362f29e055b6761310f1f8d620e32c7ca41b926 --- /dev/null +++ b/pipy/tutorial/16-serve-static/config/router.json @@ -0,0 +1,7 @@ +{ + "routes": { + "/hi/*": { "service": "service-hi" }, + "/echo": { "service": "service-echo" }, + "/ip/*": { "service": "service-tell-ip", "rewrite": ["^/ip/?", "/"] } + } +} diff --git a/pipy/tutorial/16-serve-static/config/serve-static.json b/pipy/tutorial/16-serve-static/config/serve-static.json new file mode 100644 index 0000000000000000000000000000000000000000..ea1085f3d7ef62dafa70ac307edcfcec525788a6 --- /dev/null +++ b/pipy/tutorial/16-serve-static/config/serve-static.json @@ -0,0 +1,7 @@ +{ + "services": { + "service-tell-ip": { + "root": "www" + } + } +} diff --git a/pipy/tutorial/16-serve-static/plugins/balancer.js b/pipy/tutorial/16-serve-static/plugins/balancer.js new file mode 100644 index 0000000000000000000000000000000000000000..a82b0cb05806f55447712c775db4c54da5e7d5e5 --- /dev/null +++ b/pipy/tutorial/16-serve-static/plugins/balancer.js @@ -0,0 +1,79 @@ +(config => + +pipy({ + _services: ( + Object.fromEntries( + Object.entries(config.services).map( + ([k, v]) => [ + k, new algo.RoundRobinLoadBalancer(v) + ] + ) + ) + ), + + _balancer: null, + _balancerCache: null, + _target: '', + _targetCache: null, + + _g: { + connectionID: 0, + }, + + _connectionPool: new algo.ResourcePool( + () => ++_g.connectionID + ), +}) + +.import({ + __turnDown: 'proxy', + __serviceID: 'router', +}) + +.pipeline('session') + .handleStreamStart( + () => ( + _balancerCache = new algo.Cache( + // k is a balancer, v is a target + (k ) => k.select(), + (k,v) => k.deselect(v), + ), + _targetCache = new algo.Cache( + // k is a target, v is a connection ID + (k ) => _connectionPool.allocate(k), + (k,v) => _connectionPool.free(v), + ) + ) + ) + .handleStreamEnd( + () => ( + _targetCache.clear(), + _balancerCache.clear() + ) + ) + +.pipeline('request') + .handleMessageStart( + () => ( + _balancer = _services[__serviceID], + _balancer && (_target = _balancerCache.get(_balancer)), + _target && (__turnDown = true) + ) + ) + .link( + 'forward', () => Boolean(_target), + '' + ) + +.pipeline('forward') + .muxHTTP( + 'connection', + () => _targetCache.get(_target) + ) + +.pipeline('connection') + .connect( + () => _target + ) + +)(JSON.decode(pipy.load('config/balancer.json'))) diff --git a/pipy/tutorial/16-serve-static/plugins/default.js b/pipy/tutorial/16-serve-static/plugins/default.js new file mode 100644 index 0000000000000000000000000000000000000000..ad2165691db61e45de67ba52a7558d5c74fc4f0c --- /dev/null +++ b/pipy/tutorial/16-serve-static/plugins/default.js @@ -0,0 +1,6 @@ +pipy() + +.pipeline('request') + .replaceMessage( + new Message({ status: 404 }, 'No handler') + ) diff --git a/pipy/tutorial/16-serve-static/plugins/router.js b/pipy/tutorial/16-serve-static/plugins/router.js new file mode 100644 index 0000000000000000000000000000000000000000..d27b3f19967df53b70b00f4c6ed884cbd3af82a5 --- /dev/null +++ b/pipy/tutorial/16-serve-static/plugins/router.js @@ -0,0 +1,47 @@ +(config => + +pipy({ + _router: new algo.URLRouter( + Object.fromEntries( + Object.entries(config.routes).map( + ([k, v]) => [ + k, + { + ...v, + rewrite: v.rewrite ? [ + new RegExp(v.rewrite[0]), + v.rewrite[1], + ] : undefined, + } + ] + ) + ) + ), + + _route: null, +}) + +.export('router', { + __serviceID: '', +}) + +.pipeline('request') + .handleMessageStart( + msg => ( + _route = _router.find( + msg.head.headers.host, + msg.head.path, + ), + _route && ( + __serviceID = _route.service, + _route.rewrite && ( + msg.head.path = msg.head.path.replace( + _route.rewrite[0], + _route.rewrite[1], + ) + ) + ) + ) + ) + +)(JSON.decode(pipy.load('config/router.json'))) diff --git a/pipy/tutorial/16-serve-static/plugins/serve-static.js b/pipy/tutorial/16-serve-static/plugins/serve-static.js new file mode 100644 index 0000000000000000000000000000000000000000..906937ec762e69e52bbd974f73191203517b54a5 --- /dev/null +++ b/pipy/tutorial/16-serve-static/plugins/serve-static.js @@ -0,0 +1,37 @@ +(config => + +pipy({ + _root: '', + _file: null, +}) + +.import({ + __turnDown: 'proxy', + __serviceID: 'router', +}) + +.pipeline('request') + .handleMessageStart( + msg => ( + _root = config.services[__serviceID]?.root, + _root && ( + _file = http.File.from(_root + msg.head.path) + ) + ) + ) + .link( + 'serve', () => Boolean(_file), + 'bypass' + ) + +.pipeline('serve') + .replaceMessage( + msg => ( + __turnDown = true, + _file.toMessage(msg.head.headers['accept-encoding']) + ) + ) + +.pipeline('bypass') + +)(JSON.decode(pipy.load('config/serve-static.json'))) diff --git a/pipy/tutorial/16-serve-static/proxy.js b/pipy/tutorial/16-serve-static/proxy.js new file mode 100644 index 0000000000000000000000000000000000000000..e16f3176d4957aa4b8d158aaf5775aa452a6fd36 --- /dev/null +++ b/pipy/tutorial/16-serve-static/proxy.js @@ -0,0 +1,21 @@ +(config => + +pipy() + +.export('proxy', { + __turnDown: false, +}) + +.listen(config.listen) + .use(config.plugins, 'session') + .demuxHTTP('request') + +.pipeline('request') + .use( + config.plugins, + 'request', + 'response', + () => __turnDown + ) + +)(JSON.decode(pipy.load('config/proxy.json'))) diff --git a/pipy/tutorial/16-serve-static/www/index.html b/pipy/tutorial/16-serve-static/www/index.html new file mode 100644 index 0000000000000000000000000000000000000000..cd7d4600ac4650f12b2cc4209d6238e3f238552a --- /dev/null +++ b/pipy/tutorial/16-serve-static/www/index.html @@ -0,0 +1,9 @@ + +
+ +Hello!
+ + diff --git a/pipy/tutorial/17-body-transform/config/balancer.json b/pipy/tutorial/17-body-transform/config/balancer.json new file mode 100644 index 0000000000000000000000000000000000000000..047a6413ce88727d8654331c2325b02288971c22 --- /dev/null +++ b/pipy/tutorial/17-body-transform/config/balancer.json @@ -0,0 +1,7 @@ +{ + "services": { + "service-hi" : ["127.0.0.1:8080", "127.0.0.1:8082"], + "service-echo" : ["127.0.0.1:8081"], + "service-tell-ip" : ["127.0.0.1:8082"] + } +} diff --git a/pipy/tutorial/17-body-transform/config/proxy.json b/pipy/tutorial/17-body-transform/config/proxy.json new file mode 100644 index 0000000000000000000000000000000000000000..dff544580958e87913c2ec92191a00f27488bea1 --- /dev/null +++ b/pipy/tutorial/17-body-transform/config/proxy.json @@ -0,0 +1,9 @@ +{ + "listen": 8000, + "plugins": [ + "plugins/router.js", + "plugins/transform.js", + "plugins/balancer.js", + "plugins/default.js" + ] +} diff --git a/pipy/tutorial/17-body-transform/config/router.json b/pipy/tutorial/17-body-transform/config/router.json new file mode 100644 index 0000000000000000000000000000000000000000..a362f29e055b6761310f1f8d620e32c7ca41b926 --- /dev/null +++ b/pipy/tutorial/17-body-transform/config/router.json @@ -0,0 +1,7 @@ +{ + "routes": { + "/hi/*": { "service": "service-hi" }, + "/echo": { "service": "service-echo" }, + "/ip/*": { "service": "service-tell-ip", "rewrite": ["^/ip/?", "/"] } + } +} diff --git a/pipy/tutorial/17-body-transform/config/transform.json b/pipy/tutorial/17-body-transform/config/transform.json new file mode 100644 index 0000000000000000000000000000000000000000..457f6c9bca6781bf76fdc6210fdf1ea8888c97f8 --- /dev/null +++ b/pipy/tutorial/17-body-transform/config/transform.json @@ -0,0 +1,7 @@ +{ + "services": { + "service-echo": { + "jsonToXml": true + } + } +} diff --git a/pipy/tutorial/17-body-transform/plugins/balancer.js b/pipy/tutorial/17-body-transform/plugins/balancer.js new file mode 100644 index 0000000000000000000000000000000000000000..a82b0cb05806f55447712c775db4c54da5e7d5e5 --- /dev/null +++ b/pipy/tutorial/17-body-transform/plugins/balancer.js @@ -0,0 +1,79 @@ +(config => + +pipy({ + _services: ( + Object.fromEntries( + Object.entries(config.services).map( + ([k, v]) => [ + k, new algo.RoundRobinLoadBalancer(v) + ] + ) + ) + ), + + _balancer: null, + _balancerCache: null, + _target: '', + _targetCache: null, + + _g: { + connectionID: 0, + }, + + _connectionPool: new algo.ResourcePool( + () => ++_g.connectionID + ), +}) + +.import({ + __turnDown: 'proxy', + __serviceID: 'router', +}) + +.pipeline('session') + .handleStreamStart( + () => ( + _balancerCache = new algo.Cache( + // k is a balancer, v is a target + (k ) => k.select(), + (k,v) => k.deselect(v), + ), + _targetCache = new algo.Cache( + // k is a target, v is a connection ID + (k ) => _connectionPool.allocate(k), + (k,v) => _connectionPool.free(v), + ) + ) + ) + .handleStreamEnd( + () => ( + _targetCache.clear(), + _balancerCache.clear() + ) + ) + +.pipeline('request') + .handleMessageStart( + () => ( + _balancer = _services[__serviceID], + _balancer && (_target = _balancerCache.get(_balancer)), + _target && (__turnDown = true) + ) + ) + .link( + 'forward', () => Boolean(_target), + '' + ) + +.pipeline('forward') + .muxHTTP( + 'connection', + () => _targetCache.get(_target) + ) + +.pipeline('connection') + .connect( + () => _target + ) + +)(JSON.decode(pipy.load('config/balancer.json'))) diff --git a/pipy/tutorial/17-body-transform/plugins/default.js b/pipy/tutorial/17-body-transform/plugins/default.js new file mode 100644 index 0000000000000000000000000000000000000000..ad2165691db61e45de67ba52a7558d5c74fc4f0c --- /dev/null +++ b/pipy/tutorial/17-body-transform/plugins/default.js @@ -0,0 +1,6 @@ +pipy() + +.pipeline('request') + .replaceMessage( + new Message({ status: 404 }, 'No handler') + ) diff --git a/pipy/tutorial/17-body-transform/plugins/router.js b/pipy/tutorial/17-body-transform/plugins/router.js new file mode 100644 index 0000000000000000000000000000000000000000..d27b3f19967df53b70b00f4c6ed884cbd3af82a5 --- /dev/null +++ b/pipy/tutorial/17-body-transform/plugins/router.js @@ -0,0 +1,47 @@ +(config => + +pipy({ + _router: new algo.URLRouter( + Object.fromEntries( + Object.entries(config.routes).map( + ([k, v]) => [ + k, + { + ...v, + rewrite: v.rewrite ? [ + new RegExp(v.rewrite[0]), + v.rewrite[1], + ] : undefined, + } + ] + ) + ) + ), + + _route: null, +}) + +.export('router', { + __serviceID: '', +}) + +.pipeline('request') + .handleMessageStart( + msg => ( + _route = _router.find( + msg.head.headers.host, + msg.head.path, + ), + _route && ( + __serviceID = _route.service, + _route.rewrite && ( + msg.head.path = msg.head.path.replace( + _route.rewrite[0], + _route.rewrite[1], + ) + ) + ) + ) + ) + +)(JSON.decode(pipy.load('config/router.json'))) diff --git a/pipy/tutorial/17-body-transform/plugins/transform.js b/pipy/tutorial/17-body-transform/plugins/transform.js new file mode 100644 index 0000000000000000000000000000000000000000..27ed8c563a29adf9c639ad050d0bcf2cf9a571e8 --- /dev/null +++ b/pipy/tutorial/17-body-transform/plugins/transform.js @@ -0,0 +1,94 @@ +(config => + +pipy({ + _services: config.services, + _service: null, + + _obj2xml: node => ( + typeof node === 'object' ? ( + Object.entries(node).flatMap( + ([k, v]) => ( + v instanceof Array && ( + v.map(e => new XML.Node(k, null, _obj2xml(e))) + ) || + v instanceof Object && ( + new XML.Node(k, null, _obj2xml(v)) + ) || ( + new XML.Node(k, null, [v]) + ) + ) + ) + ) : [ + new XML.Node('body', null, ['']) + ] + ), + + _xml2obj: node => ( + node ? ( + (( + children, + previous, + obj, k, v, + ) => ( + obj = {}, + node.children.forEach(node => ( + (children = node.children) && ( + k = node.name, + v = children.every(i => typeof i === 'string') ? children.join('') : _xml2obj(node), + previous = obj[k], + previous instanceof Array ? ( + previous.push(v) + ) : ( + obj[k] = previous ? [previous, v] : v + ) + ) + )), + obj + ))() + ) : {} + ), +}) + +.import({ + __serviceID: 'router', +}) + +.pipeline('request') + .handleStreamStart( + () => _service = _services[__serviceID] + ) + .link( + 'json2xml', () => Boolean(_service?.jsonToXml), + 'xml2json', () => Boolean(_service?.xmlToJson), + 'bypass' + ) + +.pipeline('json2xml') + .replaceMessageBody( + data => ( + XML.encode( + new XML.Node( + '', null, [ + new XML.Node( + 'root', null, + _obj2xml(JSON.decode(data)) + ) + ] + ), + 2, + ) + ) + ) + +.pipeline('xml2json') + .replaceMessageBody( + data => ( + JSON.encode( + _xml2obj(XML.decode(data)) + ) + ) + ) + +.pipeline('bypass') + +)(JSON.decode(pipy.load('config/transform.json'))) diff --git a/pipy/tutorial/17-body-transform/proxy.js b/pipy/tutorial/17-body-transform/proxy.js new file mode 100644 index 0000000000000000000000000000000000000000..e16f3176d4957aa4b8d158aaf5775aa452a6fd36 --- /dev/null +++ b/pipy/tutorial/17-body-transform/proxy.js @@ -0,0 +1,21 @@ +(config => + +pipy() + +.export('proxy', { + __turnDown: false, +}) + +.listen(config.listen) + .use(config.plugins, 'session') + .demuxHTTP('request') + +.pipeline('request') + .use( + config.plugins, + 'request', + 'response', + () => __turnDown + ) + +)(JSON.decode(pipy.load('config/proxy.json'))) diff --git a/pipy/tutorial/18-tls/config/balancer.json b/pipy/tutorial/18-tls/config/balancer.json new file mode 100644 index 0000000000000000000000000000000000000000..047a6413ce88727d8654331c2325b02288971c22 --- /dev/null +++ b/pipy/tutorial/18-tls/config/balancer.json @@ -0,0 +1,7 @@ +{ + "services": { + "service-hi" : ["127.0.0.1:8080", "127.0.0.1:8082"], + "service-echo" : ["127.0.0.1:8081"], + "service-tell-ip" : ["127.0.0.1:8082"] + } +} diff --git a/pipy/tutorial/18-tls/config/proxy.json b/pipy/tutorial/18-tls/config/proxy.json new file mode 100644 index 0000000000000000000000000000000000000000..d91b632d420c58f50215269bfa41d03d7fd309fb --- /dev/null +++ b/pipy/tutorial/18-tls/config/proxy.json @@ -0,0 +1,9 @@ +{ + "listen": 8000, + "listenTLS": 8443, + "plugins": [ + "plugins/router.js", + "plugins/balancer.js", + "plugins/default.js" + ] +} diff --git a/pipy/tutorial/18-tls/config/router.json b/pipy/tutorial/18-tls/config/router.json new file mode 100644 index 0000000000000000000000000000000000000000..a362f29e055b6761310f1f8d620e32c7ca41b926 --- /dev/null +++ b/pipy/tutorial/18-tls/config/router.json @@ -0,0 +1,7 @@ +{ + "routes": { + "/hi/*": { "service": "service-hi" }, + "/echo": { "service": "service-echo" }, + "/ip/*": { "service": "service-tell-ip", "rewrite": ["^/ip/?", "/"] } + } +} diff --git a/pipy/tutorial/18-tls/plugins/balancer.js b/pipy/tutorial/18-tls/plugins/balancer.js new file mode 100644 index 0000000000000000000000000000000000000000..a82b0cb05806f55447712c775db4c54da5e7d5e5 --- /dev/null +++ b/pipy/tutorial/18-tls/plugins/balancer.js @@ -0,0 +1,79 @@ +(config => + +pipy({ + _services: ( + Object.fromEntries( + Object.entries(config.services).map( + ([k, v]) => [ + k, new algo.RoundRobinLoadBalancer(v) + ] + ) + ) + ), + + _balancer: null, + _balancerCache: null, + _target: '', + _targetCache: null, + + _g: { + connectionID: 0, + }, + + _connectionPool: new algo.ResourcePool( + () => ++_g.connectionID + ), +}) + +.import({ + __turnDown: 'proxy', + __serviceID: 'router', +}) + +.pipeline('session') + .handleStreamStart( + () => ( + _balancerCache = new algo.Cache( + // k is a balancer, v is a target + (k ) => k.select(), + (k,v) => k.deselect(v), + ), + _targetCache = new algo.Cache( + // k is a target, v is a connection ID + (k ) => _connectionPool.allocate(k), + (k,v) => _connectionPool.free(v), + ) + ) + ) + .handleStreamEnd( + () => ( + _targetCache.clear(), + _balancerCache.clear() + ) + ) + +.pipeline('request') + .handleMessageStart( + () => ( + _balancer = _services[__serviceID], + _balancer && (_target = _balancerCache.get(_balancer)), + _target && (__turnDown = true) + ) + ) + .link( + 'forward', () => Boolean(_target), + '' + ) + +.pipeline('forward') + .muxHTTP( + 'connection', + () => _targetCache.get(_target) + ) + +.pipeline('connection') + .connect( + () => _target + ) + +)(JSON.decode(pipy.load('config/balancer.json'))) diff --git a/pipy/tutorial/18-tls/plugins/default.js b/pipy/tutorial/18-tls/plugins/default.js new file mode 100644 index 0000000000000000000000000000000000000000..ad2165691db61e45de67ba52a7558d5c74fc4f0c --- /dev/null +++ b/pipy/tutorial/18-tls/plugins/default.js @@ -0,0 +1,6 @@ +pipy() + +.pipeline('request') + .replaceMessage( + new Message({ status: 404 }, 'No handler') + ) diff --git a/pipy/tutorial/18-tls/plugins/router.js b/pipy/tutorial/18-tls/plugins/router.js new file mode 100644 index 0000000000000000000000000000000000000000..d27b3f19967df53b70b00f4c6ed884cbd3af82a5 --- /dev/null +++ b/pipy/tutorial/18-tls/plugins/router.js @@ -0,0 +1,47 @@ +(config => + +pipy({ + _router: new algo.URLRouter( + Object.fromEntries( + Object.entries(config.routes).map( + ([k, v]) => [ + k, + { + ...v, + rewrite: v.rewrite ? [ + new RegExp(v.rewrite[0]), + v.rewrite[1], + ] : undefined, + } + ] + ) + ) + ), + + _route: null, +}) + +.export('router', { + __serviceID: '', +}) + +.pipeline('request') + .handleMessageStart( + msg => ( + _route = _router.find( + msg.head.headers.host, + msg.head.path, + ), + _route && ( + __serviceID = _route.service, + _route.rewrite && ( + msg.head.path = msg.head.path.replace( + _route.rewrite[0], + _route.rewrite[1], + ) + ) + ) + ) + ) + +)(JSON.decode(pipy.load('config/router.json'))) diff --git a/pipy/tutorial/18-tls/proxy.js b/pipy/tutorial/18-tls/proxy.js new file mode 100644 index 0000000000000000000000000000000000000000..89d261111a24ffff3fced0001928ca4bfdd78efe --- /dev/null +++ b/pipy/tutorial/18-tls/proxy.js @@ -0,0 +1,36 @@ +(config => + +pipy() + +.export('proxy', { + __turnDown: false, + __isTLS: false, +}) + +.listen(config.listen) + .link('tls-offloaded') + +.listen(config.listenTLS) + .handleStreamStart( + () => __isTLS = true + ) + .acceptTLS('tls-offloaded', { + certificate: config.listenTLS ? { + cert: new crypto.CertificateChain(pipy.load('secret/server-cert.pem')), + key: new crypto.PrivateKey(pipy.load('secret/server-key.pem')), + } : undefined, + }) + +.pipeline('tls-offloaded') + .use(config.plugins, 'session') + .demuxHTTP('request') + +.pipeline('request') + .use( + config.plugins, + 'request', + 'response', + () => __turnDown + ) + +)(JSON.decode(pipy.load('config/proxy.json'))) diff --git a/pipy/tutorial/18-tls/secret/server-cert.pem b/pipy/tutorial/18-tls/secret/server-cert.pem new file mode 100644 index 0000000000000000000000000000000000000000..900243e32b726b3ff3258f022edebbfe68590033 --- /dev/null +++ b/pipy/tutorial/18-tls/secret/server-cert.pem @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDwTCCAakCCQDE6AHhVbuTAjANBgkqhkiG9w0BAQsFADAiMQswCQYDVQQGEwJj +bjETMBEGA1UEAwwKZmxvbWVzaC5pbzAeFw0yMTA3MjAxNDA0NDBaFw00ODEyMDUx +NDA0NDBaMCMxCzAJBgNVBAYTAmNuMRQwEgYDVQQDDAtleGFtcGxlLmNvbTCCASIw +DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALY1yArHBxYssX61RXIFauyX6Mne +maS1/zj+wnzCqUy7KvLmbLKSh26bfP7Dmq1SbPOwzA374zpoSb5yca6BJj38yTxT +mezgrUHVSdajBF/r1LkCeVfZf9v4FMABDonw55pK4ZpyEZS3JrKG35jKSa74j8WM +RVbnB+2XWhJQ0C2F3qdOwPK3M3sekp3sh+5JQ7U/8vkbkegRdqTQtIMdwiGbfl5k +GzVCGTqKUxmLVHcwSvghZbpYYbT+zHjczx3f8zs+qX1CXbts91shtsXSfBXnPpTJ +KpddCbf/uJZtpV2dFMAYwhD+/FSIUwMML5mLdj33pyOCU6hCWxIALOXma8sCAwEA +ATANBgkqhkiG9w0BAQsFAAOCAgEARcw9hS5nWId8gIh1x83E5I4cvwzhOe7IJKSv +2Mfnj7hp4UgjJCSPOGOGpk5cScBoMNBl45pT/8NzEDdPtiitTML0d7ytLJVc3byK +/yeoiKeqsF+SWDMD78Ea92Lb5IaacmfkjnPLQKus+UToKoQv6HYJDynQr50kTgmv +TDSkMLjHTPgv9FsP9wDLxo5xr8DoM/p/8rnh6/plOo5f1fi7ZyQE8yjjzUPW9o5+ +zdMH3edTvw9boC4g8h0+4PXDOqdsnDCvI3E7duwl2t22WujLZbkw9HBBsV7IFWtb +9Y1d5vWTfUC1JMUbUC9mJUXLLj+XFNfAAqvsHFFEWiDVYcru1OuoLy/9jOFOeco+ +e1RYWPsBRg/mi7kaxxx9mf6+iFlN5MYeHOabk1iL7ufhV+gnav+m9AhijW1FD3WA +rD+q2OvohP0oz+mBxVJGdcdY7KoQsl0pxprJNSZqmmvEDmYYxe5Q6BPI44bpltcm +lmPwCWsEpYg37RLg5VsZSikiOs67tWO86Ya0muJbMfdxKC5H+wdXbwLL8BAYUYAz +nTnQd+vCWBhcWejQULYv9Nv0E1viqY4J/DtowitIvqvj+ZZehnMoOY81pbiOeXqY +wZLO9QTXU5yzRKF8okBE/+BITWwCd/wfUJwTg23x/USjh2R/uUmmwYrtP2nQ+dtA +l7wEJqQ= +-----END CERTIFICATE----- diff --git a/pipy/tutorial/18-tls/secret/server-key.pem b/pipy/tutorial/18-tls/secret/server-key.pem new file mode 100644 index 0000000000000000000000000000000000000000..41015c14ea3cd48165fb89960012813540c7c74e --- /dev/null +++ b/pipy/tutorial/18-tls/secret/server-key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAtjXICscHFiyxfrVFcgVq7Jfoyd6ZpLX/OP7CfMKpTLsq8uZs +spKHbpt8/sOarVJs87DMDfvjOmhJvnJxroEmPfzJPFOZ7OCtQdVJ1qMEX+vUuQJ5 +V9l/2/gUwAEOifDnmkrhmnIRlLcmsobfmMpJrviPxYxFVucH7ZdaElDQLYXep07A +8rczex6SneyH7klDtT/y+RuR6BF2pNC0gx3CIZt+XmQbNUIZOopTGYtUdzBK+CFl +ulhhtP7MeNzPHd/zOz6pfUJdu2z3WyG2xdJ8Fec+lMkql10Jt/+4lm2lXZ0UwBjC +EP78VIhTAwwvmYt2PfenI4JTqEJbEgAs5eZrywIDAQABAoIBACkhltin+PeOezrJ +HLYSdwKVgB0hvLkrxENPbwPOxXzmu1k9nsfov92+B8dSzHXXp3STMlztwWuL4ym0 +l+j2inVvvNV7YcHRLgswZIypG/GzQL/KyS2FkZFMPRLiqi+FTCLOSIMewM07Uub1 +/z90WpG+1mWXtodZe7asdc6wo23C6u/EnMnHBidM1uHT76Wn/axVrzafLVuYi0ZZ +NSaDPgpdoU7oeZByNkqD+fKGxFYX9C7GJjbHMKfLZF+bBHSM/6bhego0TXl7Wgrg +JM3BPEjbpO+nKy9RzUyuzvKu1ZkO/GywQhvZPymKGQp/DoBdxC17jRYL4nXYDnfr +5tUAT9ECgYEA2KCIWtU1ie6jJNlIfPoM8Ng/ZqbGZIKiTKbt63Y7oftEgOujJjkn +pBoilCxbAwg98UbN0Rc/O8112geba/ZUwbLXefiFn5I42ilR29oBIKhIAklBJYEL +nwDWJQhuElEfOdVmkt5pxlrmAd3T7TB7L7FpE2Xyaitw6nciXP7fHq0CgYEA11Pb +MGVbdKBmR54tK9bb80TFGSxlGP+VLxBS7QxSoO9r7xOMlgC1zYn91d0Dn5lw6Mlo +iU3VOJxUX6Zj5B0Z81fcvDVFGzounpegldgXF8RIgocx3VA8XTWwjnEABPt0Pi4R +NLLRp5TmsY4DU1gQ7HnNwzsAanqGZyg8fP/J21cCgYBHwQTpcW4GfjEz53UHUJww +urBlhCB68npoAXMVZ1hhUyVhvquP4aaryKxjgD21R2mdHeLWu2iKmofK7HIi1LlG +X8LX9+Xq8Tg7qyweMpvlAVi4ySL3FUOQK2rDYYN0NcKuGW2cgGdw+HLvc1bbXg+v +wIgZkM83M6R3RXbwbsvhEQKBgAYWk+L5aq2Qf3PU8BgGOi/VqPD+RVmAmHo9LusO +5yMvwdB/sADpwjouhaPPXPPdRcj/MNp4/edc3DHcJYTkocA0osQbiXmsmnbXeK3j +7zs3zIkMQ4erZhmIlBZi8YoyheXgLZJc3/qqBh/NESEBI3FcPEoh1/AqyLQP+i3a +mZ1/AoGBAJnrNVqJdxN/oZxRy2ZIOQmhdVbr3/yLq09JFaUHeQD2umUdxo7ubUGK +zymjmXiXeyhXVC/coXLHf1yHuKkcE1Slj8aKw2ozFd1zXiTwF40amOVm1YcvLVM+ +Jol9F5yTyY3tmYkmPMHCX+9LHUqOdWlVPnT8DpUCX1mFvonSUh8G +-----END RSA PRIVATE KEY----- diff --git a/pipy/tutorial/19-load-balancing-advanced/config/balancer.json b/pipy/tutorial/19-load-balancing-advanced/config/balancer.json new file mode 100644 index 0000000000000000000000000000000000000000..a687cae166574cb25a09d370481299bec5f826d1 --- /dev/null +++ b/pipy/tutorial/19-load-balancing-advanced/config/balancer.json @@ -0,0 +1,15 @@ +{ + "services": { + "service-hi": { + "targets": ["127.0.0.1:8080", "127.0.0.1:8082"], + "alg": "RoundRobinLoadBalancer", + "sticky": true + }, + "service-echo": { + "targets": ["127.0.0.1:8081"] + }, + "service-tell-ip": { + "targets": ["127.0.0.1:8082"] + } + } +} diff --git a/pipy/tutorial/19-load-balancing-advanced/config/proxy.json b/pipy/tutorial/19-load-balancing-advanced/config/proxy.json new file mode 100644 index 0000000000000000000000000000000000000000..208a199a284343980a57c39fda4c40058dd0ea54 --- /dev/null +++ b/pipy/tutorial/19-load-balancing-advanced/config/proxy.json @@ -0,0 +1,8 @@ +{ + "listen": 8000, + "plugins": [ + "plugins/router.js", + "plugins/balancer.js", + "plugins/default.js" + ] +} diff --git a/pipy/tutorial/19-load-balancing-advanced/config/router.json b/pipy/tutorial/19-load-balancing-advanced/config/router.json new file mode 100644 index 0000000000000000000000000000000000000000..a362f29e055b6761310f1f8d620e32c7ca41b926 --- /dev/null +++ b/pipy/tutorial/19-load-balancing-advanced/config/router.json @@ -0,0 +1,7 @@ +{ + "routes": { + "/hi/*": { "service": "service-hi" }, + "/echo": { "service": "service-echo" }, + "/ip/*": { "service": "service-tell-ip", "rewrite": ["^/ip/?", "/"] } + } +} diff --git a/pipy/tutorial/19-load-balancing-advanced/plugins/balancer.js b/pipy/tutorial/19-load-balancing-advanced/plugins/balancer.js new file mode 100644 index 0000000000000000000000000000000000000000..40db9592c435676da210af97dcfda2a7f1d7036d --- /dev/null +++ b/pipy/tutorial/19-load-balancing-advanced/plugins/balancer.js @@ -0,0 +1,98 @@ +(config => + +pipy({ + _services: ( + Object.fromEntries( + Object.entries(config.services).map( + ([k, v]) => ( + ((balancer => ( + balancer = new algo[v.alg || 'RoundRobinLoadBalancer'](v.targets), + [k, { + balancer, + cache: v.sticky && new algo.Cache( + () => balancer.select() + ) + }] + ))() + ) + ) + ) + ) + ), + + _service: null, + _serviceCache: null, + _target: '', + _targetCache: null, + + _g: { + connectionID: 0, + }, + + _connectionPool: new algo.ResourcePool( + () => ++_g.connectionID + ), + + _selectKey: null, + _select: (service, key) => ( + service.cache && key ? ( + service.cache.get(key) + ) : ( + service.balancer.select() + ) + ), +}) + +.import({ + __turnDown: 'proxy', + __serviceID: 'router', +}) + +.pipeline('session') + .handleStreamStart( + () => ( + _serviceCache = new algo.Cache( + // k is a service, v is a target + (k ) => _select(k, _selectKey), + (k,v) => k.balancer.deselect(v), + ), + _targetCache = new algo.Cache( + // k is a target, v is a connection ID + (k ) => _connectionPool.allocate(k), + (k,v) => _connectionPool.free(v), + ) + ) + ) + .handleStreamEnd( + () => ( + _targetCache.clear(), + _serviceCache.clear() + ) + ) + +.pipeline('request') + .handleMessageStart( + (msg) => ( + _selectKey = msg.head?.headers?.['authorization'], + _service = _services[__serviceID], + _service && (_target = _serviceCache.get(_service)), + _target && (__turnDown = true) + ) + ) + .link( + 'forward', () => Boolean(_target), + '' + ) + +.pipeline('forward') + .muxHTTP( + 'connection', + () => _targetCache.get(_target) + ) + +.pipeline('connection') + .connect( + () => _target + ) + +)(JSON.decode(pipy.load('config/balancer.json'))) diff --git a/pipy/tutorial/19-load-balancing-advanced/plugins/default.js b/pipy/tutorial/19-load-balancing-advanced/plugins/default.js new file mode 100644 index 0000000000000000000000000000000000000000..ad2165691db61e45de67ba52a7558d5c74fc4f0c --- /dev/null +++ b/pipy/tutorial/19-load-balancing-advanced/plugins/default.js @@ -0,0 +1,6 @@ +pipy() + +.pipeline('request') + .replaceMessage( + new Message({ status: 404 }, 'No handler') + ) diff --git a/pipy/tutorial/19-load-balancing-advanced/plugins/router.js b/pipy/tutorial/19-load-balancing-advanced/plugins/router.js new file mode 100644 index 0000000000000000000000000000000000000000..d27b3f19967df53b70b00f4c6ed884cbd3af82a5 --- /dev/null +++ b/pipy/tutorial/19-load-balancing-advanced/plugins/router.js @@ -0,0 +1,47 @@ +(config => + +pipy({ + _router: new algo.URLRouter( + Object.fromEntries( + Object.entries(config.routes).map( + ([k, v]) => [ + k, + { + ...v, + rewrite: v.rewrite ? [ + new RegExp(v.rewrite[0]), + v.rewrite[1], + ] : undefined, + } + ] + ) + ) + ), + + _route: null, +}) + +.export('router', { + __serviceID: '', +}) + +.pipeline('request') + .handleMessageStart( + msg => ( + _route = _router.find( + msg.head.headers.host, + msg.head.path, + ), + _route && ( + __serviceID = _route.service, + _route.rewrite && ( + msg.head.path = msg.head.path.replace( + _route.rewrite[0], + _route.rewrite[1], + ) + ) + ) + ) + ) + +)(JSON.decode(pipy.load('config/router.json'))) diff --git a/pipy/tutorial/19-load-balancing-advanced/proxy.js b/pipy/tutorial/19-load-balancing-advanced/proxy.js new file mode 100644 index 0000000000000000000000000000000000000000..db859ed702263132f04b03e35e47663610604cea --- /dev/null +++ b/pipy/tutorial/19-load-balancing-advanced/proxy.js @@ -0,0 +1,22 @@ +(config => + +pipy() + +.export('proxy', { + __turnDown: false, + __isTLS: false, +}) + +.listen(config.listen) + .use(config.plugins, 'session') + .demuxHTTP('request') + +.pipeline('request') + .use( + config.plugins, + 'request', + 'response', + () => __turnDown + ) + +)(JSON.decode(pipy.load('config/proxy.json')))