diff --git a/Readme.md b/Readme.md index e806aaf3c..0671d53f5 100644 --- a/Readme.md +++ b/Readme.md @@ -219,6 +219,7 @@ Server options include the below: - `ca`: An array of strings or Buffers of trusted certificates in PEM format. If this is omitted several well known "root" CAs will be used, like VeriSign. These are used to authorize connections. - `crl` : Either a string or list of strings of PEM encoded CRLs (Certificate Revocation List) - `ciphers`: A string describing the ciphers to use or exclude, separated by :. The default cipher suite is: +- `enableChunkedEncoding`: A boolean for controlling chunked transfer encoding in response. Some client (such as Windows 10's MDM enrollment SOAP client) is sensitive to transfer-encoding mode and can't accept chunked response. This option let user disable chunked transfer encoding for such a client. Default to `true` for backward compatibility. ``` javascript var xml = require('fs').readFileSync('myservice.wsdl', 'utf8'); diff --git a/lib/server.js b/lib/server.js index a5098d073..cda278315 100644 --- a/lib/server.js +++ b/lib/server.js @@ -40,6 +40,8 @@ var Server = function (server, path, services, wsdl, options) { this.suppressStack = options && options.suppressStack; this.returnFault = options && options.returnFault; this.onewayOptions = options && options.oneWay || {}; + this.enableChunkedEncoding = + options.enableChunkedEncoding === undefined ? true : !!options.enableChunkedEncoding; if (path[path.length - 1] !== '/') path += '/'; @@ -144,11 +146,7 @@ Server.prototype._processRequestXml = function (req, res, xml) { self.log("received", xml); } self._process(xml, req, function (result, statusCode) { - if (statusCode) { - res.statusCode = statusCode; - } - res.write(result); - res.end(); + self._sendHttpResponse(res, statusCode, result); if (typeof self.log === 'function') { self.log("replied", result); } @@ -156,18 +154,14 @@ Server.prototype._processRequestXml = function (req, res, xml) { } catch (err) { if (err.Fault !== undefined) { return self._sendError(err.Fault, function (result, statusCode) { - res.statusCode = statusCode || 500; - res.write(result); - res.end(); + self._sendHttpResponse(res, statusCode || 500, result); if (typeof self.log === 'function') { self.log("error", err); } }, new Date().toISOString()); } else { error = err.stack ? (self.suppressStack === true ? err.message : err.stack) : err; - res.statusCode = 500; - res.write(error); - res.end(); + self._sendHttpResponse(res, /* statusCode */ 500, error); if (typeof self.log === 'function') { self.log("error", error); } @@ -517,4 +511,25 @@ Server.prototype._sendError = function (soapFault, callback, includeTimestamp) { return callback(self._envelope(fault, '', includeTimestamp), statusCode); }; +Server.prototype._sendHttpResponse = function (res, statusCode, result) { + if (statusCode) { + res.statusCode = statusCode; + } + + /* + * Calling res.write(result) follow by res.end() will cause Node.js to use + * chunked encoding, while calling res.end(result) directly will cause + * Node.js to calculate and send Content-Length header. See + * nodejs/node#26005. + */ + + if (this.enableChunkedEncoding) { + res.write(result); + res.end(); + } + else { + res.end(result); + } +} + exports.Server = Server; diff --git a/lib/soap.d.ts b/lib/soap.d.ts index 2a5e81108..09e75223c 100644 --- a/lib/soap.d.ts +++ b/lib/soap.d.ts @@ -97,6 +97,7 @@ export interface IServerOptions extends IWsdlBaseOptions { uri?: string; suppressStack?: boolean; oneWay?: IOneWayOptions; + enableChunkedEncoding?: boolean; [key: string]: any; } diff --git a/test/server-options-test.js b/test/server-options-test.js index 6665c4c38..726557fed 100644 --- a/test/server-options-test.js +++ b/test/server-options-test.js @@ -460,4 +460,99 @@ describe('SOAP Server with Options', function() { }); }); }); + it('should use chunked transfer encoding by default', function (done) { + test.server.listen(15099, null, null, function() { + test.soapServer = soap.listen(test.server, { + path: '/stockquote', + services: test.service, + xml: test.wsdl, + }, test.service, test.wsdl); + test.baseUrl = 'http://' + test.server.address().address + ":" + test.server.address().port; + + //windows return 0.0.0.0 as address and that is not + //valid to use in a request + if (test.server.address().address === '0.0.0.0' || test.server.address().address === '::') { + test.baseUrl = 'http://127.0.0.1:' + test.server.address().port; + } + + soap.createClient(test.baseUrl + '/stockquote?wsdl', function(err, client) { + assert.ifError(err); + + client.on('response', function(body, response, eid) { + var headers = response.headers; + assert.strictEqual(headers['transfer-encoding'], 'chunked'); + assert.equal(headers['content-length'], undefined); + }) + + client.SetTradePrice({ tickerSymbol: 'GOOG' }, function(err, result, body) { + assert.ifError(err); + done(); + }); + }); + }); + }); + it('should use chunked transfer encoding when enabled in options', function (done) { + test.server.listen(15099, null, null, function() { + test.soapServer = soap.listen(test.server, { + path: '/stockquote', + services: test.service, + xml: test.wsdl, + enableChunkedEncoding: true, + }, test.service, test.wsdl); + test.baseUrl = 'http://' + test.server.address().address + ":" + test.server.address().port; + + //windows return 0.0.0.0 as address and that is not + //valid to use in a request + if (test.server.address().address === '0.0.0.0' || test.server.address().address === '::') { + test.baseUrl = 'http://127.0.0.1:' + test.server.address().port; + } + + soap.createClient(test.baseUrl + '/stockquote?wsdl', function(err, client) { + assert.ifError(err); + + client.on('response', function(body, response, eid) { + var headers = response.headers; + assert.strictEqual(headers['transfer-encoding'], 'chunked'); + assert.equal(headers['content-length'], undefined); + }) + + client.SetTradePrice({ tickerSymbol: 'GOOG' }, function(err, result, body) { + assert.ifError(err); + done(); + }); + }); + }); + }); + it('should not use chunked transfer encoding when disabled in options', function (done) { + test.server.listen(15099, null, null, function() { + test.soapServer = soap.listen(test.server, { + path: '/stockquote', + services: test.service, + xml: test.wsdl, + enableChunkedEncoding: false, + }, test.service, test.wsdl); + test.baseUrl = 'http://' + test.server.address().address + ":" + test.server.address().port; + + //windows return 0.0.0.0 as address and that is not + //valid to use in a request + if (test.server.address().address === '0.0.0.0' || test.server.address().address === '::') { + test.baseUrl = 'http://127.0.0.1:' + test.server.address().port; + } + + soap.createClient(test.baseUrl + '/stockquote?wsdl', function(err, client) { + assert.ifError(err); + + client.on('response', function(body, response, eid) { + var headers = response.headers; + assert.notEqual(headers['content-length'], undefined); + assert.equal(headers['transfer-encoding'], undefined); + }) + + client.SetTradePrice({ tickerSymbol: 'GOOG' }, function(err, result, body) { + assert.ifError(err); + done(); + }); + }); + }); + }); });