Repository: aliyun/fun
Pull request #218 (2019-05-15 08:57:47)

fix wrong binary body for http request
fix https://github.com/aliyun/fun/issues/206


Patch:
diff --git a/.travis.yml b/.travis.yml
index f80d4b2..8808113 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -20,8 +20,8 @@ cache:
 before_install:
   - > 
     if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then 
-      # docker pull aliyunfc/runtime-python2.7:build-1.5.2
-      # docker pull aliyunfc/runtime-python3.6:build-1.5.2
+      # docker pull aliyunfc/runtime-python2.7:build-1.5.3
+      # docker pull aliyunfc/runtime-python3.6:build-1.5.3
       docker pull bitnami/minideb:jessie
     fi
   # install make for windows
diff --git a/lib/docker-opts.js b/lib/docker-opts.js
index 3bbe2b2..74c96f0 100644
--- a/lib/docker-opts.js
+++ b/lib/docker-opts.js
@@ -30,9 +30,9 @@ function resolveRuntimeToDockerImage(runtime, isBuild) {
     const name = runtimeImageMap[runtime];
     var imageName;
     if (isBuild) {
-      imageName = `aliyunfc/runtime-${name}:build-1.5.2`;
+      imageName = `aliyunfc/runtime-${name}:build-1.5.3`;
     } else {
-      imageName = `aliyunfc/runtime-${name}:1.5.2`;
+      imageName = `aliyunfc/runtime-${name}:1.5.3`;
     }
 
     debug('imageName: ' + imageName);
diff --git a/lib/local/http.js b/lib/local/http.js
index 6e65725..6c41f0a 100644
--- a/lib/local/http.js
+++ b/lib/local/http.js
@@ -5,7 +5,7 @@ const getProfile = require('../profile').getProfile;
 const FC = require('@alicloud/fc2');
 
 const debug = require('debug')('fun:local');
-const httpParser = require('http-string-parser');
+const { parseHeaders, parseStatusLine } = require('http-string-parser');
 
 // https://stackoverflow.com/questions/14313183/javascript-regex-how-do-i-check-if-the-string-is-ascii-only
 /* eslint-disable */
@@ -144,6 +144,33 @@ function filterFunctionResponseAndExecutionInfo(response) {
   return [httpResponse, executionInfo];
 }
 
+// copied from http-string-parser library
+// only change: 
+// use \r\n instead of \r?\n in responseString.split
+// see https://stackoverflow.com/a/27966412/6602338
+// see http.test.js "test image response"
+function parseResponse(responseString) {
+  var headerLines, line, lines, parsedStatusLine, response;
+  response = {};
+  lines = responseString.split(/\r\n/);
+
+  parsedStatusLine = parseStatusLine(lines.shift());
+  response['protocolVersion'] = parsedStatusLine['protocol'];
+  response['statusCode'] = parsedStatusLine['statusCode'];
+  response['statusMessage'] = parsedStatusLine['statusMessage'];
+  headerLines = [];
+  while (lines.length > 0) {
+    line = lines.shift();
+    if (line === '') {
+      break;
+    }
+    headerLines.push(line);
+  }
+  response['headers'] = parseHeaders(headerLines);
+  response['body'] = lines.join('\r\n');
+  return response;
+}
+
 function parseOutputStream(outputStream) {
   // 这里的 outputStream 包含 mock.sh 原始内容，以及 base64 后的 curl 的 response，因此可以直接按照 utf8 toString
   const response = outputStream.toString().split('\n');
@@ -156,7 +183,7 @@ function parseOutputStream(outputStream) {
   // 然后通过使用 binary 将 body 中的二进制数据编码，后面 parser 内部会调用 toString，转换成 binary 后，可以安全地进行 parse 了。
   const rawResponse = Buffer.from(functionBase64Response, 'base64').toString('binary');
 
-  const parsedResponse = httpParser.parseResponse(rawResponse);
+  const parsedResponse = parseResponse(rawResponse);
 
   // 将 binary 的 body 转换回来
   const body = Buffer.from(parsedResponse.body, 'binary');
@@ -205,5 +232,6 @@ module.exports = {
   generateHttpParams, getHttpRawBody,
   validateSignature, parseOutputStream,
   parseHttpTriggerHeaders, validateHeader, filterFunctionResponseAndExecutionInfo,
-  normalizeMultiValues, normalizeRawHeaders
+  normalizeMultiValues, normalizeRawHeaders,
+  parseResponse
 };
\ No newline at end of file


Test patch:
diff --git a/test/docker-opts.test.js b/test/docker-opts.test.js
index fa5d5e8..fffdb64 100644
--- a/test/docker-opts.test.js
+++ b/test/docker-opts.test.js
@@ -71,7 +71,7 @@ describe('test generateLocalInvokeOpts', () => {
       'OpenStdin': true,
       'StdinOnce': true,
       'Tty': false,
-      'Image': 'aliyunfc/runtime-nodejs8:1.5.2',
+      'Image': 'aliyunfc/runtime-nodejs8:1.5.3',
       'HostConfig': {
         'AutoRemove': true,
         'Mounts': [
@@ -115,7 +115,7 @@ describe('test generateLocalInvokeOpts', () => {
       'StdinOnce': true,
       'Tty': false,
       'User': null,
-      'Image': 'aliyunfc/runtime-nodejs8:1.5.2',
+      'Image': 'aliyunfc/runtime-nodejs8:1.5.3',
       'Env': [
         'LD_LIBRARY_PATH=/code/.fun/root/usr/lib:/code/.fun/root/usr/lib/x86_64-linux-gnu:/code:/code/lib:/usr/local/lib',
         'PATH=/code/.fun/root/usr/local/bin:/code/.fun/root/usr/local/sbin:/code/.fun/root/usr/bin:/code/.fun/root/usr/sbin:/code/.fun/root/sbin:/code/.fun/root/bin:/code/.fun/python/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin:/sbin:/bin',
diff --git a/test/local/api-invoke.test.js b/test/local/api-invoke.test.js
index 65e464e..b26e348 100644
--- a/test/local/api-invoke.test.js
+++ b/test/local/api-invoke.test.js
@@ -24,7 +24,7 @@ var FCClient = require('@alicloud/fc2');
 const apiOutputSream = `FC Invoke Start RequestId: 65ca478d-b3cf-41d5-b668-9b89a4d481d8
 load code for handler:read.handler
 --------------------response begin-----------------
-SFRUUC8xLjEgMjAwIE9LCgp0ZXN0Qm9keQ==
+${Buffer.from('HTTP/1.1 200 OK\r\n\r\ntestBody').toString('base64')}
 --------------------response end-----------------
 --------------------execution info begin-----------------
 OWM4MWI1M2UtZWQxNy00MzI3LWFjNzctMjhkYWMzNzRlMDU1CjE4MgoxOTk4CjIwCg==
diff --git a/test/local/http-invoke.test.js b/test/local/http-invoke.test.js
index e6d0450..bdcd1c8 100644
--- a/test/local/http-invoke.test.js
+++ b/test/local/http-invoke.test.js
@@ -26,11 +26,9 @@ const { setProcess } = require('../test-utils');
 const httpOutputStream = `"FC Invoke Start RequestId: 65ca478d-b3cf-41d5-b668-9b89a4d481d8
 load code for handler:read.handler
 --------------------response begin-----------------
-SFRUUC8xLjEgMjAwIE9LCngtZmMtaHR0cC1wYXJhbXM6IGV5Snp
-kR0YwZFhNaU9qSXdNQ3dpYUdWaFpHVnljeUk2ZXlKamIyNTBaVz
-UwTFhSNWNHVWlPaUpoY0hCc2FXTmhkR2x2Ymk5cWMyOXVJbjBzS
-W1obFlXUmxjbk5OWVhBaU9uc2lZMjl1ZEdWdWRDMTBlWEJsSWpw
-YkltRndjR3hwWTJGMGFXOXVMMnB6YjI0aVhYMTkKCnRlc3RCb2R5
+${Buffer.from('HTTP/1.1 200 OK\r\n'
+  + 'x-fc-http-params: eyJzdGF0dXMiOjIwMCwiaGVhZGVycyI6eyJjb250ZW50LXR5cGUiOiJhcHBsaWNhdGlvbi9qc29uIn0sImhlYWRlcnNNYXAiOnsiY29udGVudC10eXBlIjpbImFwcGxpY2F0aW9uL2pzb24iXX19'
+  + '\r\n\r\ntestBody').toString('base64')}
 --------------------response end-----------------
 --------------------execution info begin-----------------
 OWM4MWI1M2UtZWQxNy00MzI3LWFjNzctMjhkYWMzNzRlMDU1CjE4MgoxOTk4CjIwCg==
@@ -41,7 +39,7 @@ OWM4MWI1M2UtZWQxNy00MzI3LWFjNzctMjhkYWMzNzRlMDU1CjE4MgoxOTk4CjIwCg==
 `;
 
 const httpErrorOutputStream = `--------------------response begin-----------------
-SFRUUC8xLjEgNDAwIE9LCgp0ZXN0Qm9keQ==
+SFRUUC8xLjEgNDAwIE9LDQoNCnRlc3RCb2R5
 --------------------response end-----------------`;
 
 describe('test http response', async () => {
@@ -56,6 +54,8 @@ describe('test http response', async () => {
     };
 
     httpInvoke.response(httpOutputStream, '', resp);
+    console.log(Buffer.from('HTTP/1.1 400 OK\r\n\r\ntestBody').toString('base64'));
+
   
     assert.calledWith(resp.status, 200);
     assert.calledWith(resp.setHeader, 'content-type', ['application/json']);
diff --git a/test/local/http.test.js b/test/local/http.test.js
index 8bdbb0b..1647cc8 100644
--- a/test/local/http.test.js
+++ b/test/local/http.test.js
@@ -13,11 +13,7 @@ const { setProcess } = require('../test-utils');
 const httpOutputStream = `"FC Invoke Start RequestId: 65ca478d-b3cf-41d5-b668-9b89a4d481d8
 load code for handler:read.handler
 --------------------response begin-----------------
-SFRUUC8xLjEgMjAwIE9LCngtZmMtaHR0cC1wYXJhbXM6IGV5Snp
-kR0YwZFhNaU9qSXdNQ3dpYUdWaFpHVnljeUk2ZXlKamIyNTBaVz
-UwTFhSNWNHVWlPaUpoY0hCc2FXTmhkR2x2Ymk5cWMyOXVJbjBzS
-W1obFlXUmxjbk5OWVhBaU9uc2lZMjl1ZEdWdWRDMTBlWEJsSWpw
-YkltRndjR3hwWTJGMGFXOXVMMnB6YjI0aVhYMTkKCnRlc3RCb2R5
+SFRUUC8xLjEgMjAwIE9LDQp4LWZjLWh0dHAtcGFyYW1zOiBleUp6ZEdGMGRYTWlPakl3TUN3aWFHVmhaR1Z5Y3lJNmV5SmpiMjUwWlc1MExYUjVjR1VpT2lKaGNIQnNhV05oZEdsdmJpOXFjMjl1SW4wc0ltaGxZV1JsY25OTllYQWlPbnNpWTI5dWRHVnVkQzEwZVhCbElqcGJJbUZ3Y0d4cFkyRjBhVzl1TDJwemIyNGlYWDE5DQoNCnRlc3RCb2R5
 --------------------response end-----------------
 --------------------execution info begin-----------------
 OWM4MWI1M2UtZWQxNy00MzI3LWFjNzctMjhkYWMzNzRlMDU1CjE4MgoxOTk4CjIwCg==
@@ -108,6 +104,29 @@ describe('test parseOutputStream', async () => {
   });
 });
 
+// response 所包含的 body，是一张简单的图片，原有的 http-string-parser 在 split 时使用 \r?\n 的方式 split 会导致有问题
+describe('test image response', async () => {
+  const responseString = `SFRUUC8xLjEgMjAwIE9LDQpYLVBvd2VyZWQtQnk6IEV4cHJlc3MNCngtZmMtaHR0cC1wYXJhbXM6
+IGV5SnpkR0YwZFhNaU9qSXdNQ3dpYUdWaFpHVnljeUk2ZXlKamIyNTBaVzUwTFhSNWNHVWlPaUpw
+YldGblpTOXdibWNpZlN3aWFHVmhaR1Z5YzAxaGNDSTZleUpqYjI1MFpXNTBMWFI1Y0dVaU9sc2lh
+VzFoWjJVdmNHNW5JbDE5ZlE9PQ0KQ29udGVudC1UeXBlOiBhcHBsaWNhdGlvbi9vY3RldC1zdHJl
+YW0NCkNvbnRlbnQtTGVuZ3RoOiA5Nw0KRVRhZzogVy8iNjEtTGJKdGZrck01a0NoRnV4cnh4WVNM
+bmZQRHVZIg0KRGF0ZTogV2VkLCAxNSBNYXkgMjAxOSAwNjo0MDo1MiBHTVQNCkNvbm5lY3Rpb246
+IGtlZXAtYWxpdmUNCg0KiVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAKElEQVQ4
+jWNgYGD4Twzu6FhFFGYYNXDUwGFpIAk2E4dHDRw1cDgaCAASFOffhEIO3gAAAABJRU5ErkJggg==`;
+
+  it('test parse response', async () => {
+    const rawResponse = Buffer.from(responseString, 'base64').toString('binary');
+
+    const parsedResponse = http.parseResponse(rawResponse);
+
+    console.log('response is ' + parsedResponse.body);
+
+    const base64Body = Buffer.from(parsedResponse.body, 'binary').toString('base64');
+    expect(base64Body).to.eql('iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAKElEQVQ4jWNgYGD4Twzu6FhFFGYYNXDUwGFpIAk2E4dHDRw1cDgaCAASFOffhEIO3gAAAABJRU5ErkJggg==');
+  });
+});
+
 describe('test validateSignature', async () => {
 
   let restoreProcess;
diff --git a/test/local/invoke.test.js b/test/local/invoke.test.js
index 88b379b..64bd7e2 100644
--- a/test/local/invoke.test.js
+++ b/test/local/invoke.test.js
@@ -77,9 +77,9 @@ describe('test invoke construct and init', async () => {
     expect(invoke.codeMount).to.eql(codeMount);
     expect(invoke.mounts).to.eql([codeMount]);
     expect(invoke.containerName).to.contain('fun_local_');
-    expect(invoke.imageName).to.contain('aliyunfc/runtime-python3.6:1.5.2');
+    expect(invoke.imageName).to.contain('aliyunfc/runtime-python3.6:1.5.3');
 
-    assert.calledWith(docker.pullImageIfNeed, 'aliyunfc/runtime-python3.6:1.5.2');
+    assert.calledWith(docker.pullImageIfNeed, 'aliyunfc/runtime-python3.6:1.5.3');
   });
 
   it('test init with nas config', async () => {
@@ -109,9 +109,9 @@ describe('test invoke construct and init', async () => {
     expect(invoke.codeMount).to.eql(codeMount);
     expect(invoke.mounts).to.eql([codeMount, ...nasMounts]);
     expect(invoke.containerName).to.contain('fun_local_');
-    expect(invoke.imageName).to.eql('aliyunfc/runtime-python3.6:1.5.2');
+    expect(invoke.imageName).to.eql('aliyunfc/runtime-python3.6:1.5.3');
 
-    assert.calledWith(docker.pullImageIfNeed, 'aliyunfc/runtime-python3.6:1.5.2');
+    assert.calledWith(docker.pullImageIfNeed, 'aliyunfc/runtime-python3.6:1.5.3');
   });
 });