diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f09b3d4..75c01bd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -228,4 +228,4 @@ jobs: token: ${{ secrets.NPM_TOKEN }} access: public if: | - matrix.config.os == 'ubuntu-18.04' && matrix.config.node == '14' && steps.extract_branch.outputs.branch == 'main' + matrix.config.os == 'ubuntu-18.04' && matrix.config.node == '14' && matrix.architecture == 'x64' && steps.extract_branch.outputs.branch == 'main' diff --git a/lib/close.js b/lib/close.js new file mode 100644 index 0000000..bb70392 --- /dev/null +++ b/lib/close.js @@ -0,0 +1,29 @@ +/* +Copyright Netfoundry, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + + +/** + * close() + * + * @param {*} conn + */ +const close = ( conn, ) => { + + ziti.ziti_close( conn ); + +}; + +exports.close = close; diff --git a/lib/express-listener.js b/lib/express-listener.js index 1a62450..05fd1cc 100644 --- a/lib/express-listener.js +++ b/lib/express-listener.js @@ -16,6 +16,8 @@ limitations under the License. const zitiListen = require('./listen').listen; const EventEmitter = require('events'); +const { ZitiSocket } = require('./ziti-socket'); + const normalizedArgsSymbol = Symbol('normalizedArgs'); @@ -30,8 +32,6 @@ var _servers = new Map(); */ Server.prototype.on_listen = ( status ) => { - console.log('----------- Now inside on_listen callback ----------, status is: %o', status); - }; /** @@ -41,8 +41,6 @@ Server.prototype.on_listen = ( status ) => { */ Server.prototype.on_listen_client = ( obj ) => { - console.log('----------- Now inside on_listen_client callback ----------, obj is: %o', obj); - }; /** @@ -51,9 +49,7 @@ Server.prototype.on_listen = ( status ) => { * @param {*} obj */ Server.prototype.on_client_write = ( obj ) => { - - console.log('----------- Now inside on_client_write callback ----------, obj is: %o', obj); - + }; /** @@ -63,16 +59,13 @@ Server.prototype.on_listen = ( status ) => { */ Server.prototype.on_listen_client_connect = ( obj ) => { - console.log('----------- Now inside on_listen_client_connect callback ----------, obj is: %o', obj); - let self = _servers.get(obj.js_arb_data); - // console.log('----------- Now inside on_listen_client_connect callback ----------, self is: %o', self); + const socket = new ZitiSocket({ client: obj.client }); - console.log('----------- Now inside on_listen_client_connect callback ----------, emitting `connection` event for client/socket: %o', obj.client); - self.emit('connection', obj.client); - - + self._socket = socket; + + self.emit('connection', socket); }; /** @@ -81,13 +74,11 @@ Server.prototype.on_listen = ( status ) => { * @param {*} obj */ Server.prototype.on_listen_client_data = ( obj ) => { - - console.log('----------- Now inside on_listen_client_data callback ----------, obj is: %o', obj); - - if (obj.app_data) { - console.log('----------- app_data ----------, app_data string is: \n%o', obj.app_data.toString()); - } - + + let self = _servers.get(obj.js_arb_data); + let socket = self._socket; + + socket.captureData(obj.app_data); }; @@ -173,7 +164,6 @@ function Server(serviceName, options, connectionListener) { this.noDelay = Boolean(options.noDelay); this.keepAlive = Boolean(options.keepAlive); this.keepAliveInitialDelay = ~~(options.keepAliveInitialDelay / 1000); - } Object.setPrototypeOf(Server.prototype, EventEmitter.prototype); Object.setPrototypeOf(Server, EventEmitter); @@ -181,11 +171,8 @@ Object.setPrototypeOf(Server, EventEmitter); Server.prototype.listen = function( serviceName, ...args ) { - console.log('=======================> express-listener: Server.prototype.listen() entered: arguments: ', arguments); - let normalized = normalizeArgs(args); normalized = normalizeArgs(normalized[0]); - console.log('=======================> express-listener: Server.prototype.listen() normalized: ', normalized); // let options = normalized[0]; // we currently ignore options (a.k.a. `port`) let cb = normalized[1]; @@ -195,7 +182,6 @@ Server.prototype.listen = function( serviceName, ...args ) { _servers.set(index, this); zitiListen( serviceName, index, cb, this.on_listen_client, this.on_listen_client_connect, this.on_listen_client_data ); - }; Server.prototype.address = function() { @@ -263,5 +249,5 @@ Object.defineProperty(Server.prototype, 'listening', { module.exports = { Server, - }; +}; \ No newline at end of file diff --git a/lib/express.js b/lib/express.js index 8527ebb..0ecacc0 100644 --- a/lib/express.js +++ b/lib/express.js @@ -14,14 +14,18 @@ See the License for the specific language governing permissions and limitations under the License. */ -const net = require('./express-listener'); - +const expressListener = require('./express-listener'); const { Server } = require('_http_server'); // from NodeJS internals - -const getWrappedExpressApp = ( express, serviceName ) => { +/** + * express() + * + * @param {*} express + * @param {*} serviceName + */ +const express = ( express, serviceName ) => { var wrappedExpressApp = express(); @@ -30,47 +34,19 @@ const getWrappedExpressApp = ( express, serviceName ) => { * * A node `http.Server` is returned, with this * application (which is a `Function`) as its - * callback. If you wish to create both an HTTP - * and HTTPS server you may do so with the "http" - * and "https" modules as shown here: - * - * var http = require('http') - * , https = require('https') - * , express = require('express') - * , app = express(); - * - * http.createServer(app).listen(80); - * https.createServer({ ... }, app).listen(443); + * callback. * * @return {http.Server} * @public */ wrappedExpressApp.listen = function() { - console.log('=======================> wrappedExpressApp.listen() entered: arguments: ', arguments); - - // var server = http.createServer(this); - // console.log('=======================> wrappedExpressApp.listen() 1 server: ', server); - - Object.setPrototypeOf(Server.prototype, net.Server.prototype); - Object.setPrototypeOf(Server, net.Server); + Object.setPrototypeOf(Server.prototype, expressListener.Server.prototype); + Object.setPrototypeOf(Server, expressListener.Server); var server = new Server(this); + expressListener.Server.call( server, serviceName, { } ); - net.Server.call( - server, - serviceName, - { - // allowHalfOpen: true, - // noDelay: options.noDelay, - // keepAlive: options.keepAlive, - // keepAliveInitialDelay: options.keepAliveInitialDelay - }); - - - // zitiListen( serviceName, on_listen, on_listen_client, on_listen_client_connect, on_listen_client_data ); - - // return server.listen.apply(server, serviceName, arguments); return server.listen(serviceName, arguments); }; @@ -79,42 +55,4 @@ const getWrappedExpressApp = ( express, serviceName ) => { }; - -/** - * express() - * - * @param {*} express - * @param {*} serviceName - */ -const express = ( express, serviceName ) => { - - var app = getWrappedExpressApp( express, serviceName); - - // console.log('wrappedExpressApp: ', app); - - // const wrappedExpressResponse = Object.create( app.response, { - - // data: { - // value: function(data) { - // return this.status(200).json({status: true, data: data}); - // }, - // }, - - // message: { - // value: function(msg) { - // return this.status(200).json({status: true, message: msg}); - // }, - // }, - - // }); - - // app.response = Object.create(wrappedExpressResponse); - - return app; - -}; - exports.express = express; - - - diff --git a/lib/write.js b/lib/write.js index 30abb78..f3b9a39 100644 --- a/lib/write.js +++ b/lib/write.js @@ -15,6 +15,15 @@ limitations under the License. */ +/** + * on_write() + * + */ +const on_write = ( status ) => { + +}; + + /** * write() * @@ -22,9 +31,17 @@ limitations under the License. * @param {*} buf * @param {*} on_write callback */ -const write = ( conn, buf, on_write ) => { +const write = ( conn, buf, on_write_cb ) => { + + let cb; + + if (typeof on_write_cb === 'undefined') { + cb = on_write; + } else { + cb = on_write_cb; + } - ziti.ziti_write( conn, buf, on_write ); + ziti.ziti_write( conn, buf, cb ); }; diff --git a/lib/ziti-socket.js b/lib/ziti-socket.js new file mode 100644 index 0000000..9894e47 --- /dev/null +++ b/lib/ziti-socket.js @@ -0,0 +1,119 @@ +/* +Copyright Netfoundry, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +https://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +const EventEmitter = require('events'); +const stream = require('node:stream'); +const zitiWrite = require('./write').write; + + + +class ZitiSocket extends EventEmitter { + + constructor(opts) { + + super(); + + if (typeof opts !== 'undefined') { + if (typeof opts.client !== 'undefined') { + this.client = opts.client; + } + } + + this._writableState = new stream.Writable.WritableState({}, this, true); + + /** + * This stream is where we'll put any data returned from Ziti (see on_listen_client_data cb) + */ + let self = this; + this.readableZitiStream = new ReadableStream({ + start(controller) { + self.readableZitiStreamController = controller; + } + }); + } + + + /** + * + */ + captureData(data) { + + if ((typeof data !== 'undefined') && (data.byteLength > 0)) { + + this.readableZitiStreamController.enqueue(data); + this.emit('data', data); + + } else { + + this.emit('close'); + + } + } + + + /** + * Implements the writeable stream method `_write` by pushing the data onto the underlying Ziti connection. + */ + async write(chunk, encoding, cb) { + + let buffer; + + if (typeof chunk === 'string' || chunk instanceof String) { + buffer = Buffer.from(chunk, 'utf8'); + } else if (Buffer.isBuffer(chunk)) { + buffer = chunk; + } else if (chunk instanceof Uint8Array) { + buffer = Buffer.from(chunk, 'utf8'); + } else { + throw new Error('chunk type of [' + typeof chunk + '] is not a supported type'); + } + + if (buffer.length > 0) { + zitiWrite(this.client, buffer); + } + if (cb) { + cb(); + } + } + + /** + * + */ + _read() { /* NOP */ } + read() { /* NOP */ } + destroy() { /* NOP */ } + cork() { /* NOP */ } + uncork() { /* NOP */ } + pause() { /* NOP */ } + resume() { /* NOP */ } + destroy() { /* NOP */ } + end(data, encoding, callback) { /* NOP */ } + _final(cb) { cb(); } + setTimeout() { /* NOP */ } + setNoDelay() { /* NOP */ } + unshift(head) { /* NOP */ } +} + +Object.defineProperty(ZitiSocket.prototype, 'writable', { + get() { + return ( + true + ); + } +}); + + +exports.ZitiSocket = ZitiSocket; diff --git a/lib/ziti.js b/lib/ziti.js index c7ac2b0..eb3d530 100644 --- a/lib/ziti.js +++ b/lib/ziti.js @@ -18,7 +18,6 @@ var binding; function importAll (r) { r.keys().forEach(key => { - console.log('importAll() addon key is: ', key); binding = r(key); // Load the addon }); } @@ -44,6 +43,7 @@ ziti = module.exports = exports = binding; /** * Attach the external, app-facing, API to the 'ziti' object */ +exports.close = require('./close').close; exports.express = require('./express').express; exports.init = require('./init').init; exports.listen = require('./listen').listen; diff --git a/package-lock.json b/package-lock.json index d51f765..a72f287 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@openziti/ziti-sdk-nodejs", - "version": "0.8.0", + "version": "0.9.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@openziti/ziti-sdk-nodejs", - "version": "0.8.0", + "version": "0.9.0", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { diff --git a/package.json b/package.json index fc298b4..879cd54 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@openziti/ziti-sdk-nodejs", "description": "A NodeJS-based SDK for delivering secure applications over a Ziti Network", - "version": "0.8.0", + "version": "0.9.0", "main": "./lib/ziti", "scripts": { "build": "npm run build:init; npm run build:c-sdk; npm install --build-from-source --clang=1", diff --git a/src/ziti_listen.c b/src/ziti_listen.c index dc3e43f..b1ae300 100644 --- a/src/ziti_listen.c +++ b/src/ziti_listen.c @@ -50,7 +50,7 @@ static void CallJs_on_listen_client_data(napi_env env, napi_value js_cb, void* c if (env != NULL) { // const obj = {} - napi_value undefined, js_client_item, js_client, js_buffer; + napi_value undefined, js_client_item, js_client, js_buffer, js_arb_data; void* result_data; // Retrieve the JavaScript `undefined` value so we can use it as the `this` @@ -65,6 +65,21 @@ static void CallJs_on_listen_client_data(napi_env env, napi_value js_cb, void* c napi_throw_error(env, "EINVAL", "failure to create object"); } + // js_client_item.js_arb_data = js_arb_data + if (item->js_arb_data) { + rc = napi_create_int64(env, item->js_arb_data, &js_arb_data); + if (rc != napi_ok) { + napi_throw_error(env, "EINVAL", "failure to create obj.js_arb_data"); + } + rc = napi_set_named_property(env, js_client_item, "js_arb_data", js_arb_data); + if (rc != napi_ok) { + napi_throw_error(env, "EINVAL", "failure to set named property status"); + } + ZITI_NODEJS_LOG(DEBUG, "js_arb_data: %lld", item->js_arb_data); + } else { + rc = napi_set_named_property(env, js_client_item, "js_arb_data", undefined); + } + // js_client_item.client = client napi_create_int64(env, (int64_t)item->client, &js_client); if (rc != napi_ok) { @@ -472,13 +487,13 @@ void on_listen_client(ziti_connection serv, ziti_connection client, int status, const char *source_identity = clt_ctx->caller_id; if (source_identity != NULL) { - ZITI_NODEJS_LOG(DEBUG, "on_listen_client: incoming connection from '%s'\n", source_identity ); + ZITI_NODEJS_LOG(DEBUG, "on_listen_client: incoming connection from '%s'", source_identity ); } else { ZITI_NODEJS_LOG(DEBUG, "on_listen_client: incoming connection from unidentified client" ); } if (clt_ctx->app_data != NULL) { - ZITI_NODEJS_LOG(DEBUG, "on_listen_client: got app data '%.*s'!\n", (int) clt_ctx->app_data_sz, clt_ctx->app_data ); + ZITI_NODEJS_LOG(DEBUG, "on_listen_client: got app data '%.*s'!", (int) clt_ctx->app_data_sz, clt_ctx->app_data ); } ziti_accept(client, on_listen_client_connect, on_listen_client_data); @@ -715,7 +730,7 @@ napi_value _ziti_listen(napi_env env, const napi_callback_info info) { } // Start listening - ZITI_NODEJS_LOG(DEBUG, "calling ziti_listen_with_options: %p", ztx); + ZITI_NODEJS_LOG(DEBUG, "calling ziti_listen_with_options: %p, addon_data: %p", ztx, addon_data); ziti_listen_opts listen_opts = { .bind_using_edge_identity = false, }; diff --git a/src/ziti_write.c b/src/ziti_write.c index 3ba3830..f7f60c7 100644 --- a/src/ziti_write.c +++ b/src/ziti_write.c @@ -32,6 +32,8 @@ typedef struct WriteItem { static void CallJs_on_write(napi_env env, napi_value js_cb, void* context, void* data) { napi_status status; + ZITI_NODEJS_LOG(DEBUG, "CallJs_on_write entered"); + // This parameter is not used. (void) context; @@ -106,6 +108,8 @@ static void on_write(ziti_connection conn, ssize_t status, void *ctx) { ConnAddonData* addon_data = (ConnAddonData*) ziti_conn_data(conn); + ZITI_NODEJS_LOG(DEBUG, "on_write cb entered: addon_data: %p", addon_data); + WriteItem* item = memset(malloc(sizeof(*item)), 0, sizeof(*item)); item->conn = conn; item->status = status; @@ -164,6 +168,8 @@ napi_value _ziti_write(napi_env env, const napi_callback_info info) { // Obtain ptr to JS 'write' callback function napi_value js_write_cb = args[2]; + ZITI_NODEJS_LOG(DEBUG, "js_write_cb: %p", js_write_cb); + napi_value work_name; // Create a string to describe this asynchronous operation. @@ -195,7 +201,9 @@ napi_value _ziti_write(napi_env env, const napi_callback_info info) { } // Now, call the C-SDK to actually write the data over to the service + ZITI_NODEJS_LOG(DEBUG, "call ziti_write"); ziti_write(conn, chunk, bufferLength, on_write, NULL); + ZITI_NODEJS_LOG(DEBUG, "back from ziti_write"); return NULL; } diff --git a/tests/https-test.js b/tests/https-test.js index d927e95..0ba0c9d 100644 --- a/tests/https-test.js +++ b/tests/https-test.js @@ -30,7 +30,9 @@ const Ziti_http_request = async (url, method, headers) => { // on_resp_data callback (obj) => { console.log('----------- Now inside Ziti_http_request on_resp_data callback ----------, obj is: \n%o', obj); - console.log('----------- obj.body is: \n%o', obj.body.toString()); + if (obj.body) { + console.log('----------- obj.body is: \n%o', obj.body.toString()); + } }, ); @@ -167,21 +169,21 @@ const sendChunk = (req) => { process.exit(-1); }); - console.log('inside JS main(), req is (%o)', req); + // console.log('inside JS main(), req is (%o)', req); // setTimeout( async () => { - for (let i=0; i<3; i++ ) { + // for (let i=0; i<3; i++ ) { - let req2 = await Ziti_http_request(url, method, []).catch((err) => { - console.log('Ziti_http_request failed with error (%o)', err); - process.exit(-1); - }); + // let req2 = await Ziti_http_request(url, method, []).catch((err) => { + // console.log('Ziti_http_request failed with error (%o)', err); + // process.exit(-1); + // }); - console.log('inside JS main() setTimeout(), req is (%o)', req2); + // console.log('inside JS main() setTimeout(), req is (%o)', req2); - } + // } // }, 100);