From 902592d78d54d0830438dad1728ee10ddfeb6ae5 Mon Sep 17 00:00:00 2001 From: Oleg Zenchenko Date: Tue, 7 Apr 2020 14:49:21 -0400 Subject: [PATCH] Sample code to handle notifications --- devtools/README.md | 38 +++++++++++++++++ devtools/node/.eslintrc.json | 19 +++++++++ devtools/node/httpserver.js | 80 +++++++++++++++++++++++++++++++++++ devtools/node/package.json | 22 ++++++++++ devtools/python/httpserver.py | 71 +++++++++++++++++++++++++++++++ package.json | 1 + 6 files changed, 231 insertions(+) create mode 100644 devtools/README.md create mode 100644 devtools/node/.eslintrc.json create mode 100644 devtools/node/httpserver.js create mode 100644 devtools/node/package.json create mode 100644 devtools/python/httpserver.py diff --git a/devtools/README.md b/devtools/README.md new file mode 100644 index 00000000..1460606e --- /dev/null +++ b/devtools/README.md @@ -0,0 +1,38 @@ +#### Dev Tools + +#### HTTP server for local debug + +In order to confirm SNS subscriptions / receive notifications a local server could be useful. The sample server allows to: +- confirm a subscription +- log notification messages + +To start the server +```sh +cd python +# starts the server at specified interface and port 8080 +python httpserver.py --bind 127.0.0.1 + +# starts the server at custom interface and port 8000 +python httpserver.py 8000 --bind 127.0.0.1 +``` +or for node server +```sh +cd node +npm install + +# starts the server at 127.0.0.1:8080 +node httpserver.js + +# starts the server at custom interface and port +node httpserver.js 127.0.0.1 8000 +``` + +To expose the server IP to the internet some tools such as [ngrok](https://ngrok.com/download) can be used: + +```sh +ngrok http 8080 +... +Forwarding http://.ngrok.io -> http://localhost:8080 +Forwarding https://.ngrok.io -> http://localhost:8080 +... +``` diff --git a/devtools/node/.eslintrc.json b/devtools/node/.eslintrc.json new file mode 100644 index 00000000..ef7eee10 --- /dev/null +++ b/devtools/node/.eslintrc.json @@ -0,0 +1,19 @@ +{ + "env": { + "browser": true, + "commonjs": true, + "es6": true + }, + "extends": [ + "standard" + ], + "globals": { + "Atomics": "readonly", + "SharedArrayBuffer": "readonly" + }, + "parserOptions": { + "ecmaVersion": 2018 + }, + "rules": { + } +} diff --git a/devtools/node/httpserver.js b/devtools/node/httpserver.js new file mode 100644 index 00000000..a56312cf --- /dev/null +++ b/devtools/node/httpserver.js @@ -0,0 +1,80 @@ +/* + Copyright (c) 2020, Circle Internet Financial Trading Company Limited. + All rights reserved. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL CIRCLE INTERNET FINANCIAL TRADING COMPANY + LIMITED BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. +*/ + +/* jshint esversion: 6 */ + +const http = require('http') +const r = require('request') + +const server = http.createServer((request, response) => { + if (request.method === 'POST') { + let body = '' + request.on('data', (data) => { + body += data + }) + request.on('end', () => { + console.log(`POST request, \nPath: ${request.url}`) + console.log('Headers: ') + console.dir(request.headers) + console.log(`Body: ${body}`) + + response.writeHead(200, { + 'Content-Type': 'text/html' + }) + response.end(`POST request for ${request.url}`) + handleBody(body) + }) + } else { + console.log('GET methods not supported') + } + + const handleBody = (body) => { + const envelope = JSON.parse(body) + switch (envelope.Type) { + case 'SubscriptionConfirmation': { + r(envelope.SubscribeURL, (err) => { + if (err) { + console.err('Subscription NOT confirmed.') + } else { + console.log('Subscription confirmed.') + } + }) + break + } + case 'Notification': { + console.log(`Received message ${envelope.Message}`) + break + } + default: { + console.error(`Message of type ${body.Type} not supported`) + } + } + } +}) + +const args = process.argv.slice(2) +let host = '127.0.0.1' +let port = 8080 +if (args.length === 2) { + host = args[0] + port = args[1] +} +if (args.length === 1) { + port = args[0] +} +server.listen(port, host) +console.log(`Starting httpd on port ${port}`) diff --git a/devtools/node/package.json b/devtools/node/package.json new file mode 100644 index 00000000..d528c62a --- /dev/null +++ b/devtools/node/package.json @@ -0,0 +1,22 @@ +{ + "name": "devtools", + "version": "1.0.0", + "description": "#### HTTP server for local debug", + "main": "httpserver.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Circle", + "license": "MIT", + "devDependencies": { + "eslint": "^6.8.0", + "eslint-config-standard": "^14.1.1", + "eslint-plugin-import": "^2.20.2", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-promise": "^4.2.1", + "eslint-plugin-standard": "^4.0.1" + }, + "dependencies": { + "request": "^2.88.2" + } +} diff --git a/devtools/python/httpserver.py b/devtools/python/httpserver.py new file mode 100644 index 00000000..3b9d5953 --- /dev/null +++ b/devtools/python/httpserver.py @@ -0,0 +1,71 @@ +# Copyright (c) 2020, Circle Internet Financial Trading Company Limited. +# All rights reserved. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL CIRCLE INTERNET FINANCIAL TRADING COMPANY +# LIMITED BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, +# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +from http.server import BaseHTTPRequestHandler, HTTPServer +import logging +import json +import urllib.request + + +class Server(BaseHTTPRequestHandler): + def _set_response(self, code=200): + self.send_response(code) + self.send_header('Content-type', 'application/json') + self.end_headers() + + def do_POST(self): + content_length = int(self.headers['Content-Length']) + post_data = self.rfile.read(content_length) + logging.info("POST request,\nPath: %s\nHeaders:\n%s\n\nBody:\n%s\n", + str(self.path), str(self.headers), post_data.decode('utf-8')) + + url = json.loads(post_data.decode('utf-8')).get("SubscribeURL") + + if url is not None and url.lower().startswith('http'): + req = urllib.request.Request(url) + # nosec as url is from AWS + with urllib.request.urlopen(req) as response: + if response.status == 200: + logging.info("Subscription confirmed for url %s", url) + else: + logging.error("GET request to %s failed.", url) + self._set_response(code=response.status) + else: + logging.info("Received message %s.", post_data.decode('utf-8')) + self._set_response() + + self.wfile.write("POST request for {}".format(self.path).encode('utf-8')) + + +def run(server_class=HTTPServer, handler_class=Server, port=8080): + logging.basicConfig(level=logging.INFO) + server_address = ('', port) + httpd = server_class(server_address, handler_class) + logging.info('Starting httpd on port %s\n', str(port)) + try: + httpd.serve_forever() + except KeyboardInterrupt: + pass + httpd.server_close() + logging.info('Stopping httpd...\n') + + +if __name__ == '__main__': + from sys import argv + + if len(argv) > 1: + run(port=int(argv[1])) + else: + run() diff --git a/package.json b/package.json index e52e518d..fa5901e0 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "1.0.0", "description": "Sample App for Payments API", "author": "Circle", + "license": "MIT", "private": true, "scripts": { "dev": "cross-env NODE_ENV=development nuxt-ts",