diff --git a/README.md b/README.md index ce440f52..d9db1b61 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,64 @@ in the [Cloud Console][cloud-run-console]. [cloud-run-regions]: https://cloud.google.com/run/docs/locations [cloud-run-console]: https://console.cloud.google.com/run +## Use CloudEvents + +The Functions Framework can unmarshall incoming [CloudEvents][cloud-events] +payloads to a `cloudevent` object. This will be passed as arguments to your +function when it receives a request. Note that your function must use the +cloudevent function signature: + +```php +use Google\CloudFunctions\CloudEvent; + +function helloCloudEvents(CloudEvent $cloudevent) +{ + // Print the whole CloudEvent + $stdout = fopen('php://stdout', 'wb'); + fwrite($stdout, $cloudevent); +} +``` + +You will also need to set the `FUNCTION_SIGNATURE_TYPE` environment +variable to `cloudevent`. + +```sh +export FUNCTION_TARGET=helloCloudEvent +export FUNCTION_SIGNATURE_TYPE=cloudevent +php -S localhost:8080 vendor/bin/router.php +``` + +In a separate tab, make a cURL request in Cloud Event format to your function: + +``` +curl localhost:8080 \ + -H "ce-id: 1234567890" \ + -H "ce-source: //pubsub.googleapis.com/projects/MY-PROJECT/topics/MY-TOPIC" \ + -H "ce-specversion: 1.0" \ + -H "ce-type: com.google.cloud.pubsub.topic.publish" \ + -d '{"foo": "bar"}' +``` + +Your original process should output the following: + +``` +CLOUDEVENT metadata: +- id: 1234567890 +- source: //pubsub.googleapis.com/projects/MY-PROJECT/topics/MY-TOPIC +- specversion: 1.0 +- type: com.google.cloud.pubsub.topic.publish +- datacontenttype: +- dataschema: +- subject: +- time: +``` + +**IMPORTANT**: The above tutorials to deploy to a docker container and to +Cloud Run work for CloudEvents as well, as long as `FUNCTION_TARGET` and +`FUNCTION_SIGNATURE_TYPE` are set appropriately. + +[cloud-events]: http://cloudevents.io + ## Working with PSR-7 HTTP Objects The first parameter of your function is a `Request` object which implements the @@ -237,30 +295,6 @@ You can configure the Functions Framework using the environment variables shown | `FUNCTION_SOURCE` | The name of the file containing the source code for your function to load. Default: **`index.php`** (if it exists) | `FUNCTION_SIGNATURE_TYPE` | The signature used when writing your function. Controls unmarshalling rules and determines which arguments are used to invoke your function. Can be either `http`, `event`, or `cloudevent`. Default: **`http`** -# Enable CloudEvents - -The Functions Framework can unmarshall incoming [CloudEvents](http://cloudevents.io) -payloads to a `cloudevent` object. This will be passed as arguments to your function when it -receives a request. Note that your function must use the cloudevent-style function -signature: - -```php -use Google\CloudFunctions\CloudEvent; - -function helloCloudEvents(CloudEvent $cloudevent) -{ - // Get a single property - printf('id: %s', $cloudevent->getId()); - printf('type: %s', $cloudevent->getType()); - - // Print the whole CloudEvent - print($cloudevent); -} -``` - -To enable automatic unmarshalling, set the `FUNCTION_SIGNATURE_TYPE` environment -variable to `cloudevent`. - # Contributing Contributions to this library are welcome and encouraged. See diff --git a/examples/hello/composer.json b/examples/hello/composer.json index 82c6a917..368bfbdf 100644 --- a/examples/hello/composer.json +++ b/examples/hello/composer.json @@ -1,5 +1,5 @@ { "require": { - "google/cloud-functions-framework": "^0.3" + "google/cloud-functions-framework": "dev-master as 0.6.0" } } diff --git a/examples/hello/index.php b/examples/hello/index.php index 7fd93862..b0ac3fb8 100644 --- a/examples/hello/index.php +++ b/examples/hello/index.php @@ -15,6 +15,12 @@ * limitations under the License. */ +/** + * To use this, set the following environment variables: + * FUNCTION_TARGET=helloHttp + * FUNCTION_EVENT_TYPE=http + */ + use Psr\Http\Message\ServerRequestInterface; function helloHttp(ServerRequestInterface $request) @@ -22,3 +28,17 @@ function helloHttp(ServerRequestInterface $request) return sprintf("Hello %s from PHP HTTP function!" . PHP_EOL, $request->getQueryParams()['name'] ?? 'World'); } + +/** + * To use this, set the following environment variables: + * FUNCTION_TARGET=helloCloudEvent + * FUNCTION_EVENT_TYPE=cloudevent + */ + +use Google\CloudFunctions\CloudEvent; + +function helloCloudEvent(CloudEvent $cloudevent) +{ + $stdout = fopen('php://stdout', 'wb'); + fwrite($stdout, $cloudevent); +} diff --git a/tests/exampleTest.php b/tests/exampleTest.php index 0c63cd17..191110b0 100644 --- a/tests/exampleTest.php +++ b/tests/exampleTest.php @@ -45,33 +45,77 @@ public static function setUpBeforeClass(): void $cmd = sprintf('docker build %s -t %s', $exampleDir, self::$imageId); passthru($cmd, $output); + } + public function testHttp(): void + { $cmd = 'docker run -d -p 8080:8080 ' . '-e FUNCTION_TARGET=helloHttp ' . self::$imageId; exec($cmd, $output); self::$containerId = $output[0]; + // Tests fail if we do not wait before sending requests in sleep(1); - } - public function testIndex(): void - { exec('curl -v http://localhost:8080', $output); $this->assertContains('Hello World from PHP HTTP function!', $output); - } - public function testIndexWithQuery(): void - { exec('curl -v http://localhost:8080?name=Foo', $output); $this->assertContains('Hello Foo from PHP HTTP function!', $output); + + passthru('docker rm -f ' . self::$containerId); + self::$containerId = null; + } + + public function testCloudEvent(): void + { + $cmd = 'docker run -d -t -p 8080:8080 ' + . '-e FUNCTION_TARGET=helloCloudEvent ' + . '-e FUNCTION_SIGNATURE_TYPE=cloudevent ' + . self::$imageId; + + exec($cmd, $output); + self::$containerId = $output[0]; + + // Tests fail if we do not wait before sending requests in + sleep(1); + + $curl = 'curl -v localhost:8080 ' + . '-H "ce-id: 1234567890" ' + . ' -H "ce-source: //pubsub.googleapis.com/projects/MY-PROJECT/topics/MY-TOPIC" ' + . '-H "ce-specversion: 1.0" ' + . '-H "ce-type: com.google.cloud.pubsub.topic.publish" ' + . '-d \'{"foo": "bar"}\' &> /dev/stdout'; + + exec($curl); + + exec('docker logs ' . self::$containerId, $output); + + $outputAsString = implode("\n", $output); + $this->assertStringContainsString('- id: 1234567890', $outputAsString); + $this->assertStringContainsString( + '- type: com.google.cloud.pubsub.topic.publish', + $outputAsString + ); + $this->assertStringContainsString( + '- source: //pubsub.googleapis.com/projects/MY-PROJECT/topics/MY-TOPIC"', + $outputAsString + ); + + passthru('docker rm -f ' . self::$containerId); + self::$containerId = null; } public static function tearDownAfterClass(): void { + // If a test failed before it could delete its container if (self::$containerId) { passthru('docker rm -f ' . self::$containerId); + } + // Remove the test image + if (self::$imageId) { passthru('docker rmi -f ' . self::$imageId); } }