Optimize memory usage by proxy class
MODIFY
lib/jsonwp-proxy/proxy.js
lib/jsonwp-proxy/proxy.js
@@ -131,12 +131,19 @@ class JWProxy {
timeout: this.timeout,
forever: this.keepAlive,
};
- if (body !== null) {
+ if (util.hasValue(body)) {
if (typeof body !== 'object') {
- body = JSON.parse(body);
+ try {
+ reqOpts.json = JSON.parse(body);
+ } catch (e) {
+ throw new Error('Cannot interpret the request body as valid JSON: ' +
+ _.truncate(_.isString(body) ? body : JSON.stringify(body),
+ {length: LOG_OBJ_LENGTH}));
}
+ } else {
reqOpts.json = body;
}
+ }
// GET methods shouldn't have any body. Most servers are OK with this, but WebDriverAgent throws 400 errors
if (method === 'GET') {
@@ -144,60 +151,62 @@ class JWProxy {
}
log.debug(`Proxying [${method} ${url || '/'}] to [${method} ${newUrl}] ` +
- (body ? `with body: ${_.truncate(JSON.stringify(body), {length: LOG_OBJ_LENGTH})}` : 'with no body'));
+ (body ? `with body: ${_.truncate(_.isString(body) ? body : JSON.stringify(body),
+ {length: LOG_OBJ_LENGTH})}` : 'with no body'));
- let res, resBody;
- try {
- res = await this.request(reqOpts);
- resBody = res.body;
- log.debug(`Got response with status ${res.statusCode}: ${_.truncate(JSON.stringify(resBody), {length: LOG_OBJ_LENGTH})}`);
- if (/\/session$/.test(url) && method === 'POST') {
- if (res.statusCode === 200) {
- this.sessionId = resBody.sessionId;
- } else if (res.statusCode === 303) {
- this.sessionId = /\/session\/([^/]+)/.exec(resBody)[1];
- }
- }
- const resBodyObj = util.safeJsonParse(resBody);
- if (!this.downstreamProtocol) {
- this.downstreamProtocol = this.getProtocolFromResBody(resBodyObj);
- log.debug(`Determined that the downstream protocol for proxy is ${this.downstreamProtocol}`);
- }
- if (res.statusCode < 400 && this.downstreamProtocol === MJSONWP && parseInt(resBodyObj.status, 10) !== 0) {
- // Some servers, like chromedriver may return response code 200 for non-zero JSONWP statuses
+ const throwProxyError = (error) => {
const message = `The request to ${url} has failed`;
const err = new Error(message);
err.message = message;
- err.error = resBody;
+ err.error = error;
err.statusCode = 500;
throw err;
+ };
+ try {
+ const res = await this.request(reqOpts);
+ // `res.body` might be really big
+ // Be careful while handling it to avoid memory leaks
+ const resObj = util.safeJsonParse(res.body);
+ if (!_.isPlainObject(resObj)) {
+ // The response should be a valid JSON object
+ // If it cannot be coerced to an object then the response is wrong
+ throwProxyError(res.body);
+ }
+ log.debug(`Got response with status ${res.statusCode}: ` +
+ _.truncate(_.isString(res.body) ? res.body : JSON.stringify(res.body), {
+ length: LOG_OBJ_LENGTH,
+ })
+ );
+ if (/\/session$/.test(url) && method === 'POST' && res.statusCode === 200) {
+ this.sessionId = resObj.sessionId;
+ }
+ if (!this.downstreamProtocol) {
+ this.downstreamProtocol = this.getProtocolFromResBody(resObj);
+ log.debug(`Determined the downstream protocol for the proxy as '${this.downstreamProtocol}'`);
}
+ if (res.statusCode < 400 && this.downstreamProtocol === MJSONWP &&
+ _.has(resObj, 'status') && parseInt(resObj.status, 10) !== 0) {
+ // Some servers, like chromedriver may return response code 200 for non-zero JSONWP statuses
+ throwProxyError(resObj);
+ }
+ return [res, resObj];
} catch (e) {
- let responseError = e.error;
- try {
- responseError = JSON.parse(responseError);
- } catch (e1) {
+ if (util.hasValue(e.error)) {
log.warn(`Got an unexpected response: ` +
- _.truncate(_.isString(responseError) ? responseError : JSON.stringify(responseError), {length: 300}));
+ _.truncate(_.isString(e.error) ? e.error : JSON.stringify(e.error), {length: LOG_OBJ_LENGTH}));
+ } else {
+ log.debug(e.stack);
}
throw new errors.ProxyRequestError(`Could not proxy command to remote server. ` +
- `Original error: ${e.message}`, responseError, e.statusCode);
+ `Original error: ${e.message}`, e.error, e.statusCode);
}
- return [res, resBody];
}
- getProtocolFromResBody (resBody) {
- if (!_.isPlainObject(resBody)) {
- try {
- resBody = JSON.parse(resBody);
- } catch (err) {
- return;
- }
- }
- if (util.hasValue(resBody.status)) {
+ getProtocolFromResBody (resObj) {
+ if (util.hasValue(resObj.status)) {
return MJSONWP;
}
- if (util.hasValue(resBody.value)) {
+ if (util.hasValue(resObj.value)) {
return W3C;
}
}
@@ -229,25 +238,24 @@ class JWProxy {
async command (url, method, body = null) {
let response;
- let resBody;
+ let resObj;
try {
- [response, resBody] = await this.proxyCommand(url, method, body);
+ [response, resObj] = await this.proxyCommand(url, method, body);
} catch (err) {
if (isErrorType(err, errors.ProxyRequestError)) {
throw err.getActualError();
}
throw new errors.UnknownError(err.message);
}
- resBody = util.safeJsonParse(resBody);
- const protocol = this.getProtocolFromResBody(resBody);
+ const protocol = this.getProtocolFromResBody(resObj);
if (protocol === MJSONWP) {
// Got response in MJSONWP format
- if (response.statusCode === 200 && resBody.status === 0) {
- return resBody.value;
+ if (response.statusCode === 200 && resObj.status === 0) {
+ return resObj.value;
}
- const status = parseInt(resBody.status, 10);
+ const status = parseInt(resObj.status, 10);
if (!isNaN(status) && status !== 0) {
- let message = resBody.value;
+ let message = resObj.value;
if (_.has(message, 'message')) {
message = message.message;
}
@@ -256,17 +264,17 @@ class JWProxy {
} else if (protocol === W3C) {
// Got response in W3C format
if (response.statusCode < 300) {
- return resBody.value;
+ return resObj.value;
}
- if (_.isPlainObject(resBody.value) && resBody.value.error) {
- throw errorFromW3CJsonCode(resBody.value.error, resBody.value.message, resBody.value.stacktrace);
+ if (_.isPlainObject(resObj.value) && resObj.value.error) {
+ throw errorFromW3CJsonCode(resObj.value.error, resObj.value.message, resObj.value.stacktrace);
}
} else if (response.statusCode === 200) {
// Unknown protocol. Keeping it because of the backward compatibility
- return resBody;
+ return resObj;
}
throw new errors.UnknownError(`Did not know what to do with response code '${response.statusCode}' ` +
- `and response body '${_.truncate(JSON.stringify(resBody), {length: 300})}'`);
+ `and response body '${_.truncate(JSON.stringify(resObj), {length: 300})}'`);
}
getSessionIdFromUrl (url) {

MODIFY
test/jsonwp-proxy/mock-request.js
test/jsonwp-proxy/mock-request.js
-function resFixture (url, method, json) {
+function resFixture (url, method) {
if (/\/status$/.test(url)) {
return [200, {status: 0, value: {foo: 'bar'}}];
}
@@ -12,12 +12,8 @@ function resFixture (url, method, json) {
return [200, {status: 0, sessionId: 'innersessionid', value: 'foobar'}];
}
if (/\/session$/.test(url) && method === 'POST') {
- if (json.desiredCapabilities && json.desiredCapabilities.redirect) {
- return [303, 'http://localhost:4444/wd/hub/session/123'];
- } else {
return [200, {status: 0, sessionId: '123', value: {browserName: 'boo'}}];
}
- }
if (/\/nochrome$/.test(url)) {
return [100, {status: 0, value: {message: 'chrome not reachable'}}];
}

MODIFY
test/jsonwp-proxy/proxy-e2e-specs.js
test/jsonwp-proxy/proxy-e2e-specs.js
@@ -18,7 +18,6 @@ describe('proxy', function () {
it('should proxy status straight', async function () {
let [res, resBody] = await jwproxy.proxy('/status', 'GET');
- resBody = JSON.parse(resBody);
res.statusCode.should.equal(200);
resBody.status.should.equal(0);
resBody.value.should.equal(`I'm fine`);

MODIFY
test/jsonwp-proxy/proxy-specs.js
test/jsonwp-proxy/proxy-specs.js
@@ -49,13 +49,6 @@ describe('proxy', function () {
body.should.eql({status: 0, sessionId: '123', value: {browserName: 'boo'}});
j.sessionId.should.equal('123');
});
- it('should save session id on session creation with 303', async function () {
- let j = mockProxy();
- let [res, body] = await j.proxy('/session', 'POST', {desiredCapabilities: {redirect: true}});
- res.statusCode.should.equal(303);
- body.should.eql('http://localhost:4444/wd/hub/session/123');
- j.sessionId.should.equal('123');
- });
describe('getUrlForProxy', function () {
it('should modify session id, host, and port', function () {
let j = mockProxy({sessionId: '123'});

Optimize memory usage by proxy class (#334)