Skip to content

Commit

Permalink
server: add option enableChunkedEncoding
Browse files Browse the repository at this point in the history
Using res.end() to write result instead of res.write() followed by
res.end() will cause node not to use chunked encoding and include
Content-Length header. (See nodejs/node#26005) This small distinction is
important with some client. For example, Windows 10's MDM enrollment
system will not accept chunked response
(https://docs.microsoft.com/en-us/windows/client-management/mdm/on-premise-authentication-device-enrollment).

This might improve compatibility with some other clients, too. However,
there's a small chance that some client may expect the response to be
chunked. So, I put this behind an option which defaults to enabled and
can be disabled if needed.
  • Loading branch information
peat-psuwit committed Feb 8, 2019
1 parent 5b45068 commit 01fb356
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 11 deletions.
1 change: 1 addition & 0 deletions Readme.md
Expand Up @@ -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');
Expand Down
37 changes: 26 additions & 11 deletions lib/server.js
Expand Up @@ -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 += '/';
Expand Down Expand Up @@ -144,30 +146,22 @@ 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);
}
});
} 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);
}
Expand Down Expand Up @@ -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;
95 changes: 95 additions & 0 deletions test/server-options-test.js
Expand Up @@ -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();
});
});
});
});
});

0 comments on commit 01fb356

Please sign in to comment.