├── test ├── ip2tele │ ├── logs │ │ └── dummy │ ├── cfg.js │ ├── readme.md │ ├── ip2tele.js │ ├── fake_server.js │ └── ip2tele.wsdl ├── vac │ ├── logs │ │ └── vac_trace.xml │ ├── EVacSyncService_SPClient.wsdl │ └── vac.js ├── wsdl │ └── strict │ │ ├── CyberSourceTransaction_1.26_3.wsdl │ │ ├── CyberSourceTransaction_1.26.wsdl │ │ ├── CyberSourceTransaction_1.26_2.wsdl │ │ ├── stockquote.wsdl │ │ ├── logincms.wsdl │ │ ├── ip2tele.wsdl │ │ ├── EVacSyncService_SPClient.wsdl │ │ └── DE.wsdl └── server-test.js ├── .gitignore ├── index.js ├── package.json ├── lib ├── http.js ├── soap.js ├── client.js ├── server.js └── wsdl.js └── Readme.md /test/ip2tele/logs/dummy: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/soap'); 2 | -------------------------------------------------------------------------------- /test/ip2tele/cfg.js: -------------------------------------------------------------------------------- 1 | var cfg = { 2 | 'ServerID': 'tjcc', 3 | fake_port: '8008', 4 | wsdl_file: require('path').resolve("./ip2tele.wsdl"), 5 | path: '/webservice_iuim/services/QueryUserInfoServiceApply', 6 | site_port: 8012 7 | } 8 | module.exports = cfg; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "soap", 3 | "version": "0.2.4", 4 | "description": "A minimal node SOAP client", 5 | "engines": { "node": ">=0.8.0" }, 6 | "author": "Vinay Pulim ", 7 | "dependencies": { 8 | "node-expat": "git://github.com/TooTallNate/node-expat.git#gyp", 9 | "request": ">=2.9.0" 10 | }, 11 | "repository" : { 12 | "type":"git", 13 | "url":"https://github.com/milewise/node-soap.git" }, 14 | "main": "./index.js", 15 | "directories": { "lib": "./lib" }, 16 | "scripts": {"test": "mocha -R spec -u exports test/*-test.js"}, 17 | "keywords": ["soap"], 18 | "licenses": [{ 19 | "type" : "MIT License", 20 | "url" : "http://www.opensource.org/licenses/mit-license.php" }] 21 | } 22 | -------------------------------------------------------------------------------- /test/ip2tele/readme.md: -------------------------------------------------------------------------------- 1 | Document Style web service request and response test 2 | ---------- 3 | 4 | Introduction of Business 5 | ===== 6 | 7 | The test suite establish a web server, when browser access this web server from cellular mobile network of Tianjin CHINA UNICOM, the web server will make request to a web service that can tell which mobile number is accessing the web server. So any mobile site can easily determine the client's mobile number. 8 | 9 | test remarks 10 | ===== 11 | The production ip2tele web service is located at 3rd-party which can not guarantee availability all the time, but I provide a fake\_server(fake\_server.js), you can start the fake server and call ip2tele with args to specify call fake server instead of the real server. 12 | 13 | # for normal case to ask for production web-service at 3rd-party's 14 | node ip2tele.js 15 | 16 | # for closed test case to ask for fake web service withing this testing suite 17 | node fake_server.js & 18 | node ip2tele.js fake 19 | 20 | When you use your mobile phone to access the web server, the response page will tell you your mobile number and all the json request/response text. 21 | 22 | Since the WSDL use document style binding, I test the node\-soap module for document style. Notice that this soap module can allow single part message, server javascript will get only the internal contents of the top Element, but without the Element name. And client javascript should send internal contents of the top Element only, without TopElement:{internal content} form. -------------------------------------------------------------------------------- /lib/http.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Vinay Pulim 3 | * MIT Licensed 4 | */ 5 | 6 | var url = require('url'), 7 | req = require('request'); 8 | 9 | var VERSION = "0.2.0"; 10 | 11 | exports.request = function(rurl, data, callback, exheaders, exoptions) { 12 | var curl = url.parse(rurl); 13 | var secure = curl.protocol == 'https:'; 14 | var host = curl.hostname; 15 | var port = parseInt(curl.port || (secure ? 443 : 80)); 16 | var path = [curl.pathname || '/', curl.search || '', curl.hash || ''].join(''); 17 | var method = data ? "POST" : "GET"; 18 | var headers = { 19 | "User-Agent": "node-soap/" + VERSION, 20 | "Accept" : "text/html,application/xhtml+xml,application/xml", 21 | "Accept-Encoding": "none", 22 | "Accept-Charset": "utf-8", 23 | "Connection": "close", 24 | "Host" : host 25 | }; 26 | 27 | if (typeof data == 'string') { 28 | headers["Content-Length"] = Buffer.byteLength(data, 'utf8');; 29 | headers["Content-Type"] = "application/x-www-form-urlencoded"; 30 | } 31 | 32 | exheaders = exheaders || {}; 33 | for (var attr in exheaders) { headers[attr] = exheaders[attr]; } 34 | 35 | var options = { 36 | uri: curl, 37 | method: method, 38 | headers: headers 39 | }; 40 | 41 | exoptions = exoptions || {}; 42 | for (var attr in exoptions) { options[attr] = exoptions[attr]; } 43 | 44 | var request = req(options, function (error, res, body) { 45 | if (error) { 46 | callback(error); 47 | } else { 48 | callback(null, res, body); 49 | } 50 | }); 51 | request.on('error', callback); 52 | request.end(data); 53 | } 54 | 55 | -------------------------------------------------------------------------------- /test/vac/logs/vac_trace.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 1493804 9 | 10 | 860224922113 11 | 1 12 | 60123 13 | 4212345681 14 | 15 | 20120301092540 16 | 1 17 | 18 | 19 | 20120228163657 20 | 20500101000000 21 | 0328125033 22 | 23 | 24 | 8615620001781 25 | 26 | 1 27 | 28 | 29 | -------------------------------------------------------------------------------- /test/wsdl/strict/CyberSourceTransaction_1.26_3.wsdl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | CyberSource Web Service 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /test/wsdl/strict/CyberSourceTransaction_1.26.wsdl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | CyberSource Web Service 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /test/wsdl/strict/CyberSourceTransaction_1.26_2.wsdl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | CyberSource Web Service 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /test/ip2tele/ip2tele.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | var soap = require('../..'); 3 | var inspect = require('util').inspect; 4 | var url = require('url'); 5 | var path = require('path'); 6 | var ip2tele_client; 7 | var cfg = require('./cfg.js'); 8 | 9 | if (process.argv[2] || process.argv[2] === 'fake') { 10 | var endpoint = 'http://127.0.0.1:' + cfg.fake_port + cfg.path + '?wsdl'; 11 | var fake = 'fake'; 12 | } else { 13 | console.log('Usage : node ip2tele.js [fake]'); 14 | console.log('[fake] call fake service instead of the real production server.'); 15 | } 16 | 17 | soap.createClient(cfg.wsdl_file, 18 | function(err, client) { 19 | ip2tele_client = client; 20 | }, 21 | endpoint 22 | ); 23 | 24 | 25 | var web_server = http.createServer(function(req, res) { 26 | var cSock = req.connection; 27 | var sAddr = res.socket.address(); 28 | var QueryUserInfoRequest = { 29 | 'UserInfo': { 30 | "IP": cSock.remoteAddress, 31 | "Port": cSock.remotePort, 32 | 'ServerIP': sAddr.address, 33 | 'ServerPort': sAddr.port, 34 | 'SessionID': '', 35 | 'SKey': '' 36 | }, 37 | 'ServerInfo': { 38 | 'ServerID': cfg.ServerID, 39 | 'TimeStamp': +new Date 40 | } 41 | } 42 | console.log(QueryUserInfoRequest); 43 | 44 | ip2tele_client.QueryUserInfoServiceApply.QueryUserInfoServiceApplyHttpPort.QueryUserInfoServiceApply(QueryUserInfoRequest, 45 | function(err, QueryUserInfoResponse, body) { 46 | if (err) console.log(err); 47 | var path = url.parse(req.url).pathname; 48 | console.log(QueryUserInfoResponse); 49 | var tele = QueryUserInfoResponse.UserInfo[0].UserName; 50 | var loc = 'http://61.181.22.71:81/tjuc/get_tele_b.show_tele?tele=' + tele; 51 | if (path === '/') { 52 | res.writeHead(200, { 53 | 'Content-Type': 'text/html', 54 | 'Transfer-Encoding': 'chunked' 55 | }); 56 | res.write('

This is a call to ' + (fake || 'real') + ' ip2tele web service

'); 57 | res.write('

your tele is ' + tele + '

'); 58 | res.write('
your request is
' + inspect(QueryUserInfoRequest)); 59 | res.end('
your response is
' + inspect(QueryUserInfoResponse)); 60 | } else { 61 | res.writeHead(303, { 62 | "Location": loc 63 | }); 64 | res.end(); 65 | } 66 | }); 67 | 68 | }).listen(cfg.site_port); 69 | -------------------------------------------------------------------------------- /test/wsdl/strict/stockquote.wsdl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /test/ip2tele/fake_server.js: -------------------------------------------------------------------------------- 1 | var cfg = require('./cfg.js'); 2 | var soap = require('../..'); 3 | var path = require('path'); 4 | var http = require('http'); 5 | var QueryString = require('querystring'); 6 | var Request = require('request'); 7 | var fs = require('fs'); 8 | 9 | var SoapServices = { 10 | 'QueryUserInfoServiceApply': { 11 | 'QueryUserInfoServiceApplyHttpPort': { 12 | 'QueryUserInfoServiceApply': function(args) { 13 | console.log('received args = '); 14 | console.log(args); 15 | return ({ 16 | 'tns:ServerInfo': { 17 | 'ResultCode': '0', 18 | 'Description': 'success' 19 | }, 20 | 'tns:UserInfo': { 21 | 'UserName': '15620001781' 22 | } 23 | }); 24 | } 25 | } 26 | } 27 | } 28 | 29 | var server = http.createServer(function(req, res) { 30 | res.end("404: Not Found: " + request.url); 31 | }); 32 | 33 | 34 | server.listen(cfg.fake_port); 35 | var wsdl_string = require('fs').readFileSync(path.resolve(cfg.wsdl_file), 'utf8'); 36 | var soap_server = soap.listen(server, cfg.path, SoapServices, wsdl_string); 37 | soap_server.logger_req = function(xml, req, res) { 38 | req.__time = +new Date; 39 | var cip = req.connection.remoteAddress; 40 | var filename = 'logs/svr-' + req.__time + '-' + cip + '-req.log.xml'; 41 | var ws = fs.createWriteStream(filename); 42 | ws.write(xml); 43 | ws.end(); 44 | }; 45 | soap_server.logger_res = function(xml, req, res) { 46 | var cip = req.connection.remoteAddress; 47 | var filename = 'logs/svr-' + req.__time + '-' + cip + '-res.log.xml'; 48 | var ws = fs.createWriteStream(filename); 49 | ws.write(xml); 50 | ws.end(); 51 | }; 52 | 53 | var defLog = false; 54 | setTimeout(function() { 55 | if (!defLog) return; 56 | var def = soap_server.wsdl.definitions; 57 | var message = def.messages[Object.keys(def.messages)[0]]; 58 | var service = def.services[Object.keys(def.services)[0]]; 59 | var binding = def.bindings[Object.keys(def.bindings)[0]]; 60 | var portType = def.portTypes[Object.keys(def.portTypes)[0]]; 61 | console.log(def); 62 | console.log('\nmessage='); 63 | console.log(message); 64 | console.log('\nservice='); 65 | console.log(service); 66 | console.log('\nbinding='); 67 | console.log(binding); 68 | console.log('\nportType='); 69 | console.log(portType); 70 | console.log('\nbinding.method='); 71 | console.log(binding.methods[Object.keys(binding.methods)[0]]); 72 | console.log('\nportType.method='); 73 | console.log(portType.methods[Object.keys(portType.methods)[0]]); 74 | }, 75 | 0); 76 | 77 | if (process.argv[2]) { 78 | switch (process.argv[2].toLowerCase()) { 79 | case 'deflog': 80 | defLog = true; 81 | break; 82 | } 83 | } else { 84 | console.log("Usage: node fake_server.js [deflog]"); 85 | console.log('[deflog] will log parsed wsdl definition parts;'); 86 | console.log(); 87 | } 88 | -------------------------------------------------------------------------------- /test/wsdl/strict/logincms.wsdl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | -------------------------------------------------------------------------------- /test/server-test.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'), 2 | soap = require('..'), 3 | assert = require('assert'), 4 | request = require('request'), 5 | http = require('http'); 6 | 7 | var service = { 8 | StockQuoteService: { 9 | StockQuotePort: { 10 | GetLastTradePrice: function(args) { 11 | return { price: 19.56 }; 12 | } 13 | } 14 | } 15 | } 16 | 17 | var wsdlStrictTests = {}, 18 | wsdlNonStrictTests = {}; 19 | 20 | fs.readdirSync(__dirname+'/wsdl/strict').forEach(function(file) { 21 | if (!/.wsdl$/.exec(file)) return; 22 | wsdlStrictTests['should parse '+file] = function(done) { 23 | soap.createClient(__dirname+'/wsdl/strict/'+file, {strict: true}, function(err, client) { 24 | assert.ok(!err); 25 | done(); 26 | }); 27 | }; 28 | }) 29 | 30 | fs.readdirSync(__dirname+'/wsdl').forEach(function(file) { 31 | if (!/.wsdl$/.exec(file)) return; 32 | wsdlNonStrictTests['should parse '+file] = function(done) { 33 | soap.createClient(__dirname+'/wsdl/'+file, function(err, client) { 34 | assert.ok(!err); 35 | done(); 36 | }); 37 | }; 38 | }) 39 | 40 | module.exports = { 41 | 'SOAP Server': { 42 | 43 | 'should start': function(done) { 44 | var wsdl = fs.readFileSync(__dirname+'/wsdl/strict/stockquote.wsdl', 'utf8'), 45 | server = http.createServer(function(req, res) { 46 | res.statusCode = 404; 47 | res.end(); 48 | }); 49 | server.listen(15099); 50 | soap.listen(server, '/stockquote', service, wsdl); 51 | request('http://localhost:15099', function(err, res, body) { 52 | assert.ok(!err); 53 | done(); 54 | }) 55 | }, 56 | 57 | 'should 404 on non-WSDL path': function(done) { 58 | request('http://localhost:15099', function(err, res, body) { 59 | assert.ok(!err); 60 | assert.equal(res.statusCode, 404); 61 | done(); 62 | }) 63 | }, 64 | 65 | 'should server up WSDL': function(done) { 66 | request('http://localhost:15099/stockquote?wsdl', function(err, res, body) { 67 | assert.ok(!err); 68 | assert.equal(res.statusCode, 200); 69 | assert.ok(body.length); 70 | done(); 71 | }) 72 | }, 73 | 74 | 'should return complete client description': function(done) { 75 | soap.createClient('http://localhost:15099/stockquote?wsdl', function(err, client) { 76 | assert.ok(!err); 77 | var description = client.describe(), 78 | expected = { input: { tickerSymbol: "string" }, output:{ price: "float" } }; 79 | assert.deepEqual(expected , description.StockQuoteService.StockQuotePort.GetLastTradePrice ); 80 | done(); 81 | }); 82 | }, 83 | 84 | 'should return correct results': function(done) { 85 | soap.createClient('http://localhost:15099/stockquote?wsdl', function(err, client) { 86 | assert.ok(!err); 87 | client.GetLastTradePrice({ tickerSymbol: 'AAPL'}, function(err, result) { 88 | assert.ok(!err); 89 | assert.equal(19.56, parseFloat(result.price)); 90 | done(); 91 | }); 92 | }); 93 | } 94 | }, 95 | 'WSDL Parser (strict)': wsdlStrictTests, 96 | 'WSDL Parser (non-strict)': wsdlNonStrictTests 97 | } 98 | -------------------------------------------------------------------------------- /test/ip2tele/ip2tele.wsdl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /test/wsdl/strict/ip2tele.wsdl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | This module lets you connect to web services using SOAP. It also provides a server that allows you to run your own SOAP services. 2 | 3 | Features: 4 | 5 | * Very simple API 6 | * Handles both RPC and Document schema types 7 | * Supports multiRef SOAP messages (thanks to [@kaven276](https://github.com/kaven276)) 8 | * Support for both synchronous and asynchronous method handlers 9 | * WS-Security (currently only UsernameToken and PasswordText encoding is supported) 10 | 11 | ## Install 12 | 13 | Install with [npm](http://github.com/isaacs/npm): 14 | 15 | ``` 16 | npm install soap 17 | ``` 18 | ## Module 19 | 20 | ### soap.createClient(url, callback) - create a new SOAP client from a WSDL url 21 | 22 | ``` javascript 23 | var soap = require('soap'); 24 | var url = 'http://example.com/wsdl?wsdl'; 25 | var args = {name: 'value'}; 26 | soap.createClient(url, function(err, client) { 27 | client.MyFunction(args, function(err, result) { 28 | console.log(result); 29 | }); 30 | }); 31 | ``` 32 | 33 | ### soap.listen(*server*, *path*, *services*, *wsdl*) - create a new SOAP server that listens on *path* and provides *services*. 34 | *wsdl* is an xml string that defines the service. 35 | 36 | ``` javascript 37 | var myService = { 38 | MyService: { 39 | MyPort: { 40 | MyFunction: function(args) { 41 | return { 42 | name: args.name 43 | }; 44 | } 45 | 46 | // This is how to define an asynchronous function. 47 | MyAsyncFunction: function(args, callback) { 48 | // do some work 49 | callback({ 50 | name: args.name 51 | }) 52 | } 53 | } 54 | } 55 | } 56 | 57 | var xml = require('fs').readFileSync('myservice.wsdl', 'utf8'), 58 | server = http.createServer(function(request,response) { 59 | response.end("404: Not Found: "+request.url) 60 | }); 61 | 62 | server.listen(8000); 63 | soap.listen(server, '/wsdl', myService, xml); 64 | ``` 65 | 66 | ### server logging 67 | 68 | If the log method is defined it will be called with 'received' and 'replied' 69 | along with data. 70 | 71 | ``` javascript 72 | server = soap.listen(...) 73 | server.log = function(type, data) { 74 | // type is 'received' or 'replied' 75 | }; 76 | ``` 77 | 78 | ### server security example using PasswordDigest 79 | 80 | If server.authenticate is not defined no authentation will take place. 81 | 82 | ``` javascript 83 | server = soap.listen(...) 84 | server.authenticate = function(security) { 85 | var created, nonce, password, user, token; 86 | token = security.UsernameToken, user = token.Username, 87 | password = token.Password, nonce = token.Nonce, created = token.Created; 88 | return user === 'user' && password === soap.passwordDigest(nonce, created, 'password'); 89 | }; 90 | ``` 91 | 92 | ### server connection authorization 93 | 94 | This is called prior to soap service method 95 | If the method is defined and returns false the incoming connection is 96 | terminated. 97 | 98 | ``` javascript 99 | server = soap.listen(...) 100 | server.authorizeConnection = function(req) { 101 | return true; // or false 102 | }; 103 | ``` 104 | 105 | 106 | ## Client 107 | 108 | An instance of Client is passed to the soap.createClient callback. It is used to execute methods on the soap service. 109 | 110 | ### Client.describe() - description of services, ports and methods as a JavaScript object 111 | 112 | ``` javascript 113 | client.describe() // returns 114 | { 115 | MyService: { 116 | MyPort: { 117 | MyFunction: { 118 | input: { 119 | name: 'string' 120 | } 121 | } 122 | } 123 | } 124 | } 125 | ``` 126 | 127 | ### Client.setSecurity(security) - use the specified security protocol (see WSSecurity below) 128 | 129 | ``` javascript 130 | client.setSecurity(new WSSecurity('username', 'password')) 131 | ``` 132 | 133 | ### Client.*method*(args, callback) - call *method* on the SOAP service. 134 | 135 | ``` javascript 136 | client.MyFunction({name: 'value'}, function(err, result) { 137 | // result is a javascript object 138 | }) 139 | ``` 140 | ### Client.*service*.*port*.*method*(args, callback) - call a *method* using a specific *service* and *port* 141 | 142 | ``` javascript 143 | client.MyService.MyPort.MyFunction({name: 'value'}, function(err, result) { 144 | // result is a javascript object 145 | }) 146 | ``` 147 | 148 | ## WSSecurity 149 | 150 | WSSecurity implements WS-Security. UsernameToken and PasswordText/PasswordDigest is supported. An instance of WSSecurity is passed to Client.setSecurity. 151 | 152 | ``` javascript 153 | new WSSecurity(username, password, passwordType) 154 | //'PasswordDigest' or 'PasswordText' default is PasswordText 155 | ``` 156 | -------------------------------------------------------------------------------- /lib/soap.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Vinay Pulim 3 | * MIT Licensed 4 | */ 5 | 6 | var Client = require('./client').Client, 7 | Server = require('./server').Server, 8 | open_wsdl = require('./wsdl').open_wsdl, 9 | crypto = require('crypto'), 10 | WSDL = require('./wsdl').WSDL; 11 | 12 | var WSDL = require('./wsdl').WSDL; 13 | var _wsdlCache = {}; 14 | 15 | function _requestWSDL(url, options, callback) { 16 | if (typeof options === 'function') { 17 | callback = options; 18 | options = {}; 19 | } 20 | 21 | var wsdl = _wsdlCache[url]; 22 | if (wsdl) { 23 | callback(null, wsdl); 24 | } 25 | else { 26 | open_wsdl(url, options, function(err, wsdl) { 27 | if (err) 28 | return callback(err); 29 | else 30 | _wsdlCache[url] = wsdl; 31 | callback(null, wsdl); 32 | }) 33 | } 34 | } 35 | 36 | function createClient(url, options, callback, endpoint) { 37 | if (typeof options === 'function') { 38 | endpoint = callback; 39 | callback = options; 40 | options = {}; 41 | } 42 | endpoint = options.endpoint || endpoint; 43 | _requestWSDL(url, options, function(err, wsdl) { 44 | callback(err, wsdl && new Client(wsdl, endpoint)); 45 | }) 46 | } 47 | 48 | function listen(server, pathOrOptions, services, xml) { 49 | var options = {}, 50 | path = pathOrOptions; 51 | 52 | if (typeof pathOrOptions === 'object') { 53 | options = pathOrOptions; 54 | path = options.path; 55 | services = options.services; 56 | xml = options.xml; 57 | } 58 | 59 | var wsdl = new WSDL(xml || services, null, options); 60 | return new Server(server, path, services, wsdl); 61 | } 62 | 63 | function BasicAuthSecurity(username, password) { 64 | this._username = username; 65 | this._password = password; 66 | } 67 | 68 | BasicAuthSecurity.prototype.addHeaders = function (headers) { 69 | headers['Authorization'] = "Basic " + new Buffer((this._username + ':' + this._password) || '').toString('base64'); 70 | } 71 | 72 | BasicAuthSecurity.prototype.toXML = function() { 73 | return ""; 74 | } 75 | 76 | function WSSecurity(username, password, passwordType) { 77 | this._username = username; 78 | this._password = password; 79 | this._passwordType = passwordType || 'PasswordText'; 80 | } 81 | 82 | var passwordDigest = function(nonce, created, password) { 83 | // digest = base64 ( sha1 ( nonce + created + password ) ) 84 | var pwHash = crypto.createHash('sha1'); 85 | var rawNonce = new Buffer(nonce || '', 'base64').toString('binary'); 86 | pwHash.update(rawNonce + created + password); 87 | var passwordDigest = pwHash.digest('base64'); 88 | return passwordDigest; 89 | } 90 | 91 | WSSecurity.prototype.toXML = function() { 92 | // avoid dependency on date formatting libraries 93 | function getDate(d) { 94 | function pad(n){return n<10 ? '0'+n : n} 95 | return d.getUTCFullYear()+'-' 96 | + pad(d.getUTCMonth()+1)+'-' 97 | + pad(d.getUTCDate())+'T' 98 | + pad(d.getUTCHours())+':' 99 | + pad(d.getUTCMinutes())+':' 100 | + pad(d.getUTCSeconds())+'Z'; 101 | } 102 | var now = new Date(); 103 | var created = getDate( now ); 104 | var expires = getDate( new Date(now.getTime() + (1000 * 600)) ); 105 | 106 | // nonce = base64 ( sha1 ( created + random ) ) 107 | var nHash = crypto.createHash('sha1'); 108 | nHash.update(created + Math.random()); 109 | var nonce = nHash.digest('base64'); 110 | 111 | return "" + 112 | "" + 113 | ""+created+"" + 114 | ""+expires+"" + 115 | "" + 116 | "" + 117 | ""+this._username+"" + 118 | (this._passwordType === 'PasswordText' ? 119 | ""+this._password+"" 120 | : 121 | ""+passwordDigest(nonce, created, this._password)+"" 122 | ) + 123 | ""+nonce+"" + 124 | ""+created+"" + 125 | "" + 126 | "" 127 | } 128 | 129 | exports.BasicAuthSecurity = BasicAuthSecurity; 130 | exports.WSSecurity = WSSecurity; 131 | exports.createClient = createClient; 132 | exports.passwordDigest = passwordDigest; 133 | exports.listen = listen; 134 | exports.WSDL = WSDL; 135 | -------------------------------------------------------------------------------- /lib/client.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Vinay Pulim 3 | * MIT Licensed 4 | */ 5 | 6 | function findKey(obj, val) { 7 | for (var n in obj) if (obj[n] === val) return n; 8 | } 9 | 10 | var http = require('./http'), 11 | assert = require('assert'), 12 | url = require('url'); 13 | 14 | var Client = function(wsdl, endpoint) { 15 | this.wsdl = wsdl; 16 | this._initializeServices(endpoint); 17 | } 18 | 19 | Client.prototype.setEndpoint = function(endpoint) { 20 | this.endpoint = endpoint; 21 | this._initializeServices(endpoint); 22 | } 23 | 24 | Client.prototype.describe = function() { 25 | var types = this.wsdl.definitions.types; 26 | return this.wsdl.describeServices(); 27 | } 28 | 29 | Client.prototype.setSecurity = function(security) { 30 | this.security = security; 31 | } 32 | 33 | Client.prototype.setSOAPAction = function(SOAPAction) { 34 | this.SOAPAction = SOAPAction; 35 | } 36 | 37 | Client.prototype._initializeServices = function(endpoint) { 38 | var definitions = this.wsdl.definitions, 39 | services = definitions.services; 40 | for (var name in services) { 41 | this[name] = this._defineService(services[name], endpoint); 42 | } 43 | } 44 | 45 | Client.prototype._defineService = function(service, endpoint) { 46 | var ports = service.ports, 47 | def = {}; 48 | for (var name in ports) { 49 | def[name] = this._definePort(ports[name], endpoint ? endpoint : ports[name].location); 50 | } 51 | return def; 52 | } 53 | 54 | Client.prototype._definePort = function(port, endpoint) { 55 | var location = endpoint, 56 | binding = port.binding, 57 | methods = binding.methods, 58 | def = {}; 59 | for (var name in methods) { 60 | def[name] = this._defineMethod(methods[name], location); 61 | if (!this[name]) this[name] = def[name]; 62 | } 63 | return def; 64 | } 65 | 66 | Client.prototype._defineMethod = function(method, location) { 67 | var self = this; 68 | return function(args, callback) { 69 | if (typeof args === 'function') { 70 | callback = args; 71 | args = {}; 72 | } 73 | self._invoke(method, args, location, function(error, result, raw) { 74 | callback(error, result, raw); 75 | }) 76 | } 77 | } 78 | 79 | Client.prototype._invoke = function(method, arguments, location, callback) { 80 | var self = this, 81 | name = method.$name, 82 | input = method.input, 83 | output = method.output, 84 | style = method.style, 85 | defs = this.wsdl.definitions, 86 | ns = defs.$targetNamespace, 87 | encoding = '', 88 | message = '', 89 | xml = null, 90 | headers = { 91 | SOAPAction: this.SOAPAction ? this.SOAPAction(ns, name) : (((ns.lastIndexOf("/") != ns.length - 1) ? ns + "/" : ns) + name), 92 | 'Content-Type': "text/xml; charset=utf-8" 93 | }, 94 | options = {}, 95 | alias = findKey(defs.xmlns, ns); 96 | 97 | // Allow the security object to add headers 98 | if (self.security && self.security.addHeaders) 99 | self.security.addHeaders(headers); 100 | if (self.security && self.security.addOptions) 101 | self.security.addOptions(options); 102 | 103 | if (input.parts) { 104 | assert.ok(!style || style == 'rpc', 'invalid message definition for document style binding'); 105 | message = self.wsdl.objectToRpcXML(name, arguments, alias, ns); 106 | (method.inputSoap === 'encoded') && (encoding = 'soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" '); 107 | } 108 | else if (typeof(arguments) === 'string') { 109 | message = arguments; 110 | } 111 | else { 112 | assert.ok(!style || style == 'document', 'invalid message definition for rpc style binding'); 113 | message = self.wsdl.objectToDocumentXML(input.$name, arguments, input.targetNSAlias, input.targetNamespace); 114 | } 115 | xml = "' + 119 | "" + 120 | (self.security ? self.security.toXML() : "") + 121 | "" + 122 | "" + 123 | message + 124 | "" + 125 | ""; 126 | 127 | http.request(location, xml, function(err, response, body) { 128 | if (err) { 129 | callback(err); 130 | } 131 | else { 132 | try { 133 | var obj = self.wsdl.xmlToObject(body); 134 | } 135 | catch (error) { 136 | return callback(error, response, body); 137 | } 138 | var result = obj.Body[output.$name]; 139 | // RPC/literal response body may contain element named after the method + 'Response' 140 | // This doesn't necessarily equal the ouput message name. See WSDL 1.1 Section 2.4.5 141 | if(!result) { 142 | result = obj.Body[name + 'Response']; 143 | } 144 | callback(null, result, body); 145 | } 146 | }, headers, options); 147 | } 148 | 149 | exports.Client = Client; 150 | -------------------------------------------------------------------------------- /test/vac/EVacSyncService_SPClient.wsdl: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /test/wsdl/strict/EVacSyncService_SPClient.wsdl: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /lib/server.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Vinay Pulim 3 | * MIT Licensed 4 | */ 5 | 6 | function findKey(obj, val) { 7 | for (var n in obj) if (obj[n] === val) return n; 8 | } 9 | 10 | var url = require('url'), 11 | compress = null; 12 | 13 | try { compress = require("compress"); } catch(e) {} 14 | 15 | var Server = function(server, path, services, wsdl) { 16 | var self = this, 17 | listeners = server.listeners('request'); 18 | 19 | this.services = services; 20 | this.wsdl = wsdl; 21 | 22 | if (path[path.length-1] != '/') path += '/'; 23 | wsdl.onReady(function(err) { 24 | server.removeAllListeners('request'); 25 | server.addListener('request', function(req, res) { 26 | if (typeof self.authorizeConnection === 'function') { 27 | if (!self.authorizeConnection(req.connection.remoteAddress)) { 28 | res.end(); 29 | return; 30 | } 31 | } 32 | var reqPath = url.parse(req.url).pathname; 33 | if (reqPath[reqPath.length-1] != '/') reqPath += '/'; 34 | if (path === reqPath) { 35 | self._requestListener(req, res); 36 | } 37 | else { 38 | for (var i = 0, len = listeners.length; i < len; i++){ 39 | listeners[i].call(this, req, res); 40 | } 41 | } 42 | }); 43 | }) 44 | } 45 | 46 | Server.prototype._requestListener = function(req, res) { 47 | var self = this; 48 | if (req.method === 'GET') { 49 | var search = url.parse(req.url).search; 50 | if (search && search.toLowerCase() === '?wsdl') { 51 | res.setHeader("Content-Type", "application/xml"); 52 | res.write(self.wsdl.toXML()); 53 | } 54 | res.end(); 55 | } 56 | else if (req.method === 'POST') { 57 | res.setHeader('Content-Type', req.headers['content-type']); 58 | var chunks = [], gunzip; 59 | if (compress && req.headers["content-encoding"] == "gzip") { 60 | gunzip = new compress.Gunzip; 61 | gunzip.init(); 62 | } 63 | req.on('data', function(chunk) { 64 | if (gunzip) chunk = gunzip.inflate(chunk, "binary"); 65 | chunks.push(chunk); 66 | }) 67 | req.on('end', function() { 68 | var xml = chunks.join(''), result; 69 | if (gunzip) { 70 | gunzip.end(); 71 | gunzip = null 72 | } 73 | try { 74 | self._process(xml, req.url, function(result) { 75 | res.write(result); 76 | res.end(); 77 | if (typeof self.log === 'function') { 78 | self.log("received", xml); 79 | self.log("replied", result); 80 | } 81 | }); 82 | } 83 | catch(err) { 84 | err = err.stack || err; 85 | res.write(err); 86 | res.end(); 87 | if (typeof self.log === 'function') { 88 | self.log("error", err); 89 | } 90 | } 91 | }); 92 | } 93 | else { 94 | res.end(); 95 | } 96 | } 97 | 98 | Server.prototype._process = function(input, URL, callback) { 99 | var self = this, 100 | pathname = url.parse(URL).pathname.replace(/\/$/,''), 101 | obj = this.wsdl.xmlToObject(input), 102 | body = obj.Body, 103 | bindings = this.wsdl.definitions.bindings, binding, 104 | methods, method, methodName, 105 | serviceName, portName; 106 | 107 | if (typeof self.authenticate === 'function') { 108 | if (obj.Header == null || obj.Header.Security == null) { 109 | throw new Error('No security header'); 110 | } 111 | if (!self.authenticate(obj.Header.Security)) { 112 | throw new Error('Invalid username or password'); 113 | } 114 | } 115 | 116 | // use port.location and current url to find the right binding 117 | binding = (function(self){ 118 | var services = self.wsdl.definitions.services; 119 | for(serviceName in services ) { 120 | var service = services[serviceName]; 121 | var ports = service.ports; 122 | for(portName in ports) { 123 | var port = ports[portName]; 124 | var portPathname = url.parse(port.location).pathname.replace(/\/$/,''); 125 | if(portPathname===pathname) 126 | return port.binding; 127 | } 128 | } 129 | })(this); 130 | 131 | methods = binding.methods; 132 | 133 | if(binding.style === 'rpc') { 134 | methodName = Object.keys(body)[0]; 135 | self._executeMethod({ 136 | serviceName: serviceName, 137 | portName: portName, 138 | methodName: methodName, 139 | outputName: methodName + 'Response', 140 | args: body[methodName], 141 | style: 'rpc' 142 | }, callback); 143 | } else { 144 | var messageElemName = Object.keys(body)[0]; 145 | var pair = binding.topElements[messageElemName]; 146 | self._executeMethod({ 147 | serviceName: serviceName, 148 | portName: portName, 149 | methodName: pair.methodName, 150 | outputName: pair.outputName, 151 | args: body[messageElemName], 152 | style: 'document' 153 | }, callback); 154 | } 155 | } 156 | 157 | Server.prototype._executeMethod = function(options, callback) { 158 | options = options || {}; 159 | var self = this, 160 | method, body, 161 | serviceName = options.serviceName, 162 | portName = options.portName, 163 | methodName = options.methodName, 164 | outputName = options.outputName, 165 | args = options.args, 166 | style = options.style, 167 | handled = false; 168 | 169 | try { 170 | method = this.services[serviceName][portName][methodName]; 171 | } catch(e) { 172 | return callback(this._envelope('')); 173 | } 174 | 175 | function handleResult(result) { 176 | if (handled) return; 177 | handled = true; 178 | 179 | if(style==='rpc') { 180 | body = self.wsdl.objectToRpcXML(outputName, result, '', self.wsdl.definitions.$targetNamespace); 181 | } else { 182 | var element = self.wsdl.definitions.services[serviceName].ports[portName].binding.methods[methodName].output; 183 | body = self.wsdl.objectToDocumentXML(outputName, result, element.targetNSAlias, element.targetNamespace); 184 | } 185 | callback(self._envelope(body)); 186 | } 187 | 188 | var result = method(args, handleResult); 189 | if (typeof result !== 'undefined') { 190 | handleResult(result); 191 | } 192 | } 193 | 194 | Server.prototype._envelope = function(body) { 195 | var defs = this.wsdl.definitions, 196 | ns = defs.$targetNamespace, 197 | encoding = '', 198 | alias = findKey(defs.xmlns, ns); 199 | var xml = "" + 200 | "' + 203 | "" + 204 | body + 205 | "" + 206 | ""; 207 | return xml; 208 | } 209 | 210 | exports.Server = Server; 211 | -------------------------------------------------------------------------------- /test/vac/vac.js: -------------------------------------------------------------------------------- 1 | var cfg = { 2 | wsdl_file: 'EVacSyncService_SPClient.wsdl', 3 | service_url_path: '/services/ESyncNotifySP', 4 | service_port: 8007, 5 | psp_url: 'http://61.181.22.71:81/admin/if_vac_h' 6 | } 7 | var soap = require('../..'); 8 | var path = require('path'); 9 | var http = require('http'); 10 | var QueryString = require('querystring'); 11 | var Request = require('request'); 12 | var fs = require('fs'); 13 | 14 | var reqSeq = 0; 15 | var defLog = false; 16 | 17 | var SoapServices = { 18 | 'ESyncNotifySPServiceService': { 19 | 'ESyncNotifySP': { 20 | 'eOrderRelationUpdateNotify': function(args, callback) { 21 | var oraReqNV = args["eOrderRelationUpdateNotifyRequest"]; 22 | oraReqNV.SubInfo.split(',').forEach(function(line) { 23 | if (!line) return; 24 | var nv = line.split('='); 25 | if (!nv[0]) return; 26 | oraReqNV[nv[0]] = nv[1]; 27 | }); 28 | Request({ 29 | uri: cfg.psp_url + '.e', 30 | method: 'post', 31 | headers: { 32 | 'Content-Type': "application/x-www-form-urlencoded" 33 | }, 34 | body: QueryString.stringify(oraReqNV) 35 | }, 36 | function(error, response, body) { 37 | if (!error && response.statusCode === 200) { 38 | callback({ 39 | 'eOrderRelationUpdateNotifyResponse': { 40 | 'RecordSequenceID': oraReqNV.RecordSequenceID, 41 | 'ResultCode': body 42 | } 43 | }); 44 | } else { 45 | callback({ 46 | 'eOrderRelationUpdateNotifyResponse': { 47 | 'RecordSequenceID': oraReqNV.RecordSequenceID, 48 | 'ResultCode': 1 49 | } 50 | }); 51 | } 52 | }); 53 | }, 54 | 'eMemOrderRelationUpdateNotify': function(args, callback) { 55 | args = args["eMemOrderRelationUpdateNotifyRequest"]; 56 | var oraReqNV = { 57 | 'billnum': args.EUserId, 58 | 'UpdateType': args.UpdateType, 59 | 'ProductId': args.ProductId, 60 | 'stime': args.EffectiveDate, 61 | 'etime': args.ExpireDate, 62 | 'mobile': args.UserId.substr( - 11) 63 | }; 64 | Request({ 65 | uri: cfg.psp_url + '.m', 66 | method: 'post', 67 | headers: { 68 | 'Content-Type': "application/x-www-form-urlencoded" 69 | }, 70 | body: QueryString.stringify(oraReqNV) 71 | }, 72 | function(error, response, body) { 73 | if (!error && response.statusCode === 200) { 74 | callback({ 75 | 'eMemOrderRelationUpdateNotifyResponse': { 76 | 'RecordSequenceID': args.RecordSequenceID, 77 | 'ResultCode': body 78 | } 79 | }); 80 | } else { 81 | callback({ 82 | 'eMemOrderRelationUpdateNotifyResponse': { 83 | 'RecordSequenceID': args.RecordSequenceID, 84 | 'ResultCode': 1 85 | } 86 | }); 87 | } 88 | }); 89 | } 90 | } 91 | } 92 | } 93 | 94 | var server = http.createServer(function(req, res) { 95 | res.end("404: Not Found: " + request.url); 96 | }); 97 | server.listen(cfg.service_port); 98 | var wsdl_string = require('fs').readFileSync(path.resolve(cfg.wsdl_file), 'utf8'); 99 | var soap_server = soap.listen(server, cfg.service_url_path, SoapServices, wsdl_string); 100 | 101 | soap_server.logger_req = function(xml, req, res) { 102 | req.__time = +new Date; 103 | var cip = req.connection.remoteAddress; 104 | var filename = 'logs/srv-' + req.__time + '-' + (++reqSeq) + '-' + cip + '-req.log.xml'; 105 | var ws = fs.createWriteStream(filename); 106 | ws.write(xml); 107 | ws.end(); 108 | }; 109 | soap_server.logger_res = function(xml, req, res) { 110 | var cip = req.connection.remoteAddress; 111 | var filename = 'logs/srv-' + req.__time + '-' + '-' + (++reqSeq) + '-' + cip + '-res.log.xml'; 112 | var ws = fs.createWriteStream(filename); 113 | ws.write(xml); 114 | ws.end(); 115 | }; 116 | 117 | setTimeout(function() { 118 | if (!defLog) return; 119 | var def = soap_server.wsdl.definitions; 120 | var schema = def.schemas[Object.keys(def.schemas)[0]]; 121 | var service = def.services[Object.keys(def.services)[0]]; 122 | var binding = def.bindings[Object.keys(def.bindings)[0]]; 123 | var portType = def.portTypes[Object.keys(def.portTypes)[0]]; 124 | var binding_method = binding.methods[Object.keys(binding.methods)[0]]; 125 | var portType_method = portType.methods[Object.keys(portType.methods)[0]]; 126 | var input = portType_method.input; 127 | var type = schema.complexTypes[Object.keys(schema.complexTypes)[0]]; 128 | console.log(def); 129 | console.log('\nschema='); 130 | console.log(schema); 131 | console.log('\nservice='); 132 | console.log(service); 133 | console.log('\nbinding='); 134 | console.log(binding); 135 | console.log('\nportType='); 136 | console.log(portType); 137 | console.log('\nbinding.method='); 138 | console.log(binding_method); 139 | console.log('\nportType.method='); 140 | console.log(portType_method); 141 | console.log('---'); 142 | console.log(portType_method.description(def)); 143 | console.log('\nportType.method.input='); 144 | console.log(input); 145 | console.log('\nportType.method.input.parts='); 146 | console.log(input.parts); 147 | }, 148 | 0); 149 | 150 | var logSeq = 0; 151 | function do_test(client) { 152 | client.logger_req = function(xml) { 153 | var time = +new Date; 154 | var filename = 'logs/cli-' + time + '-' + (++logSeq) + '-req.log.xml'; 155 | var ws = fs.createWriteStream(filename); 156 | ws.write(xml); 157 | ws.end(); 158 | }; 159 | client.logger_res = function(xml) { 160 | var time = +new Date; 161 | var filename = 'logs/cli-' + time + '-' + '-' + (++logSeq) + '-res.log.xml'; 162 | var ws = fs.createWriteStream(filename); 163 | ws.write(xml); 164 | ws.end(); 165 | }; 166 | client.ESyncNotifySPServiceService.ESyncNotifySP.eOrderRelationUpdateNotify({ 167 | "eOrderRelationUpdateNotifyRequest": { 168 | "RecordSequenceID": 12345678901, 169 | "UserIdType": 1, 170 | "UserId": "8612312345678", 171 | "ServiceType": "ServiceType", 172 | "SpId": "SpId", 173 | "ProductId": "ProductId", 174 | "UpdateType": 1, 175 | "UpdateTime": "20120228163657", 176 | "UpdateDesc": "UpdateDesc", 177 | "LinkID": "LinkID", 178 | "Content": "Content", 179 | "EffectiveDate": "20120228163657", 180 | "ExpireDate": "20120228163657", 181 | "Time_Stamp": "20120228163657", 182 | "EncodeStr": "EncodeStr", 183 | "SubInfo": "gid=tjuc" 184 | } 185 | }, 186 | function(err, result, body) { 187 | if (err) { 188 | console.log(err); 189 | return; 190 | } 191 | console.log(result); 192 | }); 193 | client.ESyncNotifySPServiceService.ESyncNotifySP.eMemOrderRelationUpdateNotify({ 194 | "eMemOrderRelationUpdateNotifyRequest": { 195 | "RecordSequenceID": 12345678902, 196 | "UserIdType": 1, 197 | "UserId": "15620009233", 198 | "ServiceType": "ServiceType", 199 | "SpId": "SpId", 200 | "ProductId": "ProductId", 201 | "UpdateType": 1, 202 | "UpdateTime": "UpdateTime", 203 | "UpdateDesc": "UpdateDesc", 204 | "LinkID": "LinkID", 205 | "Content": "Content", 206 | "EffectiveDate": "20120228163657", 207 | "ExpireDate": "20120228163657", 208 | "Time_Stamp": "20120228163657", 209 | "EncodeStr": "EncodeStr", 210 | "EUserIdType": 1, 211 | "EUserId": "8612312345678" 212 | } 213 | }, 214 | function(err, result) { 215 | if (err) { 216 | console.log(err); 217 | return; 218 | } 219 | console.log(result); 220 | }); 221 | } 222 | 223 | 224 | if (process.argv[2]) { 225 | switch (process.argv[2].toLowerCase()) { 226 | case 'log': 227 | defLog = true; 228 | break; 229 | case 'test': 230 | soap.createClient('http://127.0.0.1:' + cfg.service_port + cfg.service_url_path + '?wsdl', 231 | function(err, client) { 232 | setTimeout(function(){ 233 | console.log('-- in client ' + Object.keys(client.wsdl)); 234 | do_test(client); 235 | },3); 236 | }); 237 | } 238 | } else { 239 | console.log("Usage: node vac.js [test|log]"); 240 | console.log('test will make test requests;'); 241 | console.log('log will log parsed wsdl definition parts;'); 242 | console.log(); 243 | } 244 | 245 | 246 | -------------------------------------------------------------------------------- /lib/wsdl.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2011 Vinay Pulim 3 | * MIT Licensed 4 | */ 5 | 6 | var expat = require('node-expat'), 7 | inherits = require('util').inherits, 8 | http = require('./http'), 9 | fs = require('fs'), 10 | url = require('url'), 11 | assert = require('assert').ok; 12 | 13 | var Primitives = { 14 | string: 1, boolean: 1, decimal: 1, float: 1, double: 1, 15 | anyType: 1, byte: 1, int: 1, long: 1, short: 1, 16 | unsignedByte: 1, unsignedInt: 1, unsignedLong: 1, unsignedShort: 1, 17 | duration: 0, dateTime: 0, time: 0, date: 0, 18 | gYearMonth: 0, gYear: 0, gMonthDay: 0, gDay: 0, gMonth: 0, 19 | hexBinary: 0, base64Binary: 0, anyURI: 0, QName: 0, NOTATION: 0 20 | }; 21 | 22 | function splitNSName(nsName) { 23 | var i = (nsName != null) ? nsName.indexOf(':') : -1; 24 | return i < 0 ? {namespace:null,name:nsName} : {namespace:nsName.substring(0, i), name:nsName.substring(i+1)}; 25 | } 26 | 27 | function xmlEscape(obj) { 28 | if (typeof(obj) === 'string') { 29 | return obj 30 | .replace(/&/g, '&') 31 | .replace(//g, '>') 33 | .replace(/"/g, '"') 34 | .replace(/'/g, ''') 35 | } 36 | 37 | return obj; 38 | } 39 | 40 | var trimLeft = /^[\s\xA0]+/; 41 | var trimRight = /[\s\xA0]+$/; 42 | 43 | function trim(text) { 44 | return text.replace(trimLeft, '').replace(trimRight, ''); 45 | } 46 | 47 | function extend(base, obj) { 48 | for (var key in obj) { 49 | if (obj.hasOwnProperty(key)) { 50 | base[key] = obj[key]; 51 | } 52 | } 53 | return base; 54 | } 55 | 56 | function findKey(obj, val) { 57 | for (var n in obj) if (obj[n] === val) return n; 58 | } 59 | 60 | var Element = function(nsName, attrs) { 61 | var parts = splitNSName(nsName); 62 | 63 | this.nsName = nsName; 64 | this.namespace = parts.namespace; 65 | this.name = parts.name; 66 | this.children = []; 67 | this.xmlns = {}; 68 | for (var key in attrs) { 69 | var match = /^xmlns:?(.*)$/.exec(key); 70 | if (match) { 71 | this.xmlns[match[1]] = attrs[key]; 72 | } 73 | else { 74 | this['$'+key] = attrs[key]; 75 | } 76 | } 77 | } 78 | Element.prototype.deleteFixedAttrs = function() { 79 | this.children && this.children.length === 0 && delete this.children; 80 | this.xmlns && Object.keys(this.xmlns).length === 0 && delete this.xmlns; 81 | delete this.nsName; 82 | delete this.namespace; 83 | delete this.name; 84 | } 85 | Element.prototype.allowedChildren = []; 86 | Element.prototype.startElement= function(stack, nsName, attrs) { 87 | if (!this.allowedChildren) return; 88 | 89 | var childClass = this.allowedChildren[splitNSName(nsName).name], 90 | element = null; 91 | 92 | if (childClass) { 93 | stack.push(new childClass(nsName, attrs)); 94 | } 95 | else { 96 | this.unexpected(nsName); 97 | } 98 | 99 | } 100 | Element.prototype.endElement = function(stack, nsName) { 101 | if (this.nsName === nsName) { 102 | if(stack.length < 2 ) return; 103 | var parent = stack[stack.length - 2]; 104 | if (this !== stack[0]) { 105 | extend(stack[0].xmlns, this.xmlns); 106 | // delete this.xmlns; 107 | parent.children.push(this); 108 | parent.addChild(this); 109 | } 110 | stack.pop(); 111 | } 112 | } 113 | Element.prototype.addChild = function(child) { return; } 114 | Element.prototype.unexpected = function(name) { 115 | throw new Error('Found unexpected element (' + name + ') inside ' + this.nsName); 116 | } 117 | Element.prototype.description = function(definitions) { 118 | return this.$name || this.name; 119 | } 120 | Element.prototype.init = function() {}; 121 | Element.createSubClass = function() { 122 | var root = this; 123 | var subElement = function() { 124 | root.apply(this, arguments); 125 | this.init(); 126 | }; 127 | // inherits(subElement, root); 128 | subElement.prototype.__proto__ = root.prototype; 129 | return subElement; 130 | } 131 | 132 | 133 | var ElementElement = Element.createSubClass(); 134 | var InputElement = Element.createSubClass(); 135 | var OutputElement = Element.createSubClass(); 136 | var SimpleTypeElement = Element.createSubClass(); 137 | var RestrictionElement = Element.createSubClass(); 138 | var EnumerationElement = Element.createSubClass(); 139 | var ComplexTypeElement = Element.createSubClass(); 140 | var SequenceElement = Element.createSubClass(); 141 | var AllElement = Element.createSubClass(); 142 | var MessageElement = Element.createSubClass(); 143 | 144 | var SchemaElement = Element.createSubClass(); 145 | var TypesElement = Element.createSubClass(); 146 | var OperationElement = Element.createSubClass(); 147 | var PortTypeElement = Element.createSubClass(); 148 | var BindingElement = Element.createSubClass(); 149 | var PortElement = Element.createSubClass(); 150 | var ServiceElement = Element.createSubClass(); 151 | var DefinitionsElement = Element.createSubClass(); 152 | 153 | var ElementTypeMap = { 154 | types: [TypesElement, 'schema'], 155 | schema: [SchemaElement, 'element complexType simpleType include import'], 156 | element: [ElementElement, 'annotation complexType'], 157 | simpleType: [SimpleTypeElement, 'restriction'], 158 | restriction: [RestrictionElement, 'enumeration'], 159 | enumeration: [EnumerationElement, ''], 160 | complexType: [ComplexTypeElement, 'annotation sequence all'], 161 | sequence: [SequenceElement, 'element'], 162 | all: [AllElement, 'element'], 163 | 164 | service: [ServiceElement, 'port documentation'], 165 | port: [PortElement, 'address'], 166 | binding: [BindingElement, '_binding SecuritySpec operation'], 167 | portType: [PortTypeElement, 'operation'], 168 | message: [MessageElement, 'part documentation'], 169 | operation: [OperationElement, 'documentation input output fault _operation'], 170 | input : [InputElement, 'body SecuritySpecRef documentation header'], 171 | output : [OutputElement, 'body SecuritySpecRef documentation header'], 172 | fault : [Element, '_fault'], 173 | definitions: [DefinitionsElement, 'types message portType binding service'] 174 | }; 175 | 176 | function mapElementTypes(types) { 177 | var types = types.split(' '); 178 | var rtn = {} 179 | types.forEach(function(type){ 180 | rtn[type.replace(/^_/,'')] = (ElementTypeMap[type] || [Element]) [0]; 181 | }); 182 | return rtn; 183 | } 184 | 185 | for(var n in ElementTypeMap) { 186 | var v = ElementTypeMap[n]; 187 | v[0].prototype.allowedChildren = mapElementTypes(v[1]); 188 | } 189 | 190 | MessageElement.prototype.init = function() { 191 | this.element = null; 192 | this.parts = null; 193 | } 194 | SchemaElement.prototype.init = function() { 195 | this.complexTypes = {}; 196 | this.types = {}; 197 | this.elements = {}; 198 | this.includes = []; 199 | } 200 | TypesElement.prototype.init = function() { 201 | this.schemas = {}; 202 | } 203 | OperationElement.prototype.init = function() { 204 | this.input = null; 205 | this.output = null; 206 | this.inputSoap = null; 207 | this.outputSoap = null; 208 | this.style = ''; 209 | this.soapAction = ''; 210 | } 211 | PortTypeElement.prototype.init = function() { 212 | this.methods = {}; 213 | } 214 | BindingElement.prototype.init = function() { 215 | this.transport = ''; 216 | this.style = ''; 217 | this.methods = {}; 218 | } 219 | PortElement.prototype.init = function() { 220 | this.location = null; 221 | } 222 | ServiceElement.prototype.init = function() { 223 | this.ports = {}; 224 | } 225 | DefinitionsElement.prototype.init = function() { 226 | if (this.name !== 'definitions') this.unexpected(nsName); 227 | this.messages = {}; 228 | this.portTypes = {}; 229 | this.bindings = {}; 230 | this.services = {}; 231 | this.schemas = {}; 232 | } 233 | 234 | SchemaElement.prototype.addChild = function(child) { 235 | if (child.$name in Primitives) return; 236 | if (child.name === 'include' || child.name === 'import') { 237 | var location = child.$schemaLocation || child.$location; 238 | if (location) { 239 | this.includes.push({ 240 | namespace: child.$namespace || child.$targetNamespace || this.$targetNamespace, 241 | location: location 242 | }); 243 | } 244 | } 245 | else if (child.name === 'complexType') { 246 | this.complexTypes[child.$name] = child; 247 | } 248 | else if (child.name === 'element') { 249 | this.elements[child.$name] = child; 250 | } 251 | else if (child.$name) { 252 | this.types[child.$name] = child; 253 | } 254 | this.children.pop(); 255 | // child.deleteFixedAttrs(); 256 | } 257 | TypesElement.prototype.addChild = function(child) { 258 | assert(child instanceof SchemaElement); 259 | this.schemas[child.$targetNamespace] = child; 260 | } 261 | InputElement.prototype.addChild = function(child) { 262 | if (child.name === 'body') { 263 | this.use = child.$use; 264 | if (this.use === 'encoded') { 265 | this.encodingStyle = child.$encodingStyle; 266 | } 267 | this.children.pop(); 268 | } 269 | } 270 | OutputElement.prototype.addChild = function(child) { 271 | if (child.name === 'body') { 272 | this.use = child.$use; 273 | if (this.use === 'encoded') { 274 | this.encodingStyle = child.$encodingStyle; 275 | } 276 | this.children.pop(); 277 | } 278 | } 279 | OperationElement.prototype.addChild = function(child) { 280 | if (child.name === 'operation') { 281 | this.soapAction = child.$soapAction || ''; 282 | this.style = child.$style || ''; 283 | this.children.pop(); 284 | } 285 | } 286 | BindingElement.prototype.addChild = function(child) { 287 | if (child.name === 'binding') { 288 | this.transport = child.$transport; 289 | this.style = child.$style; 290 | this.children.pop(); 291 | } 292 | } 293 | PortElement.prototype.addChild = function(child) { 294 | if (child.name === 'address' && typeof(child.$location) !== 'undefined') { 295 | this.location = child.$location; 296 | } 297 | } 298 | DefinitionsElement.prototype.addChild = function(child) { 299 | var self = this; 300 | if (child instanceof TypesElement) { 301 | self.schemas = child.schemas; 302 | } 303 | else if (child instanceof MessageElement) { 304 | self.messages[child.$name] = child; 305 | } 306 | else if (child instanceof PortTypeElement) { 307 | self.portTypes[child.$name] = child; 308 | } 309 | else if (child instanceof BindingElement) { 310 | if (child.transport === 'http://schemas.xmlsoap.org/soap/http' || 311 | child.transport === 'http://www.w3.org/2003/05/soap/bindings/HTTP/') 312 | self.bindings[child.$name] = child; 313 | } 314 | else if (child instanceof ServiceElement) { 315 | self.services[child.$name] = child; 316 | } 317 | else { 318 | assert(false, "Invalid child type"); 319 | } 320 | this.children.pop(); 321 | } 322 | 323 | 324 | MessageElement.prototype.postProcess = function(definitions) { 325 | var part = null, child, 326 | children = this.children || []; 327 | 328 | for (var i in children) { 329 | if ((child = children[i]).name === 'part') { 330 | part = child; 331 | break; 332 | } 333 | } 334 | if (!part) return; 335 | if (part.$element) { 336 | delete this.parts; 337 | var nsName = splitNSName(part.$element); 338 | var ns = nsName.namespace; 339 | this.element = definitions.schemas[definitions.xmlns[ns]].elements[nsName.name]; 340 | this.element.targetNSAlias = ns; 341 | this.element.targetNamespace = definitions.xmlns[ns]; 342 | this.children.splice(0,1); 343 | } 344 | else { 345 | // rpc encoding 346 | this.parts = {}; 347 | delete this.element; 348 | for (var i=0, part; part = this.children[i]; i++) { 349 | assert(part.name === 'part', 'Expected part element'); 350 | var nsName = splitNSName(part.$type); 351 | var ns = definitions.xmlns[nsName.namespace]; 352 | var type = nsName.name; 353 | this.parts[part.$name] = definitions.schemas[ns].types[type] || definitions.schemas[ns].complexTypes[type]; 354 | this.parts[part.$name].namespace = nsName.namespace; 355 | this.parts[part.$name].xmlns = ns; 356 | this.children.splice(i--,1); 357 | } 358 | } 359 | this.deleteFixedAttrs(); 360 | } 361 | OperationElement.prototype.postProcess = function(definitions, tag) { 362 | var children = this.children; 363 | for (var i=0, child; child=children[i]; i++) { 364 | if (child.name !== 'input' && child.name !== 'output') continue; 365 | if(tag === 'binding') { 366 | this[child.name] = child; 367 | children.splice(i--,1); 368 | continue; 369 | } 370 | var messageName = splitNSName(child.$message).name; 371 | var message = definitions.messages[messageName] 372 | message.postProcess(definitions); 373 | if (message.element) { 374 | definitions.messages[message.element.$name] = message 375 | this[child.name] = message.element; 376 | } 377 | else { 378 | this[child.name] = message; 379 | } 380 | children.splice(i--,1); 381 | } 382 | this.deleteFixedAttrs(); 383 | } 384 | PortTypeElement.prototype.postProcess = function(definitions) { 385 | var children = this.children; 386 | if (typeof children === 'undefined') return; 387 | for (var i=0, child; child=children[i]; i++) { 388 | if (child.name != 'operation') continue; 389 | child.postProcess(definitions, 'portType'); 390 | this.methods[child.$name] = child; 391 | children.splice(i--,1); 392 | } 393 | delete this.$name; 394 | this.deleteFixedAttrs(); 395 | } 396 | BindingElement.prototype.postProcess = function(definitions) { 397 | var type = splitNSName(this.$type).name, 398 | portType = definitions.portTypes[type], 399 | style = this.style, 400 | children = this.children; 401 | 402 | portType.postProcess(definitions); 403 | this.methods = portType.methods; 404 | // delete portType.methods; both binding and portType should keep the same set of operations 405 | 406 | for (var i=0, child; child=children[i]; i++) { 407 | if (child.name != 'operation') continue; 408 | child.postProcess(definitions, 'binding'); 409 | children.splice(i--,1); 410 | child.style || (child.style = style); 411 | var method = this.methods[child.$name]; 412 | method.style = child.style; 413 | method.soapAction = child.soapAction; 414 | method.inputSoap = child.input || null; 415 | method.outputSoap = child.output || null; 416 | method.inputSoap && method.inputSoap.deleteFixedAttrs(); 417 | method.outputSoap && method.outputSoap.deleteFixedAttrs(); 418 | // delete method.$name; client will use it to make right request for top element name in body 419 | // method.deleteFixedAttrs(); why ??? 420 | } 421 | 422 | delete this.$name; 423 | delete this.$type; 424 | this.deleteFixedAttrs(); 425 | } 426 | ServiceElement.prototype.postProcess = function(definitions) { 427 | var children = this.children, 428 | bindings = definitions.bindings; 429 | for (var i=0, child; child=children[i]; i++) { 430 | if (child.name != 'port') continue; 431 | var bindingName = splitNSName(child.$binding).name; 432 | var binding = bindings[bindingName]; 433 | if (binding) { 434 | binding.postProcess(definitions); 435 | this.ports[child.$name] = { 436 | location: child.location, 437 | binding: binding 438 | } 439 | children.splice(i--,1); 440 | } 441 | } 442 | delete this.$name; 443 | this.deleteFixedAttrs(); 444 | } 445 | 446 | SimpleTypeElement.prototype.description = function(definitions) { 447 | var children = this.children; 448 | for (var i=0, child; child=children[i]; i++) { 449 | if (child instanceof RestrictionElement) 450 | return this.$name+"|"+child.description(); 451 | } 452 | return {}; 453 | } 454 | 455 | RestrictionElement.prototype.description = function() { 456 | var base = this.$base ? this.$base+"|" : ""; 457 | return base + this.children.map( function(child) { 458 | return child.description(); 459 | } ).join(","); 460 | } 461 | 462 | EnumerationElement.prototype.description = function() { 463 | return this.$value; 464 | } 465 | 466 | ComplexTypeElement.prototype.description = function(definitions) { 467 | var children = this.children; 468 | for (var i=0, child; child=children[i]; i++) { 469 | if (child instanceof SequenceElement || 470 | child instanceof AllElement) { 471 | return child.description(definitions); 472 | } 473 | } 474 | return {}; 475 | } 476 | ElementElement.prototype.description = function(definitions) { 477 | var element = {}, 478 | name = this.$name, 479 | schema; 480 | if (this.$minOccurs !== this.$maxOccurs) { 481 | name += '[]'; 482 | } 483 | 484 | if (this.$type) { 485 | var typeName = splitNSName(this.$type).name, 486 | ns = definitions.xmlns[splitNSName(this.$type).namespace], 487 | schema = definitions.schemas[ns], 488 | typeElement = schema && ( schema.complexTypes[typeName] || schema.types[typeName] ); 489 | if (typeElement && !(typeName in Primitives)) { 490 | element[name] = typeElement.description(definitions); 491 | } 492 | else 493 | element[name] = this.$type; 494 | } 495 | else { 496 | var children = this.children; 497 | element[name] = {}; 498 | for (var i=0, child; child=children[i]; i++) { 499 | if (child instanceof ComplexTypeElement) 500 | element[name] = child.description(definitions); 501 | } 502 | } 503 | return element; 504 | } 505 | AllElement.prototype.description = 506 | SequenceElement.prototype.description = function(definitions) { 507 | var children = this.children; 508 | var sequence = {}; 509 | for (var i=0, child; child=children[i]; i++) { 510 | var description = child.description(definitions); 511 | for (var key in description) { 512 | sequence[key] = description[key]; 513 | } 514 | } 515 | return sequence; 516 | } 517 | MessageElement.prototype.description = function(definitions) { 518 | if (this.element) { 519 | return this.element && this.element.description(definitions); 520 | } 521 | var desc = {}; 522 | desc[this.$name] = this.parts; 523 | return desc; 524 | } 525 | PortTypeElement.prototype.description = function(definitions) { 526 | var methods = {}; 527 | for (var name in this.methods) { 528 | var method = this.methods[name]; 529 | methods[name] = method.description(definitions); 530 | } 531 | return methods; 532 | } 533 | OperationElement.prototype.description = function(definitions) { 534 | var inputDesc = this.input.description(definitions); 535 | var outputDesc = this.output.description(definitions); 536 | return { 537 | input: inputDesc && inputDesc[Object.keys(inputDesc)[0]], 538 | output: outputDesc && outputDesc[Object.keys(outputDesc)[0]] 539 | } 540 | } 541 | BindingElement.prototype.description = function(definitions) { 542 | var methods = {}; 543 | for (var name in this.methods) { 544 | var method = this.methods[name]; 545 | methods[name] = method.description(definitions); 546 | } 547 | return methods; 548 | } 549 | ServiceElement.prototype.description = function(definitions) { 550 | var ports = {}; 551 | for (var name in this.ports) { 552 | var port = this.ports[name]; 553 | ports[name] = port.binding.description(definitions); 554 | } 555 | return ports; 556 | } 557 | 558 | 559 | var WSDL = function(definition, uri, options) { 560 | var self = this, 561 | fromFunc; 562 | 563 | this.uri = uri; 564 | this.callback = function() {}; 565 | this.options = options || {}; 566 | 567 | if (typeof definition === 'string') { 568 | fromFunc = this._fromXML; 569 | } 570 | else if (typeof definition === 'object') { 571 | fromFunc = this._fromServices; 572 | } 573 | else { 574 | throw new Error('WSDL constructor takes either an XML string or service definition'); 575 | } 576 | 577 | process.nextTick(function() { 578 | fromFunc.call(self, definition); 579 | 580 | self.processIncludes(function(err) { 581 | self.definitions.deleteFixedAttrs(); 582 | var services = self.services = self.definitions.services ; 583 | if (services) { 584 | for (var name in services) { 585 | services[name].postProcess(self.definitions); 586 | } 587 | } 588 | var complexTypes = self.definitions.complexTypes; 589 | if (complexTypes) { 590 | for (var name in complexTypes) { 591 | complexTypes[name].deleteFixedAttrs(); 592 | } 593 | } 594 | 595 | // for document style, for every binding, prepare input message element name to (methodName, output message element name) mapping 596 | var bindings = self.definitions.bindings; 597 | for(var bindingName in bindings) { 598 | var binding = bindings[bindingName]; 599 | if(binding.style !== 'document') continue; 600 | var methods = binding.methods; 601 | var topEls = binding.topElements = {}; 602 | for(var methodName in methods) { 603 | var inputName = methods[methodName].input.$name; 604 | var outputName = methods[methodName].output.$name; 605 | topEls[inputName] = {"methodName": methodName, "outputName": outputName}; 606 | } 607 | } 608 | 609 | // prepare soap envelope xmlns definition string 610 | self.xmlnsInEnvelope = self._xmlnsMap(); 611 | 612 | self.callback(err, self); 613 | }); 614 | 615 | }) 616 | } 617 | 618 | WSDL.prototype.onReady = function(callback) { 619 | if (callback) this.callback = callback; 620 | } 621 | 622 | WSDL.prototype._processNextInclude = function(includes, callback) { 623 | var self = this, 624 | include = includes.shift(); 625 | 626 | if (!include) return callback() 627 | open_wsdl(url.resolve(self.uri, include.location), function(err, wsdl) { 628 | self.definitions.schemas[include.namespace || wsdl.definitions.$targetNamespace] = wsdl.definitions; 629 | self._processNextInclude(includes, function(err) { 630 | callback(err); 631 | }) 632 | }); 633 | } 634 | 635 | WSDL.prototype.processIncludes = function(callback) { 636 | var schemas = this.definitions.schemas, 637 | includes = []; 638 | 639 | for (var ns in schemas) { 640 | var schema = schemas[ns]; 641 | includes = includes.concat(schema.includes || []) 642 | } 643 | 644 | this._processNextInclude(includes, callback); 645 | } 646 | 647 | WSDL.prototype.describeServices = function() { 648 | var services = {}; 649 | for (var name in this.services) { 650 | var service = this.services[name]; 651 | services[name] = service.description(this.definitions); 652 | } 653 | return services; 654 | } 655 | 656 | WSDL.prototype.toXML = function() { 657 | return this.xml || ''; 658 | } 659 | 660 | WSDL.prototype.xmlToObject = function(xml) { 661 | var self = this, 662 | p = new expat.Parser('UTF-8'), 663 | objectName = null, 664 | root = {}, 665 | schema = { 666 | Envelope: { 667 | Header: { 668 | Security: { 669 | UsernameToken: { 670 | Username: 'string', 671 | Password: 'string' }}}, 672 | Body: { 673 | Fault: { faultcode: 'string', faultstring: 'string', detail: 'string' }}}}, 674 | stack = [{name: null, object: root, schema: schema}]; 675 | 676 | var refs = {}, id; // {id:{hrefs:[],obj:}, ...} 677 | 678 | p.on('startElement', function(nsName, attrs) { 679 | var name = splitNSName(nsName).name, 680 | top = stack[stack.length-1], 681 | topSchema = top.schema, 682 | obj = {}; 683 | var originalName = name; 684 | 685 | if (!objectName && top.name === 'Body' && name !== 'Fault') { 686 | var message = self.definitions.messages[name]; 687 | // Support RPC/literal messages where response body contains one element named 688 | // after the operation + 'Response'. See http://www.w3.org/TR/wsdl#_names 689 | if (!message) { 690 | // Determine if this is request or response 691 | var isInput = false; 692 | var isOutput = false; 693 | if ((/Response$/).test(name)) { 694 | isOutput = true; 695 | name = name.replace(/Response$/, ''); 696 | } else if ((/Request$/).test(name)) { 697 | isInput = true; 698 | name = name.replace(/Request$/, ''); 699 | } else if ((/Solicit$/).test(name)) { 700 | isInput = true; 701 | name = name.replace(/Solicit$/, ''); 702 | } 703 | // Look up the appropriate message as given in the portType's operations 704 | var portTypes = self.definitions.portTypes; 705 | var portTypeNames = Object.keys(portTypes); 706 | // Currently this supports only one portType definition. 707 | var portType = portTypes[portTypeNames[0]]; 708 | if (isInput) name = portType.methods[name].input.$name; 709 | else name = portType.methods[name].output.$name; 710 | message = self.definitions.messages[name]; 711 | // 'cache' this alias to speed future lookups 712 | self.definitions.messages[originalName] = self.definitions.messages[name]; 713 | } 714 | 715 | topSchema = message.description(self.definitions); 716 | objectName = originalName; 717 | } 718 | 719 | if(attrs.href) { 720 | id = attrs.href.substr(1); 721 | if(!refs[id]) refs[id] = {hrefs:[],obj:null}; 722 | refs[id].hrefs.push({par:top.object,key:name}); 723 | } 724 | if(id=attrs.id) { 725 | if(!refs[id]) refs[id] = {hrefs:[],obj:null}; 726 | } 727 | 728 | if (topSchema && topSchema[name+'[]']) name = name + '[]'; 729 | stack.push({name: originalName, object: obj, schema: topSchema && topSchema[name], id:attrs.id}); 730 | }) 731 | 732 | p.on('endElement', function(nsName) { 733 | var cur = stack.pop(), 734 | obj = cur.object, 735 | top = stack[stack.length-1], 736 | topObject = top.object, 737 | topSchema = top.schema, 738 | name = splitNSName(nsName).name; 739 | 740 | if (topSchema && topSchema[name+'[]']) { 741 | if (!topObject[name]) topObject[name] = []; 742 | topObject[name].push(obj); 743 | } 744 | else if (name in topObject) { 745 | if (!Array.isArray(topObject[name])) { 746 | topObject[name] = [topObject[name]]; 747 | } 748 | topObject[name].push(obj); 749 | } 750 | else { 751 | topObject[name] = obj; 752 | } 753 | 754 | if(cur.id) { 755 | refs[cur.id].obj = obj; 756 | } 757 | }) 758 | 759 | p.on('text', function(text) { 760 | text = trim(text); 761 | if (!text.length) return; 762 | 763 | var top = stack[stack.length-1]; 764 | var name = splitNSName(top.schema).name, 765 | value; 766 | if (name === 'int') { 767 | value = parseInt(text, 10); 768 | } else if (name === 'dateTime') { 769 | value = new Date(text); 770 | } else { 771 | // handle string or other types 772 | if (typeof top.object !== 'string') { 773 | value = text; 774 | } else { 775 | value = top.object + text; 776 | } 777 | } 778 | top.object = value; 779 | }); 780 | 781 | if (!p.parse(xml, false)) { 782 | throw new Error(p.getError()); 783 | } 784 | 785 | for(var n in refs) { 786 | var ref = refs[n]; 787 | var obj = ref.obj; 788 | ref.hrefs.forEach(function(href) { 789 | href.par[href.key] = obj; 790 | }); 791 | } 792 | 793 | var body = root.Envelope.Body; 794 | if (body.Fault) { 795 | throw new Error(body.Fault.faultcode+': '+body.Fault.faultstring+(body.Fault.detail ? ': ' + body.Fault.detail : '')); 796 | } 797 | return root.Envelope; 798 | } 799 | 800 | WSDL.prototype.objectToDocumentXML = function(name, params, ns, xmlns) { 801 | var args = {}; 802 | args[name] = params; 803 | return this.objectToXML(args, null, ns, xmlns); 804 | } 805 | 806 | WSDL.prototype.objectToRpcXML = function(name, params, namespace, xmlns) { 807 | var self = this, 808 | parts = [], 809 | defs = this.definitions, 810 | namespace = namespace || findKey(defs.xmlns, xmlns), 811 | xmlns = xmlns || defs.xmlns[namespace], 812 | nsAttrName = '_xmlns'; 813 | parts.push(['<',namespace,':',name,'>'].join('')); 814 | for (var key in params) { 815 | if (key != nsAttrName) { 816 | var value = params[key]; 817 | parts.push(['<',key,'>'].join('')); 818 | parts.push((typeof value==='object')?this.objectToXML(value):xmlEscape(value)); 819 | parts.push([''].join('')); 820 | } 821 | } 822 | parts.push([''].join('')); 823 | 824 | return parts.join(''); 825 | } 826 | 827 | WSDL.prototype.objectToXML = function(obj, name, namespace, xmlns) { 828 | var self = this, 829 | parts = [], 830 | xmlnsAttrib = false ? ' xmlns:'+namespace+'="'+xmlns+'"'+' xmlns="'+xmlns+'"' : '', 831 | ns = namespace ? namespace + ':' : ''; 832 | 833 | if (Array.isArray(obj)) { 834 | for (var i=0, item; item=obj[i]; i++) { 835 | if (i > 0) { 836 | parts.push([''].join('')); 837 | parts.push(['<',ns,name,xmlnsAttrib,'>'].join('')); 838 | } 839 | parts.push(self.objectToXML(item, name)); 840 | } 841 | } 842 | else if (typeof obj === 'object') { 843 | for (var name in obj) { 844 | var child = obj[name]; 845 | parts.push(['<',ns,name,xmlnsAttrib,'>'].join('')); 846 | parts.push(self.objectToXML(child, name)); 847 | parts.push([''].join('')); 848 | } 849 | } 850 | else if (obj) { 851 | parts.push(xmlEscape(obj)); 852 | } 853 | return parts.join(''); 854 | } 855 | 856 | WSDL.prototype._parse = function(xml) 857 | { 858 | var self = this, 859 | p = new expat.Parser('UTF-8'), 860 | stack = [], 861 | root = null; 862 | 863 | p.on('startElement', function(nsName, attrs) { 864 | var top = stack[stack.length - 1]; 865 | if (top) { 866 | try { 867 | top.startElement(stack, nsName, attrs); 868 | } 869 | catch(e) { 870 | if (self.options.strict) { 871 | throw e; 872 | } 873 | else { 874 | stack.push(new Element(nsName, attrs)); 875 | } 876 | } 877 | } 878 | else { 879 | var name = splitNSName(nsName).name; 880 | if (name === 'definitions') { 881 | root = new DefinitionsElement(nsName, attrs); 882 | } 883 | else if (name === 'schema') { 884 | root = new SchemaElement(nsName, attrs); 885 | } 886 | else { 887 | throw new Error('Unexpected root element of WSDL or include'); 888 | } 889 | stack.push(root); 890 | } 891 | }) 892 | 893 | p.on('endElement', function(name) { 894 | var top = stack[stack.length - 1]; 895 | assert(top, 'Unmatched close tag: ' + name); 896 | 897 | top.endElement(stack, name); 898 | }) 899 | 900 | if (!p.parse(xml, false)) { 901 | throw new Error(p.getError()); 902 | } 903 | 904 | return root; 905 | } 906 | 907 | WSDL.prototype._fromXML = function(xml) { 908 | this.definitions = this._parse(xml); 909 | this.xml = xml; 910 | } 911 | 912 | WSDL.prototype._fromServices = function(services) { 913 | 914 | } 915 | 916 | 917 | 918 | WSDL.prototype._xmlnsMap = function() { 919 | var xmlns = this.definitions.xmlns; 920 | var str = ''; 921 | for (var alias in xmlns) { 922 | if (alias === '') continue; 923 | var ns = xmlns[alias]; 924 | switch(ns) { 925 | case "http://xml.apache.org/xml-soap" : // apachesoap 926 | case "http://schemas.xmlsoap.org/wsdl/" : // wsdl 927 | case "http://schemas.xmlsoap.org/wsdl/soap/" : // wsdlsoap 928 | case "http://schemas.xmlsoap.org/soap/encoding/" : // soapenc 929 | case "http://www.w3.org/2001/XMLSchema" : // xsd 930 | continue; 931 | } 932 | if (~ns.indexOf('http://schemas.xmlsoap.org/')) continue; 933 | if (~ns.indexOf('http://www.w3.org/')) continue; 934 | if (~ns.indexOf('http://xml.apache.org/')) continue; 935 | str += ' xmlns:' + alias + '="' + ns + '"'; 936 | } 937 | return str; 938 | } 939 | 940 | function open_wsdl(uri, options, callback) { 941 | if (typeof options === 'function') { 942 | callback = options; 943 | options = {}; 944 | } 945 | 946 | var wsdl; 947 | if (!/^http/.test(uri)) { 948 | fs.readFile(uri, 'utf8', function (err, definition) { 949 | if (err) { 950 | callback(err) 951 | } 952 | else { 953 | wsdl = new WSDL(definition, uri, options); 954 | wsdl.onReady(callback); 955 | } 956 | }) 957 | } 958 | else { 959 | http.request(uri, null /* options */, function (err, response, definition) { 960 | if (err) { 961 | callback(err); 962 | } 963 | else if (response && response.statusCode == 200) { 964 | wsdl = new WSDL(definition, uri, options); 965 | wsdl.onReady(callback); 966 | } 967 | else { 968 | callback(new Error('Invalid WSDL URL: '+uri)) 969 | } 970 | }); 971 | } 972 | 973 | return wsdl; 974 | } 975 | 976 | exports.open_wsdl = open_wsdl; 977 | exports.WSDL = WSDL; 978 | 979 | 980 | -------------------------------------------------------------------------------- /test/wsdl/strict/DE.wsdl: -------------------------------------------------------------------------------- 1 | 2 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | The authentication data. 27 | 28 | 29 | 30 | 31 | 32 | The shipmentdata for creating a TD shipment. 33 | 34 | 35 | 36 | 37 | 38 | The status of the createShipment operation and the identifier for the shipment. 39 | 40 | 41 | 42 | 43 | 44 | The shipmentdata for creating a DD shipment. 45 | 46 | 47 | 48 | 49 | 50 | The status of the createShipment operation and the identifier for the shipment. 51 | 52 | 53 | 54 | 55 | 56 | The identifier for the shipment which should be deleted. 57 | 58 | 59 | 60 | 61 | 62 | The status of the deletion operation. 63 | 64 | 65 | 66 | 67 | 68 | The identifier for the shipment which should be deleted. 69 | 70 | 71 | 72 | 73 | 74 | The status of the deletion operation. 75 | 76 | 77 | 78 | 79 | 80 | The identifier for the shipment which should be manifested. 81 | 82 | 83 | 84 | 85 | 86 | The status of the manifest operation. 87 | 88 | 89 | 90 | 91 | 92 | The identifier for the shipment which should be manifested. 93 | 94 | 95 | 96 | 97 | 98 | The status of the manifest operation. 99 | 100 | 101 | 102 | 103 | 104 | The identifier for the TD shipment for which the label url is requested. 105 | 106 | 107 | 108 | 109 | 110 | The status of the operation and the label url (if available). 111 | 112 | 113 | 114 | 115 | 116 | The identifier for the DD shipment for which the label url is requested. 117 | 118 | 119 | 120 | 121 | 122 | The status of the operation and the label url (if available). 123 | 124 | 125 | 126 | 127 | 128 | The data for a pickup order. 129 | 130 | 131 | 132 | 133 | 134 | The status of the book pickup operation and a confirmation number (if available). 135 | 136 | 137 | 138 | 139 | 140 | The confirmation number of the pickup order which should be canceled. 141 | 142 | 143 | 144 | 145 | 146 | The status of cancel pickup operation. 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | The version of webservice implementation. 155 | 156 | 157 | 158 | 159 | 160 | The identifier for the TD shipment for which the label url is requested. 161 | 162 | 163 | 164 | 165 | 166 | The status of the operation and the label url (if available). 167 | 168 | 169 | 170 | 171 | 172 | The identifier for the DD shipment for which the label url is requested. 173 | 174 | 175 | 176 | 177 | 178 | The status of the operation and the label url (if available). 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | Creates TD shipments. 187 | 188 | The shipment data. 189 | 190 | 191 | The status of the createShipment operation and the identifier for the shipment. 192 | 193 | 194 | 195 | 196 | Creates DD shipments. 197 | 198 | The shipment data. 199 | 200 | 201 | The status of the createShipment operation and the identifier for the shipment. 202 | 203 | 204 | 205 | 206 | Deletes the requested TD shipments. 207 | 208 | The identifier for the shipment which should be deleted. 209 | 210 | 211 | The status of the deletion operation. 212 | 213 | 214 | 215 | 216 | Deletes the requested DD shipments. 217 | 218 | The identifier for the shipment which should be deleted. 219 | 220 | 221 | The status of the deletion operation. 222 | 223 | 224 | 225 | 226 | Manifest the requested TD shipments. 227 | 228 | The identifier for the shipment which should be manifested. 229 | 230 | 231 | The status of the manifest operation. 232 | 233 | 234 | 235 | 236 | Manifest the requested DD shipments. 237 | 238 | The identifier for the shipment which should be manifested. 239 | 240 | 241 | The status of the manifest operation. 242 | 243 | 244 | 245 | 246 | Returns the request-url for getting a TD label. 247 | 248 | The identifier for the TD shipment for which the label url is requested. 249 | 250 | 251 | The status of the operation and the label url (if available). 252 | 253 | 254 | 255 | 256 | Returns the request-url for getting a DD label. 257 | 258 | The identifier for the DD shipment for which the label url is requested. 259 | 260 | 261 | The status of the operation and the label url (if available). 262 | 263 | 264 | 265 | 266 | Books a pickup order. 267 | 268 | The data for a pickup order. 269 | 270 | 271 | The status of the book pickup operation and a confirmation number (if available). 272 | 273 | 274 | 275 | 276 | Cancels a pickup order. 277 | 278 | The confirmation number of the pickup order which should be canceled. 279 | 280 | 281 | The status of cancel pickup operation. 282 | 283 | 284 | 285 | 286 | Returns the actual version of the implementation of the whole ISService webservice. 287 | 288 | 289 | The version of webservice implementation. 290 | 291 | 292 | 293 | 294 | Returns the request-url for getting a TD export document. 295 | 296 | The identifier for the TD shipment for which the export document url is requested. 297 | 298 | 299 | The status of the operation and the export document url (if available). 300 | 301 | 302 | 303 | 304 | Returns the request-url for getting a DD export document. 305 | 306 | The identifier for the DD shipment for which the export document url is requested. 307 | 308 | 309 | The status of the operation and the export document url (if available). 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | Creates TD shipments. 320 | 321 | 322 | The authentication data and the shipment data. 323 | 324 | 325 | 326 | 327 | 328 | The status of the operation and the shipment identifier. 329 | 330 | 331 | 332 | 333 | 334 | Creates DD shipments. 335 | 336 | 337 | The authentication data and the shipment data. 338 | 339 | 340 | 341 | 342 | 343 | The status of the operation and the shipment identifier. 344 | 345 | 346 | 347 | 348 | 349 | Deletes the requested TD shipments. 350 | 351 | 352 | The authentication data and the shipment identifier. 353 | 354 | 355 | 356 | 357 | 358 | The status of the operation. 359 | 360 | 361 | 362 | 363 | 364 | Deletes the requested DD shipments. 365 | 366 | 367 | The authentication data and the shipment identifier. 368 | 369 | 370 | 371 | 372 | 373 | The status of the operation. 374 | 375 | 376 | 377 | 378 | 379 | Manifest the requested TD shipments. 380 | 381 | 382 | The authentication data and the shipment identifier. 383 | 384 | 385 | 386 | 387 | 388 | The status of the operation. 389 | 390 | 391 | 392 | 393 | 394 | Manifest the requested DD shipments. 395 | 396 | 397 | The authentication data and the shipment identifier. 398 | 399 | 400 | 401 | 402 | 403 | The status of the operation. 404 | 405 | 406 | 407 | 408 | 409 | Returns the request-url for getting a TD label. 410 | 411 | 412 | The authentication data and the shipment identifier. 413 | 414 | 415 | 416 | 417 | 418 | The status of the operation and the url for requesting the label. 419 | 420 | 421 | 422 | 423 | 424 | Returns the request-url for getting a DD label. 425 | 426 | 427 | The authentication data and the shipment identifier. 428 | 429 | 430 | 431 | 432 | 433 | The status of the operation and the url for requesting the label. 434 | 435 | 436 | 437 | 438 | 439 | Books a pickup order. 440 | 441 | 442 | The authentication data and the order data for the pickup 443 | 444 | 445 | 446 | 447 | 448 | The status of the operation and the confirmation number (if available). 449 | 450 | 451 | 452 | 453 | 454 | Cancels a pickup order. 455 | 456 | 457 | The authentication data and the confirmation number of the pickuporder, which should be canceled 458 | 459 | 460 | 461 | 462 | 463 | The status of the operation. 464 | 465 | 466 | 467 | 468 | 469 | 470 | Returns the actual version of the implementation of the whole ISService webservice. 471 | 472 | 473 | 474 | 475 | 476 | The version of the implementation. 477 | 478 | 479 | 480 | 481 | 482 | Returns the request-url for getting a TD export document. 483 | 484 | 485 | The authentication data and the shipment identifier. 486 | 487 | 488 | 489 | 490 | 491 | The status of the operation and the url for requesting the export document. 492 | 493 | 494 | 495 | 496 | 497 | Returns the request-url for getting a DD export document. 498 | 499 | 500 | The authentication data and the shipment identifier. 501 | 502 | 503 | 504 | 505 | The status of the operation and the url for requesting the export document. 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | Creates TD shipments. 515 | 516 | 517 | The authentication data and the shipment data. 518 | 519 | 520 | 521 | 522 | 523 | The status of the operation and the shipment identifier. 524 | 525 | 526 | 527 | 528 | 529 | Creates DD shipments. 530 | 531 | 532 | The authentication data and the shipment data. 533 | 534 | 535 | 536 | 537 | 538 | The status of the operation and the shipment identifier. 539 | 540 | 541 | 542 | 543 | 544 | Deletes the requested TD shipments. 545 | 546 | 547 | The authentication data and the shipment identifier. 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | Deletes the requested DD shipments. 559 | 560 | 561 | The authentication data and the shipment identifier. 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | Manifest the requested TD shipments. 573 | 574 | 575 | The authentication data and the shipment identifier. 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | Manifest the requested DD shipments. 587 | 588 | 589 | The authentication data and the shipment identifier. 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | Returns the request-url for getting a TD label. 601 | 602 | 603 | The authentication data and the shipment identifier. 604 | 605 | 606 | 607 | 608 | 609 | The status of the operation and the url for requesting the label. 610 | 611 | 612 | 613 | 614 | 615 | Returns the request-url for getting a DD label. 616 | 617 | 618 | The authentication data and the shipment identifier. 619 | 620 | 621 | 622 | 623 | 624 | The status of the operation and the url for requesting the label. 625 | 626 | 627 | 628 | 629 | 630 | Books a pickup order. 631 | 632 | 633 | The authentication data and the order data for the pickup 634 | 635 | 636 | 637 | 638 | 639 | The status of the operation and the confirmation number (if available). 640 | 641 | 642 | 643 | 644 | 645 | Cancels a pickup order. 646 | 647 | 648 | The authentication data and the confirmation number of the pickuporder, which should be canceled 649 | 650 | 651 | 652 | 653 | 654 | The status of the operation. 655 | 656 | 657 | 658 | 659 | 660 | Returns the actual version of the implementation of the whole ISService webservice. 661 | 662 | 663 | 664 | 665 | 666 | The version of the implementation. 667 | 668 | 669 | 670 | 671 | 672 | Returns the request-url for getting a TD export document. 673 | 674 | 675 | The authentication data and the shipment identifier. 676 | 677 | 678 | 679 | 680 | 681 | The status of the operation and the url for requesting the export document. 682 | 683 | 684 | 685 | 686 | 687 | Returns the request-url for getting a DD export document. 688 | 689 | 690 | The authentication data and the shipment identifier. 691 | 692 | 693 | 694 | 695 | 696 | The status of the operation and the url for requesting the export document. 697 | 698 | 699 | 700 | 701 | 702 | 703 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | --------------------------------------------------------------------------------