├── proxies ├── stubs │ ├── php │ │ ├── .gitignore │ │ ├── test.php │ │ ├── rpctunnel.php │ │ └── GeneratedTest.php │ ├── python │ │ ├── .gitignore │ │ ├── requirements_34.txt │ │ ├── requirements_33.txt │ │ ├── requirements_27.txt │ │ ├── getPythonProxy.js │ │ ├── test.py │ │ └── GeneratedTest.py │ ├── csharp │ │ ├── app.config │ │ ├── tests │ │ │ ├── packages.config │ │ │ ├── Properties │ │ │ │ └── AssemblyInfo.cs │ │ │ ├── tests.csproj │ │ │ └── tests.cs │ │ ├── packages.config │ │ ├── Properties │ │ │ └── AssemblyInfo.cs │ │ ├── csharp.sln │ │ ├── Program.cs │ │ ├── csharp.csproj │ │ └── RpcTunnel.cs │ └── java │ │ ├── java.iml │ │ └── src │ │ └── com │ │ └── chaosgroup │ │ └── vraycloud │ │ └── stubs │ │ └── Main.java ├── Php.ejs ├── Python.ejs ├── JavaScript.ejs └── Java.ejs ├── .eslintignore ├── client.js ├── examples ├── adhoc │ ├── examples │ │ ├── examples.py │ │ ├── examples.js │ │ └── snippets.js │ ├── app.js │ └── api.js └── resources │ ├── snippets │ ├── httpTest.curl │ ├── pyTest.py │ ├── rbTest.rb │ ├── javaTest.java │ ├── cTest.c │ ├── csTest.cs │ ├── cppTest.cpp │ ├── nodeTest.js │ └── jsTest.js │ ├── examples.curl │ ├── examples_snippets.java │ ├── examples_snippets.node.js │ ├── server-def-json-include-bottom.js │ ├── server-def-json-include-top.js │ ├── server-def-code.js │ ├── examples_snippets_sio.js │ ├── server-def-json.js │ └── examples_snippets.js ├── .istanbul.yml ├── .gitattributes ├── conf.json ├── static ├── event.svg ├── type.svg ├── snippet.svg ├── prettify.css ├── method.svg └── javascript.js ├── .editorconfig ├── .travis.yml ├── .prettierrc.json ├── test ├── dump-peers.js └── unit │ └── lib │ ├── api.js │ └── error.js ├── .eslintrc ├── .gitignore ├── lib ├── file-utils.js ├── get-language-proxy.js ├── get-metadata-page.js ├── get-playground-page.js ├── trace.js ├── error.js ├── transport │ ├── json-rpc.js │ ├── socket-io-transport.js │ ├── http-transport.js │ └── ws-transport.js ├── client │ ├── transports │ │ ├── ws.js │ │ ├── socket-io.js │ │ └── http.js │ └── index.js ├── service │ └── types.js └── registry.js ├── templates └── services.ejs ├── .npmignore ├── LICENSE ├── package.json └── index.js /proxies/stubs/php/.gitignore: -------------------------------------------------------------------------------- 1 | phpunit 2 | -------------------------------------------------------------------------------- /proxies/stubs/python/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | -------------------------------------------------------------------------------- /proxies/stubs/python/requirements_34.txt: -------------------------------------------------------------------------------- 1 | autobahn==0.9.3 2 | six==1.8.0 3 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | static/**/* 3 | output/**/* 4 | proxies/**/* 5 | -------------------------------------------------------------------------------- /client.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = require('./lib/client/index'); 4 | -------------------------------------------------------------------------------- /examples/adhoc/examples/examples.py: -------------------------------------------------------------------------------- 1 | // {example:sum} 2 | print(proxy.sum(2, 3)) 3 | // {example} 4 | -------------------------------------------------------------------------------- /.istanbul.yml: -------------------------------------------------------------------------------- 1 | reporting: 2 | reports: 3 | - text-summary 4 | - html 5 | dir: ./output/coverage 6 | -------------------------------------------------------------------------------- /proxies/stubs/python/requirements_33.txt: -------------------------------------------------------------------------------- 1 | autobahn==0.9.3 2 | enum34==1.0.3 3 | six==1.8.0 4 | trollius==1.0.2 5 | -------------------------------------------------------------------------------- /examples/resources/snippets/httpTest.curl: -------------------------------------------------------------------------------- 1 | // {snippet:htmlSnippet} 2 | curl https://curl.haxx.se 3 | // {snippet} 4 | -------------------------------------------------------------------------------- /proxies/stubs/python/requirements_27.txt: -------------------------------------------------------------------------------- 1 | autobahn==0.9.3 2 | enum34==1.0.3 3 | futures==2.2.0 4 | six==1.8.0 5 | trollius==1.0.2 6 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * -text 2 | *.js text eol=lf 3 | *.json text eol=lf 4 | *.html text eol=lf 5 | *.css text eol=lf 6 | *.md text eol=lf 7 | -------------------------------------------------------------------------------- /examples/resources/examples.curl: -------------------------------------------------------------------------------- 1 | // {example:method1} 2 | [1, 2] 3 | // {example} 4 | 5 | // {example:method2} 6 | [] 7 | // {example} -------------------------------------------------------------------------------- /examples/adhoc/examples/examples.js: -------------------------------------------------------------------------------- 1 | // {example:sum} 2 | proxy.sum(2, 3, function(err, result) { 3 | console.log(err || result); 4 | }); 5 | // {example} 6 | -------------------------------------------------------------------------------- /proxies/stubs/csharp/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /proxies/stubs/csharp/tests/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "source": { 3 | "include": ["lib", "./"], 4 | "exclude": ["static", "test", "proxies"] 5 | }, 6 | "opts": { 7 | "destination": "./output/doc" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/resources/snippets/pyTest.py: -------------------------------------------------------------------------------- 1 | // {example:pyMethod} 2 | a = proxy.method1(1, 2).get() 3 | // {example} 4 | 5 | // {snippet:snippet1} 6 | a = proxy.method1(1, 2).get() 7 | proxy.method2().get(); 8 | // {snippet} 9 | -------------------------------------------------------------------------------- /static/event.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /examples/resources/examples_snippets.java: -------------------------------------------------------------------------------- 1 | // {example:method1} 2 | int a = proxy.method1(1, 2).get(); 3 | // {example} 4 | 5 | // {snippet:snippet1} 6 | int a = proxy.method1(1, 2).get(); 7 | proxy.method2().get(); 8 | // {snippet} 9 | -------------------------------------------------------------------------------- /examples/resources/snippets/rbTest.rb: -------------------------------------------------------------------------------- 1 | // Documentation examples and snippets 2 | proxy = Proxy.new(); 3 | proxy.otherMethod() 4 | 5 | // {snippet:rubySnippet} 6 | // ruby snippet 7 | a = proxy.method1(1, 2) 8 | proxy.method2() 9 | // {snippet} 10 | -------------------------------------------------------------------------------- /examples/resources/snippets/javaTest.java: -------------------------------------------------------------------------------- 1 | // {example:method1} 2 | int a = proxy.method1(1, 2).get(); 3 | // {example} 4 | 5 | // {snippet:snippet1} 6 | int a = proxy.method1(1, 2).get(); 7 | proxy.method2().get(); 8 | proxy.exampleFunction(); 9 | // {snippet} -------------------------------------------------------------------------------- /proxies/stubs/csharp/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /examples/resources/snippets/cTest.c: -------------------------------------------------------------------------------- 1 | // {example:cMethod} 2 | int a = proxy.method1(1, 2).get(); 3 | // {example} 4 | 5 | // {snippet:cSnippet} 6 | int a = proxy.method1(1, 2).get(); 7 | proxy.method2().get(); 8 | proxy.exampleCppFunction(); 9 | // {snippet} 10 | -------------------------------------------------------------------------------- /examples/resources/snippets/csTest.cs: -------------------------------------------------------------------------------- 1 | // {example:csMethod} 2 | int a = proxy.method1(1, 2).get(); 3 | // {example} 4 | 5 | // {snippet:csSnippet} 6 | int a = proxy.method1(1, 2).get(); 7 | proxy.method2().get(); 8 | proxy.exampleFunction(); 9 | // {snippet} 10 | -------------------------------------------------------------------------------- /examples/resources/snippets/cppTest.cpp: -------------------------------------------------------------------------------- 1 | // {example:cppMethod} 2 | int a = proxy.method1(1, 2).get(); 3 | // {example} 4 | 5 | // {snippet:cppSnippet} 6 | int a = proxy.method1(1, 2).get(); 7 | proxy.method2().get(); 8 | proxy.exampleCppFunction(); 9 | // {snippet} 10 | -------------------------------------------------------------------------------- /static/type.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /examples/resources/snippets/nodeTest.js: -------------------------------------------------------------------------------- 1 | // {node} 2 | // Documentation examples and snippets 3 | const proxy = new Proxy(); 4 | 5 | proxy.otherMethod(); 6 | 7 | // {snippet:snippet1} 8 | // node snippet 9 | const a = proxy.method1(1, 2); 10 | proxy.method2(); 11 | // {snippet} 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | end_of_line = lf 4 | insert_final_newline = true 5 | 6 | [*.js] 7 | indent_style = tab 8 | 9 | [{package.json,.jscsrc,.jshintrc}] 10 | indent_style = space 11 | indent_size = 2 12 | 13 | [*.ejs] 14 | indent_style = tab 15 | indent_size = 2 16 | -------------------------------------------------------------------------------- /examples/resources/examples_snippets.node.js: -------------------------------------------------------------------------------- 1 | // {node} 2 | // Documentation examples and snippets 3 | const proxy = new Proxy(); 4 | 5 | proxy.otherMethod(); 6 | 7 | // {snippet:snippet1} 8 | // node snippet 9 | const a = proxy.method1(1, 2); 10 | proxy.method2(); 11 | // {snippet} 12 | -------------------------------------------------------------------------------- /static/snippet.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | sudo: false 4 | 5 | compiler: 6 | - gcc 7 | install: 8 | - export CXX="g++-4.8" CC="gcc-4.8" 9 | - npm install 10 | - node test/dump-peers.js | xargs npm install 11 | addons: 12 | apt: 13 | sources: 14 | - ubuntu-toolchain-r-test 15 | packages: 16 | - gcc-4.8 17 | - g++-4.8 18 | 19 | node_js: 20 | - "8.9" 21 | -------------------------------------------------------------------------------- /examples/resources/snippets/jsTest.js: -------------------------------------------------------------------------------- 1 | // Documentation examples and snippets 2 | const proxy = new Proxy(); 3 | 4 | proxy.otherMethod(); 5 | 6 | // {snippet:snippet1} 7 | // js snippet 8 | const a = proxy.jsMethod(1, 2); 9 | proxy.method2(); 10 | // {snippet} 11 | 12 | // {snippet:snippet2} 13 | // js snippet 14 | const a = proxy.jsMethod(1, 2); 15 | proxy.method2(); 16 | // {snippet} 17 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "tabWidth": 4, 4 | "useTabs": true, 5 | "semi": true, 6 | "singleQuote": true, 7 | "trailingComma": "es5", 8 | "bracketSpacing": true, 9 | "overrides": [ 10 | { 11 | "files": ".prettierrc.json", 12 | "options": { 13 | "parser": "json" 14 | } 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /examples/resources/server-def-json-include-bottom.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const path = require('path'); 3 | module.exports = { 4 | types: { 5 | DependendType: { 6 | struct: { 7 | key: 'Test', 8 | }, 9 | }, 10 | Test: { 11 | struct: { 12 | mode: 'RenderMode', 13 | width: 'int', 14 | height: 'int', 15 | }, 16 | }, 17 | }, 18 | import: [path.join(__dirname, 'server-def-json.js')], 19 | }; 20 | -------------------------------------------------------------------------------- /examples/resources/server-def-json-include-top.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const path = require('path'); 3 | module.exports = { 4 | import: [path.join(__dirname, 'server-def-json.js')], 5 | types: { 6 | DependendType: { 7 | struct: { 8 | key: 'Test', 9 | }, 10 | }, 11 | Test: { 12 | struct: { 13 | mode: 'RenderMode', 14 | width: 'int', 15 | height: 'int', 16 | }, 17 | }, 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /test/dump-peers.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const packageJson = require('../package.json'); 4 | 5 | const peerDependencies = packageJson.peerDependencies || {}; 6 | 7 | const peersToInstall = []; 8 | for (const peerDependency in peerDependencies) { 9 | peersToInstall.push(peerDependency + '@' + peerDependencies[peerDependency]); 10 | } 11 | 12 | console.log(peersToInstall.join('\n')); // eslint-disable-line no-console 13 | -------------------------------------------------------------------------------- /examples/resources/server-def-code.js: -------------------------------------------------------------------------------- 1 | module.exports = function setup(api) { 2 | api.enum('RenderMode', { 3 | Production: -1, 4 | RtCpu: 0, 5 | }); 6 | 7 | api.type('RenderOptions', { 8 | mode: 'RenderMode', 9 | width: 'int', 10 | height: 'int', 11 | }); 12 | 13 | api.event('ontest', 'test event'); 14 | 15 | api.define({ name: 'sum', description: 'Returns the sum of two numbers' }, function(a, b) { 16 | return a + b; 17 | }); 18 | }; 19 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "node": true, 5 | "mocha": true, 6 | "es6": true 7 | }, 8 | "extends": "prettier", 9 | "parserOptions": { 10 | "ecmaVersion": 2017 11 | }, 12 | "plugins": ["prettier"], 13 | "rules": { 14 | "no-var": "error", 15 | "prefer-const": ["error", { 16 | "destructuring": "any", 17 | "ignoreReadBeforeAssign": false 18 | }], 19 | "prettier/prettier": "error" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /examples/resources/examples_snippets_sio.js: -------------------------------------------------------------------------------- 1 | const TestApi = require('TestApi'); 2 | 3 | const sioTransport = require('./transports/sio'); 4 | 5 | const proxy = new TestApi( 6 | new sioTransport('http://localhost:3000/endpoint/tralala/test-api/1.0', { 7 | serviceName: 'test-api', 8 | serviceVersion: '1.0', 9 | path: '/test-api/socket.io', 10 | validationParams: { 11 | sessionId: 'tralala', 12 | }, 13 | }) 14 | ); 15 | 16 | proxy.sum(2, 3, function(err, result) { 17 | console.log(err || result); 18 | }); 19 | -------------------------------------------------------------------------------- /examples/resources/server-def-json.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | enums: { 3 | RenderMode: { 4 | description: 'RenderMode', 5 | struct: { 6 | Production: -1, 7 | RtCpu: 0, 8 | }, 9 | }, 10 | }, 11 | 12 | types: { 13 | RenderOptions: { 14 | struct: { 15 | mode: 'RenderMode', 16 | width: 'int', 17 | height: 'int', 18 | }, 19 | }, 20 | }, 21 | 22 | events: { 23 | ontest: 'test event', 24 | }, 25 | 26 | methods: { 27 | sum: 'Returns the sum of two numbers', 28 | }, 29 | }; 30 | -------------------------------------------------------------------------------- /proxies/stubs/python/getPythonProxy.js: -------------------------------------------------------------------------------- 1 | var request = require('request'); 2 | var fs = require('fs'); 3 | 4 | var serverUrl = 'http://localhost:3000/endpoint/1.0'; 5 | var language = 'Python'; 6 | var localName = 'GeneratedTest'; 7 | 8 | requestUrl = serverUrl + '?proxy=' + language + '&localName=' + localName; 9 | request.get(requestUrl, function(err, response, body) { 10 | if (err || response.statusCode != 200) { 11 | console.log(err || response.statusCode); 12 | } else { 13 | console.log(body); 14 | } 15 | }); 16 | -------------------------------------------------------------------------------- /examples/adhoc/examples/snippets.js: -------------------------------------------------------------------------------- 1 | // {snippet:sumArray} 2 | proxy.sumArray([1, 2, 3], function(err, result) { 3 | console.log(err || result); 4 | }); 5 | // {snippet} 6 | 7 | // {snippet:sum} 8 | proxy.sum(2, 3, function(err, result) { 9 | console.log(err || result); 10 | }); 11 | // {snippet} 12 | 13 | // {snippet:sum2} 14 | proxy.sum(2, 3, function(err, result) { 15 | console.log(err || result); 16 | }); 17 | // {snippet} 18 | 19 | // {snippet:sum3} 20 | proxy.sum(2, 3, function(err, result) { 21 | console.log(err || result); 22 | }); 23 | // {snippet} 24 | -------------------------------------------------------------------------------- /examples/resources/examples_snippets.js: -------------------------------------------------------------------------------- 1 | // Documentation examples and snippets 2 | const proxy = new Proxy(); 3 | 4 | // {example:method1} 5 | proxy.method1(1, 2, function(err, result) { 6 | console.log(err || result); 7 | }); 8 | // {example} 9 | 10 | proxy.otherMethod(); 11 | 12 | // {example:method2} 13 | proxy.method2(); 14 | // {example} 15 | 16 | // {snippet:snippet1} 17 | const a = proxy.method1(1, 2); 18 | proxy.method2(); 19 | // {snippet} 20 | 21 | // {snippet:snippet2} 22 | const b = proxy.method1(1, 2); 23 | proxy.method2(); 24 | // {snippet} 25 | -------------------------------------------------------------------------------- /static/prettify.css: -------------------------------------------------------------------------------- 1 | .pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} -------------------------------------------------------------------------------- /proxies/stubs/java/java.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDE stuff 2 | .idea 3 | 4 | # Logs 5 | logs 6 | *.log 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | 13 | # Directory for instrumented libs generated by jscoverage/JSCover 14 | lib-cov 15 | 16 | # Coverage directory used by tools like istanbul 17 | coverage 18 | 19 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 20 | .grunt 21 | 22 | # node-waf configuration 23 | .lock-wscript 24 | 25 | # Compiled binary addons (http://nodejs.org/api/addons.html) 26 | build/Release 27 | 28 | # Dependency directory 29 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 30 | node_modules 31 | 32 | # Various tools output dir: 33 | /output 34 | -------------------------------------------------------------------------------- /lib/file-utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | 6 | /** 7 | * Returns object containing the file's content in the specified encoding for every file. 8 | * @param {Array.} filesPaths Files paths relative or absolute to the static directory. 9 | * @param {string} encoding 10 | * @returns {Object} 11 | */ 12 | exports.getFilesContent = function(filesPaths, encoding) { 13 | encoding = encoding || 'utf8'; 14 | const filesContent = {}; 15 | for (const filePath of filesPaths) { 16 | let absPath = filePath; 17 | if (!path.isAbsolute(filePath)) { 18 | absPath = path.join(__dirname, filePath); 19 | } 20 | filesContent[filePath] = fs.readFileSync(absPath, encoding); 21 | } 22 | return filesContent; 23 | }; 24 | -------------------------------------------------------------------------------- /static/method.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /templates/services.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Service Registry 6 | 7 | 16 | 17 | 18 | 19 |
20 | 21 | 25 | 26 | <% locals.services.forEach(function(service) { %> 27 |

<%= service.metadata.name %> <%= service.metadata.version %> (<%= service.path %>)

28 | <%= service.metadata.description %> 29 |
30 | <% }); %> 31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # IDE stuff 2 | .idea 3 | 4 | # Logs 5 | logs 6 | *.log 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | 13 | # Directory for instrumented libs generated by jscoverage/JSCover 14 | lib-cov 15 | 16 | # Coverage directory used by tools like istanbul 17 | coverage 18 | 19 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 20 | .grunt 21 | 22 | # node-waf configuration 23 | .lock-wscript 24 | 25 | # Compiled binary addons (http://nodejs.org/api/addons.html) 26 | build/Release 27 | 28 | # Dependency directory 29 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 30 | node_modules 31 | 32 | # Various tools output dir: 33 | /output 34 | 35 | /test 36 | /proxies/stubs 37 | .eslintrc 38 | .eslintignore 39 | .editorconfig 40 | .gitattributes 41 | .istanbul.yml 42 | .travis.yml 43 | conf.json 44 | .gitignore 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-2017 Chaos Software Ltd. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /lib/get-language-proxy.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const ejs = require('ejs'); 6 | const _ = require('lodash'); 7 | 8 | /** 9 | * Return a language proxy for the specified service instance and 10 | * @param options 11 | * @param options.serviceInstance {object} JSON-WS service instance 12 | * @param options.language {string} target language, e.g. "JavaScript" or "Python" 13 | * @param [options.localName] {string} the name of the proxy class. Defaults to "Proxy" 14 | * @returns {object} Promise 15 | */ 16 | function getLanguageProxy(options) { 17 | // Try and find if one is available 18 | const proxyScript = path.resolve(__dirname, '..', 'proxies', options.language + '.ejs'); 19 | 20 | return new Promise(function(resolve, reject) { 21 | fs.stat(proxyScript, function(err) { 22 | if (err) { 23 | return reject(err); 24 | } 25 | 26 | ejs.renderFile( 27 | proxyScript, 28 | { 29 | metadata: options.serviceInstance.metadata, 30 | localName: options.localName || 'Proxy', 31 | _: _, 32 | }, 33 | { _with: false }, 34 | function(err, html) { 35 | if (err) { 36 | return reject(err); 37 | } 38 | 39 | resolve(html); 40 | } 41 | ); 42 | }); 43 | }); 44 | } 45 | 46 | module.exports = getLanguageProxy; 47 | -------------------------------------------------------------------------------- /proxies/stubs/csharp/tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("8703ea69-c995-4a97-bbe3-b15e077c259a")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /examples/adhoc/app.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Example/test application 4 | 5 | const http = require('http'); 6 | const express = require('express'); 7 | const bodyParser = require('body-parser'); 8 | const jsonws = require('../../index.js'); 9 | const transport = jsonws.transport; 10 | const SocketIOTransport = require('../../lib/transport/socket-io-transport'); 11 | const serviceApi = require('./api.js'); 12 | const path = require('path'); 13 | 14 | const expressApp = express(); 15 | const httpServer = http.createServer(expressApp); 16 | const registry = jsonws.registry({ 17 | rootPath: '/endpoint/:sessionId', 18 | httpServer, 19 | }); 20 | 21 | expressApp.set('port', 3000); 22 | expressApp.use(bodyParser.json()); 23 | expressApp.use(express.static(path.join(__dirname, '..', 'browser'))); 24 | expressApp.use(registry.getRouter()); 25 | 26 | expressApp.get('/', function(req, res) { 27 | res.send('hello world'); 28 | }); 29 | 30 | expressApp.get('/test', function(req, res) { 31 | res.sendFile(path.join(__dirname, '..', 'browser', 'test.html')); 32 | }); 33 | 34 | httpServer.listen(expressApp.get('port'), function() { 35 | registry.addTransport(transport.HTTP); 36 | // registry.addTransport(transport.WebSocket); 37 | // see 'examples_snippets_sio.js' for client transport example to connect to this socket 38 | registry.addTransport(new SocketIOTransport(registry, '/test-api/socket.io')); 39 | registry.addService(serviceApi); 40 | console.log('Express server listening on ' + JSON.stringify(httpServer.address())); 41 | }); 42 | -------------------------------------------------------------------------------- /proxies/stubs/csharp/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("csharp")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("csharp")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("2df624d0-52d9-4805-a231-ca3e8494ab57")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /proxies/stubs/csharp/csharp.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Visual Studio 2010 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "csharp", "csharp.csproj", "{E441329A-B069-4DA2-A39C-A35F5ED40C20}" 5 | EndProject 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "tests", "tests\tests.csproj", "{588E2DAF-CE6B-4109-8C33-BD9632311A4C}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{CB36FC62-5F0B-4708-B4D9-0DE16F8B6D01}" 9 | ProjectSection(SolutionItems) = preProject 10 | csharp.vsmdi = csharp.vsmdi 11 | Local.testsettings = Local.testsettings 12 | TraceAndTestImpact.testsettings = TraceAndTestImpact.testsettings 13 | EndProjectSection 14 | EndProject 15 | Global 16 | GlobalSection(TestCaseManagementSettings) = postSolution 17 | CategoryFile = csharp.vsmdi 18 | EndGlobalSection 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|Any CPU = Debug|Any CPU 21 | Release|Any CPU = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {E441329A-B069-4DA2-A39C-A35F5ED40C20}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {E441329A-B069-4DA2-A39C-A35F5ED40C20}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {E441329A-B069-4DA2-A39C-A35F5ED40C20}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {E441329A-B069-4DA2-A39C-A35F5ED40C20}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {588E2DAF-CE6B-4109-8C33-BD9632311A4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {588E2DAF-CE6B-4109-8C33-BD9632311A4C}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {588E2DAF-CE6B-4109-8C33-BD9632311A4C}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {588E2DAF-CE6B-4109-8C33-BD9632311A4C}.Release|Any CPU.Build.0 = Release|Any CPU 32 | EndGlobalSection 33 | GlobalSection(SolutionProperties) = preSolution 34 | HideSolutionNode = FALSE 35 | EndGlobalSection 36 | EndGlobal 37 | -------------------------------------------------------------------------------- /lib/get-metadata-page.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const ejs = require('ejs'); 6 | const getFilesContent = require('./file-utils').getFilesContent; 7 | 8 | /** 9 | * Returns the html of the rendered metadata page. 10 | * @param options 11 | * @param options.isStatic {boolean} Indicates if the page should be static. 12 | * @param options.service {object} JSON-WS service instance. 13 | * @param options.root {string} Mount point of the service registry. 14 | * @returns {object} Promise 15 | */ 16 | function getMetadataPage(options) { 17 | return new Promise(function(resolve, reject) { 18 | // Find out list of proxy generators 19 | fs.readdir(__dirname + '/../proxies/', (err, files) => { 20 | if (err) { 21 | return reject(err); 22 | } 23 | 24 | const proxies = files 25 | .filter(f => f.endsWith('.ejs')) 26 | .map(f => f.substr(0, f.length - 4)); 27 | const images = [ 28 | path.join('..', 'static', 'event.svg'), 29 | path.join('..', 'static', 'type.svg'), 30 | path.join('..', 'static', 'snippet.svg'), 31 | path.join('..', 'static', 'method.svg'), 32 | ]; 33 | const jsFiles = [path.join('..', 'static', 'prettify.js')]; 34 | const cssFiles = [path.join('..', 'static', 'prettify.css')]; 35 | const templateData = { 36 | metadata: options.service, 37 | ejs, 38 | proxies, 39 | isStatic: options.isStatic, 40 | root: options.root, 41 | images: getFilesContent(images, 'base64'), 42 | js: getFilesContent(jsFiles), 43 | css: getFilesContent(cssFiles), 44 | }; 45 | 46 | // Render the metadata template 47 | ejs.renderFile( 48 | __dirname + '/../templates/metadata.ejs', 49 | templateData, 50 | { _with: false }, 51 | (err, html) => { 52 | if (err) { 53 | return reject(err); 54 | } 55 | resolve(html); 56 | } 57 | ); 58 | }); 59 | }); 60 | } 61 | 62 | module.exports = getMetadataPage; 63 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "json-ws", 3 | "version": "3.5.1", 4 | "author": "ChaosGroup (c) 2013-2018", 5 | "description": "JSON-RPC based Web Services with automatic help and client side source creation for Node.js/Go/JavaScript/Java/.Net/Python/PHP", 6 | "keywords": [ 7 | "json", 8 | "rpc", 9 | "remote procedure call", 10 | "json-ws", 11 | "json-rpc", 12 | "web service", 13 | "web socket", 14 | "socket-io", 15 | "javascript", 16 | "go", 17 | "golang" 18 | ], 19 | "publishConfig": { 20 | "registry": "https://registry.npmjs.com" 21 | }, 22 | "homepage": "https://github.com/ChaosGroup/json-ws", 23 | "repository": "https://github.com/ChaosGroup/json-ws", 24 | "bugs": "https://github.com/ChaosGroup/json-ws/issues", 25 | "license": "MIT", 26 | "dependencies": { 27 | "co": "^4.5.2", 28 | "ejs": "^2.3.4", 29 | "express": "^4.13.4", 30 | "lodash": "^4.17.4", 31 | "node-uuid": "^1.4.8", 32 | "path-to-regexp": "^1.2.1", 33 | "request": "^2.67.0", 34 | "semver": "^5.1.0" 35 | }, 36 | "peerDependencies": { 37 | "socket.io": "^2.1.1", 38 | "socket.io-client": "^2.1.1", 39 | "ws": "^1.0.1" 40 | }, 41 | "devDependencies": { 42 | "bluebird": "^3.2.1", 43 | "body-parser": "^1.14.2", 44 | "chai": "^3.5.0", 45 | "eslint": "5.2.0", 46 | "eslint-config-prettier": "2.9.0", 47 | "eslint-plugin-eslint-plugin": "^1.4.0", 48 | "eslint-plugin-prettier": "2.6.2", 49 | "istanbul": "^0.4.2", 50 | "jsdoc": "^3.4.0", 51 | "mocha": "^3.2.0", 52 | "nodemon": "^1.11.0", 53 | "prettier": "1.13.7" 54 | }, 55 | "scripts": { 56 | "test": "mocha 'test/integration/**/*.js' 'test/unit/**/*.js'", 57 | "test-unit": "mocha 'test/unit/**/*.js'", 58 | "test-integration": "mocha 'test/integration/**/*.js'", 59 | "lint": "eslint .", 60 | "jsdoc": "jsdoc . -c conf.json", 61 | "cover": "istanbul cover _mocha 'test/unit/**/*.js'", 62 | "example-app": "nodemon examples/adhoc/app.js" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /proxies/stubs/php/test.php: -------------------------------------------------------------------------------- 1 | gt = new GeneratedTest('http://localhost:3000/endpoint/1.0'); 8 | } 9 | 10 | public function testEcho() { 11 | $renderOptions = GeneratedTest::RenderOptions(array( 12 | 'renderMode' => 'RtGpuCuda', 13 | 'width' => 320, 14 | 'height' => 240 15 | )); 16 | 17 | $result = $this->gt->echo_($renderOptions); 18 | 19 | $this->assertEquals($renderOptions, $result); 20 | } 21 | 22 | public function testSum() { 23 | $result = $this->gt->sum(2, 3); 24 | 25 | $this->assertEquals(5, $result); 26 | } 27 | 28 | public function testEchoObject() { 29 | $obj = (object) array('foo' => 'bar'); 30 | $result = $this->gt->echoObject($obj); 31 | 32 | $this->assertEquals($obj, $result); 33 | } 34 | 35 | public function testNs1Method1() { 36 | $result = $this->gt->ns1_method1(); 37 | 38 | $this->assertEquals('test1', $result); 39 | } 40 | 41 | public function testTestDefaultArray() { 42 | $result = $this->gt->testDefaultArray(GeneratedTest::DefaultArray(array( 43 | 'property' => array(1, 2, 3) 44 | ))); 45 | 46 | $this->assertEquals(NULL, $result); 47 | } 48 | 49 | public function testGetRenderOptions() { 50 | $result = $this->gt->getRenderOptions(); 51 | 52 | $this->assertEquals(count($result), 3); 53 | $this->assertEquals((object) array( 54 | 'width' => 640, 55 | 'height' => 360, 56 | 'renderMode' => 'RtCpu' 57 | ), $result[0]); 58 | } 59 | 60 | public function testNs1Sub1Sub2Method1() { 61 | $this->setExpectedException('ExecutionException'); 62 | 63 | $result = $this->gt->ns1_sub1_sub2_method1(); 64 | } 65 | 66 | public function testOptionalArgs() { 67 | $this->gt->optionalArgs(true, 1, 2); 68 | $this->gt->optionalArgs(true, 1); 69 | $this->gt->optionalArgs(true); 70 | } 71 | 72 | public function testEchoStringAsBuffer() { 73 | $result = $this->gt->echoStringAsBuffer('abcd'); 74 | 75 | $this->assertEquals('abcd', $result); 76 | } 77 | 78 | public function testGetBufferSize() { 79 | $result = $this->gt->getBufferSize('abcd'); 80 | 81 | $this->assertEquals(4, $result); 82 | } 83 | 84 | public function testGetSeconds() { 85 | $result = $this->gt->getSeconds(new DateTime('2012-12-21 11:14:12')); 86 | 87 | $this->assertEquals(12, $result); 88 | } 89 | 90 | public function testGetNow() { 91 | $result = $this->gt->getNow(); 92 | 93 | $this->assertInstanceOf('DateTime', $result); 94 | } 95 | } 96 | 97 | ?> 98 | -------------------------------------------------------------------------------- /test/unit/lib/api.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Tests the API of the JSON-WS library 3 | */ 4 | 5 | 'use strict'; 6 | 7 | const expect = require('chai').expect; 8 | const jsonws = require('../../../index.js'); 9 | const Service = jsonws.service; 10 | 11 | describe('Converters', function() { 12 | let api; 13 | let testType; 14 | const converting = function(value) { 15 | return function() { 16 | testType.convert(value); 17 | }; 18 | }; 19 | 20 | beforeEach(function() { 21 | api = new Service('1.0.0', 'test-api'); 22 | api.type('TestType', { 23 | intField: { 24 | type: 'int', 25 | required: false, 26 | }, 27 | floatField: { 28 | type: 'float', 29 | required: false, 30 | }, 31 | booleanField: { 32 | type: 'boolean', 33 | required: false, 34 | }, 35 | dateField: { 36 | type: 'date', 37 | required: false, 38 | }, 39 | urlField: { 40 | type: 'url', 41 | required: false, 42 | }, 43 | bufferField: { 44 | type: 'buffer', 45 | required: false, 46 | }, 47 | errorField: { 48 | type: 'error', 49 | required: false, 50 | }, 51 | }); 52 | 53 | testType = api.type('TestType'); 54 | }); 55 | 56 | it('adds the field name for invalid integer values', function() { 57 | expect(converting({ intField: null })).to.throw(/\[TestType.intField\].*invalid integer/i); 58 | }); 59 | 60 | it('adds the field name for invalid float values', function() { 61 | expect(converting({ floatField: null })).to.throw( 62 | /\[TestType.floatField\].*invalid number/i 63 | ); 64 | }); 65 | 66 | it('adds the field name for invalid boolean values', function() { 67 | expect(converting({ booleanField: 'INVALID_BOOLEAN' })).to.throw( 68 | /\[TestType.booleanField\].*invalid boolean/i 69 | ); 70 | }); 71 | 72 | it('adds the field name for invalid date values', function() { 73 | expect(converting({ dateField: { INVALID_DATE: true } })).to.throw( 74 | /\[TestType.dateField\].*invalid date/i 75 | ); 76 | }); 77 | 78 | it('adds the field name for invalid url values', function() { 79 | expect(converting({ urlField: 1234 })).to.throw(/\[TestType.urlField\].*invalid URL/i); 80 | }); 81 | 82 | it('adds the field name for invalid buffer values', function() { 83 | expect(converting({ bufferField: null })).to.throw( 84 | /\[TestType.bufferField\].*invalid buffer/i 85 | ); 86 | }); 87 | 88 | it('adds the field name for invalid error values', function() { 89 | expect(converting({ bufferField: '', errorField: 42 })).to.throw( 90 | /\[TestType.errorField\].*invalid error/i 91 | ); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /lib/get-playground-page.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const ejs = require('ejs'); 4 | const _ = require('lodash'); 5 | const getFilesContent = require('./file-utils').getFilesContent; 6 | 7 | /** 8 | * Returns the html of the rendered metadata page. 9 | * @param options 10 | * @param options.isStatic {boolean} Indicates if the page should be static. 11 | * @param options.service {object} JSON-WS service instance. 12 | * @param options.root {string} Mount point of the service registry. 13 | * @returns {object} Promise 14 | */ 15 | function getPlaygroundPage(options) { 16 | return new Promise(function(resolve, reject) { 17 | // Render viewer/experiment page 18 | const examples = []; 19 | const service = options.service; 20 | // TODO: update these loops when methodMap and snippetMap become maps: 21 | for (const method in service.methodMap) { 22 | if (service.methodMap[method].examples['JavaScript']) { 23 | examples.push(method); 24 | } 25 | } 26 | const snippets = []; 27 | for (const snippet in service.snippetMap) { 28 | if (service.snippetMap[snippet]['JavaScript']) { 29 | snippets.push(snippet); 30 | } 31 | } 32 | let code = null; 33 | let name = options.example; 34 | if (examples.indexOf(name) > -1) { 35 | code = service.methodMap[name].examples['JavaScript']; 36 | } else { 37 | name = options.snippet; 38 | if (snippets.indexOf(name) > -1) { 39 | code = service.snippetMap[name]['JavaScript']; 40 | } 41 | } 42 | 43 | let socketIoBackendTransportInstanceRegistered = false; 44 | if (options.registry && options.registry.transports) { 45 | for (const transport of options.registry.transports.values()) { 46 | if (transport.constructor.type === 'SocketIO') { 47 | socketIoBackendTransportInstanceRegistered = true; 48 | break; 49 | } 50 | } 51 | } 52 | 53 | const jsFiles = [ 54 | './client/jsonws-polyfill.js', 55 | './client/transports/http.js', 56 | './client/transports/ws.js', 57 | './client/index.js', 58 | ]; 59 | 60 | if (socketIoBackendTransportInstanceRegistered) { 61 | jsFiles.push(require.resolve('socket.io-client/dist/socket.io.js')); 62 | jsFiles.push('./client/transports/socket-io.js'); 63 | } 64 | 65 | const templateData = { 66 | metadata: service, 67 | constructorName: _.upperFirst(_.camelCase(service.name)), 68 | ejs, 69 | code, 70 | title: `${service.name} ${service.version}`, 71 | examples, 72 | snippets, 73 | jsFiles: getFilesContent(jsFiles), 74 | }; 75 | 76 | // Render the metadata template 77 | ejs.renderFile( 78 | __dirname + '/../templates/viewer.ejs', 79 | templateData, 80 | { _with: false }, 81 | (err, html) => { 82 | if (err) { 83 | return reject(err); 84 | } 85 | resolve(html); 86 | } 87 | ); 88 | }); 89 | } 90 | 91 | module.exports = getPlaygroundPage; 92 | -------------------------------------------------------------------------------- /lib/trace.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const url = require('url'); 4 | const ServiceError = require('./error'); 5 | 6 | class Trace { 7 | constructor(bunyanLogger) { 8 | this._enabled = !!bunyanLogger; 9 | this._logger = bunyanLogger; 10 | } 11 | 12 | _stringify(object) { 13 | if (!this._enabled) { 14 | return null; 15 | } 16 | 17 | return JSON.stringify( 18 | object, 19 | function(key, value) { 20 | if (value instanceof Buffer) { 21 | return '(buffer)'; 22 | } else if (value instanceof Array && value.length > 1000) { 23 | return '(large array)'; 24 | } else if (typeof value == 'string' && value.length > 1000) { 25 | return '(large string)'; 26 | } else if (value instanceof url.Url) { 27 | return value.format(); 28 | } 29 | return value; 30 | }, 31 | 2 32 | ); 33 | } 34 | 35 | /** 36 | * Logs the given message if the logger is enabled, using the appropriate logLevel 37 | * The default logLevel is "trace" 38 | * 39 | * @param {object} context 40 | * @param {string} message 41 | * @param {object} [logFields={}] 42 | * @param {string} [logLevel="trace"] 43 | * @private 44 | */ 45 | _log(context, message, logFields = {}, logLevel = 'trace') { 46 | if (this._enabled) { 47 | this._logger[logLevel](logFields, `[JSON-WS] client :: ${message}`); 48 | } 49 | } 50 | 51 | connect(context) { 52 | this._log(context, 'connected'); 53 | } 54 | 55 | disconnect(context) { 56 | this._log(context, 'disconnected'); 57 | } 58 | 59 | call(context, methodInfo, args) { 60 | this._log(context, `method "${methodInfo.name}" call`, { 61 | method: methodInfo.name, 62 | args: this._stringify(args, null, 2), 63 | }); 64 | } 65 | 66 | response(context, methodInfo, value) { 67 | this._log(context, `method "${methodInfo.name}" response`, { 68 | method: methodInfo.name, 69 | return: this._stringify(value, null, 2), 70 | }); 71 | } 72 | 73 | error(context, methodInfo, error) { 74 | let logFields = { stack: error.stack }; 75 | 76 | if (error instanceof ServiceError) { 77 | logFields = error.logFields(); 78 | } 79 | if (methodInfo) { 80 | logFields.method = methodInfo.name; 81 | } 82 | 83 | this._log(context, error.message, logFields, 'error'); 84 | } 85 | 86 | event(context, eventInfo, args) { 87 | this._log(context, `event: "${eventInfo.name}"`, { 88 | event: eventInfo.name, 89 | args: this._stringify(args, null, 2), 90 | }); 91 | } 92 | 93 | subscribe(context, eventInfo) { 94 | this._log(context, `subscribed to event "${eventInfo.name}"`, { event: eventInfo.name }); 95 | } 96 | 97 | unsubscribe(context, eventInfo) { 98 | this._log(context, `unsubscribed from event "${eventInfo.name}"`, { 99 | event: eventInfo.name, 100 | }); 101 | } 102 | } 103 | 104 | module.exports = Trace; 105 | -------------------------------------------------------------------------------- /lib/error.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const uuid = require('node-uuid'); 4 | 5 | /** 6 | * Structured error containing context. 7 | * @extends Error 8 | */ 9 | class ServiceError { 10 | /** 11 | * Create a structured error. Required properties are id, reporter, code and timestamp. 12 | * 13 | * @param {string} reporter The name of the entity which reports the error. 14 | * @param {number} code System-wide unique integer that identifies a specific type of error. 15 | * @param {object} details Information about the context of the error. 16 | * @param {string} details.message Human-readable short error message. 17 | * @param {ServiceError} details.cause The cause of the error. 18 | * @param {object} details.metadata Error payload which provides additional context. 19 | */ 20 | constructor(reporter, code, { message, cause, metadata }) { 21 | Error.captureStackTrace(this, this.constructor); 22 | 23 | this.id = uuid.v4(); 24 | this.reporter = reporter; 25 | this.code = code; 26 | this.message = message; 27 | this.cause = cause; 28 | this.metadata = metadata; 29 | this.timestamp = new Date(); 30 | } 31 | 32 | /** 33 | * Presents the error in an appropriate format. 34 | * @returns {string} 35 | */ 36 | toString() { 37 | let output = 38 | `reporter: ${this.reporter}\n` + `code: ${this.code}\n` + `message: ${this.message}\n`; 39 | if (this.cause) { 40 | output += `cause: ${this.cause.code} ${this.cause.message}\n`; 41 | } 42 | return output + `metadata: ${JSON.stringify(this.metadata)}`; 43 | } 44 | 45 | logFields() { 46 | return { 47 | id: this.id, 48 | reporter: this.reporter, 49 | code: this.code, 50 | message: this.message, 51 | cause: this.cause, 52 | metadata: this.metadata, 53 | stack: this.stack, 54 | errorTimestamp: this.timestamp, 55 | }; 56 | } 57 | } 58 | 59 | const codes = { 60 | InternalServerError: 100, 61 | }; 62 | 63 | module.exports = ServiceError; 64 | 65 | module.exports.AsServiceError = (err, reporter, code, metadata) => { 66 | code = code || code === 0 ? code : codes.InternalServerError; 67 | 68 | if (err instanceof ServiceError) { 69 | err.code = err.code || err.code === 0 ? err.code : codes.InternalServerError; 70 | if (reporter && err.reporter !== reporter) { 71 | return new ServiceError(reporter, err.code, { 72 | message: err.message, 73 | cause: err, 74 | metadata, 75 | }); 76 | } 77 | return err; 78 | } 79 | 80 | // Check for a JSON-RPC error. 81 | if (err.hasOwnProperty('data')) { 82 | code = err.data.code || err.data.code === 0 ? err.data.code : code; 83 | return new ServiceError(reporter, code, { 84 | message: err.data.message, 85 | cause: err.data, 86 | metadata, 87 | }); 88 | } 89 | 90 | if (err instanceof Error) { 91 | return new ServiceError(reporter, code, { 92 | message: err.message, 93 | metadata, 94 | }); 95 | } 96 | 97 | return new ServiceError(reporter, code, { 98 | message: err, 99 | metadata, 100 | }); 101 | }; 102 | -------------------------------------------------------------------------------- /proxies/stubs/csharp/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using ChaosGroup.JsonWS.Proxies; 4 | using Newtonsoft.Json.Linq; 5 | 6 | namespace csharp 7 | { 8 | internal class Program 9 | { 10 | private static void TestMethods() 11 | { 12 | using (var proxy = new GeneratedTest("http://localhost:3000/endpoint/1.0")) 13 | { 14 | try 15 | { 16 | Console.WriteLine(proxy/*.UseWS()*/.ThrowError().Result); 17 | } 18 | catch (Exception e) 19 | { 20 | Console.WriteLine(e.ToString()); 21 | } 22 | 23 | var task = proxy.Sum(1, 2); 24 | Console.WriteLine(task.Result); 25 | 26 | var r = new GeneratedTest.RenderOptions {width = 1, height = 2, renderMode = GeneratedTest.RenderMode.Production}; 27 | var taskEcho = proxy.Echo(r); 28 | Console.WriteLine(taskEcho.Result.ToString()); 29 | 30 | var o = new JObject(); 31 | o["a"] = JToken.FromObject(new[] { 1, 2 }); 32 | var taskEcho2 = proxy.EchoObject(o); 33 | Console.WriteLine(taskEcho2.Result.ToString()); 34 | 35 | // binary message 36 | var bytes = proxy.EchoStringAsBuffer("binary").Result; 37 | Console.WriteLine("bytes: {0}, message: {1}", bytes.Length, 38 | System.Text.Encoding.Default.GetString(bytes)); 39 | 40 | var bytesLength = proxy.GetBufferSize(bytes).Result; 41 | Console.WriteLine("bytes buffer size: {0}", bytesLength); 42 | } 43 | } 44 | 45 | private static void TestEvents() 46 | { 47 | using (var proxy = new GeneratedTest("http://localhost:3000/endpoint/1.0")) 48 | { 49 | int n1 = 0; 50 | int n2 = 0; 51 | int n3 = 0; 52 | 53 | proxy.TestEvent2 += (sender, data) => 54 | { 55 | Console.WriteLine("event2:{0}", data.Data[0].width); 56 | n2++; 57 | }; 58 | 59 | proxy.TestEvent += (sender, data) => 60 | { 61 | Console.WriteLine("event1:{0}", data.Data); 62 | n1++; 63 | }; 64 | 65 | proxy.TestEvent3 += (sender, data) => 66 | { 67 | Console.WriteLine("event3:{0}", data.Data); 68 | n3++; 69 | }; 70 | 71 | for (long i = 0, prev = i; i < 20; i++) 72 | { 73 | Console.WriteLine(prev = proxy.UseWS().Sum(i, prev).Result); 74 | } 75 | 76 | Thread.Sleep(10000); 77 | Console.WriteLine("{0}, {1}, {2}", n1, n2, n3); 78 | } 79 | } 80 | 81 | private static void TestUnsubscribe() 82 | { 83 | var proxy = new GeneratedTest("http://localhost:3000/endpoint/1.0"); 84 | int n1 = 0; 85 | EventHandler> testEventHandler = (sender, data) => 86 | { 87 | Console.WriteLine("event1:{0}", data.Data); 88 | n1++; 89 | }; 90 | 91 | proxy.TestEvent += testEventHandler; 92 | Thread.Sleep(5000); 93 | Console.WriteLine(n1); 94 | 95 | proxy.TestEvent -= testEventHandler; 96 | Thread.Sleep(5000); 97 | Console.WriteLine(n1); 98 | } 99 | 100 | private static void Main(string[] args) 101 | { 102 | //TestUnsubscribe(); 103 | //TestEvents(); 104 | TestMethods(); 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const vm = require('vm'); 4 | const Module = require('module'); 5 | const path = require('path'); 6 | const request = require('request'); 7 | 8 | try { 9 | require.resolve('json-ws'); 10 | } catch (err) { 11 | // Import error, make json-ws requireable (for require('json-ws/client') in proxies): 12 | module.paths.unshift(path.resolve(__dirname, '..')); 13 | } 14 | 15 | const Service = (module.exports.service = require('./lib/service/service.js')); 16 | 17 | module.exports.client = require('./lib/client/index.js').RpcClient; 18 | 19 | module.exports.registry = require('./lib/registry'); 20 | 21 | module.exports.getLanguageProxy = require('./lib/get-language-proxy'); 22 | 23 | module.exports.transport = { 24 | HTTP: require('./lib/transport/http-transport'), 25 | WebSocket: require('./lib/transport/ws-transport'), 26 | }; 27 | 28 | /** 29 | * Fetches proxy code from a URL 30 | * Attempts to run the script in the VM and return to result to the caller 31 | * @param {String} proxyUrl 32 | * @param {Object} sslSettings 33 | * @param {Function} callback 34 | */ 35 | module.exports.proxy = function(proxyUrl, sslSettings, callback) { 36 | if (!callback) { 37 | callback = sslSettings; 38 | sslSettings = {}; 39 | } 40 | 41 | request( 42 | proxyUrl, 43 | { 44 | agentOptions: sslSettings, 45 | }, 46 | function(err, response, body) { 47 | if (err) { 48 | callback(err); 49 | return; 50 | } 51 | 52 | if (response.statusCode !== 200) { 53 | callback(new Error('Proxy not available at ' + proxyUrl)); 54 | return; 55 | } 56 | 57 | const proxyModule = { exports: {} }; 58 | 59 | try { 60 | const moduleWrapper = vm.runInThisContext(Module.wrap(body), { 61 | filename: proxyUrl, 62 | }); 63 | moduleWrapper(proxyModule.exports, require, proxyModule); 64 | } catch (vmError) { 65 | const err = new Error('Error loading proxy'); 66 | err.stack = vmError.stack; 67 | callback(err); 68 | return; 69 | } 70 | 71 | callback(null, proxyModule.exports); 72 | } 73 | ); 74 | }; 75 | 76 | module.exports.getClientProxy = function(apiRoot, apiType, version, sslSettings, callback) { 77 | if (!callback) { 78 | callback = sslSettings; 79 | sslSettings = {}; 80 | } 81 | const serviceUrl = apiRoot + '/' + apiType + '/' + version; 82 | const proxyClassName = 83 | apiType 84 | .split(/\W+/) 85 | .map(function(string) { 86 | return string[0].toUpperCase() + string.slice(1).toLowerCase(); 87 | }) 88 | .join('') + 'Proxy'; 89 | const proxyUrl = serviceUrl + '?proxy=JavaScript&localName=' + proxyClassName; 90 | module.exports.proxy(proxyUrl, sslSettings, function(err, proxy) { 91 | if (err) { 92 | callback(err, null); 93 | } else { 94 | const ProxyClass = proxy[proxyClassName]; 95 | if (ProxyClass) { 96 | callback(null, new ProxyClass(serviceUrl, sslSettings)); 97 | } else { 98 | callback(new Error('Proxy not available')); 99 | } 100 | } 101 | }); 102 | }; 103 | 104 | module.exports.setUseStringEnums = function setUseStringEnums(useStringEnums) { 105 | Service.setUseStringEnums(useStringEnums); 106 | }; 107 | -------------------------------------------------------------------------------- /proxies/stubs/csharp/tests/tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | 7 | 8 | 2.0 9 | {588E2DAF-CE6B-4109-8C33-BD9632311A4C} 10 | Library 11 | Properties 12 | tests 13 | tests 14 | v4.0 15 | 512 16 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 17 | 18 | 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | 38 | ..\packages\Newtonsoft.Json.6.0.1\lib\net40\Newtonsoft.Json.dll 39 | 40 | 41 | 42 | 3.5 43 | 44 | 45 | 46 | 47 | False 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | {E441329A-B069-4DA2-A39C-A35F5ED40C20} 60 | csharp 61 | 62 | 63 | 64 | 65 | 66 | 67 | 74 | -------------------------------------------------------------------------------- /proxies/stubs/php/rpctunnel.php: -------------------------------------------------------------------------------- 1 | timeString = $d->format(self::TIME_FORMAT); 12 | } 13 | 14 | public function jsonSerialize() { 15 | return $this->timeString; 16 | } 17 | } 18 | 19 | class HTTPTransport { 20 | private $handle; 21 | 22 | private static $HEADERS = array(); 23 | 24 | public function __construct($url) { 25 | $this->handle = curl_init($url); 26 | 27 | if (!$this->handle) { 28 | throw new Exception('Unable to create curl handle to ' . $url); 29 | } 30 | 31 | curl_setopt($this->handle, CURLOPT_CUSTOMREQUEST, 'POST'); 32 | curl_setopt($this->handle, CURLOPT_RETURNTRANSFER, true); 33 | } 34 | 35 | public function sendMessage(array $payload, $convertTo = NULL) { 36 | $payload = json_encode($payload); 37 | 38 | curl_setopt($this->handle, CURLOPT_POSTFIELDS, $payload); 39 | curl_setopt($this->handle, CURLOPT_HTTPHEADER, array( 40 | 'Content-Type: application/json', 41 | 'Content-Length: ' . strlen($payload) 42 | )); 43 | 44 | return $this->parseResponse(curl_exec($this->handle)); 45 | } 46 | 47 | private function parseResponse($response) { 48 | $curlInfo = curl_getinfo($this->handle); 49 | 50 | if ($curlInfo['http_code'] == 500) { 51 | $json = json_decode($response); 52 | 53 | throw new ExecutionException($json->error->message); 54 | } 55 | 56 | if (stristr($curlInfo['content_type'], 'application/json')) { 57 | $json = json_decode($response); 58 | 59 | return $json->result; 60 | } 61 | 62 | 63 | if (stristr($curlInfo['content_type'], 'application/octet-stream')) { 64 | return $response; 65 | } 66 | 67 | throw new ExecutionException('Unsupported response type: ' . $curlInfo['content_type']); 68 | } 69 | } 70 | 71 | class RpcTunnel { 72 | private $url; 73 | private $transport; 74 | private $transportCache = array(); 75 | const HTTP_TRANSPORT = 'HTTP_TRANSPORT'; 76 | 77 | public static function toDateTime($timeString) { 78 | return new DateTime($timeString); 79 | } 80 | 81 | public function __construct($url) { 82 | $this->url = $url; 83 | $this->useHTTP(); 84 | } 85 | 86 | public function call($method_name, array $method_params, callable $convertTo = NULL) { 87 | $payload = array( 88 | 'jsonrpc' => '2.0', 89 | 'method' => $method_name, 90 | 'params' => $method_params, 91 | 'id' => uniqid() 92 | ); 93 | 94 | $result = $this->transport->sendMessage($payload); 95 | 96 | if (is_callable($convertTo)) { 97 | return call_user_func($convertTo, $result); 98 | } else { 99 | return $result; 100 | } 101 | } 102 | 103 | public function useHTTP() { 104 | $this->transport = $this->getTransport(self::HTTP_TRANSPORT); 105 | } 106 | 107 | private function getTransport($transportName) { 108 | if (!in_array($transportName, $this->transportCache)) { 109 | switch ($transportName) { 110 | case self::HTTP_TRANSPORT: 111 | $this->transportCache[$transportName] = new HTTPTransport($this->url); 112 | break; 113 | default: 114 | throw new Exception('Transport ' . $transportName . ' not implemented'); 115 | } 116 | } 117 | 118 | return $this->transportCache[$transportName]; 119 | } 120 | } 121 | 122 | ?> 123 | -------------------------------------------------------------------------------- /proxies/stubs/csharp/tests/tests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | using System.Threading; 4 | using ChaosGroup; 5 | using ChaosGroup.JsonWS.Proxies; 6 | using Microsoft.VisualStudio.TestTools.UnitTesting; 7 | using Newtonsoft.Json.Linq; 8 | 9 | namespace tests 10 | { 11 | [TestClass] 12 | public class Tests 13 | { 14 | private void TestMethods(GeneratedTest proxy) 15 | { 16 | try 17 | { 18 | Console.WriteLine(proxy.ThrowError().Result); 19 | Assert.Fail("Server-side exception was not rethrown on the client"); 20 | } 21 | catch (Exception) 22 | { 23 | } 24 | 25 | var task = proxy.Sum(1, 2); 26 | Assert.AreEqual(task.Result, 3); 27 | 28 | var r = new GeneratedTest.RenderOptions { width = 1, height = 2, renderMode = GeneratedTest.RenderMode.Production }; 29 | var taskEcho = proxy.Echo(r); 30 | Assert.AreEqual(r.width, taskEcho.Result.width); 31 | Assert.AreEqual(r.height, taskEcho.Result.height); 32 | Assert.AreEqual(r.renderMode, taskEcho.Result.renderMode); 33 | 34 | var o = new JObject(); 35 | o["a"] = JToken.FromObject(new[] { 1, 2 }); 36 | var taskEcho2 = proxy.EchoObject(o); 37 | Assert.AreEqual(o["a"].ToString(), taskEcho2.Result["a"].ToString()); 38 | 39 | var bytes = proxy.EchoStringAsBuffer("binary").Result; 40 | Assert.AreEqual("binary", Encoding.UTF8.GetString(bytes)); 41 | 42 | var bytesLength = proxy.GetBufferSize(bytes).Result; 43 | Assert.AreEqual(bytes.LongLength, bytesLength); 44 | } 45 | 46 | [TestMethod] 47 | public void TestMethodsWS() 48 | { 49 | using (var proxy = new GeneratedTest("http://localhost:3000/endpoint/1.0").UseWS()) 50 | { 51 | TestMethods(proxy); 52 | } 53 | } 54 | 55 | [TestMethod] 56 | public void TestMethodsHTTP() 57 | { 58 | using (var proxy = new GeneratedTest("http://localhost:3000/endpoint/1.0").UseHTTP()) 59 | { 60 | TestMethods(proxy); 61 | } 62 | } 63 | 64 | [TestMethod] 65 | public void TestEvents() 66 | { 67 | using (var proxy = new GeneratedTest("http://localhost:3000/endpoint/1.0")) 68 | { 69 | int n1 = 0; 70 | int n2 = 0; 71 | int n3 = 0; 72 | 73 | proxy.TestEvent2 += (sender, data) => 74 | { 75 | Console.WriteLine("event2:{0}", data.Data[0].width); 76 | n2++; 77 | }; 78 | 79 | proxy.TestEvent += (sender, data) => 80 | { 81 | Console.WriteLine("event1:{0}", data.Data); 82 | n1++; 83 | }; 84 | 85 | proxy.TestEvent3 += (sender, data) => 86 | { 87 | Console.WriteLine("event3:{0}", data.Data); 88 | n3++; 89 | }; 90 | 91 | string s = null; 92 | proxy.TestBinaryEvent += (sender, data) => 93 | { 94 | s = Encoding.UTF8.GetString(data.Data); 95 | Console.WriteLine(s); 96 | }; 97 | 98 | Thread.Sleep(6000); 99 | Assert.IsTrue(n1 > 0); 100 | Assert.IsTrue(n2 > 0); 101 | Assert.IsTrue(n3 > 0); 102 | Assert.AreEqual(s, "test binary event"); 103 | } 104 | } 105 | 106 | [TestMethod] 107 | public void TestUnsubscribe() 108 | { 109 | var proxy = new GeneratedTest("http://localhost:3000/endpoint/1.0"); 110 | var n1 = 0; 111 | EventHandler> testEventHandler = (sender, data) => 112 | { 113 | n1++; 114 | }; 115 | proxy.TestEvent += testEventHandler; 116 | Thread.Sleep(1500); 117 | proxy.TestEvent -= testEventHandler; 118 | Thread.Sleep(2000); 119 | Assert.AreEqual(n1, 1); 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /static/javascript.js: -------------------------------------------------------------------------------- 1 | ace.define("ace/snippets/javascript",["require","exports","module"],function(e,t,n){"use strict";t.snippetText='# Prototype\nsnippet proto\n ${1:class_name}.prototype.${2:method_name} = function(${3:first_argument}) {\n ${4:// body...}\n };\n# Function\nsnippet fun\n function ${1?:function_name}(${2:argument}) {\n ${3:// body...}\n }\n# Anonymous Function\nregex /((=)\\s*|(:)\\s*|(\\()|\\b)/f/(\\))?/\nsnippet f\n function${M1?: ${1:functionName}}($2) {\n ${0:$TM_SELECTED_TEXT}\n }${M2?;}${M3?,}${M4?)}\n# Immediate function\ntrigger \\(?f\\(\nendTrigger \\)?\nsnippet f(\n (function(${1}) {\n ${0:${TM_SELECTED_TEXT:/* code */}}\n }(${1}));\n# if\nsnippet if\n if (${1:true}) {\n ${0}\n }\n# if ... else\nsnippet ife\n if (${1:true}) {\n ${2}\n } else {\n ${0}\n }\n# tertiary conditional\nsnippet ter\n ${1:/* condition */} ? ${2:a} : ${3:b}\n# switch\nsnippet switch\n switch (${1:expression}) {\n case \'${3:case}\':\n ${4:// code}\n break;\n ${5}\n default:\n ${2:// code}\n }\n# case\nsnippet case\n case \'${1:case}\':\n ${2:// code}\n break;\n ${3}\n\n# while (...) {...}\nsnippet wh\n while (${1:/* condition */}) {\n ${0:/* code */}\n }\n# try\nsnippet try\n try {\n ${0:/* code */}\n } catch (e) {}\n# do...while\nsnippet do\n do {\n ${2:/* code */}\n } while (${1:/* condition */});\n# Object Method\nsnippet :f\nregex /([,{[])|^\\s*/:f/\n ${1:method_name}: function(${2:attribute}) {\n ${0}\n }${3:,}\n# setTimeout function\nsnippet setTimeout\nregex /\\b/st|timeout|setTimeo?u?t?/\n setTimeout(function() {${3:$TM_SELECTED_TEXT}}, ${1:10});\n# Get Elements\nsnippet gett\n getElementsBy${1:TagName}(\'${2}\')${3}\n# Get Element\nsnippet get\n getElementBy${1:Id}(\'${2}\')${3}\n# console.log (Firebug)\nsnippet cl\n console.log(${1});\n# return\nsnippet ret\n return ${1:result}\n# for (property in object ) { ... }\nsnippet fori\n for (var ${1:prop} in ${2:Things}) {\n ${0:$2[$1]}\n }\n# hasOwnProperty\nsnippet has\n hasOwnProperty(${1})\n# docstring\nsnippet /**\n /**\n * ${1:description}\n *\n */\nsnippet @par\nregex /^\\s*\\*\\s*/@(para?m?)?/\n @param {${1:type}} ${2:name} ${3:description}\nsnippet @ret\n @return {${1:type}} ${2:description}\n# JSON.parse\nsnippet jsonp\n JSON.parse(${1:jstr});\n# JSON.stringify\nsnippet jsons\n JSON.stringify(${1:object});\n# self-defining function\nsnippet sdf\n var ${1:function_name} = function(${2:argument}) {\n ${3:// initial code ...}\n\n $1 = function($2) {\n ${4:// main code}\n };\n }\n# singleton\nsnippet sing\n function ${1:Singleton} (${2:argument}) {\n // the cached instance\n var instance;\n\n // rewrite the constructor\n $1 = function $1($2) {\n return instance;\n };\n \n // carry over the prototype properties\n $1.prototype = this;\n\n // the instance\n instance = new $1();\n\n // reset the constructor pointer\n instance.constructor = $1;\n\n ${3:// code ...}\n\n return instance;\n }\n# class\nsnippet class\nregex /^\\s*/clas{0,2}/\n var ${1:class} = function(${20}) {\n $40$0\n };\n \n (function() {\n ${60:this.prop = ""}\n }).call(${1:class}.prototype);\n \n exports.${1:class} = ${1:class};\n# \nsnippet for-\n for (var ${1:i} = ${2:Things}.length; ${1:i}--; ) {\n ${0:${2:Things}[${1:i}];}\n }\n# for (...) {...}\nsnippet for\n for (var ${1:i} = 0; $1 < ${2:Things}.length; $1++) {\n ${3:$2[$1]}$0\n }\n# for (...) {...} (Improved Native For-Loop)\nsnippet forr\n for (var ${1:i} = ${2:Things}.length - 1; $1 >= 0; $1--) {\n ${3:$2[$1]}$0\n }\n\n\n#modules\nsnippet def\n define(function(require, exports, module) {\n "use strict";\n var ${1/.*\\///} = require("${1}");\n \n $TM_SELECTED_TEXT\n });\nsnippet req\nguard ^\\s*\n var ${1/.*\\///} = require("${1}");\n $0\nsnippet requ\nguard ^\\s*\n var ${1/.*\\/(.)/\\u$1/} = require("${1}").${1/.*\\/(.)/\\u$1/};\n $0\n',t.scope="javascript"}) -------------------------------------------------------------------------------- /proxies/stubs/csharp/csharp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | x86 6 | 8.0.30703 7 | 2.0 8 | {E441329A-B069-4DA2-A39C-A35F5ED40C20} 9 | Exe 10 | Properties 11 | csharp 12 | csharp 13 | v4.0 14 | 15 | 16 | 512 17 | 18 | 19 | x86 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | 28 | 29 | x86 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | true 39 | bin\Debug\ 40 | DEBUG;TRACE 41 | full 42 | AnyCPU 43 | prompt 44 | false 45 | false 46 | false 47 | 48 | 49 | bin\Release\ 50 | TRACE 51 | true 52 | pdbonly 53 | AnyCPU 54 | prompt 55 | 56 | 57 | 58 | packages\Newtonsoft.Json.6.0.1\lib\net40\Newtonsoft.Json.dll 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | packages\WebSocket4Net.0.8\lib\net40\WebSocket4Net.dll 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 89 | -------------------------------------------------------------------------------- /lib/transport/json-rpc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Utilities to parse/generate valid JSON-RPC 2.0 messages. 3 | * For more information please visit http://www.jsonrpc.org/specification. 4 | */ 5 | 'use strict'; 6 | 7 | const { AsServiceError } = require('../error'); 8 | 9 | const jsonrpc = (module.exports = { 10 | // Make sure our messages always have the 2.0 version tag 11 | jsonrpc: function(o) { 12 | o.jsonrpc = '2.0'; 13 | return o; 14 | }, 15 | 16 | /** 17 | * Generates a JSON-RPC response object 18 | * @param id The ID of the message 19 | * @param err 20 | * @param [result] 21 | * @returns {jsonrpc} A JSON-RPC 2.0-formatted object 22 | */ 23 | response: function(id, err, result) { 24 | const res = { 25 | id: typeof id === 'undefined' ? null : id, 26 | }; 27 | if (err) { 28 | res.error = err; 29 | } else if (typeof result !== 'undefined') { 30 | res.result = result; 31 | } 32 | return jsonrpc.jsonrpc(res); 33 | }, 34 | 35 | // Generates an error object, part of the response 36 | error: function(code, message, data) { 37 | return { code, message, data }; 38 | }, 39 | 40 | // Check if a JSON message is a valid JSON-RPC request 41 | // @param methodMap Contains a map with valid method names and parameters for the request 42 | validateRequest: function(req, api) { 43 | const name = api.name; 44 | if (!req.hasOwnProperty('jsonrpc') || req.jsonrpc !== '2.0') { 45 | return jsonrpc.error( 46 | -32600, 47 | 'Invalid request', 48 | AsServiceError("jsonrpc protocol version MUST be '2.0'", name) 49 | ); 50 | } 51 | 52 | if (!req.hasOwnProperty('method') || !!req.method == false) { 53 | return jsonrpc.error( 54 | -32601, 55 | 'Method not found', 56 | AsServiceError("The 'method' property is undefined or empty", name) 57 | ); 58 | } 59 | 60 | if (req.method.indexOf('rpc.') === 0) { 61 | const eventName = req.params[0]; 62 | if (!eventName) { 63 | return jsonrpc.error(-32601, 'Invalid event operation'); 64 | } 65 | if (!api.eventMap.hasOwnProperty(eventName)) { 66 | return jsonrpc.error(-32601, 'Event not found', AsServiceError(eventName, name)); 67 | } 68 | return null; 69 | } 70 | 71 | if (!api.methodMap.hasOwnProperty(req.method)) { 72 | return jsonrpc.error(-32601, 'Method not found', AsServiceError(req.method, name)); 73 | } 74 | 75 | const methodInfoParams = api.methodMap[req.method].params; 76 | /* BH-178 Methods with no parameters do not enforce input parameters length check 77 | if (methodInfoParams.length == 0) { 78 | return null; 79 | }*/ 80 | 81 | const requiredParamsCount = methodInfoParams.reduce(function(prev, param) { 82 | return prev + (param.default === undefined ? 1 : 0); 83 | }, 0); 84 | 85 | if (requiredParamsCount > 0 && (!req.hasOwnProperty('params') || !req.params)) { 86 | return jsonrpc.error(-32602, 'Missing parameters', AsServiceError(req.method, name)); 87 | } 88 | 89 | if (Array.isArray(req.params)) { 90 | if ( 91 | req.params.length < requiredParamsCount || 92 | req.params.length > methodInfoParams.length 93 | ) { 94 | return jsonrpc.error( 95 | -32602, 96 | 'Invalid parameters', 97 | AsServiceError( 98 | 'The specified number of parameters does not match the method signature', 99 | name 100 | ) 101 | ); 102 | } 103 | } else if (typeof req.params === 'object') { 104 | for (let i = 0; i < methodInfoParams.length; i++) { 105 | if ( 106 | i < requiredParamsCount && 107 | !req.params.hasOwnProperty(methodInfoParams[i].name) 108 | ) { 109 | return jsonrpc.error( 110 | -32602, 111 | 'Invalid parameters', 112 | AsServiceError( 113 | 'The specified parameters do not match the method signature', 114 | name 115 | ) 116 | ); 117 | } 118 | } 119 | /*var paramsContainInvalidKey = Object.keys(req.params).some(function(p) { 120 | return !methodInfoParams.hasOwnProperty(p) 121 | }); 122 | if (paramsContainInvalidKey) { 123 | return jsonrpc.error(-32602, 124 | "Invalid parameters", 125 | AsServiceError("The specified parameter names do not match the method signature", name)); 126 | }*/ 127 | } 128 | 129 | return null; 130 | }, 131 | }); 132 | -------------------------------------------------------------------------------- /lib/client/transports/ws.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | (function(module, define) { 3 | // using non-strict mode, otherwise the re-assignment of require would throw TypeError 4 | if (typeof require !== 'function') { 5 | require = module.require; 6 | } 7 | 8 | var webSocketSupportsSendCallback = true; 9 | define('ws', function WebSocket(url, protocols) { 10 | webSocketSupportsSendCallback = false; 11 | 12 | if (typeof window.WebSocket === 'function') { 13 | return new window.WebSocket(url, protocols); 14 | } else { 15 | throw new Error('No WebSocket implementation available'); 16 | } 17 | }); 18 | 19 | var EventEmitter = require('events'); 20 | var inherits = require('util').inherits; 21 | var WS = require('ws'); 22 | 23 | module.exports = WebSocketTransport; 24 | 25 | // Defined with this name to keep node.js compatibility: 26 | define('./transports/ws', WebSocketTransport); 27 | 28 | function WebSocketTransport(url, settings) { 29 | this.closed = false; 30 | this.settings = settings || {}; 31 | this.url = url; 32 | this.ready = false; 33 | this.ws = new WS( 34 | url.replace('http:', 'ws:').replace('https:', 'wss:'), 35 | settings 36 | ); 37 | 38 | this.attachEvents(); 39 | } 40 | inherits(WebSocketTransport, EventEmitter); 41 | 42 | Object.defineProperty(WebSocketTransport.prototype, 'name', { 43 | value: 'ws' 44 | }); 45 | 46 | WebSocketTransport.prototype.attachEvents = function () { 47 | var self = this; 48 | var callbacks = this.callbacks = []; 49 | 50 | function on(event, fn) { 51 | if (typeof self.ws.on === 'function') { 52 | self.ws.on(event, fn); 53 | } else { 54 | self.ws['on' + event] = fn; 55 | } 56 | } 57 | 58 | on('message', function (event) { 59 | try { 60 | var msg = JSON.parse(event.data || event); 61 | 62 | if (msg.error) { 63 | var error = msg.error; 64 | var errorMessage = error.data && error.data.message; 65 | var errorInstance = new Error(errorMessage || error.message); 66 | errorInstance.data = error.data; 67 | errorInstance.code = error.code; 68 | msg.error = errorInstance; 69 | } 70 | 71 | if (Object.keys(msg).length === 0) { 72 | return; 73 | } 74 | var cb = callbacks[msg.id]; 75 | if (cb) { 76 | delete callbacks[msg.id]; 77 | cb(msg.error, msg.result); 78 | } else { 79 | self.emit('event', { name: msg.id, data: msg.result }); 80 | } 81 | } catch (e) { 82 | self.emit('error', e); 83 | } 84 | }); 85 | 86 | on('open', function () { 87 | self.ready = true; 88 | }); 89 | 90 | on('close', function () { 91 | self.closed = true; 92 | self.emit('close'); 93 | }); 94 | 95 | on('error', function (err) { 96 | self.emit('error', err); 97 | }); 98 | }; 99 | 100 | WebSocketTransport.prototype.send = function (message, callback) { 101 | var self = this; 102 | 103 | if (this.closed) { 104 | return; 105 | } 106 | 107 | if (!this.ready) { 108 | setTimeout(function () { 109 | self.send(message, callback); 110 | }, 50); 111 | return; 112 | } 113 | 114 | if (message.id !== undefined) { 115 | this.callbacks[message.id] = callback; 116 | } 117 | 118 | try { 119 | if (webSocketSupportsSendCallback) { 120 | this.ws.send(JSON.stringify(message), function (error) { 121 | if (error) { 122 | if (message.id !== undefined) { 123 | delete self.callbacks[message.id]; 124 | } 125 | callback(error); 126 | } 127 | }); 128 | } else { 129 | this.ws.send(JSON.stringify(message)); 130 | } 131 | 132 | } catch (e) { 133 | callback(e); 134 | } 135 | }; 136 | 137 | WebSocketTransport.prototype.close = function () { 138 | if (!this.closed) { 139 | this.ws.close(); 140 | } 141 | }; 142 | 143 | }.apply(null, (function() { 144 | 'use strict'; 145 | 146 | if (typeof module !== 'undefined') { 147 | // node.js and webpack 148 | return [module, function() {}]; 149 | } 150 | 151 | if (typeof window !== 'undefined') { 152 | // browser 153 | if (typeof window.jsonws === 'undefined') { 154 | throw new Error('No json-ws polyfills found.'); 155 | } 156 | 157 | var jsonws = window.jsonws; 158 | 159 | return [jsonws, jsonws.define]; 160 | } 161 | 162 | throw new Error('Unknown environment, this code should be used in node.js/webpack/browser'); 163 | }()))); 164 | -------------------------------------------------------------------------------- /lib/client/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Client RPC Tunnels 3 | */ 4 | /* eslint-disable */ 5 | (function (module, define) { 6 | // using non-strict mode, otherwise the re-assignment of require would throw TypeError 7 | if (typeof require !== 'function') { 8 | require = module.require; 9 | } 10 | 11 | module.exports = RpcTunnel; 12 | 13 | // Defined with this name to keep node.js compatibility: 14 | define('json-ws/client', RpcTunnel); 15 | 16 | // Empty callback trap 17 | var noop = function() {}; 18 | 19 | /** 20 | * Client RPC tunnel 21 | */ 22 | function RpcTunnel(url, sslSettings) { 23 | var self = this; 24 | if (! (this instanceof RpcTunnel)) { 25 | return new RpcTunnel(url, sslSettings); 26 | } 27 | 28 | this.transports = {}; 29 | 30 | if (!url) { 31 | throw new Error('Missing url parameter, which should be either a URL or client transport.') 32 | } 33 | 34 | var transport = null; 35 | 36 | if (typeof url === 'string') { 37 | var HttpTransport = require('./transports/http'); 38 | transport = new HttpTransport(url, sslSettings); 39 | 40 | // As a special case when adding an HTTP transport, set up the WebSocket transport 41 | // It is lazy-loaded, so the WebSocket connection is only created if/when the transport is used 42 | Object.defineProperty(this.transports, 'ws', { 43 | enumerable: true, // JSWS-47 ensure we can discover the ws transport 44 | get: (function () { 45 | var webSocketTransport; 46 | var WebSocketTransport = require('./transports/ws'); 47 | return function () { 48 | if (!webSocketTransport) { 49 | webSocketTransport = new WebSocketTransport(url, sslSettings); 50 | webSocketTransport.on('event', function (e) { 51 | self.emit('event', e); 52 | }); 53 | } 54 | return webSocketTransport; 55 | }; 56 | }()) 57 | }); 58 | } else { 59 | transport = url; 60 | transport.on('event', function (e) { 61 | self.emit('event', e); 62 | }); 63 | } 64 | 65 | transport.on('error', function(err) { 66 | self.emit('error', err); 67 | }); 68 | 69 | this.url = transport.url; 70 | this.transports[transport.name] = transport; 71 | } 72 | 73 | var EventEmitter = require('events'); 74 | var inherits = require('util').inherits; 75 | inherits(RpcTunnel, EventEmitter); 76 | 77 | RpcTunnel.prototype._nextId = (function () { 78 | var nextId = 0; 79 | return function () { 80 | return nextId++; 81 | }; 82 | }()); 83 | 84 | RpcTunnel.prototype.call = function (options, callback) { 85 | var method = options.method; 86 | var params = options.params; 87 | var expectReturn = !!options.expectReturn; 88 | var transport = options.transport || 'http'; 89 | 90 | if (!method) { 91 | return; 92 | } 93 | 94 | if (!this.transports[transport]) { 95 | throw new Error('Invalid method transport requsted: ' + transport); 96 | } 97 | 98 | if (callback && typeof callback !== 'function') { 99 | throw new Error('Invalid callback function.'); 100 | } 101 | 102 | var msg = { 103 | 'jsonrpc': '2.0', 104 | 'method': method, 105 | 'params': params 106 | }; 107 | 108 | if (expectReturn) { 109 | msg.id = this._nextId(); 110 | if (callback) { 111 | this.transports[transport].send(msg, callback); 112 | } else if (typeof Promise == 'function') { 113 | return new Promise(function(resolve, reject) { 114 | this.transports[transport].send(msg, function (error, result) { 115 | if (error) { 116 | reject(error); 117 | } else { 118 | resolve(result); 119 | } 120 | }); 121 | }.bind(this)); 122 | } else { 123 | throw new Error('No callback and no Promise support.'); 124 | } 125 | } else { 126 | // JSONWS-6 The server does not produce a response for this call 127 | // We still need to make the call without notifying the client 128 | this.transports[transport].send(msg, noop); 129 | } 130 | }; 131 | 132 | RpcTunnel.prototype.close = function () { 133 | Object.keys(this.transports).forEach(function (key) { 134 | this.transports[key].close(); 135 | }.bind(this)); 136 | }; 137 | }.apply(null, (function() { 138 | 'use strict'; 139 | 140 | if (typeof module !== 'undefined') { 141 | // node.js and webpack 142 | return [module, function() {}]; 143 | } 144 | 145 | if (typeof window !== 'undefined') { 146 | // browser 147 | if (typeof window.jsonws === 'undefined') { 148 | throw new Error('No json-ws polyfills found.'); 149 | } 150 | 151 | var jsonws = window.jsonws; 152 | 153 | return [jsonws, jsonws.define]; 154 | } 155 | 156 | throw new Error('Unknown environment, this code should be used in node.js/webpack/browser'); 157 | }()))); 158 | -------------------------------------------------------------------------------- /lib/transport/socket-io-transport.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Implementation of a SocketIO transport for the JSON-WS module 3 | */ 4 | 5 | 'use strict'; 6 | 7 | const socketIO = require('socket.io'); 8 | const jsonrpc = require('./json-rpc'); 9 | const BaseTransport = require('./base-transport'); 10 | const stream = require('stream'); 11 | 12 | class SocketIOTransport extends BaseTransport { 13 | static get type() { 14 | return 'SocketIO'; 15 | } 16 | 17 | constructor(registry, path = '/socket.io') { 18 | super(registry); 19 | if (path[0] !== '/') { 20 | throw new Error("socket.io demands that 'path' begins with slash '/'"); 21 | } 22 | this.nextConnectionId = 0; 23 | 24 | this._serviceNamespaces = new Map /* servicePrefix -> socketIONamespace*/(); 25 | this._socketIO = socketIO(registry.httpServer, { path }); 26 | 27 | for (const [servicePrefix, service] of registry.services.entries()) { 28 | this._addServiceNamespace(servicePrefix, service); 29 | } 30 | 31 | registry.on('service-added', ({ servicePrefix, service }) => { 32 | this._addServiceNamespace(servicePrefix, service); 33 | }); 34 | } 35 | 36 | sendMessage(msg, context /*, format*/) { 37 | if (!(context && context.socket && context.socket.connected)) { 38 | return; 39 | } 40 | 41 | if (msg.id !== undefined) { 42 | try { 43 | if (msg.result && Buffer.isBuffer(msg.result)) { 44 | msg.result = msg.result.toString('base64'); 45 | } 46 | 47 | if (msg.result && msg.result instanceof stream.Readable) { 48 | context.socket.send( 49 | JSON.stringify( 50 | jsonrpc.response( 51 | msg.id, 52 | jsonrpc.error( 53 | -32000, 54 | 'SocketIO', 55 | 'Streaming over SocketIO is not supported' 56 | ) 57 | ) 58 | ) 59 | ); 60 | msg.result.destroy(); 61 | } 62 | 63 | context.socket.send(JSON.stringify(msg)); 64 | } catch (e) { 65 | console.log(msg); //eslint-disable-line no-console 66 | console.log(e); //eslint-disable-line no-console 67 | } 68 | } 69 | } 70 | 71 | _connectionContextForService(socket, service) { 72 | const connectionContext = Object.create( 73 | {}, 74 | { 75 | socket: { 76 | get() { 77 | return socket; 78 | }, 79 | }, 80 | service: { 81 | get() { 82 | return service; 83 | }, 84 | }, 85 | objectId: { value: `!#ConnectionCtx:${this.nextConnectionId++}` }, 86 | toString: { 87 | value: () => this.objectId, 88 | }, 89 | } 90 | ); 91 | connectionContext.params = {}; 92 | 93 | return connectionContext; 94 | } 95 | 96 | _addServiceNamespace(servicePrefix, service) { 97 | const serviceNamespace = this._socketIO.of(servicePrefix); 98 | this._serviceNamespaces.set(servicePrefix, serviceNamespace); 99 | 100 | serviceNamespace.on('connection', socket => { 101 | this._attachEvents(this._connectionContextForService(socket, service)); 102 | }); 103 | } 104 | 105 | /** 106 | * @param {SocketIO.Socket} socket 107 | * @private 108 | */ 109 | _attachEvents(connectionContext) { 110 | const socket = connectionContext.socket; 111 | this.onConnect(socket); 112 | 113 | let validationParamsReceived = false; 114 | socket.on('rpc.sio.setConnectionContext', ({ validationParams = {} }, cb) => { 115 | if (!validationParamsReceived) { 116 | connectionContext.params = validationParams; 117 | validationParamsReceived = true; 118 | } 119 | cb(); 120 | }); 121 | 122 | socket.on('message', message => { 123 | try { 124 | message = typeof message === 'string' ? JSON.parse(message) : message; 125 | } catch (ex) { 126 | // Parse error 127 | this.trace.error(connectionContext, null, ex); 128 | socket.send( 129 | jsonrpc.response(null, jsonrpc.error(-32700, 'Parse error', ex.toString())) 130 | ); 131 | return; 132 | } 133 | 134 | const continueImmediately = this.validateMessage( 135 | connectionContext.service, 136 | message, 137 | connectionContext.params, 138 | (err, data) => { 139 | if (err) { 140 | socket.send( 141 | jsonrpc.response( 142 | message.id, 143 | jsonrpc.error(-32000, 'Bad Request', err.message) 144 | ) 145 | ); 146 | } else { 147 | connectionContext.data = data; // Update with the latest data from the validator. 148 | this.handleMessage(connectionContext.service, message, connectionContext); 149 | } 150 | } 151 | ); 152 | 153 | if (continueImmediately) { 154 | this.handleMessage(connectionContext.service, message, connectionContext); 155 | } 156 | }); 157 | 158 | socket.on('disconnect', () => { 159 | this.onDisconnect(connectionContext); 160 | }); 161 | } 162 | } 163 | 164 | module.exports = SocketIOTransport; 165 | -------------------------------------------------------------------------------- /proxies/stubs/java/src/com/chaosgroup/vraycloud/stubs/Main.java: -------------------------------------------------------------------------------- 1 | package com.chaosgroup.jsonws.stubs; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.JsonObject; 5 | import com.chaosgroup.jsonws.stubs.GeneratedTest.*; 6 | 7 | import java.text.SimpleDateFormat; 8 | import java.util.Date; 9 | import java.util.TimeZone; 10 | import java.util.concurrent.ExecutionException; 11 | 12 | public class Main { 13 | public static void main(String[] args) throws Exception { 14 | 15 | /*SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); 16 | sdf.setTimeZone(TimeZone.getTimeZone("UTC")); 17 | System.out.println("Date: " + sdf.format(new Date(System.currentTimeMillis()))); 18 | String dateString = "2013-11-27T10:39:45.197Z"; 19 | //Date date = sdf.parse(dateString); 20 | Date date = javax.xml.bind.DatatypeConverter.parseDateTime(dateString).getTime(); 21 | System.out.println("Date: " + date); 22 | if (true) return;*/ 23 | 24 | GeneratedTest.RenderOptions r = new GeneratedTest.RenderOptions(); 25 | r.width = 640L; 26 | r.height = 360L; 27 | r.renderMode = RenderMode.RtGpuCuda; 28 | 29 | try (GeneratedTest proxy = new GeneratedTest("http://localhost:3000/endpoint/1.0")) { 30 | GeneratedTest.RenderOptions echoed = proxy.useHTTP().echo(r).get(); 31 | System.out.println("HTTP RenderOptions: " + new Gson().toJson(echoed)); 32 | r.width = 800L; 33 | r.height = 600L; 34 | r.renderMode = RenderMode.RtCpu; 35 | 36 | System.out.println("WS RenderOptions: " + new Gson().toJson(proxy.useWS().echo(r).get())); 37 | proxy.onTestEvent(new GeneratedTest.TestEventHandler() { 38 | @Override 39 | public void onTestEvent(Long data) { 40 | System.out.println("Test event data " + data); 41 | } 42 | }); 43 | 44 | System.out.println("HTTP Echo object: " + 45 | proxy.useHTTP().echoObject( 46 | (JsonObject) new Gson().toJsonTree(r, GeneratedTest.RenderOptions.class)).get()); 47 | 48 | System.out.println("HTTP Sum = " + proxy.sum(5L, 6L).get()); 49 | 50 | System.out.println("HTTP Array Sum = " + proxy.sumArray(new Long[]{1L, 2L, 3L, 4L}).get()); 51 | 52 | Long[] numsTo20 = proxy.returnFrom0ToN(20L).get(); 53 | for (long n : numsTo20) System.out.println("N = " + n); 54 | 55 | RenderOptions[] options = proxy.getRenderOptions().get(); 56 | for (RenderOptions o : options) { 57 | System.out.println(new Gson().toJsonTree(o, GeneratedTest.RenderOptions.class)); 58 | } 59 | 60 | byte[] stringBytes = proxy.useHTTP().echoStringAsBuffer("Hello, HTTP world!").get(); 61 | System.out.println(new String(stringBytes, "UTF-8")); 62 | 63 | stringBytes = proxy.useWS().echoStringAsBuffer("Hello, WS world!").get(); 64 | System.out.println(new String(stringBytes, "UTF-8")); 65 | 66 | System.out.println("Original string bytes length: " + stringBytes.length); 67 | System.out.println("HTTP String bytes length: " + proxy.useHTTP().getBufferSize(stringBytes).get()); 68 | System.out.println("WS String bytes length: " + proxy.useWS().getBufferSize(stringBytes).get()); 69 | 70 | timeHttp(proxy); 71 | timeWS(proxy); 72 | 73 | long prev = 1, curr = 1; 74 | for (int i = 0; i < 5; i++) { 75 | long pprev = prev; 76 | prev = curr; 77 | curr = proxy.useWS().sum(pprev, curr).get(); 78 | System.out.println("WS Fib = " + curr); 79 | } 80 | 81 | System.out.println("HTTP Sum = " + proxy.useHTTP().sum(2L, 2L).get()); 82 | 83 | // Test namespaces 84 | System.out.println("ns1.method1: " + proxy.ns1.method1().get()); 85 | 86 | Thread.sleep(5000); 87 | } 88 | } 89 | 90 | private static void timeHttp(GeneratedTest proxy) { 91 | long startTime = System.currentTimeMillis(); 92 | try { 93 | for (int i = 0; i < 10; i++) { 94 | proxy.useHTTP().echoStringAsBuffer("Hello, world!").get(); 95 | } 96 | } catch (Exception e) { 97 | e.printStackTrace(); 98 | } 99 | System.out.println("HTTP took " + (System.currentTimeMillis() - startTime) + " ms"); 100 | } 101 | 102 | private static void timeWS(GeneratedTest proxy) { 103 | long startTime = System.currentTimeMillis(); 104 | try { 105 | for (int i = 0; i < 10; i++) { 106 | proxy.useWS().echoStringAsBuffer("Hello, world!").get(); 107 | } 108 | } catch (Exception e) { 109 | e.printStackTrace(); 110 | } 111 | System.out.println("WS took " + (System.currentTimeMillis() - startTime) + " ms"); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /lib/client/transports/socket-io.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | (function(module, define) { 3 | // using non-strict mode, otherwise the re-assignment of require would throw TypeError 4 | if (typeof require !== 'function') { 5 | require = module.require; 6 | } 7 | 8 | define('socket.io-client', function SocketIO() { 9 | var args = Array.prototype.slice.call(arguments); 10 | // 'socket.io-client' exposes 'io' 11 | // you must have window.io included separately 12 | if (window.io) { 13 | return window.io.apply(window, args); 14 | } else { 15 | throw new Error('No socket.io-client implementation available'); 16 | } 17 | }); 18 | 19 | var EventEmitter = require('events'); 20 | var inherits = require('util').inherits; 21 | var parseUrl = require('url').parse; 22 | 23 | module.exports = SocketIoTransport; 24 | // Defined with this name to keep node.js compatibility: 25 | define('./transports/socket-io', SocketIoTransport); 26 | 27 | var SocketIO = require('socket.io-client'); 28 | 29 | /** 30 | * 31 | * @param {string} url 32 | * @param {object} options 33 | * @param {string} [options.path] Path option to be given to the socket.io client. 34 | * @param {object} [options.validationParams] Params that are necessary to validate the client. Send to the server only once on each connect/reconnect. 35 | * @constructor 36 | */ 37 | function SocketIoTransport(url, options) { 38 | options = options || {}; 39 | 40 | if (!options.path) { 41 | options.path = '/socket.io'; 42 | } 43 | 44 | if (options.path[0] !== '/') { 45 | throw new Error("socket.io demands that 'options.path' begins with slash '/'"); 46 | } 47 | 48 | var servicePrefixMatch = url.match(/(\/[^\/]+\/v\d+)$/); 49 | 50 | if (!servicePrefixMatch) { 51 | throw new Error('Invalid url, it should end in //v'); 52 | } 53 | 54 | var serviceNamespace = servicePrefixMatch[0]; 55 | 56 | var validationParams = options.validationParams || {}; 57 | 58 | var parsedUrl = parseUrl(url); 59 | if (!parsedUrl.host) { 60 | throw new Error('Invalid url and/or host: ' + url); 61 | } 62 | 63 | this.url = url; 64 | this.socket = SocketIO(parsedUrl.protocol + '//' + parsedUrl.host + serviceNamespace, {path: options.path}); 65 | this._attachEvents(validationParams); 66 | 67 | this._contextAcknowledged = false; 68 | } 69 | 70 | inherits(SocketIoTransport, EventEmitter); 71 | 72 | Object.defineProperty(SocketIoTransport.prototype, 'name', { 73 | value: 'socket.io' 74 | }); 75 | 76 | /** 77 | * @param {object} message - rpc message to send 78 | * @param callback 79 | */ 80 | SocketIoTransport.prototype.send = function(message, callback) { 81 | var self = this; 82 | 83 | if (!this.socket || this.socket.disconnected || !self._contextAcknowledged) { 84 | setTimeout(function () { 85 | self.send(message, callback); 86 | }, 50); 87 | return; 88 | } 89 | 90 | if (message.id !== undefined) { 91 | this.callbacks[message.id] = callback; 92 | } 93 | 94 | try { 95 | this.socket.send(JSON.stringify(message), function (error) { 96 | if (error) { 97 | if (message.id !== undefined) { 98 | delete self.callbacks[message.id]; 99 | } 100 | callback(error); 101 | } 102 | }); 103 | } catch (e) { 104 | callback(e); 105 | } 106 | }; 107 | 108 | SocketIoTransport.prototype.close = function() { 109 | this.socket.disconnect(); 110 | }; 111 | 112 | SocketIoTransport.prototype._attachEvents = function(validationParams) { 113 | var self = this; 114 | var callbacks = this.callbacks = []; 115 | 116 | this.socket.on('message', function (event) { 117 | try { 118 | if (typeof event === 'string') { 119 | event = JSON.parse(event); 120 | } 121 | 122 | var msg = event.data || event; 123 | 124 | if (msg.error) { 125 | var error = msg.error; 126 | var errorMessage = error.data && error.data.message; 127 | var errorInstance = new Error(errorMessage || error.message); 128 | errorInstance.data = error.data; 129 | errorInstance.code = error.code; 130 | msg.error = errorInstance; 131 | } 132 | 133 | if (Object.keys(msg).length === 0) { 134 | return; 135 | } 136 | var cb = callbacks[msg.id]; 137 | if (cb) { 138 | delete callbacks[msg.id]; 139 | cb(msg.error, msg.result); 140 | } else { 141 | self.emit('event', { name: msg.id, data: msg.result }); 142 | } 143 | } catch (e) { 144 | self.emit('error', e); 145 | } 146 | }); 147 | 148 | this.socket.on('connect', function () { 149 | self.socket.emit('rpc.sio.setConnectionContext', { 150 | validationParams: validationParams 151 | }, function(/*ack*/) { 152 | self._contextAcknowledged = true; 153 | }); 154 | }); 155 | 156 | this.socket.on('disconnect', function () { 157 | self._contextAcknowledged = false; 158 | self.emit('close'); 159 | }); 160 | 161 | this.socket.on('error', function (err) { 162 | self.emit(err); 163 | }); 164 | }; 165 | 166 | }.apply(null, (function() { 167 | 'use strict'; 168 | 169 | if (typeof module !== 'undefined') { 170 | // node.js and webpack 171 | return [module, function() {}]; 172 | } 173 | 174 | if (typeof window !== 'undefined') { 175 | // browser 176 | if (typeof window.jsonws === 'undefined') { 177 | throw new Error('No json-ws polyfills found.'); 178 | } 179 | 180 | var jsonws = window.jsonws; 181 | 182 | return [jsonws, jsonws.define]; 183 | } 184 | 185 | throw new Error('Unknown environment, this code should be used in node.js/webpack/browser'); 186 | }()))); 187 | -------------------------------------------------------------------------------- /test/unit/lib/error.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const chai = require('chai'); 4 | const _ = require('lodash'); 5 | const expect = chai.expect; 6 | 7 | const ServiceError = require('../../../lib/error'); 8 | const { AsServiceError } = require('../../../lib/error'); 9 | 10 | describe('Converter', function() { 11 | const REPORTER = 'service'; 12 | const ERROR_CODE = 432; 13 | const DEFAULT_ERROR_CODE = 100; 14 | const MESSAGE = 'something went wrong'; 15 | const CAUSE_REPORTER = 'reporter'; 16 | const CAUSE_CODE = 111; 17 | const CAUSE_MESSAGE = 'cause message'; 18 | 19 | it('returns ServiceError as is', function() { 20 | const error = new ServiceError(REPORTER, ERROR_CODE, { message: MESSAGE }); 21 | 22 | const actual = AsServiceError(error); 23 | expect(actual).to.be.an.instanceof(ServiceError); 24 | expect(actual.id).to.be.ok; 25 | expect(actual.timestamp).to.be.ok; 26 | 27 | expect(actual.reporter).to.equal(REPORTER); 28 | expect(actual.code).to.equal(ERROR_CODE); 29 | expect(actual.message).to.equal(MESSAGE); 30 | }); 31 | 32 | it('returns ServiceError with InternalServerError code', function() { 33 | const error = new ServiceError(REPORTER, undefined, {}); 34 | 35 | const actual = AsServiceError(error); 36 | expect(actual).to.be.an.instanceof(ServiceError); 37 | expect(actual.reporter).to.equal(REPORTER); 38 | expect(actual.code).to.equal(DEFAULT_ERROR_CODE); 39 | }); 40 | 41 | it('wraps ServiceError with different reporter', function() { 42 | const error = new ServiceError('different', CAUSE_CODE, {}); 43 | 44 | const actual = AsServiceError(error, REPORTER); 45 | expect(actual).to.be.an.instanceof(ServiceError); 46 | expect(actual.reporter).to.equal(REPORTER); 47 | expect(actual.code).to.equal(CAUSE_CODE); 48 | expect(actual.cause).to.equal(error); 49 | }); 50 | 51 | it('returns ServiceError with zero error code', function() { 52 | const actual = AsServiceError('something went wrong', REPORTER, 0); 53 | expect(actual).to.be.an.instanceof(ServiceError); 54 | expect(actual.reporter).to.equal(REPORTER); 55 | expect(actual.code).to.equal(0); 56 | }); 57 | 58 | it('wraps ServiceError with zero error code', function() { 59 | const error = new ServiceError(REPORTER, 0, {}); 60 | 61 | const actual = AsServiceError(error, REPORTER); 62 | expect(actual).to.be.an.instanceof(ServiceError); 63 | expect(actual.reporter).to.equal(REPORTER); 64 | expect(actual.code).to.equal(0); 65 | }); 66 | 67 | it('wraps Error', function() { 68 | const error = new Error(MESSAGE); 69 | 70 | const actual = AsServiceError(error, REPORTER); 71 | expect(actual).to.be.an.instanceof(ServiceError); 72 | expect(actual.id).to.be.ok; 73 | expect(actual.timestamp).to.be.ok; 74 | 75 | expect(actual.reporter).to.equal(REPORTER); 76 | expect(actual.code).to.equal(DEFAULT_ERROR_CODE); 77 | expect(actual.message).to.equal(MESSAGE); 78 | }); 79 | 80 | it('wraps Error with data property', function() { 81 | const error = new Error('some error message'); 82 | error.data = { 83 | reporter: CAUSE_REPORTER, 84 | code: CAUSE_CODE, 85 | message: CAUSE_MESSAGE, 86 | }; 87 | 88 | const actual = AsServiceError(error, REPORTER); 89 | expect(actual).to.be.an.instanceof(ServiceError); 90 | expect(actual.id).to.be.ok; 91 | expect(actual.timestamp).to.be.ok; 92 | 93 | expect(actual.reporter).to.equal(REPORTER); 94 | expect(actual.code).to.equal(CAUSE_CODE); 95 | expect(actual.message).to.equal(CAUSE_MESSAGE); 96 | 97 | expect(actual.cause).to.be.defined; 98 | expect(actual.cause.reporter).to.equal(CAUSE_REPORTER); 99 | expect(actual.cause.code).to.equal(CAUSE_CODE); 100 | expect(actual.cause.message).to.equal(CAUSE_MESSAGE); 101 | }); 102 | 103 | it('wraps JSON-RPC Error', function() { 104 | const error = { 105 | code: -32000, 106 | message: 'error message', 107 | data: { 108 | reporter: CAUSE_REPORTER, 109 | code: CAUSE_CODE, 110 | message: CAUSE_MESSAGE, 111 | }, 112 | }; 113 | 114 | const actual = AsServiceError(error, REPORTER); 115 | expect(actual).to.be.an.instanceof(ServiceError); 116 | expect(actual.id).to.be.ok; 117 | expect(actual.timestamp).to.be.ok; 118 | 119 | expect(actual.reporter).to.equal(REPORTER); 120 | expect(actual.code).to.equal(CAUSE_CODE); 121 | 122 | expect(actual.cause).to.be.defined; 123 | expect(actual.cause.reporter).to.equal(CAUSE_REPORTER); 124 | expect(actual.cause.code).to.equal(CAUSE_CODE); 125 | expect(actual.cause.message).to.equal(CAUSE_MESSAGE); 126 | }); 127 | 128 | it('wraps error message', function() { 129 | const error = 'String error message'; 130 | 131 | const actual = AsServiceError(error, REPORTER); 132 | expect(actual).to.be.an.instanceof(ServiceError); 133 | expect(actual.id).to.be.ok; 134 | expect(actual.timestamp).to.be.ok; 135 | expect(actual.reporter).to.equal(REPORTER); 136 | expect(actual.code).to.equal(DEFAULT_ERROR_CODE); 137 | expect(actual.message).to.equal(error); 138 | }); 139 | 140 | it('can be represented as logFields object', function() { 141 | const METADATA = { foo: 'bar' }; 142 | const CAUSE = { code: CAUSE_CODE }; 143 | 144 | const actual = new ServiceError(REPORTER, ERROR_CODE, { 145 | message: 'String error message', 146 | cause: CAUSE, 147 | metadata: METADATA, 148 | }); 149 | 150 | const logFields = actual.logFields(); 151 | 152 | expect(logFields).to.be.an('object'); 153 | expect(logFields).to.deep.eq( 154 | _.assign( 155 | _.pick(actual, ['id', 'reporter', 'code', 'message', 'cause', 'metadata', 'stack']), 156 | { 157 | errorTimestamp: actual.timestamp, 158 | } 159 | ) 160 | ); 161 | }); 162 | }); 163 | -------------------------------------------------------------------------------- /proxies/stubs/php/GeneratedTest.php: -------------------------------------------------------------------------------- 1 | -1, '-1' => -1, 20 | 'RtCpu' => 0, '0' => 0, 21 | 'RtGpuCuda' => 5, '5' => 5, 22 | ); 23 | 24 | static function RenderOptions($args) { 25 | return (object)$args; 26 | } 27 | 28 | static function DefaultArray($args) { 29 | return (object)$args; 30 | } 31 | 32 | 33 | /** 34 | * @param string $url The url of the web service 35 | */ 36 | public function __construct($url) { 37 | // RpcTunnel 38 | $this->rpc = new RpcTunnel($url); 39 | // The default transport is HTTP 40 | $this->useHTTP(); 41 | } 42 | 43 | public function ns1_sub1_sub2_method1() { 44 | return $this->rpc->call('ns1.sub1.sub2.method1', array()); 45 | } 46 | 47 | public function ns1_method1() { 48 | return $this->rpc->call('ns1.method1', array()); 49 | } 50 | 51 | public function ns2_sub1_sub2_method1() { 52 | return $this->rpc->call('ns2.sub1.sub2.method1', array()); 53 | } 54 | 55 | /** 56 | * Some test method example, does int sum 57 | * 58 | * @param integer $a 59 | * @param integer $b 60 | * 61 | * @return integer 62 | */ 63 | public function sum($a, $b) { 64 | return $this->rpc->call('sum', array($a, $b)); 65 | } 66 | 67 | public function sumReturn() { 68 | return $this->rpc->call('sumReturn', array()); 69 | } 70 | 71 | /** 72 | * @param RenderOptions $a 73 | * 74 | * @return RenderOptions 75 | */ 76 | public function echo_($a) { 77 | return $this->rpc->call('echo', array($a)); 78 | } 79 | 80 | /** 81 | * @param object $a 82 | * 83 | * @return object 84 | */ 85 | public function echoObject($a) { 86 | return $this->rpc->call('echoObject', array($a)); 87 | } 88 | 89 | public function throwError() { 90 | return $this->rpc->call('throwError', array()); 91 | } 92 | 93 | public function testMe() { 94 | return $this->rpc->call('testMe', array()); 95 | } 96 | 97 | public function testMe1() { 98 | return $this->rpc->call('testMe1', array()); 99 | } 100 | 101 | /** 102 | * A sample method. 103 | * 104 | * @param string $a : A simple string parameter. 105 | * 106 | * @return string 107 | */ 108 | public function testMe2($a) { 109 | return $this->rpc->call('testMe2', array($a)); 110 | } 111 | 112 | public function testMe3() { 113 | return $this->rpc->call('testMe3', array()); 114 | } 115 | 116 | public function testMe4() { 117 | return $this->rpc->call('testMe4', array()); 118 | } 119 | 120 | /** 121 | * @param DefaultArray $p 122 | */ 123 | public function TestDefaultArray($p) { 124 | return $this->rpc->call('TestDefaultArray', array($p)); 125 | } 126 | 127 | /** 128 | * @param string $u 129 | * 130 | * @return string 131 | */ 132 | public function TestUrl($u) { 133 | return $this->rpc->call('TestUrl', array($u)); 134 | } 135 | 136 | public function getRenderOptions() { 137 | return $this->rpc->call('getRenderOptions', array()); 138 | } 139 | 140 | /** 141 | * @param string $theString 142 | * 143 | * @return string 144 | */ 145 | public function echoStringAsBuffer($theString) { 146 | return $this->rpc->call('echoStringAsBuffer', array($theString)); 147 | } 148 | 149 | /** 150 | * @param string $buffer 151 | * 152 | * @return integer 153 | */ 154 | public function getBufferSize($buffer) { 155 | return $this->rpc->call('getBufferSize', array(base64_encode($buffer))); 156 | } 157 | 158 | /** 159 | * @param integer $n 160 | * 161 | * @return integer[] 162 | */ 163 | public function returnFrom0ToN($n) { 164 | return $this->rpc->call('returnFrom0ToN', array($n)); 165 | } 166 | 167 | /** 168 | * @param boolean $required 169 | * @param integer $p1 170 | * @param integer $p2 171 | */ 172 | public function optionalArgs($required, $p1=0, $p2=1) { 173 | return $this->rpc->call('optionalArgs', array($required, $p1, $p2)); 174 | } 175 | 176 | /** 177 | * @param integer[] $ints 178 | * 179 | * @return integer 180 | */ 181 | public function sumArray(array $ints) { 182 | return $this->rpc->call('sumArray', array($ints)); 183 | } 184 | 185 | /** 186 | * @param object $a 187 | * 188 | * @return object 189 | */ 190 | public function testAny($a) { 191 | return $this->rpc->call('testAny', array($a)); 192 | } 193 | 194 | /** 195 | * @param DateTime $timeParam 196 | * 197 | * @return integer 198 | */ 199 | public function getSeconds(DateTime $timeParam) { 200 | return $this->rpc->call('getSeconds', array(new JSONDate($timeParam))); 201 | } 202 | 203 | public function getNow() { 204 | return $this->rpc->call('getNow', array(), 'RpcTunnel::toDateTime'); 205 | } 206 | 207 | 208 | public function useHTTP() { 209 | $this->rpc->useHTTP(); 210 | } 211 | } 212 | ?> 213 | -------------------------------------------------------------------------------- /lib/transport/http-transport.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Implementation of an HTTP/REST transport for the JSON-WS module. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | const stream = require('stream'); 8 | 9 | const jsonrpc = require('./json-rpc'); 10 | const BaseTransport = require('./base-transport'); 11 | 12 | function param(req, name, defaultValue) { 13 | const params = req.params || {}; 14 | const body = req.body || {}; 15 | const query = req.query || {}; 16 | if (params[name] !== null && params[name] !== undefined && params.hasOwnProperty(name)) { 17 | return params[name]; 18 | } 19 | if (body[name] !== null && body[name] !== undefined) { 20 | return body[name]; 21 | } 22 | if (query[name] !== null && query[name] !== undefined) { 23 | return query[name]; 24 | } 25 | return defaultValue; 26 | } 27 | 28 | class HttpTransport extends BaseTransport { 29 | static get type() { 30 | return 'HTTP'; 31 | } 32 | 33 | constructor(registry) { 34 | super(registry); 35 | this._setupHandlers(); 36 | } 37 | 38 | // HTTP-specific sendMessage 39 | sendMessage(msg, context, format) { 40 | const res = context.http.response; 41 | res.set('Access-Control-Allow-Origin', '*'); 42 | let isSent = false; 43 | try { 44 | if (msg.error) { 45 | res.set('Content-Type', 'application/json'); 46 | res.status(500).send(JSON.stringify(msg)); 47 | isSent = true; 48 | } else if (msg.id !== undefined && msg.id !== null) { 49 | // For now, assume that no format means JSON 50 | // otherwise simply dump the message as-is into the response stream 51 | // This can be extended with custom formatters if required 52 | if (msg.result instanceof stream.Readable) { 53 | res.set('Content-Type', format || 'application/octet-stream'); 54 | msg.result.pipe(res); 55 | } else { 56 | const messageData = 57 | format || Buffer.isBuffer(msg.result) ? msg.result : JSON.stringify(msg); 58 | res.set( 59 | 'Content-Type', 60 | format || 61 | (Buffer.isBuffer(messageData) 62 | ? 'application/octet-stream' 63 | : 'application/json') 64 | ); 65 | res.send(messageData); 66 | } 67 | isSent = true; 68 | } 69 | } catch (err) { 70 | this.trace.error(context, null, err); 71 | if (msg.result instanceof stream.Readable) { 72 | msg.result.unpipe(res); 73 | msg.result.destroy(); 74 | } 75 | res.set('Content-Type', 'application/json'); 76 | res.status(500).end( 77 | JSON.stringify( 78 | jsonrpc.response( 79 | msg.id, 80 | jsonrpc.error(-32000, 'Sending response failure', err.message) 81 | ) 82 | ) 83 | ); 84 | isSent = true; 85 | } finally { 86 | if (!isSent) { 87 | res.end(); 88 | } 89 | } 90 | } 91 | 92 | // Override the attach method 93 | // Set up Express routes 94 | _setupHandlers() { 95 | const restHandler = (req, res, next) => { 96 | // Check if the URL matches a known service and if not, call next(): 97 | const servicePrefix = `/${req.params.serviceName}/${req.params.serviceVersion}`; 98 | const service = this.registry.getService(servicePrefix); 99 | 100 | if (!service) { 101 | next(); 102 | return; 103 | } 104 | 105 | const methodName = req.params.methodName || param(req, 'method') || null; 106 | const methodInfo = service.methodMap[methodName]; 107 | 108 | const json = jsonrpc.jsonrpc({ 109 | method: methodName, 110 | }); 111 | 112 | const id = param(req, 'id'); 113 | if (id !== undefined && id !== null) { 114 | json.id = id; 115 | } else if (!methodInfo || methodInfo.returns || methodInfo.async) { 116 | // auto-assign a message ID if the method has a declared return type (or is declared as async) 117 | // and no ID was given on input. Also, if the method was NOT found, assign an ID so 118 | // the error is always returned to the clients 119 | json.id = methodName; 120 | } 121 | 122 | const params = param(req, 'params'); 123 | if (params !== undefined) { 124 | json.params = params; 125 | if (typeof json.params === 'string') { 126 | try { 127 | json.params = JSON.parse(json.params); 128 | } catch (unnamedJsonParseErr) { 129 | //eslint-ignore-line no-empty-blocks 130 | } 131 | } 132 | } else if (methodInfo && methodInfo.params) { 133 | json.params = {}; 134 | for (let i = 0; i < methodInfo.params.length; i++) { 135 | const parName = methodInfo.params[i].name; 136 | const paramValue = param(req, parName); 137 | if (typeof paramValue !== 'undefined') { 138 | json.params[parName] = paramValue; 139 | if (typeof json.params[parName] === 'string') { 140 | try { 141 | json.params[parName] = JSON.parse(json.params[parName]); 142 | } catch (namedJsonParseErr) { 143 | //eslint-ignore-line no-empty-blocks 144 | } 145 | } 146 | } 147 | } 148 | if (Object.keys(json.params).length === 0) { 149 | delete json.params; 150 | } 151 | } 152 | 153 | const messageContext = { 154 | http: { 155 | request: req, 156 | response: res, 157 | }, 158 | data: null, 159 | params: req.originalParams, 160 | }; 161 | 162 | const continueImmediately = this.validateMessage( 163 | service, 164 | methodName, 165 | req.originalParams, 166 | (err, data) => { 167 | if (err) { 168 | this.trace.error(messageContext, methodInfo, err); 169 | res.set('Content-Type', 'application/json'); 170 | res.status(400).end( 171 | JSON.stringify( 172 | jsonrpc.response( 173 | json.id, 174 | jsonrpc.error(-32000, 'Bad Request', err.message) 175 | ) 176 | ) 177 | ); 178 | } else { 179 | messageContext.data = data; 180 | this.handleMessage(service, json, messageContext); 181 | } 182 | } 183 | ); 184 | 185 | if (continueImmediately) { 186 | this.handleMessage(service, json, messageContext); 187 | } 188 | }; 189 | this.registry.router.post('/:serviceName/:serviceVersion', restHandler); 190 | this.registry.router.all('/:serviceName/:serviceVersion/:methodName', restHandler); 191 | } 192 | } 193 | 194 | module.exports = HttpTransport; 195 | -------------------------------------------------------------------------------- /proxies/stubs/python/test.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from GeneratedTest import GeneratedTest 3 | from rpctunnel import RpcTunnel 4 | 5 | try: 6 | import asyncio 7 | except ImportError: 8 | import trollius as asyncio 9 | 10 | print('Running tests...') 11 | print('') 12 | 13 | RenderOptions = GeneratedTest.RenderOptions 14 | RenderMode = GeneratedTest.RenderMode 15 | DefaultArray = GeneratedTest.DefaultArray 16 | 17 | # Test the generated enums: 18 | assert RenderMode.Production == -1 19 | assert RenderMode.RtCpu == 0 20 | assert RenderMode.RtGpuCuda == 5 21 | assert RenderMode['Production'] == -1 22 | assert RenderMode['RtCpu'] == 0 23 | assert RenderMode['RtGpuCuda'] == 5 24 | print('RenderMode OK') 25 | 26 | # Test the generated types: 27 | dictionary = { 28 | 'width': 320, 29 | 'height': 240, 30 | 'renderMode': RenderMode.Production 31 | } 32 | 33 | renderOptions = RenderOptions(width=320, 34 | height=240, 35 | renderMode=RenderMode.Production) 36 | assert renderOptions['renderMode'] == RenderMode.Production 37 | assert renderOptions['width'] == 320 38 | assert renderOptions['height'] == 240 39 | assert renderOptions == RenderOptions(**dictionary) 40 | 41 | print('RenderOptions OK') 42 | 43 | print('') 44 | 45 | with GeneratedTest('http://localhost:3000/endpoint/1.0') as proxy: 46 | loop = asyncio.get_event_loop() 47 | 48 | def result(future): 49 | return loop.run_until_complete(future) 50 | 51 | print('HTTP Transport:') 52 | 53 | renderOptions = RenderOptions(renderMode=RenderMode.RtGpuCuda, width=320, height=240) 54 | echoResult = result(proxy.echo(renderOptions)) 55 | assert echoResult == renderOptions and isinstance(echoResult, RenderOptions) 56 | print('proxy.echo() OK') 57 | 58 | assert result(proxy.sum(2, 3)) == 5 59 | print('proxy.sum() OK') 60 | 61 | assert result(proxy.ns1.method1()) == 'test1' 62 | print('proxy.ns1.method1() OK') 63 | 64 | assert result(proxy.TestDefaultArray(DefaultArray(property=[1, 2, 3]))) is None 65 | print('proxy.TestDefaultArray() OK') 66 | 67 | renderOptionsList = result(proxy.getRenderOptions()) 68 | assert len(renderOptionsList) == 3 and all([isinstance(item, RenderOptions) for item in renderOptionsList]) 69 | print('proxy.getRenderOptions() OK') 70 | 71 | method1Raises = False 72 | try: 73 | result(proxy.ns1.sub1.sub2.method1()) 74 | except RpcTunnel.ExecutionException as e: 75 | method1Raises = True 76 | assert method1Raises 77 | print('proxy.ns1.sub1.sub2.method1() OK') 78 | 79 | optionalArgsRaises = False 80 | try: 81 | result(proxy.optionalArgs(True, 1, 2)) 82 | result(proxy.optionalArgs(True, 1)) 83 | result(proxy.optionalArgs(True)) 84 | except RpcTunnel.ExecutionException as e: 85 | optionalArgsRaises = True 86 | assert not optionalArgsRaises 87 | print('proxy.optionalArgs() OK') 88 | 89 | assert result(proxy.echoStringAsBuffer('Hello')) == bytearray('Hello', 'utf-8') 90 | print('proxy.echoStringAsBuffer() OK') 91 | 92 | assert result(proxy.getBufferSize(bytearray('abcd', 'utf-8'))) == 4 93 | print('proxy.getBufferSize() OK') 94 | 95 | assert result(proxy.getSeconds(datetime(2014, 5, 8, 10, 11, 12))) == 12 96 | print('proxy.getSeconds() OK') 97 | 98 | 99 | print('') 100 | print('WebSockets Transport:') 101 | proxy.useWS() 102 | 103 | assert result(proxy.testMe2('complete')) == 'test2complete' 104 | print('proxy.testMe2() OK') 105 | 106 | assert result(proxy.sumArray([1, 2, 3])) == 6 107 | print('proxy.sumArray() OK') 108 | 109 | assert result(proxy.returnFrom0ToN(3)) == [0, 1, 2] 110 | print('proxy.returnFrom0ToN() OK') 111 | 112 | assert result(proxy.TestDefaultArray(DefaultArray())) is None 113 | print('proxy.TestDefaultArray() OK') 114 | 115 | method1Raises = False 116 | try: 117 | result(proxy.ns2.sub1.sub2.method1()) 118 | except RpcTunnel.ExecutionException as e: 119 | method1Raises = True 120 | assert method1Raises 121 | print('proxy.ns2.sub1.sub2.method1() OK') 122 | 123 | assert result(proxy.echoStringAsBuffer('Hello')) == bytearray('Hello', 'utf-8') 124 | print('proxy.echoStringAsBuffer() OK') 125 | 126 | assert result(proxy.getBufferSize(bytearray('abcd', 'utf-8'))) == 4 127 | print('proxy.getBufferSize() OK') 128 | 129 | assert isinstance(result(proxy.getNow()), datetime) 130 | print('proxy.getNow() OK') 131 | 132 | print('') 133 | print('Events:') 134 | 135 | @proxy.onTestEvent 136 | def testEventHandler(number): 137 | assert isinstance(number, int) 138 | print('testEvent OK') 139 | proxy.onTestEvent() # unsubscribe after the first result 140 | loop.run_until_complete(asyncio.sleep(1)) 141 | 142 | @proxy.onTestEvent2 143 | def testEvent2Handler(list_of_options): 144 | assert len(list_of_options) == 1 145 | assert isinstance(list_of_options[0], RenderOptions) 146 | assert list_of_options[0]['width'] == 1 147 | print('testEvent2 OK') 148 | proxy.onTestEvent2() # unsubscribe after the first result 149 | loop.run_until_complete(asyncio.sleep(1)) 150 | 151 | @proxy.onTestEvent3 152 | def testEvent3Handler(result): 153 | assert isinstance(result, dict) and result['a'] == 1 154 | print('testEvent3 OK') 155 | proxy.onTestEvent3() # unsubscribe after the first result 156 | loop.run_until_complete(asyncio.sleep(1)) 157 | 158 | @proxy.onTestBinaryEvent 159 | def testBinaryEventHandler(result): 160 | assert isinstance(result, bytearray) 161 | print('testBinaryEvent OK') 162 | proxy.onTestBinaryEvent() # unsubscribe after the first result 163 | loop.run_until_complete(asyncio.sleep(1)) 164 | 165 | @proxy.onNs1_testEvent1 166 | def ns1testEvent1Handler(result): 167 | assert result is None 168 | print('ns1.testEvent1 OK') 169 | proxy.onNs1_testEvent1() # unsubscribe after the first result 170 | loop.run_until_complete(asyncio.sleep(1)) 171 | 172 | loop.stop() 173 | print('\nAll tests pass') 174 | -------------------------------------------------------------------------------- /lib/transport/ws-transport.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Implementation of a WebSocket transport for the JSON-WS module 3 | */ 4 | 5 | 'use strict'; 6 | 7 | const stream = require('stream'); 8 | const WebSocket = require('ws'); 9 | const jsonrpc = require('./json-rpc'); 10 | const BaseTransport = require('./base-transport'); 11 | const pathToRegExp = require('path-to-regexp'); 12 | 13 | const WebSocketServer = WebSocket.Server; 14 | 15 | function abortConnection(socket, code, name) { 16 | // From here: https://github.com/websockets/ws/blob/9dc7e6b4651711d47bd87bbeddb6446e5aa429e1/lib/WebSocketServer.js#L503 17 | try { 18 | socket.write(`HTTP/1.1 ${code} ${name}\r\nContent-type: text/html\r\n\r\n`); 19 | } catch (e) { 20 | // ignore errors - we've aborted this connection 21 | } finally { 22 | // ensure that an early aborted connection is shut down completely 23 | try { 24 | socket.destroy(); 25 | } catch (e) {} //eslint-disable-line no-empty 26 | } 27 | } 28 | 29 | class WebSocketTransport extends BaseTransport { 30 | static get type() { 31 | return 'WebSocket'; 32 | } 33 | 34 | constructor(registry) { 35 | super(registry); 36 | this.httpServer = registry.httpServer; 37 | this.wsServer = null; 38 | this.connectionContexts = new WeakMap(); 39 | this.nextConnectionId = 0; 40 | 41 | this.registry.httpServer.on('connection', socket => { 42 | this.onConnect(socket); 43 | socket.on('close', () => { 44 | this.onDisconnect(socket); 45 | }); 46 | }); 47 | 48 | this._setupHandlers(); 49 | } 50 | 51 | sendMessage(msg, context /*, format*/) { 52 | if (!(context && context.ws && context.ws.readyState == WebSocket.OPEN)) { 53 | return; 54 | } 55 | 56 | if (msg.id !== undefined) { 57 | try { 58 | if (msg.result && Buffer.isBuffer(msg.result)) { 59 | msg.result = msg.result.toString('base64'); 60 | } 61 | 62 | if (msg.result && msg.result instanceof stream.Readable) { 63 | context.ws.send( 64 | JSON.stringify( 65 | jsonrpc.response( 66 | msg.id, 67 | jsonrpc.error( 68 | -32000, 69 | 'WebSocket', 70 | 'Streaming over WebSockets is not supported' 71 | ) 72 | ) 73 | ) 74 | ); 75 | msg.result.destroy(); 76 | } 77 | 78 | context.ws.send(JSON.stringify(msg)); 79 | } catch (e) { 80 | console.log(msg); //eslint-disable-line no-console 81 | console.log(e); //eslint-disable-line no-console 82 | } 83 | } 84 | } 85 | 86 | _setupHandlers() { 87 | this.httpServer.on('upgrade', (req, socket) => { 88 | const serviceAndParams = this._getServiceAndParams(req.url); 89 | 90 | if (!serviceAndParams) { 91 | abortConnection(socket, 400, 'Bad Request'); 92 | return; 93 | } 94 | 95 | this.connectionContexts.set(req, { 96 | data: null, 97 | urlParams: serviceAndParams.params, 98 | service: serviceAndParams.service, 99 | }); 100 | }); 101 | 102 | this.wsServer = new WebSocketServer({ 103 | server: this.httpServer, 104 | perMessageDeflate: false, // turn off message compression by default 105 | verifyClient: (info, callback) => { 106 | const connectionContext = this.connectionContexts.get(info.req); 107 | if (!connectionContext) { 108 | callback(false, 400, 'Bad Request'); 109 | return; 110 | } 111 | 112 | const continueImmediately = this.validateMessage( 113 | connectionContext.service, 114 | null, 115 | connectionContext.urlParams, 116 | (err, data) => { 117 | if (err) { 118 | callback(false, 400, 'Bad Request'); 119 | } else { 120 | connectionContext.data = data; 121 | callback(true); 122 | } 123 | } 124 | ); 125 | 126 | if (continueImmediately) { 127 | callback(true); 128 | } 129 | }, 130 | }); 131 | this.wsServer.on('connection', ws => { 132 | this.attachEvents(ws); 133 | }); 134 | } 135 | 136 | attachEvents(ws) { 137 | const connectionContext = this.connectionContexts.get(ws.upgradeReq); 138 | 139 | const connectionCtx = Object.create( 140 | {}, 141 | { 142 | ws: { 143 | get: () => ws, 144 | }, 145 | objectId: { value: `!#ConnectionCtx:${this.nextConnectionId++}` }, 146 | toString: { 147 | value: () => this.objectId, 148 | }, 149 | } 150 | ); 151 | connectionCtx.data = connectionContext.data; 152 | connectionCtx.params = connectionContext.urlParams; 153 | 154 | this.onConnect(connectionCtx); 155 | 156 | ws.on('message', message => { 157 | try { 158 | message = typeof message === 'string' ? JSON.parse(message) : message; 159 | } catch (ex) { 160 | // Parse error 161 | this.trace.error(connectionCtx, null, ex); 162 | this.sendMessage( 163 | jsonrpc.response(null, jsonrpc.error(-32700, 'Parse error', ex.toString())), 164 | connectionCtx 165 | ); 166 | return; 167 | } 168 | const continueImmediately = this.validateMessage( 169 | connectionContext.service, 170 | message.method, 171 | connectionContext.urlParams, 172 | (err, data) => { 173 | if (err) { 174 | ws.send( 175 | jsonrpc.response( 176 | message.id, 177 | jsonrpc.error(-32000, 'Bad Request', err.message) 178 | ) 179 | ); 180 | } else { 181 | connectionCtx.data = data; // Update with the latest data from the validator. 182 | this.handleMessage(connectionContext.service, message, connectionCtx); 183 | } 184 | } 185 | ); 186 | 187 | if (continueImmediately) { 188 | this.handleMessage(connectionContext.service, message, connectionCtx); 189 | } 190 | }); 191 | 192 | ws.on('close', () => { 193 | this.onDisconnect(connectionCtx); 194 | }); 195 | 196 | ws.send('{}'); 197 | } 198 | 199 | /** 200 | * @param url 201 | * @returns {*} 202 | * @private 203 | */ 204 | _getServiceAndParams(url) { 205 | for (const route of this.registry.routes) { 206 | const routeParser = pathToRegExp(`${route}/:_service/:_version`); 207 | const execResult = routeParser.exec(url); 208 | 209 | if (!execResult) { 210 | continue; 211 | } 212 | 213 | const result = routeParser.keys.reduce(function(current, paramKey, paramIndex) { 214 | current[paramKey.name] = execResult[paramIndex + 1]; 215 | 216 | return current; 217 | }, {}); 218 | 219 | const service = this.registry.getService(`/${result._service}/${result._version}`); 220 | if (service) { 221 | delete result._service; 222 | delete result._version; 223 | return { 224 | params: result, 225 | service, 226 | }; 227 | } 228 | } 229 | 230 | return null; 231 | } 232 | } 233 | 234 | module.exports = WebSocketTransport; 235 | -------------------------------------------------------------------------------- /lib/client/transports/http.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | (function(module, define) { 3 | // using non-strict mode, otherwise the re-assignment of require would throw TypeError 4 | if (typeof require !== 'function') { 5 | require = module.require; 6 | } 7 | 8 | // Polyfill request with null in the browser, 9 | // to use XMLHttpRequest in the "send" method below: 10 | define('request', null); 11 | 12 | var EventEmitter = require('events'); 13 | var inherits = require('util').inherits; 14 | var request = require('request'); 15 | 16 | // http://www.onicos.com/staff/iz/amuse/javascript/expert/utf.txt 17 | /* utf.js - UTF-8 <=> UTF-16 convertion 18 | * 19 | * Copyright (C) 1999 Masanao Izumo 20 | * Version: 1.0 21 | * LastModified: Dec 25 1999 22 | * This library is free. You can redistribute it and/or modify it. 23 | */ 24 | function utf8ArrayToStr(array) { 25 | var out, i, len, c; 26 | var char2, char3; 27 | 28 | out = ''; 29 | len = array.length; 30 | i = 0; 31 | while (i < len) { 32 | c = array[i++]; 33 | switch(c >> 4) 34 | { 35 | case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: 36 | // 0xxxxxxx 37 | out += String.fromCharCode(c); 38 | break; 39 | case 12: case 13: 40 | // 110x xxxx 10xx xxxx 41 | char2 = array[i++]; 42 | out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F)); 43 | break; 44 | case 14: 45 | // 1110 xxxx 10xx xxxx 10xx xxxx 46 | char2 = array[i++]; 47 | char3 = array[i++]; 48 | out += String.fromCharCode(((c & 0x0F) << 12) | 49 | ((char2 & 0x3F) << 6) | 50 | ((char3 & 0x3F) << 0)); 51 | break; 52 | } 53 | } 54 | 55 | return out; 56 | } 57 | 58 | module.exports = HttpTransport; 59 | // Defined with this name to keep node.js compatibility: 60 | define('./transports/http', HttpTransport); 61 | 62 | function HttpTransport(url, settings) { 63 | this.url = url; 64 | this.settings = settings || {}; 65 | } 66 | inherits(HttpTransport, EventEmitter); 67 | 68 | Object.defineProperty(HttpTransport.prototype, 'name', { 69 | value: 'http' 70 | }); 71 | 72 | HttpTransport.prototype.close = function() {}; 73 | 74 | HttpTransport.prototype.send = function (message, callback) { 75 | // checking for XMLHttpRequest first, it's the default for the browser 76 | // On older Safari browser the type of XMLHttpRequest is "object"... 77 | if (typeof XMLHttpRequest === 'function' || typeof XMLHttpRequest === 'object') { 78 | var xhr = new XMLHttpRequest(); 79 | xhr.open('POST', this.url, true); 80 | if (this.settings.xhrFields) { 81 | for (var s in this.settings.xhrFields) { 82 | if (this.settings.xhrFields.hasOwnProperty(s)) { 83 | xhr[s] = this.settings.xhrFields[s]; 84 | } 85 | } 86 | } 87 | xhr.responseType = 'arraybuffer'; 88 | xhr.setRequestHeader('Content-Type', 'application/json; charset=UTF-8'); 89 | xhr.onreadystatechange = function () { 90 | if (xhr.readyState != 4) { 91 | return; 92 | } 93 | //request failed handling 94 | if (xhr.status === 0) { 95 | callback(new Error('Request failed')); 96 | return; 97 | } 98 | if (xhr.response && xhr.response.byteLength > 0) { 99 | var contentType = xhr.getResponseHeader('content-type'); 100 | if (contentType && contentType.indexOf('application/json') !== -1) { 101 | var data = JSON.parse(utf8ArrayToStr(new Uint8Array(xhr.response))); 102 | if (data && data.result !== undefined) { 103 | callback(null, data.result); 104 | } else { 105 | var error = data.error; 106 | var errorMessage = error.data && error.data.message; 107 | var errorInstance = new Error(errorMessage || error.message); 108 | errorInstance.data = error.data; 109 | errorInstance.code = error.code; 110 | callback(errorInstance); 111 | } 112 | } else { 113 | callback(null, xhr.response); 114 | } 115 | } else { 116 | callback(xhr.statusText); 117 | } 118 | }; 119 | xhr.send(JSON.stringify(message)); 120 | } else if (typeof request === 'function') { 121 | var requestSettings = { 122 | body: JSON.stringify(message), 123 | encoding: null, // always get the body as a Buffer 124 | headers: { 'Content-Type': 'application/json' }, 125 | url: this.url 126 | }; 127 | for (var s in this.settings) { 128 | if (this.settings.hasOwnProperty(s)) { 129 | requestSettings[s] = this.settings[s]; 130 | } 131 | } 132 | request.post(requestSettings, function (error, response, body) { 133 | if (typeof callback === 'function') { 134 | var contentType = response ? response.headers['content-type'] : null; 135 | var isJSON = contentType && contentType.indexOf('application/json') !== -1; 136 | if (error || (response && response.statusCode !== 200 && !isJSON)) { 137 | callback(error || new Error(requestSettings.url + ' responded with ' + response.statusCode), null); 138 | } else if (body) { 139 | if (!isJSON) { 140 | // send buffer response 141 | callback(null, body); 142 | return; 143 | } 144 | var jsonBody = null; 145 | try { 146 | jsonBody = JSON.parse(body); 147 | } catch (jsonParseError) { 148 | callback(jsonParseError); 149 | return; 150 | } 151 | if (jsonBody.result !== undefined) { 152 | callback(null, jsonBody.result); 153 | } else if (jsonBody.error) { 154 | var jsonError = jsonBody.error; 155 | var errorMessage = jsonError.data && jsonError.data.message; 156 | var errorInstance = new Error(errorMessage || jsonError.message); 157 | errorInstance.data = jsonError.data; 158 | errorInstance.code = jsonError.code; 159 | callback(errorInstance); 160 | } else { 161 | callback(new Error('Empty response.')); 162 | } 163 | } else { 164 | callback(new Error('Empty response.')); 165 | } 166 | } 167 | }); 168 | } else { 169 | throw new Error('json-ws client transport http needs a way to make requests - "XMLHttpRequest" or "request".'); 170 | } 171 | }; 172 | }.apply(null, (function() { 173 | 'use strict'; 174 | 175 | if (typeof module !== 'undefined') { 176 | // node.js and webpack 177 | return [module, function() {}]; 178 | } 179 | 180 | if (typeof window !== 'undefined') { 181 | // browser 182 | if (typeof window.jsonws === 'undefined') { 183 | throw new Error('No json-ws polyfills found.'); 184 | } 185 | 186 | var jsonws = window.jsonws; 187 | 188 | return [jsonws, jsonws.define]; 189 | } 190 | 191 | throw new Error('Unknown environment, this code should be used in node.js/webpack/browser'); 192 | }()))); 193 | -------------------------------------------------------------------------------- /proxies/Php.ejs: -------------------------------------------------------------------------------- 1 | <% 2 | var metadata = locals.metadata; 3 | var localName = locals.localName; 4 | 5 | function getIndentation(indentationLevel) { 6 | return Array(indentationLevel * 4 + 1).join(' '); 7 | } 8 | 9 | function mapPhpType(param) { 10 | var returnType = ''; 11 | var jsType = param.type; 12 | 13 | switch (jsType) { 14 | case '*': 15 | case 'any': 16 | returnType = 'object'; 17 | break; 18 | 19 | case 'int': 20 | case 'integer': 21 | returnType = 'integer'; 22 | break; 23 | 24 | case 'date': 25 | case 'time': 26 | returnType = 'DateTime'; 27 | break; 28 | 29 | case 'number': 30 | case 'float': 31 | case 'double': 32 | returnType = 'double'; 33 | break; 34 | 35 | case 'bool': 36 | case 'boolean': 37 | returnType = 'boolean'; 38 | break; 39 | 40 | case 'object': 41 | case 'json': 42 | returnType = 'object'; 43 | break; 44 | 45 | case 'string': 46 | returnType = 'string'; 47 | break; 48 | 49 | case 'url': 50 | returnType = 'string'; 51 | break; 52 | 53 | case 'buffer': 54 | case 'binary': 55 | case 'stream': 56 | returnType = 'string'; 57 | break; 58 | 59 | case undefined: 60 | returnType = 'NULL'; 61 | break; 62 | 63 | default: 64 | if (metadata.types[jsType]) { 65 | returnType = jsType; 66 | } else { 67 | returnType = 'object'; 68 | } 69 | } 70 | 71 | if (param.isArray) { 72 | returnType = returnType + '[]'; 73 | } 74 | 75 | return returnType; 76 | } 77 | 78 | 79 | // Generate namespace tree 80 | var namespaceRoot = { 81 | name: '', 82 | fullName: '', 83 | methods: [], 84 | children: {} 85 | }; 86 | 87 | Object.keys(metadata.methods).forEach(function(methodName) { 88 | var method = metadata.methods[methodName]; 89 | var $ = namespaceRoot; 90 | var prefixes = method.name.split('.'); 91 | prefixes.pop(); 92 | prefixes.forEach(function(prefix) { 93 | if (!$.children.hasOwnProperty(prefix)) { 94 | $.children[prefix] = { 95 | prefix: prefix, 96 | methods: [], 97 | children: {} 98 | }; 99 | } 100 | $ = $.children[prefix]; 101 | }); 102 | $.methods.push(method.name); 103 | }); 104 | 105 | function generateTypes() { 106 | var types = Object.keys(metadata.types).map(function(key) { return metadata.types[key] }); 107 | 108 | types.forEach(function(type) { 109 | if (type.enum) { 110 | %> 111 | <%=getIndentation(1)%>static $<%- type.name %> = array( 112 | <% 113 | Object.keys(type.struct).forEach(function(key) { 114 | %><%=getIndentation(2)%>'<%- key %>' => <%= type.struct[key] %>, '<%- type.struct[key] %>' => <%= type.struct[key] %>, 115 | <% 116 | }) 117 | %><%=getIndentation(1)%>); 118 | <% 119 | } else { 120 | %> 121 | <%=getIndentation(1)%>static function <%- type.name %>($args) { 122 | <%=getIndentation(2)%>return (object)$args; 123 | <%=getIndentation(1)%>} 124 | <% 125 | } 126 | }); 127 | } 128 | 129 | function getMethodArguments(params, asArguments) { 130 | var args = params.map(function(param) { 131 | var paramType = mapPhpType(param); 132 | var result = ''; 133 | 134 | if (!asArguments) { 135 | if (paramType == 'DateTime') { 136 | result += 'DateTime '; 137 | } else if (param.isArray) { 138 | result += 'array '; 139 | } 140 | } 141 | 142 | result += '$' + param.name; 143 | 144 | if (asArguments) { 145 | switch (param.type) { 146 | case 'date': 147 | case 'time': 148 | result = 'new JSONDate(' + result + ')'; 149 | break; 150 | case 'buffer': 151 | case 'binary': 152 | case 'stream': 153 | result = 'base64_encode(' + result + ')'; 154 | break; 155 | default: 156 | break; 157 | } 158 | } 159 | 160 | if (!asArguments && !param.required && typeof param.default != 'undefined') { 161 | result += '=' + param.default; 162 | } 163 | 164 | return result; 165 | }) 166 | 167 | return args.join(', '); 168 | } 169 | 170 | function generateMethodDocString(methodInfo) { 171 | var description = methodInfo.description; 172 | var params = methodInfo.params; 173 | 174 | if (!description && params.length === 0) { 175 | return; 176 | } 177 | %> 178 | <%= getIndentation(1) %>/**<% 179 | if (description) {%> 180 | <%= getIndentation(1) %> * <%= description %> 181 | <%= getIndentation(1) %> *<% 182 | } 183 | 184 | if (params) { 185 | params.forEach(function(param) { %> 186 | <%= getIndentation(1) %> * @param <%= mapPhpType(param) %> $<%= param.name %> <%= param.description ? ': ' + param.description : '' %><% 187 | }); 188 | } 189 | 190 | if (methodInfo.returns) { %> 191 | <%= getIndentation(1) %> * 192 | <%= getIndentation(1) %> * @return <%= mapPhpType({type: methodInfo.returns, isArray: methodInfo.returnsArray}) %><% 193 | } 194 | %> 195 | <%= getIndentation(1) %> */<% 196 | } 197 | 198 | function generateMethodStubs(root, indentationLevel) { 199 | if (!root) { return; } 200 | 201 | indentationLevel || (indentationLevel = 0); 202 | 203 | var methods = root.methods; 204 | var children = root.children; 205 | 206 | // Generate code for sub-namespaces 207 | children && Object.keys(children).forEach(function(key) { 208 | generateMethodStubs(children[key], indentationLevel + 1); 209 | }); 210 | 211 | // Generate method stubs 212 | methods && methods.forEach(function (method) { 213 | var functionName = method.replace(/\./g, '_'); 214 | var methodInfo = metadata.methods[method]; 215 | 216 | var returnTypeConverter = ''; 217 | 218 | if (methodInfo.returns) { 219 | switch (methodInfo.returns) { 220 | case 'date': 221 | case 'time': 222 | returnTypeConverter = ", 'RpcTunnel::toDateTime'"; 223 | break; 224 | default: 225 | break; 226 | } 227 | } 228 | 229 | %><% generateMethodDocString(methodInfo, 1, '') %> 230 | <%=getIndentation(1)%>public function <%- functionName %>(<%-getMethodArguments(methodInfo.params)%>) {<% 231 | %> 232 | <%=getIndentation(2)%>return $this->rpc->call('<%-method%>', array(<%-getMethodArguments(methodInfo.params, true)%>)<%- returnTypeConverter %>); 233 | <%=getIndentation(1)%>} 234 | <% 235 | }); 236 | } 237 | %> <%= metadata.version %> 240 | * 241 | * Part of the JSON-WS library - PHP Proxy 242 | * Copyright (c) 2014 ChaosGroup. All rights reserved. 243 | * 244 | * WebSockets transport and therefore events are not supported. 245 | */ 246 | 247 | require('./rpctunnel.php'); 248 | 249 | class <%= localName %> { 250 | <%=getIndentation(1)%>/** 251 | <%=getIndentation(1)%> * Proxy for json-ws web services. 252 | <%=getIndentation(1)%> */ 253 | <% generateTypes() %> 254 | 255 | <%=getIndentation(1)%>/** 256 | <%=getIndentation(1)%> * @param string $url The url of the web service 257 | <%=getIndentation(1)%> */ 258 | <%=getIndentation(1)%>public function __construct($url) { 259 | <%=getIndentation(2)%>// RpcTunnel 260 | <%=getIndentation(2)%>$this->rpc = new RpcTunnel($url); 261 | <%=getIndentation(2)%>// The default transport is HTTP 262 | <%=getIndentation(2)%>$this->useHTTP(); 263 | <%=getIndentation(1)%>} 264 | <% generateMethodStubs(namespaceRoot) %> 265 | 266 | <%=getIndentation(1)%>public function useHTTP() { 267 | <%=getIndentation(2)%>$this->rpc->useHTTP(); 268 | <%=getIndentation(1)%>} 269 | } 270 | ?> 271 | -------------------------------------------------------------------------------- /proxies/stubs/python/GeneratedTest.py: -------------------------------------------------------------------------------- 1 | # 2 | # Test API 1.0 3 | # 4 | # Part of the JSON-WS library - Python Proxy 5 | # Copyright (c) 2014 ChaosGroup. All rights reserved. 6 | # 7 | # This code uses the following libraries: 8 | # - autobahn.asyncio.websocket (https://pypi.python.org/pypi/autobahn/0.9.3) 9 | # 10 | # For asyncio and enum support in Python <= 3.3 install trollius and enum34: 11 | # - https://pypi.python.org/pypi/trollius/1.0.2 12 | # - https://pypi.python.org/pypi/enum34/1.0.3 13 | 14 | 15 | from datetime import datetime 16 | from rpctunnel import RpcTunnel, Optional, Type, Enum 17 | 18 | 19 | class GeneratedTest: 20 | ''' 21 | Proxy for json-ws web services. Instances can be used as context managers. 22 | ''' 23 | 24 | RenderMode = Enum('RenderMode', { 25 | 'Production' : -1, 26 | 'RtCpu' : 0, 27 | 'RtGpuCuda' : 5, 28 | }) 29 | 30 | RenderOptions = Type('RenderOptions', { 31 | 'width' : int, 32 | 'height' : int, 33 | 'renderMode' : 'RenderMode', 34 | }, lambda: GeneratedTest) 35 | 36 | DefaultArray = Type('DefaultArray', { 37 | 'property' : [str], 38 | }, lambda: GeneratedTest) 39 | 40 | 41 | class ns1: 42 | class sub1: 43 | class sub2: 44 | def __init__(self, root): 45 | self.root = root 46 | 47 | def method1(self): 48 | return self.root._rpc('ns1.sub1.sub2.method1', [], return_type=None) 49 | 50 | def __init__(self, root): 51 | self.root = root 52 | self.sub2 = self.sub2(root) 53 | 54 | def __init__(self, root): 55 | self.root = root 56 | self.sub1 = self.sub1(root) 57 | 58 | def method1(self): 59 | return self.root._rpc('ns1.method1', [], return_type=str) 60 | 61 | class ns2: 62 | class sub1: 63 | class sub2: 64 | def __init__(self, root): 65 | self.root = root 66 | 67 | def method1(self): 68 | return self.root._rpc('ns2.sub1.sub2.method1', [], return_type=None) 69 | 70 | def __init__(self, root): 71 | self.root = root 72 | self.sub2 = self.sub2(root) 73 | 74 | def __init__(self, root): 75 | self.root = root 76 | self.sub1 = self.sub1(root) 77 | 78 | def __init__(self, url): 79 | ''' 80 | Args: 81 | url (string): The url of the web service 82 | ''' 83 | 84 | # RpcTunnel 85 | self._rpc = RpcTunnel(url) 86 | # The default transport is HTTP 87 | self.useHTTP() 88 | self.ns1 = self.ns1(self) 89 | self.ns2 = self.ns2(self) 90 | 91 | def sum(self, a, b): 92 | ''' 93 | Some test method example, does int sum 94 | 95 | Args: 96 | a (int) 97 | b (int) 98 | Returns: 99 | int 100 | ''' 101 | 102 | return self._rpc('sum', [a, b], return_type=int) 103 | 104 | def sumReturn(self): 105 | return self._rpc('sumReturn', [], return_type=None) 106 | 107 | def echo(self, a): 108 | ''' 109 | Args: 110 | a (self.RenderOptions) 111 | Returns: 112 | self.RenderOptions 113 | ''' 114 | 115 | return self._rpc('echo', [a], return_type=self.RenderOptions) 116 | 117 | def echoObject(self, a): 118 | ''' 119 | Args: 120 | a (object) 121 | Returns: 122 | dict 123 | ''' 124 | 125 | return self._rpc('echoObject', [a], return_type=dict) 126 | 127 | def throwError(self): 128 | return self._rpc('throwError', [], return_type=int) 129 | 130 | def testMe(self): 131 | return self._rpc('testMe', [], return_type=None) 132 | 133 | def testMe1(self): 134 | return self._rpc('testMe1', [], return_type=None) 135 | 136 | def testMe2(self, a): 137 | ''' 138 | A sample method. 139 | 140 | Args: 141 | a (str): A simple string parameter. 142 | Returns: 143 | str 144 | ''' 145 | 146 | return self._rpc('testMe2', [a], return_type=str) 147 | 148 | def testMe3(self): 149 | return self._rpc('testMe3', [], return_type=None) 150 | 151 | def testMe4(self): 152 | return self._rpc('testMe4', [], return_type=None) 153 | 154 | def TestDefaultArray(self, p): 155 | ''' 156 | Args: 157 | p (self.DefaultArray) 158 | ''' 159 | 160 | return self._rpc('TestDefaultArray', [p], return_type=None) 161 | 162 | def TestUrl(self, u): 163 | ''' 164 | Args: 165 | u (str) 166 | Returns: 167 | str 168 | ''' 169 | 170 | return self._rpc('TestUrl', [u], return_type=str) 171 | 172 | def getRenderOptions(self): 173 | return self._rpc('getRenderOptions', [], return_type=[self.RenderOptions]) 174 | 175 | def echoStringAsBuffer(self, theString): 176 | ''' 177 | Args: 178 | theString (str) 179 | Returns: 180 | bytearray 181 | ''' 182 | 183 | return self._rpc('echoStringAsBuffer', [theString], return_type=bytearray) 184 | 185 | def getBufferSize(self, buffer): 186 | ''' 187 | Args: 188 | buffer (bytearray) 189 | Returns: 190 | int 191 | ''' 192 | 193 | return self._rpc('getBufferSize', [buffer], return_type=int) 194 | 195 | def returnFrom0ToN(self, n): 196 | ''' 197 | Args: 198 | n (int) 199 | Returns: 200 | [int] 201 | ''' 202 | 203 | return self._rpc('returnFrom0ToN', [n], return_type=[int]) 204 | 205 | def optionalArgs(self, required, p1=Optional(int), p2=Optional(int)): 206 | ''' 207 | Args: 208 | required (bool) 209 | p1 (Optional(int)) 210 | p2 (Optional(int)) 211 | ''' 212 | 213 | return self._rpc('optionalArgs', [required, p1, p2], return_type=None) 214 | 215 | def sumArray(self, ints): 216 | ''' 217 | Args: 218 | ints ([int]) 219 | Returns: 220 | int 221 | ''' 222 | 223 | return self._rpc('sumArray', [ints], return_type=int) 224 | 225 | def testAny(self, a): 226 | ''' 227 | Args: 228 | a (object) 229 | Returns: 230 | object 231 | ''' 232 | 233 | return self._rpc('testAny', [a], return_type=object) 234 | 235 | def getSeconds(self, timeParam): 236 | ''' 237 | Args: 238 | timeParam (datetime) 239 | Returns: 240 | int 241 | ''' 242 | 243 | return self._rpc('getSeconds', [timeParam], return_type=int) 244 | 245 | def getNow(self): 246 | return self._rpc('getNow', [], return_type=datetime) 247 | 248 | def __enter__(self): 249 | return self 250 | 251 | def __exit__(self, type, value, traceback): 252 | pass 253 | 254 | def useHTTP(self): 255 | self._rpc.useHTTP() 256 | 257 | def useWS(self): 258 | self._rpc.useWS() 259 | 260 | def onTestEvent(self, callback=None): 261 | self._rpc.event('testEvent', callback, return_type=int) 262 | 263 | def onTestEvent2(self, callback=None): 264 | self._rpc.event('testEvent2', callback, return_type=[GeneratedTest.RenderOptions]) 265 | 266 | def onTestEvent3(self, callback=None): 267 | self._rpc.event('testEvent3', callback, return_type=dict) 268 | 269 | def onTestEvent4(self, callback=None): 270 | self._rpc.event('testEvent4', callback, return_type=bool) 271 | 272 | def onTestBinaryEvent(self, callback=None): 273 | self._rpc.event('testBinaryEvent', callback, return_type=bytearray) 274 | 275 | def onNs1_testEvent1(self, callback=None): 276 | self._rpc.event('ns1.testEvent1', callback, return_type=None) 277 | -------------------------------------------------------------------------------- /examples/adhoc/api.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | 6 | const Service = require('../../index.js').service; 7 | const service = new Service('1.0.0', 'test-api', (methodName, options) => { 8 | //if (options.sessionId == 'pass') 9 | return Promise.resolve({ data: options.sessionId }); 10 | //return Promise.reject(new Error('Who are you?')); 11 | }); 12 | 13 | const testObj = { 14 | sum(a, b, callback) { 15 | setTimeout(function() { 16 | callback(null, a + b); 17 | }, 0); 18 | }, 19 | 20 | sumReturn(a, b) { 21 | console.log('A + B = ' + (a + b)); 22 | return a + b; 23 | }, 24 | 25 | echo(a) { 26 | console.log(a); 27 | const RenderMode = service.type('RenderMode').struct; 28 | console.log('RenderMode value: ' + RenderMode[a.renderMode]); 29 | return a; 30 | }, 31 | 32 | echoObject(a, callback) { 33 | a.b = a; 34 | callback(null, a); 35 | }, 36 | 37 | throwError(callback) { 38 | callback({ stack: 'error executing method' }); 39 | }, 40 | 41 | throwUnexpectedError() { 42 | throw new Error('Unexpected error'); 43 | }, 44 | 45 | testMe(callback, context) { 46 | console.log(context.data); 47 | callback(null, { 48 | property1: 'int', 49 | asdf: 'Аз съм Сънчо', 50 | complex: { 51 | a: 1, 52 | b: 3, 53 | }, 54 | }); 55 | }, 56 | 57 | testMe1(callback) { 58 | callback(null, 'test1'); 59 | }, 60 | 61 | testMe2(a, callback) { 62 | callback(null, 'test2' + a); 63 | }, 64 | 65 | testMe3(callback) { 66 | callback(null, 'Some async method test 3'); 67 | }, 68 | 69 | testMe4(callback) { 70 | callback(null, 'Some async method test 4'); 71 | }, 72 | 73 | getStream() { 74 | return fs.createReadStream(path.join(__dirname, '../../README.md')); 75 | }, 76 | }; 77 | 78 | let c = 0; 79 | 80 | setInterval(function() { 81 | const data = { testData: c++ }; 82 | service.emit('testEvent', data.testData); 83 | service.emit('testEvent3', { 84 | a: 1, 85 | }); 86 | service.emit('ns1.testEvent1'); 87 | service.emit('testBinaryEvent', new Buffer('test binary event')); 88 | }, 1000); 89 | 90 | setInterval(function() { 91 | service.emit('testEvent2', [ 92 | { 93 | width: 1, 94 | height: 2, 95 | renderMode: 0, 96 | }, 97 | ]); 98 | }, 2000); 99 | 100 | module.exports = (function() { 101 | service 102 | .setGroup('All Methods', 'Every single method is here') 103 | .event('testEvent', { 104 | description: 'This event is fired every second, and returns a data count.', 105 | type: 'int', 106 | }) 107 | .setNamespace('') 108 | .defineAll(testObj) 109 | .enum( 110 | 'RenderMode', 111 | { 112 | Production: -1, 113 | RtCpu: 0, 114 | RtGpuCuda: 5, 115 | }, 116 | 'Enum description' 117 | ) 118 | .enum('JobState', ['Created', 'Pending', 'Active', 'Done']) 119 | .type( 120 | 'RenderOptions', 121 | { 122 | width: { 123 | type: 'int', 124 | description: 'The desired width for rendering', 125 | }, 126 | height: { 127 | type: 'int', 128 | description: 'The desired height for rendering', 129 | }, 130 | renderMode: 'RenderMode', 131 | }, 132 | 'RenderOptions description' 133 | ) 134 | .type('DefaultArray', { 135 | property: { 136 | type: ['string'], 137 | required: false, 138 | default: [], 139 | }, 140 | }) 141 | .type('Point', { 142 | x: { 143 | type: 'number', 144 | required: false, 145 | }, 146 | y: { 147 | type: 'number', 148 | required: false, 149 | }, 150 | meta: { 151 | type: 'json', 152 | required: false, 153 | }, 154 | error: { 155 | type: 'error', 156 | required: false, 157 | }, 158 | }) 159 | .define( 160 | { name: 'TestDefaultArray', params: [{ name: 'p', type: 'DefaultArray' }] }, 161 | function(p) { 162 | console.log(p); 163 | } 164 | ) 165 | .define({ name: 'TestUrl', params: [{ name: 'u', type: 'url' }], returns: 'url' }, function( 166 | u 167 | ) { 168 | console.log(u); 169 | return u.format(); 170 | }) 171 | .define({ 172 | name: 'testMe', 173 | returns: 'json', 174 | description: 'A sample method.', 175 | }) 176 | .define({ 177 | name: 'testMe2', 178 | params: [{ name: 'a', type: 'string', description: 'A simple string parameter.' }], 179 | returns: 'string', 180 | description: 'A sample method.', 181 | }) 182 | .define({ 183 | name: 'echo', 184 | params: [{ name: 'a', type: 'RenderOptions' }], 185 | returns: 'RenderOptions', 186 | }) 187 | .define({ name: 'getRenderOptions', returns: ['RenderOptions'] }, function() { 188 | const renderOptions = { 189 | width: 640, 190 | height: 360, 191 | renderMode: 'RtCpu', 192 | }; 193 | return [renderOptions, renderOptions, renderOptions]; 194 | }) 195 | .define({ name: 'echoObject', params: ['a'], returns: 'json' }) 196 | .define({ name: 'getStream', returns: 'stream' }) 197 | .setGroup('Other methods') 198 | .define( 199 | { 200 | name: 'echoStringAsBuffer', 201 | params: [{ name: 'theString', type: 'string' }], 202 | returns: 'binary', 203 | }, 204 | function(theString) { 205 | return new Buffer(theString); 206 | } 207 | ) 208 | .define( 209 | { name: 'getBufferSize', params: [{ name: 'buffer', type: 'binary' }], returns: 'int' }, 210 | function(buffer) { 211 | console.log(buffer, buffer.length); 212 | return buffer.length; 213 | } 214 | ) 215 | .define({ name: 'throwError', returns: 'int' }) 216 | .define({ name: 'throwUnexpectedError', returns: ['object'] }) 217 | .define({ 218 | name: 'sum', 219 | description: "Some test method example,' does int sum", 220 | params: [{ name: 'a', type: 'int' }, { name: 'b', type: 'int' }], 221 | returns: 'int', 222 | }); 223 | service.define( 224 | { 225 | name: 'returnFrom0ToN', 226 | params: [{ name: 'n', type: 'int' }], 227 | returns: ['int'], 228 | }, 229 | function(n) { 230 | const arr = new Array(n); 231 | for (let i = 0; i < n; i++) { 232 | arr[i] = i; 233 | } 234 | return arr; 235 | } 236 | ); 237 | service.define( 238 | { 239 | name: 'optionalArgs', 240 | params: [ 241 | { name: 'required', type: 'bool' }, 242 | { name: 'p1', type: 'int', default: 0 }, 243 | { name: 'p2', type: 'int', default: 1 }, 244 | ], 245 | }, 246 | function(required, p1, p2) { 247 | console.log('optionalArgs called with', arguments); 248 | } 249 | ); 250 | service 251 | .define( 252 | { 253 | name: 'sumArray', 254 | params: [{ name: 'ints', type: ['int'] }], 255 | returns: 'int', 256 | }, 257 | function(ints) { 258 | let sum = 0; 259 | ints.forEach(function(i) { 260 | sum += i; 261 | }); 262 | return sum; 263 | } 264 | ) 265 | .define({ 266 | name: 'testAny', 267 | params: [{ name: 'a', type: '*' }], 268 | returns: 'any', 269 | }) 270 | .define( 271 | { 272 | name: 'getSeconds', 273 | params: [{ name: 'timeParam', type: 'date' }], 274 | returns: 'int', 275 | }, 276 | function(time) { 277 | return time.getSeconds(); 278 | } 279 | ) 280 | .define( 281 | { 282 | name: 'getNow', 283 | params: [], 284 | returns: 'date', 285 | }, 286 | function() { 287 | return Date.now(); 288 | } 289 | ) 290 | .event('testEvent2', { 291 | type: ['RenderOptions'], 292 | }) 293 | .event('testEvent3', { 294 | type: 'json', 295 | }) 296 | .event('testEvent4', { 297 | type: 'bool', 298 | }) 299 | .event('testBinaryEvent', { 300 | type: 'binary', 301 | }) 302 | .define({ name: 'testMe1', returns: 'async' }) 303 | 304 | .setNamespace('ns1') 305 | .define( 306 | { 307 | name: 'method1', 308 | returns: 'string', 309 | }, 310 | testObj.testMe1 311 | ) 312 | .event('testEvent1') 313 | 314 | .setNamespace('ns1.sub1.sub2') 315 | .define('method1') 316 | 317 | .setNamespace('ns2.sub1.sub2') 318 | .define('method1') 319 | .examples(path.join(__dirname, 'examples', 'examples.js')) 320 | .examples(path.join(__dirname, 'examples', 'examples.py')) 321 | .examples(path.join(__dirname, 'examples', 'snippets.js')); 322 | // .examples(path.resolve('test.examples.js')) 323 | // .examples(path.resolve('test.examples.node.js')) 324 | // .examples(path.resolve('test.examples.java')) 325 | // .examples(path.resolve('test.examples.curl')) 326 | return service; 327 | })(); 328 | -------------------------------------------------------------------------------- /lib/service/types.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const url = require('url'); 4 | const stream = require('stream'); 5 | 6 | const internalTypes = (() => { 7 | // Converter functions take a value, parse it and return a proper value for the expected type 8 | // The optional second argument, out, will be set to true if the type converter is called when 9 | // the value is to be sent out to clients, otherwise the function is called when a client initiates 10 | // an RPC call to the service 11 | const converters = { 12 | anyConverter(value) { 13 | /*any*/ 14 | return value; 15 | }, 16 | intConverter(value) { 17 | /*int*/ 18 | if (value === undefined) return value; 19 | const valueType = typeof value; 20 | if (valueType !== 'number' && valueType !== 'string') { 21 | throw new Error('Invalid integer value: ' + value); 22 | } 23 | return Math.floor(parseInt(value, 10)); 24 | }, 25 | numberConverter(value) { 26 | /*number*/ 27 | if (value === undefined) return value; 28 | const valueType = typeof value; 29 | if (valueType !== 'number' && valueType !== 'string') { 30 | throw new Error('Invalid number value: ' + value); 31 | } 32 | return parseFloat(value) || 0.0; 33 | }, 34 | dateConverter(value) { 35 | /*date*/ 36 | if (value === undefined || value === null) return value; 37 | const valueType = typeof value; 38 | if ( 39 | valueType !== 'number' && 40 | valueType !== 'string' && 41 | !(valueType == 'object' && value instanceof Date) 42 | ) { 43 | throw new Error('Invalid date value: ' + value); 44 | } 45 | return new Date(value); 46 | }, 47 | boolConverter(value) { 48 | /*bool*/ 49 | if (typeof value === 'string') { 50 | const lowercase = value.toLowerCase().trim(); 51 | if (lowercase == 'true') return true; 52 | if (lowercase == 'false') return false; 53 | throw new Error('Invalid boolean value: ' + value); 54 | } else { 55 | return !!value; 56 | } 57 | }, 58 | objectConverter(value) { 59 | /*object*/ 60 | return typeof value === 'string' ? JSON.parse(value) : value; 61 | }, 62 | stringConverter(value, out) { 63 | /*string*/ 64 | if (typeof value === 'string' || value == null) { 65 | return value; 66 | } else if (out) { 67 | return JSON.stringify(value); 68 | } 69 | throw new Error('Invalid string value.'); 70 | }, 71 | urlConverter(value, out) { 72 | /*url*/ 73 | if (typeof value === 'string') { 74 | const parsedUrl = url.parse(value); 75 | if (!parsedUrl.protocol || !parsedUrl.hostname || !parsedUrl.path) { 76 | throw new Error('Invalid URL value: ' + value); 77 | } 78 | return out === true ? parsedUrl.format() : parsedUrl; 79 | } else if (value instanceof url.Url) { 80 | return value.format(); 81 | } else if (value == null) { 82 | return null; 83 | } else { 84 | throw new Error('Invalid URL value: ' + value); 85 | } 86 | }, 87 | binaryConverter(value) { 88 | /*binary*/ 89 | if (typeof value === 'string') { 90 | return new Buffer(value, 'base64'); 91 | } 92 | if (!Buffer.isBuffer(value)) { 93 | throw new Error('Invalid buffer data.'); 94 | } 95 | return value; 96 | }, 97 | streamConverter(value, out) { 98 | /*stream*/ 99 | // TODO add conversion from/to buffers 100 | if (out) { 101 | if (value && value instanceof stream.Readable) { 102 | return value; 103 | } 104 | throw new Error('Readable stream expected.'); 105 | } 106 | throw new Error('Input streams are not supported'); 107 | }, 108 | errorConverter(value) { 109 | /* error */ 110 | if (value === null || value === undefined) { 111 | return value; 112 | } else if ( 113 | value instanceof Error || 114 | /Error$/.test(value.name) || 115 | typeof value.message !== 'undefined' 116 | ) { 117 | return { 118 | name: value.name || 'Error', 119 | message: value.message, 120 | }; 121 | } else if (typeof value === 'string') { 122 | return { 123 | name: 'Error', 124 | message: value, 125 | }; 126 | } else { 127 | throw new Error('Invalid error: ' + value); 128 | } 129 | }, 130 | }; 131 | 132 | return new Map([ 133 | ['*', converters.anyConverter], 134 | ['any', converters.anyConverter], 135 | ['int', converters.intConverter], 136 | ['integer', converters.intConverter], 137 | ['number', converters.numberConverter], 138 | ['float', converters.numberConverter], 139 | ['double', converters.numberConverter], 140 | ['date', converters.dateConverter], 141 | ['time', converters.dateConverter], 142 | ['bool', converters.boolConverter], 143 | ['boolean', converters.boolConverter], 144 | ['object', converters.objectConverter], 145 | ['json', converters.objectConverter], 146 | ['string', converters.stringConverter], 147 | ['url', converters.urlConverter], 148 | ['buffer', converters.binaryConverter], 149 | ['binary', converters.binaryConverter], 150 | ['stream', converters.streamConverter], 151 | ['error', converters.errorConverter], 152 | ]); 153 | })(); 154 | 155 | /** 156 | * Check if `obj` is a generator. 157 | * 158 | * @param {*} obj 159 | * @return {Boolean} 160 | * @api private 161 | */ 162 | function isGenerator(obj) { 163 | return obj && 'function' == typeof obj.next && 'function' == typeof obj.throw; 164 | } 165 | 166 | /** 167 | * Helpers dealing with internal types 168 | */ 169 | const typeHelpers = { 170 | get missingDocumentation() { 171 | return ''; 172 | }, 173 | 174 | /** 175 | * Checks if a given type name is considered/handled as internal. 176 | * @param {String} typeName The name of the type. 177 | * @returns {boolean} 178 | */ 179 | isInternal(typeName) { 180 | return typeName === 'async' || internalTypes.has(typeName); 181 | }, 182 | 183 | /** 184 | * Gets a function which returns a raw value to an internal type. 185 | * @param {String} typeName The name of the internal type. 186 | * @returns {Function} The converter function. 187 | */ 188 | converter(typeName) { 189 | const typeConverterFunction = internalTypes.get(typeName); 190 | if (!typeConverterFunction) { 191 | throw new Error(`The specified type (${typeName}) is not internal.`); 192 | } 193 | return typeConverterFunction; 194 | }, 195 | 196 | /** 197 | * Creates a type descriptor struct out of a type definition. 198 | * @param {String} typeName The name of the defined type. 199 | * @param {Object} typeDef A type definition object. 200 | * @param {String} typeDef.type The name of the type. 201 | * @param {Function} checkTypeExistsFn A function which must check if a given type is defined in the API 202 | * @returns {} 203 | */ 204 | typeStructFromDef(typeName, typeDef, checkTypeExistsFn) { 205 | const struct = {}; 206 | Object.keys(typeDef).forEach(field => { 207 | let fieldInfo = typeDef[field]; 208 | if (typeof fieldInfo == 'string') { 209 | fieldInfo = { type: fieldInfo }; 210 | } else if (Array.isArray(fieldInfo)) { 211 | fieldInfo = { 212 | type: fieldInfo[0], 213 | isArray: true, 214 | }; 215 | } else if (typeof fieldInfo !== 'object') { 216 | throw new Error(`Invalid type field definition: ${typeName}.${field}`); 217 | } else if (Array.isArray(fieldInfo.type)) { 218 | fieldInfo.type = fieldInfo.type[0]; 219 | fieldInfo.isArray = true; 220 | } 221 | 222 | if (!fieldInfo.type) { 223 | throw new Error(`Missing type for field: ${field}`); 224 | } 225 | 226 | if (typeof fieldInfo.type !== 'string') { 227 | throw new Error(`Invalid type field definition: ${typeName}.${field}`); 228 | } 229 | 230 | if (!typeHelpers.isInternal(fieldInfo.type) && !checkTypeExistsFn(fieldInfo.type)) { 231 | throw new Error(`Referenced type "${fieldInfo.type}" is undefined.`); 232 | } 233 | 234 | if (fieldInfo.type === 'stream') { 235 | throw new Error('Input streams are not supported'); 236 | } 237 | 238 | struct[field] = { 239 | name: field, 240 | type: fieldInfo.type, 241 | isArray: !!fieldInfo.isArray, 242 | required: fieldInfo.required != undefined ? !!fieldInfo.required : true, 243 | default: fieldInfo.default, 244 | description: fieldInfo.description || typeHelpers.missingDocumentation, 245 | }; 246 | }); 247 | return struct; 248 | }, 249 | 250 | methodNotImplemented(name) { 251 | return function throwMethodNotImplementedException() { 252 | throw new Error(`"${name}" is not yet implemented.`); 253 | }; 254 | }, 255 | 256 | /** 257 | * Check if `obj` is a promise. 258 | * 259 | * @param {Object} obj 260 | * @return {Boolean} 261 | * @api private 262 | */ 263 | isPromise(obj) { 264 | return obj && 'function' == typeof obj.then; 265 | }, 266 | 267 | /** 268 | * Check if `obj` is a generator function. 269 | * 270 | * @param {*} obj 271 | * @return {Boolean} 272 | * @api private 273 | */ 274 | isGeneratorFunction(obj) { 275 | const constructor = obj.constructor; 276 | if (!constructor) { 277 | return false; 278 | } 279 | if ( 280 | 'GeneratorFunction' === constructor.name || 281 | 'GeneratorFunction' === constructor.displayName 282 | ) { 283 | return true; 284 | } 285 | return isGenerator(constructor.prototype); 286 | }, 287 | }; 288 | 289 | module.exports = typeHelpers; 290 | -------------------------------------------------------------------------------- /lib/registry.js: -------------------------------------------------------------------------------- 1 | /** 2 | * JSON-WS Service Registry 3 | */ 4 | 5 | 'use strict'; 6 | 7 | const EventEmitter = require('events'); 8 | const ejs = require('ejs'); 9 | const path = require('path'); 10 | const url = require('url'); 11 | const crypto = require('crypto'); 12 | const express = require('express'); 13 | const semver = require('semver'); 14 | 15 | const getLanguageProxy = require('./get-language-proxy'); 16 | const getMetadataPage = require('./get-metadata-page'); 17 | const getPlaygroundPage = require('./get-playground-page'); 18 | const Trace = require('./trace'); 19 | 20 | const BaseTransport = require('./transport/base-transport'); 21 | 22 | // Global map of registries per root path 23 | const registries = new Map(); // rootPath: string -> registry: ServiceRegistry 24 | 25 | class ServiceRegistry extends EventEmitter { 26 | static create(options) { 27 | let registry = registries.get(options.rootPath); 28 | if (!registry) { 29 | registry = new ServiceRegistry(options); 30 | registries.set(options.rootPath, registry); 31 | } 32 | return registry; 33 | } 34 | 35 | constructor({ rootPath = '/', httpServer, logger, serveMetadata = true }) { 36 | super(); 37 | this.rootPath = rootPath.replace(/\/*$/, ''); 38 | this.httpServer = httpServer; 39 | this.trace = new Trace(logger); 40 | this.services = new Map /*serviceRootPrefix:Service*/(); 41 | this.transports = new Set /*Transport*/(); 42 | this.routes = new Set /* rootPath-prefixed routes */(); 43 | 44 | this.router = express.Router({ caseSensitive: true }); // eslint-disable-line new-cap 45 | if (serveMetadata) { 46 | this.router.use((req, res, next) => { 47 | this._routeHandler(req, res, next); 48 | }); 49 | } 50 | 51 | this.rootRouter = express.Router({ caseSensitive: true }); // eslint-disable-line new-cap 52 | this.rootRouter.use(this.rootPath, (req, res, next) => { 53 | req.originalParams = req.params; 54 | next(); 55 | }); 56 | this.rootRouter.use(this.rootPath, this.router); 57 | 58 | this._customRootHandler = null; 59 | this.rootRouter.use(this.rootPath, (req, res, next) => { 60 | if (typeof this._customRootHandler === 'function') { 61 | this._customRootHandler(req, res, next); 62 | } else { 63 | next(); 64 | } 65 | }); 66 | 67 | this.addRoute(''); // Handle the rootPath 68 | } 69 | 70 | setCustomRootHandler(handler) { 71 | if (typeof handler === 'function') { 72 | this._customRootHandler = handler; 73 | } else { 74 | throw new Error('The custom root handler must be a function'); 75 | } 76 | } 77 | 78 | getRouter() { 79 | return this.rootRouter; 80 | } 81 | 82 | addRoute(route) { 83 | route = route.replace(/\/*$/, ''); 84 | const rootPathPrefixedRoute = `${this.rootPath}${route}`; 85 | 86 | // Don't add routes more than once: 87 | if (this.routes.has(rootPathPrefixedRoute)) { 88 | return; 89 | } 90 | 91 | this.routes.add(rootPathPrefixedRoute); 92 | this.router.all(`${route}/*`, (req, res, next) => { 93 | req.originalParams = Object.assign({}, req.originalParams, req.params); 94 | next(); 95 | }); 96 | this.rootRouter.use(rootPathPrefixedRoute, this.router); 97 | } 98 | 99 | addService(service) { 100 | const version = semver(service.version); 101 | const rootPrefix = `/${service.name}/v${version.major}`; 102 | this.services.set(rootPrefix, service); 103 | 104 | this.emit('service-added', { 105 | servicePrefix: rootPrefix, 106 | service, 107 | }); 108 | 109 | return rootPrefix; 110 | } 111 | 112 | addTransport(constructorOrInstance) { 113 | let transportInstance, TransportConstructor; 114 | if (constructorOrInstance instanceof BaseTransport) { 115 | transportInstance = constructorOrInstance; 116 | TransportConstructor = transportInstance.constructor; 117 | } else if (BaseTransport.isPrototypeOf(constructorOrInstance)) { 118 | TransportConstructor = constructorOrInstance; 119 | } else { 120 | throw new Error('Invalid transport.'); 121 | } 122 | // Don't add a transport more than once 123 | for (const transport of this.transports.values()) { 124 | if (transport.constructor === TransportConstructor) { 125 | throw new Error(`Transport ${TransportConstructor.type} has already been added.`); 126 | } 127 | } 128 | if (!transportInstance) { 129 | transportInstance = new TransportConstructor(this); 130 | } 131 | this.transports.add(transportInstance); 132 | return transportInstance; 133 | } 134 | 135 | _renderMetadataPage(service, req, res) { 136 | if (req.query.json !== undefined) { 137 | // Handle the "?json" query, return an API description in JSON format 138 | res.writeHead(200, { 'Content-Type': 'application/json' }); 139 | res.write(JSON.stringify(service.metadata)); 140 | res.end(); 141 | return; 142 | } 143 | 144 | if (req.query.proxy !== undefined) { 145 | // Generate a proxy file 146 | let signature = service.timeStamp; 147 | ['proxy', 'namespace', 'localName'].forEach(param => { 148 | if (req.query.hasOwnProperty(param)) { 149 | signature += ';' + req.query[param]; 150 | } 151 | }); 152 | 153 | const sha1 = crypto.createHash('sha1'); 154 | sha1.update(signature); 155 | const etag = sha1.digest('hex'); 156 | if (req.headers['if-none-match'] === etag) { 157 | res.status(304).end(); 158 | return; 159 | } 160 | 161 | // Request for a proxy generator 162 | getLanguageProxy({ 163 | serviceInstance: service, 164 | language: req.query.proxy, 165 | localName: req.query.setNamespace || req.query.localName, 166 | }) 167 | .then(function(html) { 168 | let contentType = 'text/plain'; 169 | const proxyType = req.query.proxy.toLowerCase(); 170 | if (proxyType.indexOf('javascript') != -1) { 171 | contentType = 'application/javascript'; 172 | } else if (proxyType.indexOf('java') != -1) { 173 | contentType = 'text/java'; 174 | } 175 | res.writeHead(200, { 176 | 'Content-Type': contentType, 177 | 'Content-Length': html.length, 178 | 'Cache-Control': 'must-revalidate', 179 | ETag: etag, 180 | }); 181 | res.write(html); 182 | res.end(); 183 | }) 184 | .catch(function(err) { 185 | if (err.code === 'ENOENT') { 186 | res.status(404).send('Proxy generator not found.'); 187 | } else { 188 | res.status(500).send(err.toString()); 189 | } 190 | res.end(); 191 | }); 192 | } else if (req.query.viewer !== undefined) { 193 | getPlaygroundPage({ 194 | service, 195 | snippet: req.query.snippet, 196 | example: req.query.example, 197 | registry: this, 198 | }) 199 | .then(html => { 200 | res.send(html); 201 | }) 202 | .catch(err => { 203 | res.status(500).send(err.toString()); 204 | }); 205 | } else { 206 | getMetadataPage({ isStatic: false, service, root: this.rootPath }) 207 | .then(html => { 208 | res.send(html); 209 | }) 210 | .catch(err => { 211 | res.status(500).send(err.toString()); 212 | }); 213 | } 214 | } 215 | 216 | _routeHandler(req, res, next) { 217 | if (req.method.toUpperCase() == 'OPTIONS') { 218 | res.set('Access-Control-Allow-Origin', '*'); 219 | res.set('Access-Control-Allow-Methods', 'POST, GET, OPTIONS'); 220 | res.set('Access-Control-Max-Age', 1000); 221 | res.set('Access-Control-Allow-Headers', 'origin, x-csrftoken, content-type, accept'); 222 | res.end(); 223 | return; 224 | } 225 | 226 | const requestUrl = url.parse(req.path).pathname || ''; 227 | if (requestUrl === '/') { 228 | if (typeof this._customRootHandler === 'function') { 229 | this._customRootHandler(req, res, next); 230 | } else { 231 | ejs.renderFile( 232 | __dirname + '/../templates/services.ejs', 233 | { 234 | services: Array.from(this.services.entries()).map(entry => ({ 235 | path: `${req.baseUrl}${entry[0]}`, 236 | metadata: entry[1], 237 | })), 238 | }, 239 | { _with: false }, 240 | function(err, html) { 241 | if (err) { 242 | res.status(500).send(err.toString()); 243 | } else { 244 | res.set('Content-Type', 'text/html'); 245 | res.set('Content-Length', html.length); 246 | res.set('Cache-Control', 'must-revalidate'); 247 | res.send(html); 248 | } 249 | } 250 | ); 251 | } 252 | } else if (requestUrl.indexOf('/static/') !== -1) { 253 | const fileName = requestUrl.substring(requestUrl.lastIndexOf('/') + 1); 254 | if (fileName.indexOf('.js') !== -1) { 255 | res.set('Content-Type', 'application/javascript'); 256 | } else if (fileName.indexOf('.css') !== -1) { 257 | res.set('Content-Type', 'text/css'); 258 | } 259 | res.sendFile(path.resolve(__dirname + '/../static/' + fileName)); 260 | } else { 261 | const serviceInstance = this.getService(requestUrl); 262 | if (serviceInstance && req.method.toUpperCase() === 'GET') { 263 | this._renderMetadataPage(serviceInstance, req, res); 264 | } else { 265 | next(); 266 | } 267 | } 268 | } 269 | 270 | getService(pathPrefix) { 271 | return this.services.get(pathPrefix); 272 | } 273 | } 274 | 275 | /** 276 | * Creates an API Registry middleware for Express/Connect 277 | * Responds to OPTIONS request, renders a page listing all registered services 278 | * and serves the NodeJS/browser client library. 279 | * Registry instances are reused per rootPath 280 | */ 281 | module.exports = (...args) => ServiceRegistry.create(...args); 282 | 283 | /** 284 | * The ServiceRegistry constructor, use it to manage registries manually 285 | * @type {ServiceRegistry} 286 | */ 287 | module.exports.ServiceRegistry = ServiceRegistry; 288 | -------------------------------------------------------------------------------- /proxies/Python.ejs: -------------------------------------------------------------------------------- 1 | <% 2 | var metadata = locals.metadata; 3 | var localName = locals.localName; 4 | 5 | function getIndentation(indentationLevel) { 6 | return Array(indentationLevel * 4 + 1).join(' '); 7 | } 8 | 9 | function mapPythonType(param, prefix, options) { 10 | var returnType = ''; 11 | var jsType = param.type; 12 | options || (options = {}); 13 | 14 | if (prefix) { 15 | prefix = prefix + '.'; 16 | } else { 17 | prefix = ''; 18 | } 19 | 20 | switch (jsType) { 21 | case '*': 22 | case 'any': 23 | returnType = 'object'; 24 | break; 25 | 26 | case 'int': 27 | case 'integer': 28 | returnType = 'int'; 29 | break; 30 | 31 | case 'date': 32 | case 'time': 33 | returnType = 'datetime'; 34 | break; 35 | 36 | case 'number': 37 | case 'float': 38 | case 'double': 39 | returnType = 'float'; 40 | break; 41 | 42 | case 'bool': 43 | case 'boolean': 44 | returnType = 'bool'; 45 | break; 46 | 47 | case 'object': 48 | case 'json': 49 | returnType = 'dict'; 50 | break; 51 | 52 | case 'string': 53 | returnType = 'str'; 54 | break; 55 | 56 | case 'url': 57 | returnType = 'str'; 58 | break; 59 | 60 | case 'buffer': 61 | case 'binary': 62 | case 'stream': 63 | returnType = 'bytearray'; 64 | break; 65 | 66 | case undefined: 67 | returnType = 'None'; 68 | break; 69 | 70 | default: 71 | if (metadata.types[jsType]) { 72 | if (!options.wrapType) { 73 | returnType = prefix + jsType; 74 | } else { 75 | returnType = "'" + jsType + "'"; 76 | } 77 | } else { 78 | returnType = 'object'; 79 | } 80 | } 81 | 82 | if (param.isArray) { 83 | returnType = '[' + returnType + ']'; 84 | } 85 | 86 | if (!param.required && param.default !== undefined && !options.supressOptional) { 87 | returnType = 'Optional(' + returnType + ')'; 88 | } 89 | 90 | return returnType; 91 | } 92 | 93 | 94 | // Generate namespace tree 95 | var namespaceRoot = { 96 | name: '', 97 | fullName: '', 98 | methods: [], 99 | children: {} 100 | }; 101 | 102 | Object.keys(metadata.methods).forEach(function(methodName) { 103 | var method = metadata.methods[methodName]; 104 | var $ = namespaceRoot; 105 | var prefixes = method.name.split('.'); 106 | prefixes.pop(); 107 | prefixes.forEach(function(prefix) { 108 | if (!$.children.hasOwnProperty(prefix)) { 109 | $.children[prefix] = { 110 | prefix: prefix, 111 | methods: [], 112 | children: {} 113 | }; 114 | } 115 | $ = $.children[prefix]; 116 | }); 117 | $.methods.push(method.name); 118 | }); 119 | 120 | function camelCase(literal) { 121 | return literal.substr(0, 1).toUpperCase() + literal.substr(1); 122 | } 123 | 124 | function generateTypes() { 125 | var types = Object.keys(metadata.types).map(function(key) { return metadata.types[key] }); 126 | 127 | types.forEach(function(type) { 128 | if (type.enum) { 129 | %> 130 | <%=getIndentation(1)%><%- type.name %> = Enum('<%- type.name %>', { 131 | <% 132 | Object.keys(type.struct).forEach(function(key) { 133 | %><%=getIndentation(2)%>'<%- key %>' : <%= type.struct[key] %>, 134 | <% 135 | }) 136 | %><%=getIndentation(1)%>}) 137 | <% 138 | } else { 139 | %> 140 | <%=getIndentation(1)%><%- type.name %> = Type('<%- type.name %>', {<% 141 | Object.keys(type.struct).forEach(function(propertyName) { 142 | var property = type.struct[propertyName]; 143 | %> 144 | <%=getIndentation(2)%>'<%= propertyName %>' : <%- mapPythonType(property, localName, { supressOptional: true, wrapType: true }) %>,<% 145 | }); 146 | %> 147 | <%=getIndentation(1)%>}, lambda: <%= localName %>) 148 | <% 149 | } 150 | }); 151 | } 152 | 153 | function generateEventStubs() { 154 | var events = Object.keys(metadata.events).map(function(key) { return metadata.events[key] }); 155 | var eventInfos = {}; 156 | 157 | events.forEach(function(event) { 158 | var camelCasedName = camelCase(event.name).replace(/\./ig, '_'); 159 | %> 160 | <%=getIndentation(1)%>def on<%-camelCasedName%>(self, callback=None): 161 | <%=getIndentation(2)%>self._rpc.event('<%- event.name %>', callback, return_type=<%= event.type ? mapPythonType({type: event.type, isArray: event.isArray}, localName) : 'None' %>) 162 | <% 163 | }); 164 | } 165 | 166 | function getMethodArguments(params, namesOnly) { 167 | namesOnly || (namesOnly = false); 168 | 169 | var args = params.map(function(param) { 170 | if (param.required || param.default === undefined || namesOnly) { 171 | return param.name; 172 | } else { 173 | return param.name + '=' + mapPythonType(param); 174 | } 175 | }) 176 | 177 | if (!namesOnly) { 178 | args.unshift('self'); 179 | } 180 | 181 | return args.join(', '); 182 | } 183 | 184 | function generateMethodDocString(methodInfo, indentationLevel, prefix) { 185 | var description = methodInfo.description; 186 | var params = methodInfo.params; 187 | 188 | if (!description && params.length === 0) { 189 | return; 190 | } 191 | %> 192 | <%= getIndentation(indentationLevel) %>'''<% 193 | if (description) { %> 194 | <%= getIndentation(indentationLevel) + description %> 195 | <% 196 | } 197 | 198 | if (params) { %> 199 | <%= getIndentation(indentationLevel) %>Args:<% 200 | 201 | params.forEach(function(param) { %> 202 | <%= getIndentation(indentationLevel + 1) %><%= param.name %> (<%= mapPythonType(param, prefix) %>)<%= param.description ? ': ' + param.description : '' %><% 203 | }); 204 | } 205 | 206 | if (methodInfo.returns) { %> 207 | <%= getIndentation(indentationLevel) %>Returns: 208 | <%= getIndentation(indentationLevel + 1) %><%= mapPythonType({type: methodInfo.returns, isArray: methodInfo.returnsArray}, prefix) %><% 209 | } 210 | %> 211 | <%= getIndentation(indentationLevel) %>''' 212 | <% 213 | } 214 | 215 | function generateMethodStubs(root, indentationLevel) { 216 | if (!root) { return; } 217 | 218 | indentationLevel || (indentationLevel = 0); 219 | 220 | var methods = root.methods; 221 | var children = root.children; 222 | var prefix = 'self' 223 | 224 | if (root.prefix) { %> 225 | <%=getIndentation(indentationLevel)%>class <%=root.prefix%>:<% 226 | prefix = 'self._root'; 227 | } 228 | 229 | // Generate code for sub-namespaces 230 | children && Object.keys(children).forEach(function(key) { 231 | generateMethodStubs(children[key], indentationLevel + 1); 232 | }); 233 | 234 | 235 | if (root.prefix) { 236 | // Generate the constructor for the namespace class: 237 | %> 238 | <%=getIndentation(indentationLevel + 1)%>def __init__(self, root): 239 | <%=getIndentation(indentationLevel + 2)%>self.root = root 240 | <% 241 | // Attach any subnamespaces: 242 | children && Object.keys(children).forEach(function(childName) { 243 | %><%=getIndentation(indentationLevel + 2)%>self.<%=childName%> = self.<%=childName%>(root) 244 | <% 245 | }); 246 | } else { 247 | // Generate the constructor for the proxy 248 | %> 249 | <%=getIndentation(indentationLevel + 1)%>def __init__(self, url): 250 | <%=getIndentation(indentationLevel + 2)%>''' 251 | <%=getIndentation(indentationLevel + 2)%>Args: 252 | <%=getIndentation(indentationLevel + 3)%>url (string): The url of the web service 253 | <%=getIndentation(indentationLevel + 2)%>''' 254 | 255 | <%=getIndentation(indentationLevel + 2)%># RpcTunnel 256 | <%=getIndentation(indentationLevel + 2)%>self._rpc = RpcTunnel(url) 257 | <%=getIndentation(indentationLevel + 2)%># The default transport is HTTP 258 | <%=getIndentation(indentationLevel + 2)%>self.useHTTP() 259 | <% 260 | // Attach any subnamespaces: 261 | children && Object.keys(children).forEach(function(childName) { 262 | %><%=getIndentation(indentationLevel + 2)%>self.<%=childName%> = self.<%=childName%>(self) 263 | <% 264 | }); 265 | } 266 | 267 | // Generate method stubs 268 | methods && methods.forEach(function (method) { 269 | var idx = method.lastIndexOf('.'); 270 | var shortName = idx != -1 ? method.substr(idx + 1) : method; 271 | var methodInfo = metadata.methods[method]; 272 | var returnType = methodInfo.returns ? mapPythonType({type: methodInfo.returns, isArray: methodInfo.returnsArray}, prefix) : "None"; 273 | %> 274 | <%=getIndentation(indentationLevel + 1)%>def <%- shortName %>(<%-getMethodArguments(methodInfo.params)%>):<% 275 | %><% generateMethodDocString(methodInfo, indentationLevel + 2, prefix) %><% 276 | 277 | if (root.prefix) { 278 | %> 279 | <%=getIndentation(indentationLevel + 2)%>return self.root._rpc('<%-method%>', [<%-getMethodArguments(methodInfo.params, true)%>], return_type=<%= returnType %>) 280 | <% 281 | } else { 282 | %> 283 | <%=getIndentation(indentationLevel + 2)%>return self._rpc('<%-method%>', [<%-getMethodArguments(methodInfo.params, true)%>], return_type=<%= returnType %>) 284 | <% 285 | } 286 | }); 287 | } 288 | %># 289 | # <%= metadata.name %> <%= metadata.version %> 290 | # 291 | # Part of the JSON-WS library - Python Proxy 292 | # Copyright (c) 2014 ChaosGroup. All rights reserved. 293 | # 294 | # This code uses the following libraries: 295 | # - autobahn.asyncio.websocket (https://pypi.python.org/pypi/autobahn/0.9.3) 296 | # 297 | # For asyncio and enum support in Python <= 3.3 install trollius and enum34: 298 | # - https://pypi.python.org/pypi/trollius/1.0.2 299 | # - https://pypi.python.org/pypi/enum34/1.0.3 300 | 301 | 302 | from datetime import datetime 303 | from rpctunnel import RpcTunnel, Optional, Type, Enum 304 | 305 | 306 | class <%= localName %>: 307 | <%=getIndentation(1)%>''' 308 | <%=getIndentation(1)%>Proxy for json-ws web services. Instances can be used as context managers. 309 | <%=getIndentation(1)%>''' 310 | <% generateTypes() %> 311 | <% generateMethodStubs(namespaceRoot) %> 312 | <%=getIndentation(1)%>def __enter__(self): 313 | <%=getIndentation(2)%>return self 314 | 315 | <%=getIndentation(1)%>def __exit__(self, type, value, traceback): 316 | <%=getIndentation(2)%>pass 317 | 318 | <%=getIndentation(1)%>def useHTTP(self): 319 | <%=getIndentation(2)%>self._rpc.useHTTP() 320 | 321 | <%=getIndentation(1)%>def useWS(self): 322 | <%=getIndentation(2)%>self._rpc.useWS() 323 | <% generateEventStubs() %> 324 | -------------------------------------------------------------------------------- /proxies/stubs/csharp/RpcTunnel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Net; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using Newtonsoft.Json; 10 | using Newtonsoft.Json.Linq; 11 | using WebSocket4Net; 12 | using ErrorEventArgs = SuperSocket.ClientEngine.ErrorEventArgs; 13 | 14 | namespace ChaosGroup.JsonWS.Proxies 15 | { 16 | /// 17 | /// Copyright (c) Chaos Software Ltd. 2013-2016. All rights reserved. 18 | /// 19 | class RpcTunnel : IDisposable 20 | { 21 | private bool _isDisposed; 22 | 23 | private int _nextMessageId; 24 | private readonly Dictionary _transports = new Dictionary(); 25 | 26 | public RpcTunnel(string httpUrl, IRpcEventHandler rpcEventHandler) 27 | { 28 | _transports.Add(RpcTransport.HTTP, new HttpTransport(httpUrl)); 29 | _transports.Add(RpcTransport.WebSocket, new WebSocketTransport(httpUrl, rpcEventHandler)); 30 | } 31 | 32 | public void Dispose() 33 | { 34 | if (_isDisposed) 35 | { 36 | return; 37 | } 38 | _isDisposed = true; 39 | foreach (var transport in _transports.Values) 40 | { 41 | transport.Dispose(); 42 | } 43 | GC.SuppressFinalize(this); 44 | } 45 | 46 | public Task Call(string method, Object[] parameters, bool expectReturn, RpcTransport rpcTransport) 47 | { 48 | var json = new JObject(); 49 | json["jsonrpc"] = "2.0"; 50 | if (expectReturn) 51 | { 52 | json["id"] = Interlocked.Increment(ref _nextMessageId); 53 | } 54 | json["method"] = method; 55 | json["params"] = JToken.FromObject(parameters); 56 | return _transports[rpcTransport].SendMessage(json); 57 | } 58 | } 59 | 60 | public enum RpcTransport 61 | { 62 | HTTP, 63 | WebSocket 64 | } 65 | 66 | public class RpcMessage 67 | { 68 | public RpcMessage(JObject json) 69 | { 70 | JsonMessage = json; 71 | } 72 | 73 | public RpcMessage(byte[] bytes) 74 | { 75 | BinaryMessage = bytes; 76 | } 77 | 78 | public JObject JsonMessage { get; private set; } 79 | 80 | public byte[] BinaryMessage { get; private set; } 81 | 82 | public Exception Error 83 | { 84 | get 85 | { 86 | if (JsonMessage != null && JsonMessage["error"] != null) 87 | { 88 | throw new Exception(JsonMessage["error"].ToString()); 89 | } 90 | return null; 91 | } 92 | } 93 | 94 | public T GetResult() 95 | { 96 | if (Error != null) return default(T); 97 | if (JsonMessage == null) return (T)Convert.ChangeType(BinaryMessage, typeof(T)); 98 | if (typeof(T).IsPrimitive) 99 | { 100 | return JsonMessage["result"].Value(); 101 | } 102 | if (typeof(T) == typeof(byte[])) 103 | { 104 | return (T)Convert.ChangeType(Convert.FromBase64String(JsonMessage["result"].ToString()), typeof(T)); 105 | } 106 | return (T)JsonConvert.DeserializeObject(JsonMessage["result"].ToString(), typeof(T)); 107 | } 108 | } 109 | 110 | public interface IRpcEventHandler 111 | { 112 | void HandleRpcEvent(String eventId, JToken eventData); 113 | } 114 | 115 | internal interface IRpcTransport : IDisposable 116 | { 117 | Task SendMessage(JObject message); 118 | } 119 | 120 | internal class HttpTransport : IRpcTransport 121 | { 122 | private readonly Uri _uri; 123 | 124 | public HttpTransport(string httpUrl) 125 | { 126 | _uri = new Uri(httpUrl); 127 | } 128 | 129 | public Task SendMessage(JObject message) 130 | { 131 | return Task.Factory.StartNew(() => 132 | { 133 | var postBytes = Encoding.Default.GetBytes(message.ToString()); 134 | var webReq = (HttpWebRequest)WebRequest.Create(_uri); 135 | webReq.Method = "POST"; 136 | webReq.ContentType = "application/json"; 137 | webReq.ContentLength = postBytes.Length; 138 | 139 | using (var postStream = webReq.GetRequestStream()) 140 | { 141 | postStream.Write(postBytes, 0, postBytes.Length); 142 | } 143 | try 144 | { 145 | using (var response = webReq.GetResponse()) 146 | using (var responseStream = response.GetResponseStream()) 147 | { 148 | var isResponseDataJson = response.ContentType.StartsWith("application/json", 149 | StringComparison.InvariantCultureIgnoreCase); 150 | return isResponseDataJson 151 | ? new RpcMessage(ReadJsonInputStream(responseStream)) 152 | : new RpcMessage(ReadBinaryInputStream(responseStream)); 153 | } 154 | } 155 | catch (WebException ex) 156 | { 157 | if (ex.Response != null && ex.Response.ContentLength != 0) 158 | { 159 | var isResponseDataJson = ex.Response.ContentType.StartsWith("application/json", 160 | StringComparison.InvariantCultureIgnoreCase); 161 | var statusCode = ((HttpWebResponse)ex.Response).StatusCode; 162 | if (isResponseDataJson && statusCode == HttpStatusCode.InternalServerError) 163 | { 164 | using (var responseStream = ex.Response.GetResponseStream()) 165 | { 166 | return new RpcMessage(ReadJsonInputStream(responseStream)); 167 | } 168 | } 169 | } 170 | throw new InvalidOperationException("Failed to open HTTP transport.", ex); ; 171 | } 172 | }, TaskCreationOptions.LongRunning | TaskCreationOptions.PreferFairness); 173 | } 174 | 175 | public void Dispose() 176 | { 177 | } 178 | 179 | private static JObject ReadJsonInputStream(Stream stream) 180 | { 181 | using (var reader = new StreamReader(stream)) 182 | { 183 | return JObject.Parse(reader.ReadToEnd()); 184 | } 185 | } 186 | 187 | private static byte[] ReadBinaryInputStream(Stream stream) 188 | { 189 | var buffer = new byte[16 * 1024]; 190 | using (var ms = new MemoryStream()) 191 | { 192 | int read; 193 | while ((read = stream.Read(buffer, 0, buffer.Length)) > 0) 194 | { 195 | ms.Write(buffer, 0, read); 196 | } 197 | return ms.ToArray(); 198 | } 199 | } 200 | } 201 | 202 | internal class WebSocketTransport : IRpcTransport 203 | { 204 | /// 205 | /// Keeps track of async method invocations 206 | /// 207 | private readonly ConcurrentDictionary _messageLocks = 208 | new ConcurrentDictionary(); 209 | 210 | /// 211 | /// Keeps track of async method results 212 | /// 213 | private readonly ConcurrentDictionary _messageResults = new ConcurrentDictionary(); 214 | 215 | private readonly ManualResetEvent _openEvent = new ManualResetEvent(false); 216 | private readonly WebSocket _webSocketClient; 217 | 218 | private bool _isClosed; 219 | private Exception _lastError; 220 | private readonly IRpcEventHandler _rpcEventHandler; 221 | 222 | public WebSocketTransport(string httpUrl, IRpcEventHandler rpcEventHandler) 223 | { 224 | httpUrl = httpUrl.Replace("http://", "ws://").Replace("https://", "wss://"); 225 | _rpcEventHandler = rpcEventHandler; 226 | _webSocketClient = new WebSocket(httpUrl); 227 | _webSocketClient.Opened += webSocketClient_Opened; 228 | _webSocketClient.MessageReceived += webSocketClient_MessageReceived; 229 | _webSocketClient.Closed += webSocketClient_Closed; 230 | _webSocketClient.Error += webSocketClient_Error; 231 | _webSocketClient.Open(); 232 | } 233 | 234 | private void WaitForOpenedOrFail() 235 | { 236 | if (_webSocketClient.State == WebSocketState.Open) 237 | { 238 | return; 239 | } 240 | 241 | if (_lastError != null) 242 | { 243 | throw new InvalidOperationException("Failed to open the web socket transport.", _lastError); 244 | } 245 | 246 | _openEvent.WaitOne(); 247 | 248 | if (_lastError != null || _webSocketClient.State != WebSocketState.Open) 249 | { 250 | throw new InvalidOperationException("Failed to open the web socket transport.", _lastError); 251 | } 252 | } 253 | 254 | public Task SendMessage(JObject message) 255 | { 256 | return Task.Factory.StartNew(() => 257 | { 258 | WaitForOpenedOrFail(); 259 | lock (_webSocketClient) 260 | { 261 | _webSocketClient.Send(message.ToString()); 262 | } 263 | if (message["id"] == null) 264 | { 265 | return null; 266 | } 267 | 268 | var id = (int)message["id"]; 269 | if (!_messageResults.ContainsKey(id)) 270 | { 271 | var autoResetEvent = new AutoResetEvent(false); 272 | _messageLocks[id] = autoResetEvent; 273 | autoResetEvent.WaitOne(); 274 | } 275 | JObject result; 276 | return _messageResults.TryRemove(id, out result) ? new RpcMessage(result) : null; 277 | }, TaskCreationOptions.LongRunning | TaskCreationOptions.PreferFairness); 278 | } 279 | 280 | public void Dispose() 281 | { 282 | if (_isClosed) 283 | { 284 | return; 285 | } 286 | _isClosed = true; 287 | _openEvent.Set(); 288 | //_openEvent.Reset(); 289 | if (_webSocketClient.State == WebSocketState.Open) 290 | { 291 | _webSocketClient.Close(); 292 | } 293 | GC.SuppressFinalize(this); 294 | } 295 | 296 | private void webSocketClient_Opened(object sender, EventArgs e) 297 | { 298 | _openEvent.Set(); 299 | } 300 | 301 | private void webSocketClient_Error(object sender, ErrorEventArgs e) 302 | { 303 | _lastError = e.Exception; 304 | _openEvent.Set(); 305 | } 306 | 307 | private void webSocketClient_Closed(object sender, EventArgs e) 308 | { 309 | Dispose(); 310 | } 311 | 312 | private void webSocketClient_MessageReceived(object sender, MessageReceivedEventArgs e) 313 | { 314 | var json = JObject.Parse(e.Message); 315 | if (json["id"] == null) 316 | { 317 | return; 318 | } 319 | 320 | if (json["id"].Type == JTokenType.Integer) 321 | { 322 | var id = (int)json["id"]; 323 | _messageResults.TryAdd(id, json); 324 | AutoResetEvent autoResetEvent; 325 | if (_messageLocks.TryRemove(id, out autoResetEvent)) 326 | { 327 | autoResetEvent.Set(); 328 | } 329 | } 330 | else if (_rpcEventHandler != null) 331 | { 332 | _rpcEventHandler.HandleRpcEvent((string)json["id"], json["result"]); 333 | } 334 | } 335 | } 336 | } 337 | -------------------------------------------------------------------------------- /proxies/JavaScript.ejs: -------------------------------------------------------------------------------- 1 | <% 2 | var metadata = locals.metadata; 3 | var localName = locals.localName; 4 | %>/** 5 | * @module <%=localName%> 6 | */ 7 | (function (module, define) { 8 | // using non-strict mode, otherwise the re-assignment of require would throw TypeError 9 | if (typeof require !== 'function') { 10 | require = module.require; 11 | } 12 | 13 | var EventEmitter = require('events'); 14 | var inherits = require('util').inherits; 15 | var RpcTunnel = require('json-ws/client'); 16 | 17 | /** 18 | * @param {(url|Transport)} url - Either url of server (string) or Transport instance to be used as a sole transport. 19 | * @constructor 20 | * @alias module:<%=localName%>.<%=localName%> 21 | */ 22 | var <%=localName%> = module.exports.<%=localName%> = function <%=localName%>(url, settings) { 23 | if (!this instanceof <%=localName%>) { 24 | return new <%=localName%>(url); 25 | } 26 | if (!url) { 27 | throw new Error('Invalid proxy URL'); 28 | } 29 | this.rpc = new RpcTunnel(url, settings); 30 | if (typeof url !== 'string') { 31 | this._transport = url; 32 | } else { 33 | this._transport = this.rpc.transports.http; 34 | } 35 | var self = this; 36 | this.rpc.on('event', function(e) { 37 | self.emit(e.name, e.data); 38 | }); 39 | function rebind(obj) { 40 | var result = {}; 41 | for (var i in obj) { 42 | var prop = obj[i]; 43 | if (typeof prop === 'function') { 44 | result[i] = prop.bind(self); 45 | } else if (typeof prop === 'object') { 46 | result[i] = rebind(prop); 47 | } 48 | } 49 | return result; 50 | } 51 | for (var i in this) { 52 | if (this[i] && this[i]._ns) { 53 | this[i] = rebind(this[i]); 54 | } 55 | } 56 | }; 57 | inherits(<%=localName%>, EventEmitter); 58 | Object.defineProperty(<%= localName %>, 'VERSION', { value: '<%= metadata.version.slice(0, metadata.version.lastIndexOf('.')) %>'}); 59 | 60 | <%=localName%>.prototype.useHTTP = function() { 61 | if (!this.rpc.transports.http) { 62 | throw new Error('HTTP transport requested, but ' + this._transport.name + ' given.'); 63 | } 64 | this._transport = this.rpc.transports.http; 65 | return this; 66 | }; 67 | 68 | <%=localName%>.prototype.useWS = function() { 69 | if (!this.rpc.transports.ws) { 70 | throw new Error('WebSocket transport requested, but ' + this._transport.name + ' given.'); 71 | } 72 | this._transport = this.rpc.transports.ws; 73 | return this; 74 | }; 75 | 76 | <%=localName%>.prototype.close = function() { 77 | this.rpc.close(); 78 | }; 79 | 80 | <%=localName%>.prototype.on = <%=localName%>.prototype.addListener = function(type, listener) { 81 | if (this.listeners(type).length == 0) { 82 | this.rpc.call({ method: 'rpc.on', params: [type], transport: this.rpc.transports.http ? 'ws' : this._transport.name }); 83 | } 84 | EventEmitter.prototype.addListener.call(this, type, listener); 85 | }; 86 | 87 | <%=localName%>.prototype.removeListener = function(type, listener) { 88 | EventEmitter.prototype.removeListener.call(this, type, listener); 89 | if (this.listeners(type).length == 0) { 90 | this.rpc.call({ method: 'rpc.off', params: [type], transport: this.rpc.transports.http ? 'ws' : this._transport.name }); 91 | } 92 | }; 93 | 94 | <%=localName%>.prototype.removeAllListeners = function(type) { 95 | EventEmitter.prototype.removeAllListeners.call(this, type); 96 | this.rpc.call({ method: 'rpc.off', params: [type], transport: this.rpc.transports.http ? 'ws' : this._transport.name }); 97 | }; 98 | <% 99 | 100 | var typedefs = Object.keys(metadata.types); 101 | 102 | function getFullTypeName(type) { 103 | if (!type) { 104 | return String(type); 105 | } 106 | return typedefs.indexOf(type) !== -1 ? 'module:' + localName + '.' + localName + '.' + type : type; 107 | } 108 | 109 | (function generateTypes() { 110 | var types = Object.keys(metadata.types).map(function(key) { return metadata.types[key] }); 111 | types.forEach(function(type) { 112 | typedefs.push(type.name); 113 | if (type.enum) { %> 114 | <%=localName%>.<%-type.name%> = function (val) { 115 | switch (val) { 116 | <%-Object.keys(type.struct).map(function(key) { return 'case \'' + key + '\': return ' + type.struct[key] }).join(';\n\t\t\t')%>; 117 | <%-Object.keys(type.struct).map(function(key) { return 'case ' + type.struct[key] + ': return \'' + key + '\'' }).join(';\n\t\t\t')%>; 118 | } 119 | }; 120 | 121 | /** <% 122 | if (type.description) { %> 123 | * <%-type.description%> <% 124 | }%> 125 | * @enum {number} 126 | * @alias module:<%-localName%>.<%-localName%>.<%-type.name%> 127 | */ 128 | var <%=type.name%> = { 129 | <%-Object.keys(type.struct).map(function(key) { return key + ': ' + type.struct[key] }).join(',\n\t\t')%> 130 | }; 131 | 132 | <%-Object.keys(type.struct).map(function(key) { return localName + '.' + type.name + '.' + key + ' = ' + type.struct[key] }).join(';\n\t')%> 133 | Object.freeze(<%=localName%>.<%-type.name%>); 134 | <% 135 | } else { %> 136 | /** <% 137 | if (type.description) { %> 138 | * <%-type.description%> <% 139 | } %> 140 | * @typedef {Object} module:<%=localName%>.<%=localName%>.<%-type.name%> 141 | <%-Object.keys(type.struct).map(function(key) { 142 | var struct = type.struct[key]; 143 | return '* @property' + ' {' + getFullTypeName(struct.type) + '} ' + getParameterNameWithOptional(struct) + ' ' + struct.description; 144 | }).join('\n\t ')%> 145 | */ 146 | 147 | <% 148 | } 149 | }); 150 | }()) 151 | 152 | function printArgs(methodName) { 153 | return metadata.methods[methodName].params.map(function(p) { return p.name }).join(', '); 154 | } 155 | 156 | var stubs = {}; 157 | function stubNamespace(ns) { 158 | var dot = ns.lastIndexOf('.'); 159 | if (dot != -1) stubNamespace(ns.substr(0, dot)); 160 | if (stubs[ns]) return; 161 | stubs[ns] = 1; 162 | %> <%=localName%>.prototype.<%=ns%> = {_ns:true}; 163 | <% 164 | } 165 | 166 | // Generate namespace stubs 167 | var namespaces = []; 168 | Object.keys(metadata.methods).forEach(function(methodName) { 169 | var dot = methodName.lastIndexOf('.'); 170 | if (dot == -1) return; 171 | var namespace = methodName.substr(0, dot); 172 | if (namespaces.indexOf(namespace) == -1) namespaces.push(namespace); 173 | }); 174 | %><%-namespaces.forEach(stubNamespace)%> 175 | <% 176 | 177 | function mapJavaScriptType(jsType, isArray) { 178 | var returnType = ''; 179 | switch (jsType) { 180 | case '*': 181 | case 'any': 182 | case 'object': 183 | case 'json': 184 | returnType = 'object'; 185 | break; 186 | 187 | case 'int': 188 | case 'integer': 189 | case 'number': 190 | case 'float': 191 | case 'double': 192 | returnType = 'number'; 193 | break; 194 | 195 | case 'date': 196 | case 'time': 197 | returnType = 'Date'; 198 | break; 199 | 200 | case 'bool': 201 | case 'boolean': 202 | returnType = 'boolean'; 203 | break; 204 | 205 | 206 | case 'string': 207 | case 'url': 208 | returnType = 'string'; 209 | break; 210 | 211 | case 'buffer': 212 | case 'binary': 213 | returnType = 'Uint8Array'; 214 | break; 215 | 216 | case undefined: 217 | returnType = ''; 218 | break; 219 | 220 | default: 221 | returnType = getFullTypeName(jsType); 222 | } 223 | return returnType + (isArray ? '[]' : ''); 224 | } 225 | 226 | function getParameterNameWithOptional(param) { 227 | if (param.required === false || typeof param.default !== 'undefined') { 228 | return '[' + param.name + (param.default !== undefined ? '=' + JSON.stringify(param.default) : '') + ']'; 229 | } 230 | 231 | return param.name; 232 | } 233 | 234 | function resolveMethodName(methodName) { 235 | return methodName.indexOf('.') !== -1 ? ' "' + methodName + '"' : methodName; 236 | } 237 | 238 | function getMethodArgumentsHelp(methodInfo, length) { 239 | length = typeof length === 'number' ? length : methodInfo.params.length; 240 | return methodInfo.params.slice(0, length).map(function(param) { 241 | return '\n\t * @param {' + mapJavaScriptType(param.type, param.isArray) + '} ' + getParameterNameWithOptional(param) + ' ' + param.description; 242 | }).join(''); 243 | } 244 | // Generate method stubs 245 | Object.keys(metadata.methods).forEach(function(methodName) { 246 | var methodInfo = metadata.methods[methodName]; 247 | %> /** 248 | * <%=methodInfo.description%> 249 | * @function 250 | * @name module:<%=localName%>.<%=localName%>#<%-resolveMethodName(methodName)%><%=getMethodArgumentsHelp(methodInfo)%> 251 | <%= (methodInfo.returns) ? '\t * @returns {' + mapJavaScriptType(methodInfo.returns, methodInfo.returnsArray) + '}\n' : '' 252 | %> */ 253 | <%=localName%>.prototype.<%= methodName %> = function(<%-printArgs(methodName)%>) { 254 | var args = Array.prototype.slice.call(arguments); 255 | var callback = null; 256 | if (args.length && typeof args[args.length - 1] === 'function') { 257 | callback = args.pop(); 258 | } 259 | <%if (metadata.methods[methodName].params.length > 0) { %>args.length = Math.min(<%-metadata.methods[methodName].params.length%>, args.length);<% } else { %>args.length = 0;<% } %> 260 | return this.rpc.call({ 261 | method: '<%-methodName%>', 262 | params: args, 263 | expectReturn: <%-!!metadata.methods[methodName].returns || !!metadata.methods[methodName].async%>, 264 | transport: this._transport.name 265 | }, callback); 266 | }; 267 | 268 | <% 269 | }); 270 | 271 | (function generateEvents() { 272 | Object.keys(metadata.events).forEach(function(eventName) { 273 | var eventInfo = metadata.events[eventName]; 274 | %> /** <% 275 | if (eventInfo.description) { %> 276 | * <%-eventInfo.description%> 277 | * <% 278 | } %> 279 | * @event module:<%=localName%>.<%=localName%>."<%=eventInfo.name%>" 280 | * @type <%-getFullTypeName(eventInfo.type)%> 281 | */ 282 | 283 | <% 284 | })%> 285 | define('<%-localName%>', <%-localName%>); 286 | 287 | <%})() 288 | 289 | %>}.apply(null, (function() { 290 | 'use strict'; 291 | 292 | if (typeof module !== 'undefined') { 293 | // node.js and webpack 294 | return [module, function() {}]; 295 | } 296 | 297 | if (typeof window !== 'undefined') { 298 | // browser 299 | if (typeof window.jsonws === 'undefined') { 300 | throw new Error('No json-ws polyfills found.'); 301 | } 302 | 303 | var jsonws = window.jsonws; 304 | 305 | return [jsonws, jsonws.define]; 306 | } 307 | 308 | throw new Error('Unknown environment, this code should be used in node.js/webpack/browser'); 309 | }()))); 310 | -------------------------------------------------------------------------------- /proxies/Java.ejs: -------------------------------------------------------------------------------- 1 | <% // Code Generator utilities 2 | var metadata = locals.metadata; 3 | var localName = locals.localName; 4 | 5 | function mapJavaType(jsType, isArray) { 6 | var returnType = ''; 7 | switch (jsType) { 8 | case '*': 9 | case 'any': 10 | returnType = 'JsonElement'; 11 | break; 12 | 13 | case 'int': 14 | case 'integer': 15 | returnType = 'Long'; 16 | break; 17 | 18 | case 'date': 19 | case 'time': 20 | returnType = 'Date'; 21 | break; 22 | 23 | case 'number': 24 | case 'float': 25 | case 'double': 26 | returnType = 'Double'; 27 | break; 28 | 29 | case 'bool': 30 | case 'boolean': 31 | returnType = 'Boolean'; 32 | break; 33 | 34 | case 'object': 35 | case 'json': 36 | returnType = 'JsonObject'; 37 | break; 38 | 39 | case 'string': 40 | returnType = 'String'; 41 | break; 42 | 43 | case 'url': 44 | returnType = 'URL'; 45 | break; 46 | 47 | case 'buffer': 48 | case 'binary': 49 | case 'stream': 50 | returnType = 'byte[]'; 51 | break; 52 | 53 | case undefined: 54 | returnType = 'void'; 55 | break; 56 | 57 | default: 58 | returnType = metadata.types[jsType] ? jsType : ('/*' + jsType + '*/Object'); 59 | } 60 | return returnType + (isArray ? '[]' : ''); 61 | } 62 | 63 | function camelCase(literal) { 64 | return literal.substr(0, 1).toUpperCase() + literal.substr(1); 65 | } 66 | 67 | function generateTypes() { 68 | var types = Object.keys(metadata.types).map(function(key) { return metadata.types[key] }); 69 | types.forEach(function(type) { 70 | if (type.enum) { 71 | %>/** 72 | * <%=type.description%> 73 | */ 74 | public static enum <%-type.name%> { 75 | <%-Object.keys(type.struct).join(', ')%> 76 | } 77 | 78 | <% 79 | } else { 80 | var properties = Object.keys(type.struct).map(function(key) { return type.struct[key] }); 81 | %>/** 82 | * <%=type.description%> 83 | */ 84 | public static class <%-type.name%> extends BaseRpcObject { 85 | <%-properties.map(function(property) { 86 | return '// ' + property.description + '\n' + 87 | '\t\tpublic ' + mapJavaType(property.type, property.isArray) + ' ' + property.name + ';' 88 | }).join('\n\t\t')%> 89 | } 90 | 91 | <% 92 | } 93 | }); 94 | } 95 | 96 | function convertJsonToJavaType(jsonElement, javaType) { 97 | switch (javaType) { 98 | case 'Integer': return jsonElement + '.getAsInt()'; 99 | case 'Double': return jsonElement + '.getAsDouble()'; 100 | case 'Date': return 'javax.xml.bind.DatatypeConverter.parseDateTime(' + jsonElement + '.getAsString()).getTime()'; 101 | case 'Boolean': return jsonElement + '.getAsBoolean()'; 102 | case 'String': return jsonElement + '.getAsString()'; 103 | case 'URL': return 'new URL(' + jsonElement + '.getAsString())'; 104 | case 'JsonElement': return jsonElement; 105 | case 'JsonObject': return jsonElement + '.getAsJsonObject()'; 106 | case 'Void': return 'null'; 107 | case 'byte[]': return 'javax.xml.bind.DatatypeConverter.parseBase64Binary(' + jsonElement + '.getAsString())'; 108 | default: return '(' + javaType + ') new Gson().fromJson(' + jsonElement + ', ' + javaType + '.class)'; 109 | } 110 | } 111 | 112 | function generateEventStubs() { 113 | var events = Object.keys(metadata.events).map(function(key) { return metadata.events[key] }); 114 | var eventInfos = {}; 115 | 116 | events.forEach(function(event) { 117 | var camelCasedName = camelCase(event.name).replace(/\./ig, '_'); 118 | var normalizedName = event.name.replace(/\./ig, '_'); 119 | var eventData = event.type ? (mapJavaType(event.type, event.isArray) + ' data') : ''; 120 | eventInfos[event.name] = { 121 | camelCasedName: camelCasedName, 122 | normalizedName: normalizedName 123 | } 124 | %>private <%-camelCasedName%>Handler <%-normalizedName%>Handler; 125 | public interface <%-camelCasedName%>Handler { 126 | public static final String Name = "<%-event.name%>"; 127 | void on<%-camelCasedName%>(<%-eventData%>); 128 | } 129 | public void on<%-camelCasedName%>(<%-camelCasedName%>Handler eventHandler) { 130 | <%-normalizedName%>Handler = eventHandler; 131 | rpcTunnel.call(<%-normalizedName%>Handler == null ? "rpc.off" : "rpc.on", 132 | new Object[] { <%-camelCasedName%>Handler.Name }, false, RpcTunnel.Transport.WebSocket); 133 | } 134 | 135 | <% 136 | }); 137 | 138 | %> 139 | @SuppressWarnings("FieldCanBeLocal") 140 | private final RpcTunnel.EventHandler rpcEventHandler = new RpcTunnel.EventHandler() { 141 | @Override 142 | public void onEvent(String eventId, JsonElement eventData) { 143 | switch (eventId) { 144 | <% 145 | events.forEach(function(event) { 146 | var eventInfo = eventInfos[event.name]; 147 | var eventData = event.type ? convertJsonToJavaType('eventData', mapJavaType(event.type, event.isArray)) : ''; 148 | %> 149 | case <%-eventInfo.camelCasedName%>Handler.Name: 150 | if (<%-eventInfo.normalizedName%>Handler != null) { 151 | <%-eventInfo.normalizedName%>Handler.on<%-eventInfo.camelCasedName%>(<%-eventData%>); 152 | } 153 | break; 154 | <% }); %> 155 | } 156 | } 157 | }; 158 | 159 | <% 160 | } 161 | 162 | function generateMethodStubs(root) { 163 | if (!root) { return; } 164 | 165 | var methods = root.methods; 166 | var children = root.children; 167 | 168 | if (root.prefix) { %> 169 | public final class <%=root.prefix%> {<% } 170 | // Generate code for sub-namespaces 171 | children && Object.keys(children).forEach(function(key) { 172 | generateMethodStubs(children[key]); 173 | }); 174 | 175 | // Generate method stubs 176 | methods && methods.forEach(function (method) { 177 | var idx = method.lastIndexOf('.'); 178 | var shortName = idx != -1 ? method.substr(idx + 1) : method; 179 | var methodInfo = metadata.methods[method]; 180 | var pureReturnType = methodInfo.returns ? mapJavaType(methodInfo.returns, methodInfo.returnsArray) : "Void"; 181 | var returnType = (methodInfo.returns || methodInfo.async) ? ("ProxyFuture<" + pureReturnType + ">") : "void"; 182 | 183 | var requiredParamsCount = methodInfo.params.reduce(function(prev, param) { return prev + (param.default === undefined ? 1 : 0) }, 0); 184 | for (var paramIdx = requiredParamsCount; paramIdx <= methodInfo.params.length; paramIdx++) { 185 | %> 186 | /** 187 | * <%=methodInfo.description%><%=getMethodArgumentsHelp(methodInfo, paramIdx)%> 188 | */ 189 | public <%-returnType%> <%-shortName%>(<%-getMethodArguments(methodInfo, true, paramIdx)%>) { 190 | <% if (returnType !== 'void') { %>return new <%-returnType%>(rpcTunnel.call("<%-method%>", 191 | new Object[] { <%-getMethodArguments(methodInfo, false, paramIdx)%> }, true, defaultTransport))<% 192 | %> { 193 | @Override 194 | protected <%-pureReturnType%> convert(JsonElement result) { 195 | return <%-convertJsonToJavaType('result', pureReturnType)%>; 196 | } 197 | };<% 198 | } else { 199 | %>rpcTunnel.call("<%-method%>", new Object[] { <%-getMethodArguments(methodInfo, false, paramIdx)%> }, false, defaultTransport);<% 200 | } %> 201 | } 202 | <% 203 | } // for (var paramIdx) 204 | }); 205 | 206 | if (root.prefix) { %> 207 | } 208 | public final <%=root.prefix%> <%=root.prefix%> = new <%=root.prefix%>(); 209 | <% } 210 | } 211 | 212 | function getMethodArguments(methodInfo, includeTypes, length) { 213 | length = typeof length === 'number' ? length : methodInfo.params.length; 214 | return methodInfo.params.slice(0, length).map(function(param) { 215 | var paramName = param.name; 216 | if (param.type === 'binary' || param.type === 'buffer' || param.type === 'stream') { 217 | paramName = 'javax.xml.bind.DatatypeConverter.printBase64Binary(' + paramName + ')'; 218 | } 219 | return includeTypes ? mapJavaType(param.type, param.isArray) + ' ' + param.name : paramName; 220 | }).join(', '); 221 | } 222 | 223 | function getMethodArgumentsHelp(methodInfo, length) { 224 | length = typeof length === 'number' ? length : methodInfo.params.length; 225 | return methodInfo.params.slice(0, length).map(function(param) { 226 | return '\n\t * @param ' + param.name + ' ' + param.description; 227 | }).join(''); 228 | } 229 | 230 | // Generate namespace tree 231 | var namespaceRoot = { 232 | name: "", 233 | fullName: "", 234 | methods: [], 235 | children: {} 236 | }; 237 | Object.keys(metadata.methods).forEach(function(methodName) { 238 | var method = metadata.methods[methodName]; 239 | var $ = namespaceRoot; 240 | var prefixes = method.name.split('.'); 241 | prefixes.pop(); 242 | prefixes.forEach(function(prefix) { 243 | if (!$.children.hasOwnProperty(prefix)) { 244 | $.children[prefix] = { 245 | prefix: prefix, 246 | methods: [], 247 | children: {} 248 | }; 249 | } 250 | $ = $.children[prefix]; 251 | }); 252 | $.methods.push(method.name); 253 | }); 254 | 255 | %>/** 256 | * <%= metadata.name %> <%= metadata.version %> 257 | * 258 | * Part of the JSON-WS library - Java 1.7 Proxy 259 | * Copyright (c) 2013-2014 ChaosGroup. All rights reserved. 260 | * 261 | * This code uses the following libraries 262 | * - com.google.code.gson:gson:2.2.4 263 | * - org.java-websocket:Java-WebSocket:1.3.0 264 | */ 265 | 266 | package com.chaosgroup.jsonws.proxies.<%=localName%>; 267 | 268 | import java.net.MalformedURLException; 269 | import java.net.URISyntaxException; 270 | import java.net.URL; 271 | import com.google.gson.*; 272 | 273 | /** 274 | * <%= metadata.name %> <%= metadata.version %> Proxy 275 | */ 276 | public class <%=localName%> implements AutoCloseable { 277 | 278 | // RPC tunnel - all method calls are piped here 279 | private RpcTunnel rpcTunnel; 280 | // The transport mechanism used by the tunnel for each method call 281 | private RpcTunnel.Transport defaultTransport = RpcTunnel.Transport.HTTP; 282 | 283 | /** 284 | * Constructs a new proxy using the specified URL 285 | * @param url Full URL of the web service endpoint. 286 | * @throws java.net.MalformedURLException 287 | * @throws java.net.URISyntaxException 288 | */ 289 | public <%=localName%>(String url) throws MalformedURLException, URISyntaxException { 290 | rpcTunnel = new RpcTunnel(url, rpcEventHandler); 291 | } 292 | 293 | /** 294 | * Sets the default transport mechanism to HTTP 295 | */ 296 | public <%=localName%> useHTTP() { 297 | defaultTransport = RpcTunnel.Transport.HTTP; 298 | return this; 299 | } 300 | 301 | /** 302 | * Sets the default transport mechanism to WebSocket 303 | */ 304 | public <%=localName%> useWS() { 305 | defaultTransport = RpcTunnel.Transport.WebSocket; 306 | return this; 307 | } 308 | 309 | @Override 310 | public void close() throws Exception { 311 | rpcTunnel.close(); 312 | } 313 | 314 | <% generateEventStubs() %> 315 | public static class BaseRpcObject { 316 | @Override 317 | public String toString() { 318 | return new Gson().toJson(this); 319 | } 320 | } 321 | 322 | <% generateTypes() %> 323 | 324 | <% generateMethodStubs(namespaceRoot) %> 325 | } 326 | --------------------------------------------------------------------------------