functions_framework 0.1.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.yardopts +6 -2
- data/CHANGELOG.md +38 -0
- data/README.md +57 -137
- data/bin/functions-framework-ruby +19 -0
- data/docs/deploying-functions.md +182 -0
- data/docs/overview.md +142 -0
- data/docs/running-a-functions-server.md +122 -0
- data/docs/testing-functions.md +169 -0
- data/docs/writing-functions.md +261 -0
- data/lib/functions_framework.rb +19 -42
- data/lib/functions_framework/cli.rb +71 -13
- data/lib/functions_framework/cloud_events.rb +9 -109
- data/lib/functions_framework/cloud_events/errors.rb +42 -0
- data/lib/functions_framework/cloud_events/event.rb +51 -249
- data/lib/functions_framework/cloud_events/event/v1.rb +363 -0
- data/lib/functions_framework/cloud_events/http_binding.rb +270 -0
- data/lib/functions_framework/cloud_events/json_format.rb +122 -0
- data/lib/functions_framework/function.rb +7 -11
- data/lib/functions_framework/legacy_event_converter.rb +145 -0
- data/lib/functions_framework/registry.rb +3 -27
- data/lib/functions_framework/server.rb +63 -42
- data/lib/functions_framework/testing.rb +60 -20
- data/lib/functions_framework/version.rb +1 -1
- metadata +16 -6
- data/lib/functions_framework/cloud_events/binary_content.rb +0 -59
- data/lib/functions_framework/cloud_events/json_structure.rb +0 -88
data/docs/overview.md
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
<!--
|
2
|
+
# @title Functions Framework Overview
|
3
|
+
-->
|
4
|
+
|
5
|
+
# Functions Framework for Ruby
|
6
|
+
|
7
|
+
The Functions Framework is an open source framework for writing lightweight,
|
8
|
+
portable Ruby functions that run in a serverless environment. Functions written
|
9
|
+
to this Framework will run in many different environments, including:
|
10
|
+
|
11
|
+
* [Google Cloud Functions](https://cloud.google.com/functions) *(in preview)*
|
12
|
+
* [Cloud Run or Cloud Run for Anthos](https://cloud.google.com/run)
|
13
|
+
* Any other [Knative](https://github.com/knative)-based environment
|
14
|
+
* Your local development machine
|
15
|
+
|
16
|
+
The framework allows you to go from:
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
FunctionsFramework.http("hello") do |request|
|
20
|
+
"Hello, world!\n"
|
21
|
+
end
|
22
|
+
```
|
23
|
+
|
24
|
+
To:
|
25
|
+
|
26
|
+
```sh
|
27
|
+
curl http://my-url
|
28
|
+
# Output: Hello, world!
|
29
|
+
```
|
30
|
+
|
31
|
+
Running on a fully-managed or self-managed serverless environment, without
|
32
|
+
requiring an HTTP server or complicated request handling logic.
|
33
|
+
|
34
|
+
## Features
|
35
|
+
|
36
|
+
* Define named functions using normal Ruby constructs.
|
37
|
+
* Invoke functions in response to requests.
|
38
|
+
* Automatically unmarshal events conforming to the
|
39
|
+
[CloudEvents](https://cloudevents.io) spec.
|
40
|
+
* Automatically convert most legacy events from Google Cloud services such
|
41
|
+
as Cloud Pub/Sub and Cloud Storage, to CloudEvents.
|
42
|
+
* Spin up a local development server for quick testing.
|
43
|
+
* Integrate with standard Ruby libraries such as Rack and Minitest.
|
44
|
+
* Portable between serverless platforms.
|
45
|
+
* Supports all non-end-of-life versions of Ruby.
|
46
|
+
|
47
|
+
## Supported Ruby versions
|
48
|
+
|
49
|
+
This library is supported on Ruby 2.4+.
|
50
|
+
|
51
|
+
Google provides official support for Ruby versions that are actively supported
|
52
|
+
by Ruby Core—that is, Ruby versions that are either in normal maintenance or
|
53
|
+
in security maintenance, and not end of life. Currently, this means Ruby 2.4
|
54
|
+
and later. Older versions of Ruby _may_ still work, but are unsupported and not
|
55
|
+
recommended. See https://www.ruby-lang.org/en/downloads/branches/ for details
|
56
|
+
about the Ruby support schedule.
|
57
|
+
|
58
|
+
## Quickstart
|
59
|
+
|
60
|
+
Here is how to run a Hello World function on your local machine.
|
61
|
+
|
62
|
+
Create a `Gemfile` listing the Functions Framework as a dependency:
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
# Gemfile
|
66
|
+
source "https://rubygems.org"
|
67
|
+
gem "functions_framework", "~> 0.3"
|
68
|
+
```
|
69
|
+
|
70
|
+
Create a file called `app.rb` and include the following code. This defines a
|
71
|
+
simple function called "hello".
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
# app.rb
|
75
|
+
require "functions_framework"
|
76
|
+
|
77
|
+
FunctionsFramework.http("hello") do |request|
|
78
|
+
"Hello, world!\n"
|
79
|
+
end
|
80
|
+
```
|
81
|
+
|
82
|
+
Install the bundle, and start the framework. This spins up a local web server
|
83
|
+
running your "hello" function:
|
84
|
+
|
85
|
+
```sh
|
86
|
+
bundle install
|
87
|
+
# ...installs the functions_framework gem and other dependencies
|
88
|
+
bundle exec functions-framework-ruby --target hello
|
89
|
+
# ...starts the functions server in the foreground
|
90
|
+
```
|
91
|
+
|
92
|
+
In a separate shell, you can send requests to this function using curl:
|
93
|
+
|
94
|
+
```sh
|
95
|
+
curl http://localhost:8080
|
96
|
+
# Output: Hello, world!
|
97
|
+
```
|
98
|
+
|
99
|
+
Stop the server with `CTRL+C`.
|
100
|
+
|
101
|
+
## Documentation
|
102
|
+
|
103
|
+
These guides provide additional getting-started information.
|
104
|
+
|
105
|
+
* **{file:docs/writing-functions.md Writing Functions}** :
|
106
|
+
How to write functions that respond to HTTP requests, industry-standard
|
107
|
+
[CloudEvents](https://cloudevents.io), as well as events sent from Google
|
108
|
+
Cloud services such as [Pub/Sub](https://cloud.google.com/pubsub) and
|
109
|
+
[Storage](https://cloud.google.com/storage).
|
110
|
+
* **{file:docs/testing-functions.md Testing Functions}** :
|
111
|
+
How to use the testing features of the Functions Framework to write local
|
112
|
+
unit tests for your functions using standard Ruby testing frameworks such
|
113
|
+
as [Minitest](https://github.com/seattlerb/minitest) and
|
114
|
+
[RSpec](https://rspec.info/).
|
115
|
+
* **{file:docs/running-a-functions-server.md Running a Functions Server}** :
|
116
|
+
How to use the `functions-framework-ruby` executable to run a local
|
117
|
+
functions server.
|
118
|
+
* **{file:docs/deploying-functions.md Deploying Functions}** :
|
119
|
+
How to deploy functions to
|
120
|
+
[Google Cloud Functions](https://cloud.google.com/functions) or
|
121
|
+
[Google Cloud Run](https://cloud.google.com/run).
|
122
|
+
|
123
|
+
The library reference documentation can be found at:
|
124
|
+
https://rubydoc.info/gems/functions_framework
|
125
|
+
|
126
|
+
Additional examples are available in the GitHub repository:
|
127
|
+
https://github.com/GoogleCloudPlatform/functions-framework-ruby/blob/master/examples/
|
128
|
+
|
129
|
+
## Development
|
130
|
+
|
131
|
+
The source for the Ruby Functions Framework is available on GitHub at
|
132
|
+
https://github.com/GoogleCloudPlatform/functions-framework-ruby. For more
|
133
|
+
information on the Functions Framework contract implemented by this framework,
|
134
|
+
as well as links to Functions Frameworks for other languages, see
|
135
|
+
https://github.com/GoogleCloudPlatform/functions-framework.
|
136
|
+
|
137
|
+
The Functions Framework is open source under the Apache 2.0 license.
|
138
|
+
Contributions are welcome. Please see the contributing guide at
|
139
|
+
https://github.com/GoogleCloudPlatform/functions-framework-ruby/blob/master/.github/CONTRIBUTING.md.
|
140
|
+
|
141
|
+
Report issues at
|
142
|
+
https://github.com/GoogleCloudPlatform/functions-framework-ruby/issues.
|
@@ -0,0 +1,122 @@
|
|
1
|
+
<!--
|
2
|
+
# @title Running a Functions Server
|
3
|
+
-->
|
4
|
+
|
5
|
+
# Running a Functions Server
|
6
|
+
|
7
|
+
This guide covers how to use the `functions-framework-ruby` executable to launch
|
8
|
+
a functions server hosting Ruby functions written for the Functions Framework.
|
9
|
+
For more information about the Framework as a whole, see the
|
10
|
+
{file:docs/overview.md Overview Guide}.
|
11
|
+
|
12
|
+
## Running functions locally
|
13
|
+
|
14
|
+
The `functions-framework-ruby` command-line executable is used to run a
|
15
|
+
functions server. This executable is installed with the `functions_framework`
|
16
|
+
gem, and can be run with `bundle exec`. It wraps your function in a web server
|
17
|
+
request handler, and runs it in the [Puma](https://puma.io/) web server.
|
18
|
+
|
19
|
+
Pass the name of the function to run in the `--target` option. By default,
|
20
|
+
`functions-framework-ruby` will load functions from the file `app.rb` in the
|
21
|
+
current directory. If you want to load functions from a different file, use the
|
22
|
+
`--source` option.
|
23
|
+
|
24
|
+
```sh
|
25
|
+
bundle install
|
26
|
+
bundle exec functions-framework-ruby --source=foo.rb --target=hello
|
27
|
+
```
|
28
|
+
|
29
|
+
You can now send requests to your function. e.g.
|
30
|
+
|
31
|
+
```sh
|
32
|
+
curl http://localhost:8080/
|
33
|
+
```
|
34
|
+
|
35
|
+
The executable will write logs to the standard error stream while it is running.
|
36
|
+
To stop the server, hit `CTRL+C` or otherwise send it an appropriate signal.
|
37
|
+
The executable has no "background" or "daemon" mode. To run it in the background
|
38
|
+
from a shell, use the shell's background syntax (such as appending `&` to the
|
39
|
+
command).
|
40
|
+
|
41
|
+
By default, the executable will listen on the port specified by the `$PORT`
|
42
|
+
environment variable, or port 8080 if the variable is not set. You can also
|
43
|
+
override this by passing the `--port` option. A number of other options are
|
44
|
+
also available. See the section below on configuring the server, or pass
|
45
|
+
`--help` to the executable to display online help.
|
46
|
+
|
47
|
+
## Running functions in Docker
|
48
|
+
|
49
|
+
The `functions-framework-ruby` executable is designed to be run in a Docker
|
50
|
+
container. This is how it is run in some container-based hosting services such
|
51
|
+
as Google Cloud Run, but you can also run it in Docker locally.
|
52
|
+
|
53
|
+
First, write a Dockerfile for your project. Following is a simple starting
|
54
|
+
point; feel free to adjust it to the needs of your project:
|
55
|
+
|
56
|
+
```
|
57
|
+
FROM ruby:2.6
|
58
|
+
WORKDIR /app
|
59
|
+
COPY . .
|
60
|
+
RUN gem install --no-document bundler \
|
61
|
+
&& bundle config --local frozen true \
|
62
|
+
&& bundle config --local without "development test" \
|
63
|
+
&& bundle install
|
64
|
+
ENV PORT=8080
|
65
|
+
ENTRYPOINT ["bundle", "exec", "functions-framework-ruby"]
|
66
|
+
```
|
67
|
+
|
68
|
+
Build an image for your project using the Dockerfile.
|
69
|
+
|
70
|
+
```sh
|
71
|
+
docker build --tag my-image .
|
72
|
+
```
|
73
|
+
|
74
|
+
Then, you can run the Docker container locally as follows:
|
75
|
+
|
76
|
+
```sh
|
77
|
+
docker run --rm -it -p 8080:8080 my-image --source=foo.rb --target=hello
|
78
|
+
```
|
79
|
+
|
80
|
+
The arguments after the image name (e.g. `--source` and `--target` in the above
|
81
|
+
example) are passed to the `functions-framework-ruby` executable.
|
82
|
+
|
83
|
+
Because the docker container above maps port 8080 internally to port 8080
|
84
|
+
externally, you can use that port to send requests to your function. e.g.
|
85
|
+
|
86
|
+
```sh
|
87
|
+
curl http://localhost:8080/
|
88
|
+
```
|
89
|
+
|
90
|
+
You can stop the running Docker container with `CTRL+C` or by sending an
|
91
|
+
appropriate signal.
|
92
|
+
|
93
|
+
## Configuring the server
|
94
|
+
|
95
|
+
The Ruby Functions Framework recognizes the following command line arguments to
|
96
|
+
the `functions-framework-ruby` executable. Each argument also corresponds to an
|
97
|
+
environment variable. If you specify both, the flag takes precedence.
|
98
|
+
|
99
|
+
Command-line flag | Environment variable | Description
|
100
|
+
----------------- | -------------------- | -----------
|
101
|
+
`--port` | `PORT` | The port on which the Functions Framework listens for requests. Default: `8080`.
|
102
|
+
`--target` | `FUNCTION_TARGET` | The name of the exported function to be invoked in response to requests. Default: `function`.
|
103
|
+
`--source` | `FUNCTION_SOURCE` | The path to the file containing your function. Default: `app.rb` (in the current working directory).
|
104
|
+
`--signature-type` | `FUNCTION_SIGNATURE_TYPE` | Verifies that the function has the expected signature. Allowed values: `http`, `event`, or `cloudevent`.
|
105
|
+
`--environment` | `RACK_ENV` | Sets the Rack environment.
|
106
|
+
`--bind` | `FUNCTION_BIND_ADDR` | Binds to the given address. Default: `0.0.0.0`.
|
107
|
+
`--min-threads` | `FUNCTION_MIN_THREADS` | Sets the minimum thread pool size, overriding Puma's default.
|
108
|
+
`--max-threads` | `FUNCTION_MAX_THREADS` | Sets the maximum thread pool size, overriding Puma's default.
|
109
|
+
`--detailed-errors` | `FUNCTION_DETAILED_ERRORS` | No value. If present, shows exception details in exception responses. Defaults to false.
|
110
|
+
`--verbose` | `FUNCTION_LOGGING_LEVEL` | No value. Increases log verbosity (e.g. from INFO to DEBUG). Can be given more than once.
|
111
|
+
`--quiet` | `FUNCTION_LOGGING_LEVEL` | No value. Decreases log verbosity (e.g. from INFO to WARN). Can be given more than once.
|
112
|
+
|
113
|
+
Detailed errors are enabled by default if the `FUNCTION_DETAILED_ERRORS`
|
114
|
+
environment variable is set to a _non-empty_ string. The exact value does not
|
115
|
+
matter. Detailed errors are disabled if the variable is unset or empty.
|
116
|
+
|
117
|
+
The logging level defaults to the value of the `FUNCTION_LOGGING_LEVEL`
|
118
|
+
environment variable, which can be one of the following values: `DEBUG`, `INFO`,
|
119
|
+
`WARN`, `ERROR`, `FATAL`, or `UNKNOWN`, corresponding to Ruby's
|
120
|
+
[Logger::Severity](https://ruby-doc.org/stdlib/libdoc/logger/rdoc/Logger/Severity.html)
|
121
|
+
constants. If `FUNCTION_LOGGING_LEVEL` is not set to one of those values, it
|
122
|
+
defaults to `INFO`.
|
@@ -0,0 +1,169 @@
|
|
1
|
+
<!--
|
2
|
+
# @title Testing Functions
|
3
|
+
-->
|
4
|
+
|
5
|
+
# Testing Functions
|
6
|
+
|
7
|
+
This guide covers writing unit tests for functions using the Functions Framework
|
8
|
+
for Ruby. For more information about the Framework, see the
|
9
|
+
{file:docs/overview.md Overview Guide}.
|
10
|
+
|
11
|
+
## Overview of function testing
|
12
|
+
|
13
|
+
One of the benefits of the functions-as-a-service paradigm is that functions are
|
14
|
+
easy to test. In many cases, you can simply call a function with input, and test
|
15
|
+
the output. You do not need to set up (or mock) an actual server.
|
16
|
+
|
17
|
+
The Functions Framework provides utility methods that streamline the process of
|
18
|
+
setting up functions and the environment for testing, constructing input
|
19
|
+
parameters, and interpreting results. These are available in the
|
20
|
+
[Testing module](https://rubydoc.info/gems/functions_framework/FunctionsFramework/Testing).
|
21
|
+
Generally, you can include this module in your Minitest test class or RSpec
|
22
|
+
describe block.
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
require "minitest/autorun"
|
26
|
+
require "functions_framework/testing"
|
27
|
+
|
28
|
+
class MyTest < Minitest::Test
|
29
|
+
include FunctionsFramework::Testing
|
30
|
+
# define tests...
|
31
|
+
end
|
32
|
+
```
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
require "rspec"
|
36
|
+
require "functions_framework/testing"
|
37
|
+
|
38
|
+
describe "My functions" do
|
39
|
+
include FunctionsFramework::Testing
|
40
|
+
# define examples...
|
41
|
+
end
|
42
|
+
```
|
43
|
+
|
44
|
+
## Loading functions for testing
|
45
|
+
|
46
|
+
To test a function, you'll need to load the Ruby file that defines the function,
|
47
|
+
and run the function to test its results. The Testing module provides a method
|
48
|
+
[load_temporary](https://rubydoc.info/gems/functions_framework/FunctionsFramework/Testing#load_temporary-instance_method),
|
49
|
+
which loads a Ruby file, defining functions but only for the scope of your test.
|
50
|
+
This allows your test to coexist with tests for other functions, even functions
|
51
|
+
with the same name from a different Ruby file.
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
require "minitest/autorun"
|
55
|
+
require "functions_framework/testing"
|
56
|
+
|
57
|
+
class MyTest < Minitest::Test
|
58
|
+
include FunctionsFramework::Testing
|
59
|
+
|
60
|
+
def test_a_function
|
61
|
+
load_temporary "foo.rb" do
|
62
|
+
# Test a function defined in foo.rb
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_another_function
|
67
|
+
load_temporary "bar.rb" do
|
68
|
+
# Test a function defined in bar.rb
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
```
|
73
|
+
|
74
|
+
When running a test suite, you'll typically need to load all the Ruby files
|
75
|
+
that define your functions. While `load_temporary` can ensure that the function
|
76
|
+
definitions do not conflict, it cannot do the same for classes, methods, and
|
77
|
+
other Ruby constructs. So, for testability, it is generally good practice to
|
78
|
+
include only functions in one of these files. If you need to write supporting
|
79
|
+
helper methods, classes, constants, or other code, include them in separate
|
80
|
+
ruby files that you `require`.
|
81
|
+
|
82
|
+
## Testing HTTP functions
|
83
|
+
|
84
|
+
Testing an HTTP function is generally as simple as generating a request, calling
|
85
|
+
the function, and asserting against the response.
|
86
|
+
|
87
|
+
The input to an HTTP function is a
|
88
|
+
[Rack::Request](https://rubydoc.info/gems/rack/Rack/Request) object. It is
|
89
|
+
usually not hard to construct one of these objects, but the `Testing` module
|
90
|
+
includes helper methods that you can use to create simple requests for many
|
91
|
+
basic cases.
|
92
|
+
|
93
|
+
When you have constructed an input request, use
|
94
|
+
[call_http](https://rubydoc.info/gems/functions_framework/FunctionsFramework/Testing#call_http-instance_method)
|
95
|
+
to call a named function, passing the request object. This method returns a
|
96
|
+
[Rack::Response](https://rubydoc.info/gems/rack/Rack/Response) that you can
|
97
|
+
assert against.
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
require "minitest/autorun"
|
101
|
+
require "functions_framework/testing"
|
102
|
+
|
103
|
+
class MyTest < Minitest::Test
|
104
|
+
include FunctionsFramework::Testing
|
105
|
+
|
106
|
+
def test_http_function
|
107
|
+
load_temporary "app.rb" do
|
108
|
+
request = make_post_request "https://example.com/foo", "{\"name\":\"Ruby\"}",
|
109
|
+
["Content-Type: application/json"]
|
110
|
+
response = call_http "my_function", request
|
111
|
+
assert_equal 200, response.status
|
112
|
+
assert_equal "Hello, Ruby!", response.body.join
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
```
|
117
|
+
|
118
|
+
If the function raises an exception, the exception will be converted to a 500
|
119
|
+
response object. So if you are testing an error case, you should still check the
|
120
|
+
response object rather than looking for a raised exception.
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
require "minitest/autorun"
|
124
|
+
require "functions_framework/testing"
|
125
|
+
|
126
|
+
class MyTest < Minitest::Test
|
127
|
+
include FunctionsFramework::Testing
|
128
|
+
|
129
|
+
def test_erroring_http_function
|
130
|
+
load_temporary "app.rb" do
|
131
|
+
request = make_post_request "https://example.com/foo", "{\"name\":\"Ruby\"}",
|
132
|
+
["Content-Type: application/json"]
|
133
|
+
response = call_http "error_function", request
|
134
|
+
assert_equal 500, response.status
|
135
|
+
assert_match(/ArgumentError/, response.body.join)
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
```
|
140
|
+
|
141
|
+
## Testing CloudEvent functions
|
142
|
+
|
143
|
+
Testing a CloudEvent function works similarly. The `Testing` module provides
|
144
|
+
methods to help construct example CloudEvent objects, which can then be passed
|
145
|
+
to the method
|
146
|
+
[call_event](https://rubydoc.info/gems/functions_framework/FunctionsFramework/Testing#call_event-instance_method).
|
147
|
+
|
148
|
+
Unlike HTTP functions, event functions do not have a return value. Instead, you
|
149
|
+
will need to test side effects. A common approach is to test logs by capturing
|
150
|
+
the standard error output.
|
151
|
+
|
152
|
+
```ruby
|
153
|
+
require "minitest/autorun"
|
154
|
+
require "functions_framework/testing"
|
155
|
+
|
156
|
+
class MyTest < Minitest::Test
|
157
|
+
include FunctionsFramework::Testing
|
158
|
+
|
159
|
+
def test_event_function
|
160
|
+
load_temporary "app.rb" do
|
161
|
+
event = make_cloud_event "Hello, world!", type: "my-type"
|
162
|
+
_out, err = capture_subprocess_io do
|
163
|
+
call_event "my_function", event
|
164
|
+
end
|
165
|
+
assert_match(/Received: "Hello, world!"/, err)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
```
|
@@ -0,0 +1,261 @@
|
|
1
|
+
<!--
|
2
|
+
# @title Writing Functions
|
3
|
+
-->
|
4
|
+
|
5
|
+
# Writing Functions
|
6
|
+
|
7
|
+
This guide covers writing functions using the Functions Framework for Ruby. For
|
8
|
+
more information about the Framework, see the
|
9
|
+
{file:docs/overview.md Overview Guide}.
|
10
|
+
|
11
|
+
## About functions
|
12
|
+
|
13
|
+
Functions are Ruby blocks that are run when an input is received. Those inputs
|
14
|
+
can be HTTP requests or events in a recognized format. Functions that receive
|
15
|
+
HTTP requests return an HTTP response, but event functions have no return value.
|
16
|
+
|
17
|
+
When you define a function, you must provide an identifying name. The Functions
|
18
|
+
Framework allows you to use any string as a function name; however, many
|
19
|
+
deployment environments restrict the characters that can be used in a name. For
|
20
|
+
maximum portability, it is recommended that you use names that are allowed for
|
21
|
+
Ruby methods, i.e. beginning with a letter, and containing only letters,
|
22
|
+
numbers, and underscores.
|
23
|
+
|
24
|
+
## Defining an HTTP function
|
25
|
+
|
26
|
+
An HTTP function is a simple web service that takes an HTTP request and returns
|
27
|
+
an HTTP response. The following example defines an HTTP function named "hello"
|
28
|
+
that returns a simple message in the HTTP response body:
|
29
|
+
|
30
|
+
```ruby
|
31
|
+
require "functions_framework"
|
32
|
+
|
33
|
+
FunctionsFramework.http("hello") do |request|
|
34
|
+
# Return the response body.
|
35
|
+
"Hello, world!\n"
|
36
|
+
end
|
37
|
+
```
|
38
|
+
|
39
|
+
HTTP functions take a Rack Request object and return an HTTP response. We'll
|
40
|
+
now cover these in a bit more detail.
|
41
|
+
|
42
|
+
### Using the Request object
|
43
|
+
|
44
|
+
An HTTP function is passed a request, which is an object of type
|
45
|
+
[Rack::Request](https://rubydoc.info/gems/rack/Rack/Request). This object
|
46
|
+
provides methods for obtaining request information such as the method,
|
47
|
+
path, query parameters, body content, and headers. You can also obtain the raw
|
48
|
+
Rack environment using the `env` method. The following example includes some
|
49
|
+
request information in the response:
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
require "functions_framework"
|
53
|
+
|
54
|
+
FunctionsFramework.http("request_info") do |request|
|
55
|
+
# Include some request info in the response body.
|
56
|
+
"Received #{request.method} from #{request.url}!\n"
|
57
|
+
end
|
58
|
+
```
|
59
|
+
|
60
|
+
The Functions Framework sets up a logger in the Rack environment, so you can
|
61
|
+
use the `logger` method on the request object if you want to emit logs. These
|
62
|
+
logs will be written to the standard error stream, and will appear in the
|
63
|
+
Google Cloud Logs if your function is running on a Google Cloud serverless
|
64
|
+
hosting environment.
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
require "functions_framework"
|
68
|
+
|
69
|
+
FunctionsFramework.http("logging_example") do |request|
|
70
|
+
# Log some request info.
|
71
|
+
request.logger.info "I received #{request.method} from #{request.url}!"
|
72
|
+
# A simple response body.
|
73
|
+
"ok"
|
74
|
+
end
|
75
|
+
```
|
76
|
+
|
77
|
+
### Response types
|
78
|
+
|
79
|
+
The above examples return simple strings as the response body. Often, however,
|
80
|
+
you will need to return more complex responses such as JSON, binary data, or
|
81
|
+
even rendered HTML. The Functions Framework recognizes a variety of return
|
82
|
+
types from an HTTP function:
|
83
|
+
|
84
|
+
* **String** : If you return a string, the framework will use it as the
|
85
|
+
response body in with a 200 (success) HTTP status code. It will set the
|
86
|
+
`Content-Type` header to `text/plain`.
|
87
|
+
* **Array** : If you return an array, the framework will assume it is a
|
88
|
+
standard three-element Rack response array, as defined in the
|
89
|
+
[Rack spec](https://github.com/rack/rack/blob/master/SPEC.rdoc).
|
90
|
+
* **Rack::Response** : You can return a
|
91
|
+
[Rack::Response](https://rubydoc.info/gems/rack/Rack/Response) object. The
|
92
|
+
Framework will call `#finish` on this object and retrieve the contents.
|
93
|
+
* **Hash** : If you return a Hash, the Framework will attempt to encode it as
|
94
|
+
JSON, and return it in the response body with a 200 (success) HTTP status
|
95
|
+
code. The `Content-Type` will be set to `application/json`.
|
96
|
+
* **StandardError** : If you return an exception object, the Framework will
|
97
|
+
return a 500 (server error) response. See the section below on
|
98
|
+
Error Handling.
|
99
|
+
|
100
|
+
### Using Sinatra
|
101
|
+
|
102
|
+
The Functions Framework, and the functions-as-a-service (FaaS) solutions it
|
103
|
+
targets, are optimized for relatively simple HTTP requests such as webhooks and
|
104
|
+
simple APIs. If you want to deploy a large application or use a monolithic
|
105
|
+
framework such as Ruby on Rails, you may want to consider a solution such as
|
106
|
+
Google Cloud Run that is tailored to larger applications. However, a lightweight
|
107
|
+
framework such as Sinatra is sometimes useful when writing HTTP functions.
|
108
|
+
|
109
|
+
It is easy to connect an HTTP function to a Sinatra app. Write the Sinatra app
|
110
|
+
using the "modular" Sinatra interface (i.e. subclass `Sinatra::Base`), and then
|
111
|
+
simply run the Sinatra app as a Rack handler from the function. Here is a basic
|
112
|
+
example:
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
require "functions_framework"
|
116
|
+
require "sinatra/base"
|
117
|
+
|
118
|
+
class App < Sinatra::Base
|
119
|
+
get "/hello/:name" do
|
120
|
+
"Hello, #{params[:name]}!"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
FunctionsFramework.http "sinatra_example" do |request|
|
125
|
+
App.call request.env
|
126
|
+
end
|
127
|
+
```
|
128
|
+
|
129
|
+
This technique gives you access to pretty much any feature of the Sinatra web
|
130
|
+
framework, including routes, templates, and even custom middleware.
|
131
|
+
|
132
|
+
## Defining an Event function
|
133
|
+
|
134
|
+
An event function is a handler for a standard cloud event. It can receive
|
135
|
+
industry-standard [CloudEvents](https://cloudevents.io), as well as events sent
|
136
|
+
by Google Cloud services such as [Pub/Sub](https://cloud.google.com/pubsub) and
|
137
|
+
[Storage](https://cloud.google.com/storage). Event functions do not have a
|
138
|
+
return value.
|
139
|
+
|
140
|
+
The following is a simple event handler that receives an event and logs some
|
141
|
+
information about it:
|
142
|
+
|
143
|
+
```ruby
|
144
|
+
require "functions_framework"
|
145
|
+
|
146
|
+
FunctionsFramework.cloud_event("hello") do |event|
|
147
|
+
FunctionsFramework.logger.info "I received an event of type #{event.type}!"
|
148
|
+
end
|
149
|
+
```
|
150
|
+
|
151
|
+
The event parameter is a
|
152
|
+
[CloudEvents V1 Event](https://rubydoc.info/gems/functions_framework/FunctionsFramework/CloudEvents/Event/V1)
|
153
|
+
object. You can find detailed information about the fields of a CloudEvent from
|
154
|
+
the [CloudEvents spec](https://github.com/cloudevents/spec/blob/v1.0/spec.md).
|
155
|
+
|
156
|
+
Some Google Cloud services send events in a legacy event format that was defined
|
157
|
+
prior to CloudEvents. The Functions Framework will convert these legacy events
|
158
|
+
to an equivalent CloudEvents type, so your function will always receive a
|
159
|
+
CloudEvent object when it is sent an event from Google Cloud.
|
160
|
+
|
161
|
+
## Error handling
|
162
|
+
|
163
|
+
If your function encounters an error, it can raise an exception. The Functions
|
164
|
+
Framework will catch `StandardError` exceptions and handle them appropriately.
|
165
|
+
|
166
|
+
If you raise an exception in an HTTP function, the Functions Framework will
|
167
|
+
return a 500 (server error) response. You can control whether the exception
|
168
|
+
details (e.g. exception type, message, and backtrace) are sent with the
|
169
|
+
response by setting the detailed-errors configuration in the server. The
|
170
|
+
Framework will also log the error for you.
|
171
|
+
|
172
|
+
If you need more control over the error response, you can also construct the
|
173
|
+
HTTP response yourself. For example:
|
174
|
+
|
175
|
+
```ruby
|
176
|
+
require "functions_framework"
|
177
|
+
|
178
|
+
FunctionsFramework.http("error_reporter") do |request|
|
179
|
+
begin
|
180
|
+
raise "whoops!"
|
181
|
+
rescue RuntimeError => e
|
182
|
+
[500, {}, ["Uh, oh, got an error message: #{e.message}."]]
|
183
|
+
end
|
184
|
+
end
|
185
|
+
```
|
186
|
+
|
187
|
+
## Structuring a project
|
188
|
+
|
189
|
+
A Functions Framework based "project" or "application" is a typical Ruby
|
190
|
+
application. It should include a `Gemfile` that specifies the gem dependencies
|
191
|
+
(including the `functions_framework` gem itself), and any other dependencies
|
192
|
+
needed by the function. It must include at least one Ruby source file that
|
193
|
+
defines functions, and can also include additional Ruby files defining classes
|
194
|
+
and methods that assist in the function implementation.
|
195
|
+
|
196
|
+
The "entrypoint" to the project, also called the "source", is a Ruby file. It
|
197
|
+
can define any number of functions (with distinct names), although it is often
|
198
|
+
good practice to create a separate Ruby file per function.
|
199
|
+
|
200
|
+
By convention, the source file is often called `app.rb`, but you can give it
|
201
|
+
any name. Projects can also have multiple source files that apply to different
|
202
|
+
cases.
|
203
|
+
|
204
|
+
A simple project might look like this:
|
205
|
+
|
206
|
+
```
|
207
|
+
(project directory)
|
208
|
+
|
|
209
|
+
+- Gemfile
|
210
|
+
|
|
211
|
+
+- app.rb
|
212
|
+
|
|
213
|
+
+- lib/
|
214
|
+
| |
|
215
|
+
| +- hello.rb
|
216
|
+
|
|
217
|
+
+- test/
|
218
|
+
|
|
219
|
+
...
|
220
|
+
```
|
221
|
+
|
222
|
+
```ruby
|
223
|
+
# Gemfile
|
224
|
+
source "https://rubygems.org"
|
225
|
+
gem "functions_framework", "~> 0.3"
|
226
|
+
```
|
227
|
+
|
228
|
+
```ruby
|
229
|
+
# app.rb
|
230
|
+
require "functions_framework"
|
231
|
+
require_relative "lib/hello"
|
232
|
+
|
233
|
+
FunctionsFramework.http("hello") do |request|
|
234
|
+
Hello.new(request).build_response
|
235
|
+
end
|
236
|
+
```
|
237
|
+
|
238
|
+
```ruby
|
239
|
+
# lib/hello.rb
|
240
|
+
class Hello
|
241
|
+
def initialize(request)
|
242
|
+
@request = request
|
243
|
+
end
|
244
|
+
|
245
|
+
def build_response
|
246
|
+
"Received request: #{request.method} #{request.url}\n"
|
247
|
+
end
|
248
|
+
end
|
249
|
+
```
|
250
|
+
|
251
|
+
## Next steps
|
252
|
+
|
253
|
+
To learn about writing unit tests for functions, see
|
254
|
+
{file:docs/testing-functions.md Testing Functions}.
|
255
|
+
|
256
|
+
To learn how to run your functions in a server, see
|
257
|
+
{file:docs/running-a-functions-server.md Running a Functions Server}.
|
258
|
+
|
259
|
+
To learn how to deploy your functions to Google Cloud Functions or Google Cloud
|
260
|
+
Run, see
|
261
|
+
{file:docs/deploying-functions.md Deploying Functions}.
|