From ce63398713a77eaf83b69ad5cfa6ceb1472b7f31 Mon Sep 17 00:00:00 2001 From: Haili Zhang Date: Sat, 14 May 2022 17:38:23 +0800 Subject: [PATCH 1/7] =?UTF-8?q?=F0=9F=91=B7=20ci:=20add=20pre-push=20hook?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Haili Zhang --- package-lock.json | 297 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 6 +- 2 files changed, 302 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 6cbc416d..f98880d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,6 +46,7 @@ "gts": "^3.1.0", "mocha": "9.1.2", "pack-n-play": "^1.0.0-2", + "pre-push": "^0.1.2", "shelljs": "^0.8.5", "sinon": "^12.0.0", "supertest": "6.1.6", @@ -1384,6 +1385,12 @@ "ieee754": "^1.1.13" } }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, "node_modules/bulk-write-stream": { "version": "2.0.1", "resolved": "https://registry.npmmirror.com/bulk-write-stream/-/bulk-write-stream-2.0.1.tgz", @@ -1658,6 +1665,51 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmmirror.com/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/concat-stream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "node_modules/concat-stream/node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/concat-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/concurrently": { "version": "7.0.0", "resolved": "https://registry.npmmirror.com/concurrently/-/concurrently-7.0.0.tgz", @@ -5258,6 +5310,15 @@ "node": ">= 0.8.0" } }, + "node_modules/os-shim": { + "version": "0.1.3", + "resolved": "https://registry.npmmirror.com/os-shim/-/os-shim-0.1.3.tgz", + "integrity": "sha512-jd0cvB8qQ5uVt0lvCIexBaROw1KyKm5sbulg2fWOHjETisuCzWyt+eTZKEMs8v6HwzoGs8xik26jg7eCM6pS+A==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -5498,6 +5559,78 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pre-push": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/pre-push/-/pre-push-0.1.2.tgz", + "integrity": "sha512-WymiU20I3W1yGV4VEqa+xOqaqGHYUz49R4ARvEdmYPgL07E/Gn7NLewI9k5g6KUc2EOaWE42rBb7nfVKYtkpCA==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "cross-spawn": "^5.0.1", + "spawn-sync": "^1.0.15", + "which": "1.2.x" + } + }, + "node_modules/pre-push/node_modules/cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", + "dev": true, + "dependencies": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "node_modules/pre-push/node_modules/lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "dependencies": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "node_modules/pre-push/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "dependencies": { + "shebang-regex": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pre-push/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pre-push/node_modules/which": { + "version": "1.2.14", + "resolved": "https://registry.npmmirror.com/which/-/which-1.2.14.tgz", + "integrity": "sha512-16uPglFkRPzgiUXYMi1Jf8Z5EzN1iB4V0ZtMXcHZnwsBtQhhHeCqoWw7tsUY42hJGNDWtUsVLTjakIa5BgAxCw==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/pre-push/node_modules/yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "dev": true + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -5592,6 +5725,12 @@ "node": ">= 0.10" } }, + "node_modules/pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", + "dev": true + }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -6328,6 +6467,16 @@ "integrity": "sha512-n98l9E2RMSJ9ON1AKisHzz7V42VDiBQGY6PB1BwRglz99wpVsSuGzQ+jOi6lFXBGVTCrRpltvjm+/XA+tpeJrg==", "dev": true }, + "node_modules/spawn-sync": { + "version": "1.0.15", + "resolved": "https://registry.npmmirror.com/spawn-sync/-/spawn-sync-1.0.15.tgz", + "integrity": "sha512-9DWBgrgYZzNghseho0JOuh+5fg9u6QWhAWa51QC7+U5rCheZ/j1DrEZnyE0RBBRqZ9uEXGPgSSM0nky6burpVw==", + "dev": true, + "dependencies": { + "concat-stream": "^1.4.7", + "os-shim": "^0.1.2" + } + }, "node_modules/spdx-correct": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", @@ -6786,6 +6935,12 @@ "node": ">= 0.6" } }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmmirror.com/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "dev": true + }, "node_modules/typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", @@ -8476,6 +8631,12 @@ "ieee754": "^1.1.13" } }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, "bulk-write-stream": { "version": "2.0.1", "resolved": "https://registry.npmmirror.com/bulk-write-stream/-/bulk-write-stream-2.0.1.tgz", @@ -8696,6 +8857,50 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmmirror.com/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmmirror.com/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "concurrently": { "version": "7.0.0", "resolved": "https://registry.npmmirror.com/concurrently/-/concurrently-7.0.0.tgz", @@ -11384,6 +11589,12 @@ "word-wrap": "^1.2.3" } }, + "os-shim": { + "version": "0.1.3", + "resolved": "https://registry.npmmirror.com/os-shim/-/os-shim-0.1.3.tgz", + "integrity": "sha512-jd0cvB8qQ5uVt0lvCIexBaROw1KyKm5sbulg2fWOHjETisuCzWyt+eTZKEMs8v6HwzoGs8xik26jg7eCM6pS+A==", + "dev": true + }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", @@ -11558,6 +11769,70 @@ "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", "dev": true }, + "pre-push": { + "version": "0.1.2", + "resolved": "https://registry.npmmirror.com/pre-push/-/pre-push-0.1.2.tgz", + "integrity": "sha512-WymiU20I3W1yGV4VEqa+xOqaqGHYUz49R4ARvEdmYPgL07E/Gn7NLewI9k5g6KUc2EOaWE42rBb7nfVKYtkpCA==", + "dev": true, + "requires": { + "cross-spawn": "^5.0.1", + "spawn-sync": "^1.0.15", + "which": "1.2.x" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true + }, + "which": { + "version": "1.2.14", + "resolved": "https://registry.npmmirror.com/which/-/which-1.2.14.tgz", + "integrity": "sha512-16uPglFkRPzgiUXYMi1Jf8Z5EzN1iB4V0ZtMXcHZnwsBtQhhHeCqoWw7tsUY42hJGNDWtUsVLTjakIa5BgAxCw==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmmirror.com/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==", + "dev": true + } + } + }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -11626,6 +11901,12 @@ "ipaddr.js": "1.9.1" } }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==", + "dev": true + }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -12180,6 +12461,16 @@ "integrity": "sha512-n98l9E2RMSJ9ON1AKisHzz7V42VDiBQGY6PB1BwRglz99wpVsSuGzQ+jOi6lFXBGVTCrRpltvjm+/XA+tpeJrg==", "dev": true }, + "spawn-sync": { + "version": "1.0.15", + "resolved": "https://registry.npmmirror.com/spawn-sync/-/spawn-sync-1.0.15.tgz", + "integrity": "sha512-9DWBgrgYZzNghseho0JOuh+5fg9u6QWhAWa51QC7+U5rCheZ/j1DrEZnyE0RBBRqZ9uEXGPgSSM0nky6burpVw==", + "dev": true, + "requires": { + "concat-stream": "^1.4.7", + "os-shim": "^0.1.2" + } + }, "spdx-correct": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", @@ -12528,6 +12819,12 @@ "mime-types": "~2.1.24" } }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmmirror.com/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "dev": true + }, "typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", diff --git a/package.json b/package.json index 4edab8fc..30af98c0 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,6 @@ "scripts": { "test": "cross-env DEBUG=common:*,ofn:* mocha build/test -t 60000 --recursive --exit", "build": "npm run clean && npm run compile", - "conformance": "./run_conformance_tests.sh", "check": "gts check", "clean": "gts clean", "compile": "tsc -p .", @@ -38,6 +37,10 @@ "prepare": "npm run build", "pretest": "npm run compile" }, + "prepush": [ + "build", + "test" + ], "files": [ "build/src/**/*.js", "build/src/**/*.d.ts" @@ -69,6 +72,7 @@ "gts": "^3.1.0", "mocha": "9.1.2", "pack-n-play": "^1.0.0-2", + "pre-push": "^0.1.2", "shelljs": "^0.8.5", "sinon": "^12.0.0", "supertest": "6.1.6", From 797b912455965653a4cff1930873be57a76e3cd2 Mon Sep 17 00:00:00 2001 From: Haili Zhang Date: Sun, 15 May 2022 16:02:37 +0800 Subject: [PATCH 2/7] =?UTF-8?q?=E2=9C=A8=20feat:=20enable=20HTTP=20trigger?= =?UTF-8?q?=20async=20function?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Haili Zhang --- src/function_wrappers.ts | 50 ++++++++++++++++++++++++++-- src/functions.ts | 27 ++++++++++++++- src/openfunction/async_server.ts | 24 ++----------- src/openfunction/function_runtime.ts | 46 +++++++++++++++++++++++-- src/server.ts | 12 +++---- test/conformance/function.js | 9 ++--- test/conformance/package.json | 8 ++--- test/integration/http_binding.ts | 13 +++++--- 8 files changed, 143 insertions(+), 46 deletions(-) diff --git a/src/function_wrappers.ts b/src/function_wrappers.ts index 55ad558b..d9c36077 100644 --- a/src/function_wrappers.ts +++ b/src/function_wrappers.ts @@ -14,6 +14,10 @@ // eslint-disable-next-line node/no-deprecated-api import * as domain from 'domain'; + +import * as Debug from 'debug'; +import {get, isEmpty} from 'lodash'; + import {Request, Response, RequestHandler} from 'express'; import {sendCrashResponse} from './logger'; import {sendResponse} from './invoker'; @@ -27,9 +31,14 @@ import { CloudEventFunctionWithCallback, HandlerFunction, } from './functions'; -import {CloudEvent} from './functions'; +import {CloudEvent, OpenFunction} from './functions'; import {SignatureType} from './types'; +import {OpenFunctionContext} from './openfunction/function_context'; +import {OpenFunctionRuntime} from './openfunction/function_runtime'; + +const debug = Debug('common:wrapper'); + /** * The handler function used to signal completion of event functions. */ @@ -122,6 +131,37 @@ const wrapHttpFunction = (execute: HttpFunction): RequestHandler => { }; }; +const wrapHttpAsyncFunction = ( + userFunction: OpenFunction, + context: OpenFunctionContext +): RequestHandler => { + const ctx = OpenFunctionRuntime.ProxyContext(context); + const httpHandler = (req: Request, res: Response) => { + const callback = getOnDoneCallback(res); + + Promise.resolve() + .then(() => userFunction(ctx, req.body)) + .then(result => { + debug('ℹ️ User function returned: %j', result); + + const data = get(result, 'body'); + const code = get(result, 'code', 200); + const headers = get(result, 'headers'); + + !isEmpty(headers) && res.set(headers); + + if (data !== undefined) { + res.status(code).send(data); + } else { + res.status(code).end(); + } + }) + .catch(err => callback(err, undefined)); + }; + + return wrapHttpFunction(httpHandler); +}; + /** * Wraps an async CloudEvent function in an express RequestHandler. * @param userFunction User's function. @@ -202,10 +242,16 @@ const wrapEventFunctionWithCallback = ( */ export const wrapUserFunction = ( userFunction: HandlerFunction, - signatureType: SignatureType + signatureType: SignatureType, + context?: object ): RequestHandler => { switch (signatureType) { case 'http': + if (!isEmpty((context as OpenFunctionContext)?.outputs)) + return wrapHttpAsyncFunction( + userFunction as OpenFunction, + context as OpenFunctionContext + ); return wrapHttpFunction(userFunction as HttpFunction); case 'event': // Callback style if user function has more than 2 arguments. diff --git a/src/functions.ts b/src/functions.ts index 49a1a5ce..aa1d64f4 100644 --- a/src/functions.ts +++ b/src/functions.ts @@ -78,13 +78,38 @@ export interface CloudEventFunctionWithCallback { } /** - * A OpenFunction async function handler. + * An OpenFunction async function handler. * @public */ export interface OpenFunction { (ctx: OpenFunctionRuntime, data: {}): any; } +/** + * OpenFunction runtime as the context handler. + * @public + */ +export {OpenFunctionRuntime}; + +/** + * HTTP response ouput from OpenFunction async function + * @public + */ +export interface HttpFunctionResponse { + /** + * Status code of the response. + */ + code?: number; + /** + * Headers of the response. + */ + headers?: Record; + /** + * Body of the response. + */ + body?: any; +} + /** * A function handler. * @public diff --git a/src/openfunction/async_server.ts b/src/openfunction/async_server.ts index 1c4b7cc4..4944ce79 100644 --- a/src/openfunction/async_server.ts +++ b/src/openfunction/async_server.ts @@ -1,4 +1,4 @@ -import {forEach, has, get} from 'lodash'; +import {forEach} from 'lodash'; import {DaprServer} from 'dapr-client'; import {OpenFunction} from '../functions'; @@ -20,7 +20,7 @@ export default function ( context: OpenFunctionContext ): AsyncFunctionServer { const app = new DaprServer('localhost', context.port); - const ctx = getContextProxy(context); + const ctx = OpenFunctionRuntime.ProxyContext(context); const wrapper = async (data: object) => { await userFunction(ctx, data); @@ -44,23 +44,3 @@ export default function ( return app; } - -/** - * It creates a proxy for the runtime object, which delegates all property access to the runtime object - * @param {OpenFunctionContext} context - The context object to be proxied. - * @returns The proxy object. - */ -function getContextProxy(context: OpenFunctionContext): OpenFunctionRuntime { - // Get a proper runtime for the context - const runtime = OpenFunctionRuntime.Parse(context); - - // Create a proxy for the context - return new Proxy(runtime, { - get: (target, prop) => { - // Provide delegated property access of the context object - if (has(target.context, prop)) return get(target.context, prop); - // Otherwise, return the property of the runtime object - else return Reflect.get(target, prop); - }, - }); -} diff --git a/src/openfunction/function_runtime.ts b/src/openfunction/function_runtime.ts index c5746983..87a93353 100644 --- a/src/openfunction/function_runtime.ts +++ b/src/openfunction/function_runtime.ts @@ -1,8 +1,10 @@ import {env} from 'process'; -import {chain} from 'lodash'; +import {chain, get, has} from 'lodash'; import {DaprClient, CommunicationProtocolEnum} from 'dapr-client'; +import {HttpFunctionResponse} from '../functions'; + import { OpenFunctionComponent, OpenFunctionContext, @@ -11,6 +13,7 @@ import { /** * The OpenFunction's serving runtime abstract class. + * @public */ export abstract class OpenFunctionRuntime { /** @@ -32,6 +35,26 @@ export abstract class OpenFunctionRuntime { return new DaprRuntime(context); } + /** + * It creates a proxy for the runtime object, which delegates all property access to the runtime object + * @param context - The context object to be proxied. + * @returns The proxy object. + */ + static ProxyContext(context: OpenFunctionContext): OpenFunctionRuntime { + // Get a proper runtime for the context + const runtime = OpenFunctionRuntime.Parse(context); + + // Create a proxy for the context + return new Proxy(runtime, { + get: (target, prop) => { + // Provide delegated property access of the context object + if (has(target.context, prop)) return get(target.context, prop); + // Otherwise, return the property of the runtime object + else return Reflect.get(target, prop); + }, + }); + } + /** * Getter for the port of Dapr sidecar */ @@ -43,7 +66,26 @@ export abstract class OpenFunctionRuntime { } /** - * The promise that send data to certain ouput binding or pubsub topic. + * It returns an HTTP style response object with a `code`, `headers`, and `body` property + * @param body - The data you want to send back to the client. + * @param code - The HTTP status code to return. + * @param headers - An object containing the headers to be sent with the response. + * @returns A function that takes in data, code, and headers and returns an response object. + */ + response( + body: unknown, + code = 200, + headers?: Record + ): HttpFunctionResponse { + return { + code, + headers, + body, + }; + } + + /** + * The promise that send data to certain ouput. */ abstract send(data: object, output?: string): Promise; } diff --git a/src/server.ts b/src/server.ts index 5b762339..deea0e9c 100644 --- a/src/server.ts +++ b/src/server.ts @@ -24,13 +24,12 @@ import {cloudEventToBackgroundEventMiddleware} from './middleware/cloud_event_to import {backgroundEventToCloudEventMiddleware} from './middleware/background_event_to_cloud_event'; import {wrapUserFunction} from './function_wrappers'; -import daprOutputMiddleware from './openfunction/dapr_output_middleware'; - /** * Creates and configures an Express application and returns an HTTP server * which will run it. * @param userFunction User's function. * @param functionSignatureType Type of user's function signature. + * @param context Optional context object. * @return HTTP server. */ export function getServer( @@ -53,9 +52,6 @@ export function getServer( next(); }); - // Use OpenFunction middlewares - app.use(daprOutputMiddleware); - /** * Retains a reference to the raw body buffer to allow access to the raw body * for things like request signature validation. This is used as the "verify" @@ -139,7 +135,11 @@ export function getServer( } // Set up the routes for the user's function - const requestHandler = wrapUserFunction(userFunction, functionSignatureType); + const requestHandler = wrapUserFunction( + userFunction, + functionSignatureType, + context + ); if (functionSignatureType === 'http') { app.all('/*', requestHandler); } else { diff --git a/test/conformance/function.js b/test/conformance/function.js index 77bff4a8..cc3e71cd 100644 --- a/test/conformance/function.js +++ b/test/conformance/function.js @@ -44,9 +44,10 @@ function writeJson(content) { fs.writeFileSync(fileName, json); } -function tryKnative(req, res) { - debug('✅ Function should receive request: %o', req.body); - res.send(req.body); +async function tryKnativeAsync(ctx, data) { + debug('✅ Function should receive request: %o', data); + await ctx.send(data); + return ctx.response(data); } function tryAsync(ctx, data) { @@ -57,6 +58,6 @@ module.exports = { writeHttp, writeCloudEvent, writeLegacyEvent, - tryKnative, + tryKnativeAsync, tryAsync, }; diff --git a/test/conformance/package.json b/test/conformance/package.json index 761fec54..3270ff21 100644 --- a/test/conformance/package.json +++ b/test/conformance/package.json @@ -8,10 +8,10 @@ }, "scripts": { "start": "functions-framework", - "knative": "concurrently npm:knative:run:* npm:knative:test", - "knative:run:func": "cross-env DEBUG=test:*,common:*,ofn:* env-cmd -e knative functions-framework --target=tryKnative", - "knative:run:dapr": "dapr run -H 3500 -d ../data/components/http --log-level warn", - "knative:test": "wait-on tcp:3500 tcp:8080 && curl -s -d '{\"data\": \"hello\"}' -H 'Content-Type: application/json' localhost:8080", + "knative:async": "concurrently npm:knative:async:run:* npm:knative:async:test", + "knative:async:run:func": "cross-env DEBUG=test:*,common:*,ofn:* env-cmd -e knative functions-framework --target=tryKnativeAsync", + "knative:async:run:dapr": "dapr run -H 3500 -d ../data/components/http --log-level warn", + "knative:async:test": "wait-on tcp:3500 tcp:8080 && curl -s -d '{\"data\": \"hello\"}' -H 'Content-Type: application/json' localhost:8080", "async": "concurrently npm:async:run:*", "async:run:func": "cross-env DEBUG=test:*,common:*,ofn:* env-cmd -e async functions-framework --target=tryAsync", "async:run:dapr": "dapr run -H 3500 -p 8080 -d ../data/components/cron --log-level info" diff --git a/test/integration/http_binding.ts b/test/integration/http_binding.ts index feb5dc43..8e5712c0 100644 --- a/test/integration/http_binding.ts +++ b/test/integration/http_binding.ts @@ -3,12 +3,13 @@ import {deepStrictEqual} from 'assert'; import * as sinon from 'sinon'; import * as supertest from 'supertest'; import * as shell from 'shelljs'; -import {Request, Response} from 'express'; import {cloneDeep, forEach, set} from 'lodash'; -import {getServer} from '../../src/server'; import {OpenFunctionContext} from '../../src/openfunction/function_context'; +import {OpenFunctionRuntime} from '../../src/functions'; +import {getServer} from '../../src/server'; + const TEST_CONTEXT: OpenFunctionContext = { name: 'test-context', version: '1.0.0', @@ -81,15 +82,17 @@ describe('OpenFunction - HTTP Binding', () => { ); const server = getServer( - (req: Request, res: Response) => { - res.status(200).json(TEST_PAYLOAD); + async (ctx: OpenFunctionRuntime, data: {}) => { + await ctx.send(data); + return ctx.response(data); }, 'http', context ); await supertest(server) - .get('/') + .post('/') + .send(TEST_PAYLOAD) .expect(200) .expect(res => { deepStrictEqual(res.body, TEST_PAYLOAD); From add584485c596ee7d430ec409962ee1ee6cf34e4 Mon Sep 17 00:00:00 2001 From: Haili Zhang Date: Sun, 15 May 2022 16:14:20 +0800 Subject: [PATCH 3/7] =?UTF-8?q?=F0=9F=93=9D=20doc:=20update=20README=20and?= =?UTF-8?q?=20API=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Haili Zhang --- README.md | 41 +++- docs/generated/api.json | 443 +++++++++++++++++++++++++++++++++++++++- docs/generated/api.md | 23 ++- 3 files changed, 501 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index e6564cad..8d526053 100644 --- a/README.md +++ b/README.md @@ -196,7 +196,44 @@ Dapr bindings allows you to trigger your applications or services with events co Asynchronous function introduces Dapr pub/sub to provide a platform-agnostic API to send and receive messages. A typical use case is that you can leverage synchronous functions to receive an event in plain JSON or Cloud Events format, and then send the received event to a Dapr output binding or pub/sub component, most likely a message queue (e.g. Kafka, NATS Streaming, GCP PubSub, MQTT). Finally, the asynchronous function could be triggered from the message queue. -More details would be brought up to you in some quickstart samples, stay tuned. +Async function use below function signature which is quite difference from that of Express style sync function: + +```js +function (ctx, data) {} +``` + +* `ctx`: OpenFunction [context](https://github.com/OpenFunction/functions-framework-nodejs/blob/master/src/openfunction/function_context.ts) object + * `ctx.send(payload, output?)`: Send `payload` to all or one specific `output` of Dapr Output [Binding](https://docs.dapr.io/reference/components-reference/supported-bindings/) or Pub [Broker](https://docs.dapr.io/reference/components-reference/supported-pubsub/) + * Notice that `ctx.send` CAN be invoked where necessary, when you have certain outgoing data to send +* `data`: Data recieved from Dapr Input Binding or Sub Broker + +For more details about async function and demo, please check out our [Node.js Async Function Quickstart](https://openfunction-talks.netlify.app/2022/202-node-async/). + +#### HTTP Trigger Async Function + +Sync functions is triggered by HTTP request, so Dapr is not used in sync function input. Whenever there are functions output requirements, sync functions can also send output to Dapr output binding or pubsub components. + +Here is another function sample: + +* Users send a HTTP request to a [Knative Sync function](https://github.com/OpenFunction/samples/tree/main/functions/knative/with-output-binding). +* This sync function handles the request and then send its output to Kafka through a Dapr Kafka output binding or pubsub component. +* An [async function](https://github.com/OpenFunction/samples/tree/main/functions/async/bindings/kafka-input) is then triggered by this output event in Kafka (through a Dapr Kafka input binding or pubsub component) + +![HTTP Trigger Async Function](https://raw.githubusercontent.com/OpenFunction/samples/main/images/knative-dapr.png) + +Node.js Functions Framework also supports such use case, you can switch Express function signature to typical async style as below example indicates: + +```js +async function tryKnativeAsync(ctx, data) { + // Receive and handle data from HTTP request's body + console.log('Function should receive request: %o', data); + + // Send output in async way via Dapr + await ctx.send(data); + + // Use `response` method to prepare data as HTTP response + return ctx.response(data); +``` ### Google Cloud Functions @@ -243,5 +280,3 @@ Contributions to this library are welcome and encouraged. See [ff_node_unit_link]: https://github.com/openfunction/functions-framework-nodejs/actions?query=workflow%3A"Node.js+Unit+CI" [ff_node_lint_img]: https://github.com/openfunction/functions-framework-nodejs/workflows/Node.js%20Lint%20CI/badge.svg [ff_node_lint_link]: https://github.com/openfunction/functions-framework-nodejs/actions?query=workflow%3A"Node.js+Lint+CI" -[ff_node_conformance_img]: https://github.com/openfunction/functions-framework-nodejs/workflows/Node.js%20Conformance%20CI/badge.svg -[ff_node_conformance_link]: https://github.com/openfunction/functions-framework-nodejs/actions?query=workflow%3A"Node.js+Conformance+CI" diff --git a/docs/generated/api.json b/docs/generated/api.json index 5f84bf41..85184b4b 100644 --- a/docs/generated/api.json +++ b/docs/generated/api.json @@ -1202,6 +1202,105 @@ ], "extendsTokenRanges": [] }, + { + "kind": "Interface", + "canonicalReference": "@openfunction/functions-framework!HttpFunctionResponse:interface", + "docComment": "/**\n * HTTP response ouput from OpenFunction async function\n *\n * @public\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "export interface HttpFunctionResponse " + } + ], + "releaseTag": "Public", + "name": "HttpFunctionResponse", + "members": [ + { + "kind": "PropertySignature", + "canonicalReference": "@openfunction/functions-framework!HttpFunctionResponse#body:member", + "docComment": "/**\n * Body of the response.\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "body?: " + }, + { + "kind": "Content", + "text": "any" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isOptional": true, + "releaseTag": "Public", + "name": "body", + "propertyTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + } + }, + { + "kind": "PropertySignature", + "canonicalReference": "@openfunction/functions-framework!HttpFunctionResponse#code:member", + "docComment": "/**\n * Status code of the response.\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "code?: " + }, + { + "kind": "Content", + "text": "number" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isOptional": true, + "releaseTag": "Public", + "name": "code", + "propertyTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + } + }, + { + "kind": "PropertySignature", + "canonicalReference": "@openfunction/functions-framework!HttpFunctionResponse#headers:member", + "docComment": "/**\n * Headers of the response.\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "headers?: " + }, + { + "kind": "Reference", + "text": "Record", + "canonicalReference": "!Record:type" + }, + { + "kind": "Content", + "text": "" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isOptional": true, + "releaseTag": "Public", + "name": "headers", + "propertyTypeTokenRange": { + "startIndex": 1, + "endIndex": 3 + } + } + ], + "extendsTokenRanges": [] + }, { "kind": "TypeAlias", "canonicalReference": "@openfunction/functions-framework!LegacyCloudFunctionsContext:type", @@ -1309,7 +1408,7 @@ { "kind": "Interface", "canonicalReference": "@openfunction/functions-framework!OpenFunction:interface", - "docComment": "/**\n * A OpenFunction async function handler.\n *\n * @public\n */\n", + "docComment": "/**\n * An OpenFunction async function handler.\n *\n * @public\n */\n", "excerptTokens": [ { "kind": "Content", @@ -1818,6 +1917,348 @@ ], "extendsTokenRanges": [] }, + { + "kind": "Class", + "canonicalReference": "@openfunction/functions-framework!OpenFunctionRuntime:class", + "docComment": "/**\n * The OpenFunction's serving runtime abstract class.\n *\n * @public\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "export declare abstract class OpenFunctionRuntime " + } + ], + "releaseTag": "Public", + "name": "OpenFunctionRuntime", + "members": [ + { + "kind": "Constructor", + "canonicalReference": "@openfunction/functions-framework!OpenFunctionRuntime:constructor(1)", + "docComment": "/**\n * Constructor of the OpenFunctionRuntime.\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "constructor(context: " + }, + { + "kind": "Reference", + "text": "OpenFunctionContext", + "canonicalReference": "@openfunction/functions-framework!OpenFunctionContext:interface" + }, + { + "kind": "Content", + "text": ");" + } + ], + "releaseTag": "Public", + "overloadIndex": 1, + "parameters": [ + { + "parameterName": "context", + "parameterTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + } + } + ] + }, + { + "kind": "Property", + "canonicalReference": "@openfunction/functions-framework!OpenFunctionRuntime#context:member", + "docComment": "/**\n * The context of the OpenFunction.\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "readonly context: " + }, + { + "kind": "Reference", + "text": "OpenFunctionContext", + "canonicalReference": "@openfunction/functions-framework!OpenFunctionContext:interface" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isOptional": false, + "releaseTag": "Public", + "name": "context", + "propertyTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "isStatic": false + }, + { + "kind": "Method", + "canonicalReference": "@openfunction/functions-framework!OpenFunctionRuntime.Parse:member(1)", + "docComment": "/**\n * Static method to parse the context and get runtime.\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "static Parse(context: " + }, + { + "kind": "Reference", + "text": "OpenFunctionContext", + "canonicalReference": "@openfunction/functions-framework!OpenFunctionContext:interface" + }, + { + "kind": "Content", + "text": "): " + }, + { + "kind": "Reference", + "text": "OpenFunctionRuntime", + "canonicalReference": "@openfunction/functions-framework!OpenFunctionRuntime:class" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isOptional": false, + "isStatic": true, + "returnTypeTokenRange": { + "startIndex": 3, + "endIndex": 4 + }, + "releaseTag": "Public", + "overloadIndex": 1, + "parameters": [ + { + "parameterName": "context", + "parameterTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + } + } + ], + "name": "Parse" + }, + { + "kind": "Method", + "canonicalReference": "@openfunction/functions-framework!OpenFunctionRuntime.ProxyContext:member(1)", + "docComment": "/**\n * It creates a proxy for the runtime object, which delegates all property access to the runtime object\n *\n * @param context - The context object to be proxied.\n *\n * @returns The proxy object.\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "static ProxyContext(context: " + }, + { + "kind": "Reference", + "text": "OpenFunctionContext", + "canonicalReference": "@openfunction/functions-framework!OpenFunctionContext:interface" + }, + { + "kind": "Content", + "text": "): " + }, + { + "kind": "Reference", + "text": "OpenFunctionRuntime", + "canonicalReference": "@openfunction/functions-framework!OpenFunctionRuntime:class" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isOptional": false, + "isStatic": true, + "returnTypeTokenRange": { + "startIndex": 3, + "endIndex": 4 + }, + "releaseTag": "Public", + "overloadIndex": 1, + "parameters": [ + { + "parameterName": "context", + "parameterTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + } + } + ], + "name": "ProxyContext" + }, + { + "kind": "Method", + "canonicalReference": "@openfunction/functions-framework!OpenFunctionRuntime#response:member(1)", + "docComment": "/**\n * It returns an HTTP style response object with a `code`, `headers`, and `body` property\n *\n * @param body - The data you want to send back to the client.\n *\n * @param code - The HTTP status code to return.\n *\n * @param headers - An object containing the headers to be sent with the response.\n *\n * @returns A function that takes in data, code, and headers and returns an response object.\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "response(body: " + }, + { + "kind": "Content", + "text": "unknown" + }, + { + "kind": "Content", + "text": ", code?: " + }, + { + "kind": "Content", + "text": "number" + }, + { + "kind": "Content", + "text": ", headers?: " + }, + { + "kind": "Reference", + "text": "Record", + "canonicalReference": "!Record:type" + }, + { + "kind": "Content", + "text": "" + }, + { + "kind": "Content", + "text": "): " + }, + { + "kind": "Reference", + "text": "HttpFunctionResponse", + "canonicalReference": "@openfunction/functions-framework!HttpFunctionResponse:interface" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isOptional": false, + "isStatic": false, + "returnTypeTokenRange": { + "startIndex": 8, + "endIndex": 9 + }, + "releaseTag": "Public", + "overloadIndex": 1, + "parameters": [ + { + "parameterName": "body", + "parameterTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + } + }, + { + "parameterName": "code", + "parameterTypeTokenRange": { + "startIndex": 3, + "endIndex": 4 + } + }, + { + "parameterName": "headers", + "parameterTypeTokenRange": { + "startIndex": 5, + "endIndex": 7 + } + } + ], + "name": "response" + }, + { + "kind": "Method", + "canonicalReference": "@openfunction/functions-framework!OpenFunctionRuntime#send:member(1)", + "docComment": "/**\n * The promise that send data to certain ouput.\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "abstract send(data: " + }, + { + "kind": "Content", + "text": "object" + }, + { + "kind": "Content", + "text": ", output?: " + }, + { + "kind": "Content", + "text": "string" + }, + { + "kind": "Content", + "text": "): " + }, + { + "kind": "Reference", + "text": "Promise", + "canonicalReference": "!Promise:interface" + }, + { + "kind": "Content", + "text": "" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isOptional": false, + "isStatic": false, + "returnTypeTokenRange": { + "startIndex": 5, + "endIndex": 7 + }, + "releaseTag": "Public", + "overloadIndex": 1, + "parameters": [ + { + "parameterName": "data", + "parameterTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + } + }, + { + "parameterName": "output", + "parameterTypeTokenRange": { + "startIndex": 3, + "endIndex": 4 + } + } + ], + "name": "send" + }, + { + "kind": "Property", + "canonicalReference": "@openfunction/functions-framework!OpenFunctionRuntime#sidecarPort:member", + "docComment": "/**\n * Getter for the port of Dapr sidecar\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "get sidecarPort(): " + }, + { + "kind": "Content", + "text": "{\n HTTP: string;\n GRRC: string;\n }" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isOptional": false, + "releaseTag": "Public", + "name": "sidecarPort", + "propertyTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "isStatic": false + } + ], + "implementsTokenRanges": [] + }, { "kind": "Interface", "canonicalReference": "@openfunction/functions-framework!Request_2:interface", diff --git a/docs/generated/api.md b/docs/generated/api.md index 0128b70e..a5e9a133 100644 --- a/docs/generated/api.md +++ b/docs/generated/api.md @@ -84,6 +84,13 @@ export interface HttpFunction { (req: Request_2, res: Response_2): any; } +// @public +export interface HttpFunctionResponse { + body?: any; + code?: number; + headers?: Record; +} + // @public export type LegacyCloudFunctionsContext = CloudFunctionsContext | Data; @@ -99,8 +106,6 @@ export interface LegacyEvent { // @public export interface OpenFunction { - // Warning: (ae-forgotten-export) The symbol "OpenFunctionRuntime" needs to be exported by the entry point index.d.ts - // // (undocumented) (ctx: OpenFunctionRuntime, data: {}): any; } @@ -129,6 +134,20 @@ export interface OpenFunctionContext { version: string; } +// @public +export abstract class OpenFunctionRuntime { + constructor(context: OpenFunctionContext); + readonly context: OpenFunctionContext; + static Parse(context: OpenFunctionContext): OpenFunctionRuntime; + static ProxyContext(context: OpenFunctionContext): OpenFunctionRuntime; + response(body: unknown, code?: number, headers?: Record): HttpFunctionResponse; + abstract send(data: object, output?: string): Promise; + get sidecarPort(): { + HTTP: string; + GRRC: string; + }; +} + // @public (undocumented) interface Request_2 extends Request_3 { rawBody?: Buffer; From 63b7b79471655d06f8144f35999b44ad03db671d Mon Sep 17 00:00:00 2001 From: Haili Zhang Date: Sun, 15 May 2022 16:50:52 +0800 Subject: [PATCH 4/7] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20ci:=20fix=20mergeable?= =?UTF-8?q?=20bot=20config=20filename?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Haili Zhang --- .github/{mergable.yml => mergeable.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/{mergable.yml => mergeable.yml} (100%) diff --git a/.github/mergable.yml b/.github/mergeable.yml similarity index 100% rename from .github/mergable.yml rename to .github/mergeable.yml From da606dd3f74a9a2a82110eb81ac8902543a3928a Mon Sep 17 00:00:00 2001 From: Haili Zhang Date: Mon, 16 May 2022 21:25:17 +0800 Subject: [PATCH 5/7] =?UTF-8?q?=F0=9F=91=94=20refactor:=20add=20`openfunct?= =?UTF-8?q?ion`=20signature=20type?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Haili Zhang --- src/function_wrappers.ts | 12 ++++++------ src/types.ts | 7 ++++++- test/conformance/package.json | 4 ++-- test/integration/http_binding.ts | 2 +- 4 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/function_wrappers.ts b/src/function_wrappers.ts index d9c36077..3f416270 100644 --- a/src/function_wrappers.ts +++ b/src/function_wrappers.ts @@ -131,7 +131,7 @@ const wrapHttpFunction = (execute: HttpFunction): RequestHandler => { }; }; -const wrapHttpAsyncFunction = ( +const wrapOpenFunction = ( userFunction: OpenFunction, context: OpenFunctionContext ): RequestHandler => { @@ -247,12 +247,12 @@ export const wrapUserFunction = ( ): RequestHandler => { switch (signatureType) { case 'http': - if (!isEmpty((context as OpenFunctionContext)?.outputs)) - return wrapHttpAsyncFunction( - userFunction as OpenFunction, - context as OpenFunctionContext - ); return wrapHttpFunction(userFunction as HttpFunction); + case 'openfunction': + return wrapOpenFunction( + userFunction as OpenFunction, + context as OpenFunctionContext + ); case 'event': // Callback style if user function has more than 2 arguments. if (userFunction!.length > 2) { diff --git a/src/types.ts b/src/types.ts index 7d2d2f5b..44865026 100644 --- a/src/types.ts +++ b/src/types.ts @@ -19,7 +19,12 @@ export const FUNCTION_STATUS_HEADER_FIELD = 'X-Google-Status'; /** * List of function signature types that are supported by the framework. */ -export const SignatureType = ['http', 'event', 'cloudevent'] as const; +export const SignatureType = [ + 'http', + 'event', + 'cloudevent', + 'openfunction', +] as const; /** * Union type of all valid function SignatureType values. diff --git a/test/conformance/package.json b/test/conformance/package.json index 3270ff21..f4eb9778 100644 --- a/test/conformance/package.json +++ b/test/conformance/package.json @@ -7,9 +7,9 @@ "wait-on": "file:../../node_modules/wait-on" }, "scripts": { - "start": "functions-framework", + "start": "functions-framework --target=writeHttp", "knative:async": "concurrently npm:knative:async:run:* npm:knative:async:test", - "knative:async:run:func": "cross-env DEBUG=test:*,common:*,ofn:* env-cmd -e knative functions-framework --target=tryKnativeAsync", + "knative:async:run:func": "cross-env DEBUG=test:*,common:*,ofn:* env-cmd -e knative functions-framework --signature-type=openfunction --target=tryKnativeAsync", "knative:async:run:dapr": "dapr run -H 3500 -d ../data/components/http --log-level warn", "knative:async:test": "wait-on tcp:3500 tcp:8080 && curl -s -d '{\"data\": \"hello\"}' -H 'Content-Type: application/json' localhost:8080", "async": "concurrently npm:async:run:*", diff --git a/test/integration/http_binding.ts b/test/integration/http_binding.ts index 8e5712c0..a573d017 100644 --- a/test/integration/http_binding.ts +++ b/test/integration/http_binding.ts @@ -86,7 +86,7 @@ describe('OpenFunction - HTTP Binding', () => { await ctx.send(data); return ctx.response(data); }, - 'http', + 'openfunction', context ); From 74cbea98b61e377fd9570a122482c784b55f23e5 Mon Sep 17 00:00:00 2001 From: Haili Zhang Date: Tue, 17 May 2022 10:50:06 +0800 Subject: [PATCH 6/7] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor(ofn/runtime):?= =?UTF-8?q?=20=20refine=20context=20proxy?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Haili Zhang --- docs/generated/api.json | 209 +++++++++++++++++++++------ docs/generated/api.md | 13 +- src/function_wrappers.ts | 23 +-- src/functions.ts | 4 +- src/openfunction/function_runtime.ts | 63 +++++--- test/integration/http_binding.ts | 2 +- 6 files changed, 221 insertions(+), 93 deletions(-) diff --git a/docs/generated/api.json b/docs/generated/api.json index 85184b4b..3b526f88 100644 --- a/docs/generated/api.json +++ b/docs/generated/api.json @@ -1022,6 +1022,15 @@ "kind": "Content", "text": "> = " }, + { + "kind": "Reference", + "text": "OpenFunction", + "canonicalReference": "@openfunction/functions-framework!OpenFunction:interface" + }, + { + "kind": "Content", + "text": " | " + }, { "kind": "Reference", "text": "HttpFunction", @@ -1065,12 +1074,7 @@ }, { "kind": "Content", - "text": " | " - }, - { - "kind": "Reference", - "text": "OpenFunction", - "canonicalReference": "@openfunction/functions-framework!OpenFunction:interface" + "text": "" }, { "kind": "Content", @@ -1094,7 +1098,7 @@ ], "typeTokenRange": { "startIndex": 3, - "endIndex": 14 + "endIndex": 15 } }, { @@ -1968,7 +1972,7 @@ "excerptTokens": [ { "kind": "Content", - "text": "readonly context: " + "text": "protected readonly context: " }, { "kind": "Reference", @@ -2084,47 +2088,49 @@ "name": "ProxyContext" }, { - "kind": "Method", - "canonicalReference": "@openfunction/functions-framework!OpenFunctionRuntime#response:member(1)", - "docComment": "/**\n * It returns an HTTP style response object with a `code`, `headers`, and `body` property\n *\n * @param body - The data you want to send back to the client.\n *\n * @param code - The HTTP status code to return.\n *\n * @param headers - An object containing the headers to be sent with the response.\n *\n * @returns A function that takes in data, code, and headers and returns an response object.\n */\n", + "kind": "Property", + "canonicalReference": "@openfunction/functions-framework!OpenFunctionRuntime#req:member", + "docComment": "/**\n * Getter returns the request object from the trigger.\n *\n * @returns The request object.\n */\n", "excerptTokens": [ { "kind": "Content", - "text": "response(body: " + "text": "get req(): " }, { - "kind": "Content", - "text": "unknown" + "kind": "Reference", + "text": "Request", + "canonicalReference": "@types/express!~e.Request:interface" }, { "kind": "Content", - "text": ", code?: " + "text": "" + "text": ", " }, { - "kind": "Content", - "text": "): " + "kind": "Reference", + "text": "Record", + "canonicalReference": "!Record:type" }, { - "kind": "Reference", - "text": "HttpFunctionResponse", - "canonicalReference": "@openfunction/functions-framework!HttpFunctionResponse:interface" + "kind": "Content", + "text": "> | undefined" }, { "kind": "Content", @@ -2132,37 +2138,54 @@ } ], "isOptional": false, - "isStatic": false, - "returnTypeTokenRange": { - "startIndex": 8, + "releaseTag": "Public", + "name": "req", + "propertyTypeTokenRange": { + "startIndex": 1, "endIndex": 9 }, - "releaseTag": "Public", - "overloadIndex": 1, - "parameters": [ + "isStatic": false + }, + { + "kind": "Property", + "canonicalReference": "@openfunction/functions-framework!OpenFunctionRuntime#res:member", + "docComment": "/**\n * Getter returns the response object from the trigger.\n *\n * @returns The res property of the trigger object.\n */\n", + "excerptTokens": [ { - "parameterName": "body", - "parameterTypeTokenRange": { - "startIndex": 1, - "endIndex": 2 - } + "kind": "Content", + "text": "get res(): " }, { - "parameterName": "code", - "parameterTypeTokenRange": { - "startIndex": 3, - "endIndex": 4 - } + "kind": "Reference", + "text": "Response", + "canonicalReference": "@types/express!~e.Response:interface" }, { - "parameterName": "headers", - "parameterTypeTokenRange": { - "startIndex": 5, - "endIndex": 7 - } + "kind": "Content", + "text": "> | undefined" + }, + { + "kind": "Content", + "text": ";" } ], - "name": "response" + "isOptional": false, + "releaseTag": "Public", + "name": "res", + "propertyTypeTokenRange": { + "startIndex": 1, + "endIndex": 5 + }, + "isStatic": false }, { "kind": "Method", @@ -2229,6 +2252,68 @@ ], "name": "send" }, + { + "kind": "Method", + "canonicalReference": "@openfunction/functions-framework!OpenFunctionRuntime#setTrigger:member(1)", + "docComment": "/**\n * It sets the trigger object to the request and response objects passed in\n *\n * @param req - The HTTP request object\n *\n * @param res - The HTTP response object\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "setTrigger(req: " + }, + { + "kind": "Reference", + "text": "Request", + "canonicalReference": "@types/express!~e.Request:interface" + }, + { + "kind": "Content", + "text": ", res?: " + }, + { + "kind": "Reference", + "text": "Response", + "canonicalReference": "@types/express!~e.Response:interface" + }, + { + "kind": "Content", + "text": "): " + }, + { + "kind": "Content", + "text": "void" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isOptional": false, + "isStatic": false, + "returnTypeTokenRange": { + "startIndex": 5, + "endIndex": 6 + }, + "releaseTag": "Public", + "overloadIndex": 1, + "parameters": [ + { + "parameterName": "req", + "parameterTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + } + }, + { + "parameterName": "res", + "parameterTypeTokenRange": { + "startIndex": 3, + "endIndex": 4 + } + } + ], + "name": "setTrigger" + }, { "kind": "Property", "canonicalReference": "@openfunction/functions-framework!OpenFunctionRuntime#sidecarPort:member", @@ -2255,6 +2340,34 @@ "endIndex": 2 }, "isStatic": false + }, + { + "kind": "Property", + "canonicalReference": "@openfunction/functions-framework!OpenFunctionRuntime#trigger:member", + "docComment": "/**\n * The optional trigger of OpenFunction.\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "protected trigger?: " + }, + { + "kind": "Reference", + "text": "OpenFunctionTrigger", + "canonicalReference": "@openfunction/functions-framework!OpenFunctionTrigger:type" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isOptional": true, + "releaseTag": "Public", + "name": "trigger", + "propertyTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "isStatic": false } ], "implementsTokenRanges": [] diff --git a/docs/generated/api.md b/docs/generated/api.md index a5e9a133..6abdb6b4 100644 --- a/docs/generated/api.md +++ b/docs/generated/api.md @@ -5,8 +5,11 @@ ```ts /// +/// import { CloudEventV1 as CloudEvent } from 'cloudevents'; +import { ParamsDictionary } from 'express-serve-static-core'; +import { ParsedQs } from 'qs'; import { Request as Request_3 } from 'express'; import { Response as Response_2 } from 'express'; @@ -73,7 +76,7 @@ export interface EventFunctionWithCallback { } // @public -export type HandlerFunction = HttpFunction | EventFunction | EventFunctionWithCallback | CloudEventFunction | CloudEventFunctionWithCallback | OpenFunction; +export type HandlerFunction = OpenFunction | HttpFunction | EventFunction | EventFunctionWithCallback | CloudEventFunction | CloudEventFunctionWithCallback; // @public export const http: (functionName: string, handler: HttpFunction) => void; @@ -137,15 +140,19 @@ export interface OpenFunctionContext { // @public export abstract class OpenFunctionRuntime { constructor(context: OpenFunctionContext); - readonly context: OpenFunctionContext; + protected readonly context: OpenFunctionContext; static Parse(context: OpenFunctionContext): OpenFunctionRuntime; static ProxyContext(context: OpenFunctionContext): OpenFunctionRuntime; - response(body: unknown, code?: number, headers?: Record): HttpFunctionResponse; + get req(): Request_3> | undefined; + get res(): Response_2> | undefined; abstract send(data: object, output?: string): Promise; + setTrigger(req: Request_3, res?: Response_2): void; get sidecarPort(): { HTTP: string; GRRC: string; }; + // Warning: (ae-forgotten-export) The symbol "OpenFunctionTrigger" needs to be exported by the entry point index.d.ts + protected trigger?: OpenFunctionTrigger; } // @public (undocumented) diff --git a/src/function_wrappers.ts b/src/function_wrappers.ts index 3f416270..aaf95ff1 100644 --- a/src/function_wrappers.ts +++ b/src/function_wrappers.ts @@ -15,9 +15,6 @@ // eslint-disable-next-line node/no-deprecated-api import * as domain from 'domain'; -import * as Debug from 'debug'; -import {get, isEmpty} from 'lodash'; - import {Request, Response, RequestHandler} from 'express'; import {sendCrashResponse} from './logger'; import {sendResponse} from './invoker'; @@ -37,8 +34,6 @@ import {SignatureType} from './types'; import {OpenFunctionContext} from './openfunction/function_context'; import {OpenFunctionRuntime} from './openfunction/function_runtime'; -const debug = Debug('common:wrapper'); - /** * The handler function used to signal completion of event functions. */ @@ -136,26 +131,14 @@ const wrapOpenFunction = ( context: OpenFunctionContext ): RequestHandler => { const ctx = OpenFunctionRuntime.ProxyContext(context); + const httpHandler = (req: Request, res: Response) => { const callback = getOnDoneCallback(res); + ctx.setTrigger(req, res); Promise.resolve() .then(() => userFunction(ctx, req.body)) - .then(result => { - debug('ℹ️ User function returned: %j', result); - - const data = get(result, 'body'); - const code = get(result, 'code', 200); - const headers = get(result, 'headers'); - - !isEmpty(headers) && res.set(headers); - - if (data !== undefined) { - res.status(code).send(data); - } else { - res.status(code).end(); - } - }) + .then(() => res.end()) .catch(err => callback(err, undefined)); }; diff --git a/src/functions.ts b/src/functions.ts index aa1d64f4..cc3cba1f 100644 --- a/src/functions.ts +++ b/src/functions.ts @@ -115,12 +115,12 @@ export interface HttpFunctionResponse { * @public */ export type HandlerFunction = + | OpenFunction | HttpFunction | EventFunction | EventFunctionWithCallback | CloudEventFunction - | CloudEventFunctionWithCallback - | OpenFunction; + | CloudEventFunctionWithCallback; /** * A legacy event. diff --git a/src/openfunction/function_runtime.ts b/src/openfunction/function_runtime.ts index 87a93353..9a1ea3c9 100644 --- a/src/openfunction/function_runtime.ts +++ b/src/openfunction/function_runtime.ts @@ -1,16 +1,30 @@ import {env} from 'process'; -import {chain, get, has} from 'lodash'; +import {chain, get, has, extend} from 'lodash'; +import {Request, Response} from 'express'; import {DaprClient, CommunicationProtocolEnum} from 'dapr-client'; -import {HttpFunctionResponse} from '../functions'; - import { OpenFunctionComponent, OpenFunctionContext, ContextUtils, } from './function_context'; +/** + * Defining the interface of the HttpTarget. + * @public + */ +export interface HttpTrigger { + req?: Request; + res?: Response; +} + +/** + * Defining the type union of OpenFunction trigger. + * @public + */ +export type OpenFunctionTrigger = HttpTrigger; + /** * The OpenFunction's serving runtime abstract class. * @public @@ -19,7 +33,12 @@ export abstract class OpenFunctionRuntime { /** * The context of the OpenFunction. */ - readonly context: OpenFunctionContext; + protected readonly context: OpenFunctionContext; + + /** + * The optional trigger of OpenFunction. + */ + protected trigger?: OpenFunctionTrigger; /** * Constructor of the OpenFunctionRuntime. @@ -66,22 +85,28 @@ export abstract class OpenFunctionRuntime { } /** - * It returns an HTTP style response object with a `code`, `headers`, and `body` property - * @param body - The data you want to send back to the client. - * @param code - The HTTP status code to return. - * @param headers - An object containing the headers to be sent with the response. - * @returns A function that takes in data, code, and headers and returns an response object. + * Getter returns the request object from the trigger. + * @returns The request object. */ - response( - body: unknown, - code = 200, - headers?: Record - ): HttpFunctionResponse { - return { - code, - headers, - body, - }; + get req() { + return this.trigger?.req; + } + + /** + * Getter returns the response object from the trigger. + * @returns The res property of the trigger object. + */ + get res() { + return this.trigger?.res; + } + + /** + * It sets the trigger object to the request and response objects passed in + * @param req - The HTTP request object + * @param res - The HTTP response object + */ + setTrigger(req: Request, res?: Response) { + this.trigger = extend(this.trigger, {req, res}); } /** diff --git a/test/integration/http_binding.ts b/test/integration/http_binding.ts index a573d017..ad6f4a46 100644 --- a/test/integration/http_binding.ts +++ b/test/integration/http_binding.ts @@ -84,7 +84,7 @@ describe('OpenFunction - HTTP Binding', () => { const server = getServer( async (ctx: OpenFunctionRuntime, data: {}) => { await ctx.send(data); - return ctx.response(data); + ctx.res?.send(data); }, 'openfunction', context From 8284d9fc9951569fdf76fd271e2472f09da939e0 Mon Sep 17 00:00:00 2001 From: Haili Zhang Date: Tue, 17 May 2022 10:50:41 +0800 Subject: [PATCH 7/7] =?UTF-8?q?=F0=9F=93=9D=20doc:=20update=20REAMDE=20and?= =?UTF-8?q?=20CONTRUBUTINIG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Haili Zhang --- CONTRIBUTING.md | 43 +++++++++++++------------------------------ README.md | 14 +++++++++++--- 2 files changed, 24 insertions(+), 33 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0d5aec42..4c9412c2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,26 +1,16 @@ # How to Contribute -We'd love to accept your patches and contributions to this project. There are -just a few small guidelines you need to follow. +We'd love to accept your patches and contributions to this project. There are a few small guidelines you need to follow. ## Contributor License Agreement -Contributions to this project must be accompanied by a Contributor License -Agreement. You (or your employer) retain the copyright to your contribution; -this simply gives us permission to use and redistribute your contributions as -part of the project. Head over to to see -your current agreements on file or to sign a new one. +Contributions to this project must be accompanied by a Developer Certificate of Origin (DCO). You (or your employer) retain the copyright to your contribution; this gives us permission to use and redistribute your contributions as part of the project. Head over to to see your current agreements on file or to sign a new one. -You generally only need to submit a CLA once, so if you've already submitted one -(even if it was for a different project), you probably don't need to do it -again. +You only need to submit a DTO once, so if you've already submitted one (even if it was for a different project), you probably don't need to do it again. ## Code reviews -All submissions, including submissions by project members, require review. We -use GitHub pull requests for this purpose. Consult -[GitHub Help](https://help.github.com/articles/about-pull-requests/) for more -information on using pull requests. +All submissions, including submissions by project members, require review. We use GitHub pull requests for this purpose. Consult [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more information on using pull requests. ## Testing @@ -28,34 +18,27 @@ information on using pull requests. All pull requests should have an associated test to ensure foward compatibility. +> Make sure you have installed [Dapr](https://dapr.io/) before running unit tests, check out [Install Dapr CLI](https://docs.dapr.io/getting-started/install-dapr-cli/) for more details + To run an individual test, you can run a command such as the following: -``` +```sh npm run test -- -g 'loading function' ``` -### Conformance Tests - -To run the conformance tests, first install Go 1.16+, then run the tests: - -``` -npm run conformance -``` - ### Manual Testing -When developing a feature locally, you can install a local version of the Functions Framework -using `npm link`. First compile your local clone of the Functions Framework: +When developing a feature locally, you can install a local version of the Functions Framework using `npm link`. First compile your local clone of the Functions Framework: > You'll need to install typescript first by: `npm install typescript --save-dev` -``` +```sh npx tsc ``` Then link the Functions Framework using `npm link`. -``` +```sh npm link ``` @@ -65,7 +48,7 @@ You can then run the Functions Framework locally using `functions-framework`. This module is published using Release Please. When you merge a release PR, the npm package will be automatically published. -```shell +```sh # Login to npm registry, contact repo admin for https://www.npmjs.com/ user name and password npm login # First run a dry run to find out errors @@ -73,6 +56,7 @@ npm publish ./ --access public --dry-run # Then publish the package npm publish --access public ``` + ### Reverting a Publish If the release process fails, you can revert the publish by running the following (i.e. unpublishing `1.10.0`): @@ -101,5 +85,4 @@ The docs will be generated in [`docs/generated/`](docs/generated/). ## Community Guidelines -This project follows [Google's Open Source Community -Guidelines](https://opensource.google.com/conduct/). +This project follows [CNCF openness guidelines](https://www.cncf.io/blog/2019/06/11/cncf-openness-guidelines/). diff --git a/README.md b/README.md index 8d526053..ee6b1d02 100644 --- a/README.md +++ b/README.md @@ -168,7 +168,7 @@ ignored. | ------------------ | ------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `--port` | `PORT` | The port on which the Functions Framework listens for requests. Default: `8080` | | `--target` | `FUNCTION_TARGET` | The name of the exported function to be invoked in response to requests. Default: `function` | -| `--signature-type` | `FUNCTION_SIGNATURE_TYPE` | The signature used when writing your function. Controls unmarshalling rules and determines which arguments are used to invoke your function. Default: `http`; accepted values: `http` or `event` or `cloudevent` | +| `--signature-type` | `FUNCTION_SIGNATURE_TYPE` | The signature used when writing your function. Controls unmarshalling rules and determines which arguments are used to invoke your function. Default: `http`; accepted values: `http` or `event` or `cloudevent` or `openfunction` | | `--source` | `FUNCTION_SOURCE` | The path to the directory of your function. Default: `cwd` (the current working directory) | You can set command-line flags in your `package.json` via the `start` script. @@ -231,8 +231,16 @@ async function tryKnativeAsync(ctx, data) { // Send output in async way via Dapr await ctx.send(data); - // Use `response` method to prepare data as HTTP response - return ctx.response(data); + // Use `ctx.res` object to deal with HTTP response + ctx.res.send(data); +``` + +Remember that you also need set command-line flags `--signature-type=openfunction`, for example in your `package.json` via the `start` script: + +```js + "scripts": { + "start": "functions-framework --signature-type=openfunction --target=tryKnativeAsync" + } ``` ### Google Cloud Functions